Module: PWN::Setup
- Defined in:
- lib/pwn/setup.rb
Overview
PWN::Setup — post-install "doctor" and capability provisioner.
PWN ships as a single gem whose runtime is 100 % autoloaded
(lib/pwn.rb) — a plugin whose native gem or OS binary is missing
costs nothing until you touch that constant. PWN::Setup is the
piece that grows a bare gem install pwn into a fully-armed host
after the gem is installed, instead of before via a chain
of bash scripts that assume /opt/pwn, rvmsudo, screen, and
MacPorts.
It is the Ruby-native, versioned-with-the-gem replacement for the
case $os blocks previously buried in install.sh and
packer/provisioners/pwn.sh.
pwn setup # doctor (read-only)
pwn setup --check # same
pwn setup --deps # install OS headers for EVERY native gem
pwn setup --profile web # install just what TransparentBrowser/Burp need
pwn setup --profile sdr --yes # non-interactive
pwn setup --list-profiles
Also exposed as pwn_setup (driver) and pwn --setup[=PROFILE].
Constant Summary collapse
- NATIVE_GEMS =
Native-extension Ruby gems → OS headers/libs required to compile them,
- per package manager, plus the PWN
constants they unlock.
{ 'pg' => { apt: %w[postgresql-server-dev-all], dnf: %w[postgresql-devel], pacman: %w[postgresql-libs], brew: %w[postgresql], port: %w[postgresql16-server], plugins: %w[PWN::Plugins::DAOPostgres] }, 'sqlite3' => { apt: %w[libsqlite3-dev], dnf: %w[sqlite-devel], pacman: %w[sqlite], brew: %w[sqlite], port: %w[sqlite3], plugins: %w[PWN::Plugins::DAOSqlite3] }, 'pcaprub' => { apt: %w[libpcap-dev], dnf: %w[libpcap-devel], pacman: %w[libpcap], brew: %w[libpcap], port: %w[libpcap], plugins: %w[PWN::Plugins::Packet extro_packet] }, 'rmagick' => { apt: %w[imagemagick libmagickwand-dev], dnf: %w[ImageMagick-devel], pacman: %w[imagemagick], brew: %w[imagemagick], port: %w[imagemagick], plugins: %w[PWN::Plugins::OCR PWN::Reports] }, 'rtesseract' => { apt: %w[tesseract-ocr tesseract-ocr-all], dnf: %w[tesseract], pacman: %w[tesseract tesseract-data-eng], brew: %w[tesseract], port: %w[tesseract], plugins: %w[PWN::Plugins::OCR extro_vision] }, 'ruby-audio' => { apt: %w[libsndfile1-dev], dnf: %w[libsndfile-devel], pacman: %w[libsndfile], brew: %w[libsndfile], port: %w[libsndfile], plugins: %w[PWN::Plugins::Voice PWN::SDR extro_voice] }, 'curses' => { apt: %w[libncurses-dev], dnf: %w[ncurses-devel], pacman: %w[ncurses], brew: %w[ncurses], port: %w[ncurses], plugins: %w[PWN::Plugins::REPL] }, 'libusb' => { apt: %w[libusb-1.0-0-dev], dnf: %w[libusbx-devel], pacman: %w[libusb], brew: %w[libusb], port: %w[libusb], plugins: %w[PWN::SDR PWN::Plugins::BusPirate] }, 'ffi' => { apt: %w[libffi-dev], dnf: %w[libffi-devel], pacman: %w[libffi], brew: %w[libffi], port: %w[libffi], plugins: %w[PWN::FFI] }, 'nokogiri' => { apt: %w[libxml2-dev libxslt1-dev], dnf: %w[libxml2-devel libxslt-devel], pacman: %w[libxml2 libxslt], brew: %w[libxml2], port: %w[libxml2 libxslt], plugins: %w[PWN::Plugins::TransparentBrowser PWN::WWW] }, 'oily_png' => { apt: %w[build-essential], dnf: %w[gcc make], pacman: %w[base-devel], brew: %w[gcc], port: %w[gcc12], plugins: %w[PWN::Plugins::ScannableCodes] }, 'eventmachine' => { apt: %w[libssl-dev], dnf: %w[openssl-devel], pacman: %w[openssl], brew: %w[openssl], port: %w[openssl], plugins: %w[PWN::Plugins::Sock PWN::Plugins::IRC] }, 'gruff' => { apt: %w[imagemagick libmagickwand-dev], dnf: %w[ImageMagick-devel], pacman: %w[imagemagick], brew: %w[imagemagick], port: %w[imagemagick], plugins: %w[PWN::Reports] } }.freeze
- TOOLCHAIN =
External binaries PWN wraps → OS packages that provide them. (Extrospection.probe_toolchain already knows how to detect these; this table knows how to install them.)
{ 'nmap' => { apt: %w[nmap], dnf: %w[nmap], pacman: %w[nmap], brew: %w[nmap], port: %w[nmap], plugins: %w[PWN::Plugins::NmapIt] }, 'chromium' => { apt: %w[chromium], dnf: %w[chromium], pacman: %w[chromium], brew: %w[chromium], port: %w[chromium], plugins: %w[PWN::Plugins::TransparentBrowser extro_verify extro_watch] }, 'geckodriver' => { apt: %w[firefox-esr], dnf: %w[firefox], pacman: %w[geckodriver], brew: %w[geckodriver], port: %w[geckodriver], plugins: %w[PWN::Plugins::TransparentBrowser] }, 'msfconsole' => { apt: %w[metasploit-framework], dnf: %w[metasploit-framework], pacman: %w[metasploit], brew: %w[metasploit], port: %w[], plugins: %w[PWN::Plugins::Metasploit] }, 'burpsuite' => { apt: %w[burpsuite], dnf: %w[], pacman: %w[burpsuite], brew: %w[burp-suite], port: %w[], plugins: %w[PWN::Plugins::BurpSuite] }, 'zaproxy' => { apt: %w[zaproxy], dnf: %w[], pacman: %w[zaproxy], brew: %w[zap], port: %w[], plugins: %w[PWN::Plugins::Zaproxy] }, 'sqlmap' => { apt: %w[sqlmap], dnf: %w[sqlmap], pacman: %w[sqlmap], brew: %w[sqlmap], port: %w[], plugins: %w[PWN::Plugins::Fuzz] }, 'tor' => { apt: %w[tor], dnf: %w[tor], pacman: %w[tor], brew: %w[tor], port: %w[tor], plugins: %w[PWN::Plugins::Tor] }, 'tshark' => { apt: %w[tshark], dnf: %w[wireshark-cli], pacman: %w[wireshark-cli], brew: %w[wireshark], port: %w[wireshark], plugins: %w[PWN::Plugins::Packet extro_packet] }, 'tcpdump' => { apt: %w[tcpdump], dnf: %w[tcpdump], pacman: %w[tcpdump], brew: %w[tcpdump], port: %w[tcpdump], plugins: %w[PWN::Plugins::Packet extro_packet] }, 'tesseract' => { apt: %w[tesseract-ocr], dnf: %w[tesseract], pacman: %w[tesseract], brew: %w[tesseract], port: %w[tesseract], plugins: %w[PWN::Plugins::OCR extro_vision] }, 'zbarimg' => { apt: %w[zbar-tools], dnf: %w[zbar], pacman: %w[zbar], brew: %w[zbar], port: %w[zbar], plugins: %w[PWN::Plugins::ScannableCodes extro_vision] }, 'espeak-ng' => { apt: %w[espeak-ng], dnf: %w[espeak-ng], pacman: %w[espeak-ng], brew: %w[espeak-ng], port: %w[espeak-ng], plugins: %w[PWN::Plugins::Voice extro_voice] }, 'sox' => { apt: %w[sox], dnf: %w[sox], pacman: %w[sox], brew: %w[sox], port: %w[sox], plugins: %w[PWN::Plugins::Voice PWN::SDR] }, 'gqrx' => { apt: %w[gqrx-sdr], dnf: %w[gqrx], pacman: %w[gqrx], brew: %w[gqrx], port: %w[gqrx], plugins: %w[PWN::SDR extro_rf_tune] }, 'rtl_sdr' => { apt: %w[rtl-sdr], dnf: %w[rtl-sdr], pacman: %w[rtl-sdr], brew: %w[librtlsdr], port: %w[rtl-sdr], plugins: %w[PWN::SDR PWN::FFI::RTLSdr] }, 'hackrf_info' => { apt: %w[hackrf], dnf: %w[hackrf], pacman: %w[hackrf], brew: %w[hackrf], port: %w[hackrf], plugins: %w[PWN::SDR PWN::FFI::HackRF] }, 'SoapySDRUtil' => { apt: %w[soapysdr-tools], dnf: %w[SoapySDR], pacman: %w[soapysdr], brew: %w[soapysdr], port: %w[SoapySDR], plugins: %w[PWN::SDR PWN::FFI::SoapySDR] }, 'multimon-ng' => { apt: %w[multimon-ng], dnf: %w[multimon-ng], pacman: %w[multimon-ng], brew: %w[multimon-ng], port: %w[], plugins: %w[PWN::SDR::Decoder] }, 'dot' => { apt: %w[graphviz], dnf: %w[graphviz], pacman: %w[graphviz], brew: %w[graphviz], port: %w[graphviz], plugins: %w[documentation/diagrams] }, 'adb' => { apt: %w[adb], dnf: %w[android-tools], pacman: %w[android-tools], brew: %w[android-platform-tools], port: %w[], plugins: %w[PWN::Plugins::Android] }, 'baresip' => { apt: %w[baresip], dnf: %w[baresip], pacman: %w[baresip], brew: %w[baresip], port: %w[], plugins: %w[PWN::Plugins::BareSIP extro_telecomm] }, 'whois' => { apt: %w[whois], dnf: %w[whois], pacman: %w[whois], brew: %w[whois], port: %w[whois], plugins: %w[extro_osint] }, 'jq' => { apt: %w[jq], dnf: %w[jq], pacman: %w[jq], brew: %w[jq], port: %w[jq], plugins: %w[extro_osint] } }.freeze
- PROFILES =
Capability profiles — how you get the ergonomics of
pwn-fullwithout shipping a second gem.pwn setup --profile <name>. { core: { desc: '~/.pwn bootstrap + vault + REPL (always runs on bare `pwn setup --deps`)', gems: %w[ffi nokogiri curses eventmachine], bins: %w[] }, ai: { desc: 'verify at least one AI engine key/oauth in ~/.pwn/pwn.yaml', gems: %w[], bins: %w[jq] }, web: { desc: 'TransparentBrowser · BurpSuite · Zaproxy · extro_verify · extro_watch', gems: %w[nokogiri eventmachine], bins: %w[chromium geckodriver burpsuite zaproxy sqlmap tor] }, net: { desc: 'NmapIt · Packet · extro_packet · extro_osint', gems: %w[pcaprub], bins: %w[nmap tshark tcpdump whois jq] }, db: { desc: 'DAOPostgres · DAOSqlite3 · DAOMongo', gems: %w[pg sqlite3], bins: %w[] }, sdr: { desc: 'PWN::SDR · GQRX · FFI DSP backends · extro_rf_tune', gems: %w[ruby-audio libusb ffi], bins: %w[gqrx rtl_sdr hackrf_info SoapySDRUtil multimon-ng sox] }, vision: { desc: 'OCR · ScannableCodes · Reports · extro_vision', gems: %w[rmagick rtesseract oily_png gruff], bins: %w[tesseract zbarimg dot] }, voice: { desc: 'PWN::Plugins::Voice · extro_voice', gems: %w[ruby-audio], bins: %w[espeak-ng sox] }, exploit: { desc: 'Metasploit · sqlmap', gems: %w[], bins: %w[msfconsole sqlmap] }, hardware: { desc: 'Serial · BusPirate · Android · BareSIP · extro_serial · extro_telecomm', gems: %w[libusb], bins: %w[adb baresip] }, full: { desc: 'everything above', gems: NATIVE_GEMS.keys, bins: TOOLCHAIN.keys } }.freeze
- OK =
"\e[32mok\e[0m"- MISS =
"\e[31mMISSING\e[0m"
Class Method Summary collapse
-
.authors ⇒ Object
- Author(s)
0day Inc.
-
.check(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::Setup.check( io: 'optional - IO to write the report to (default $stdout)' ).
-
.deps(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::Setup.deps( profile: 'optional - one of PROFILES.keys (default :full)', yes: 'optional - non-interactive; assume yes to prompts (default false)', dry_run: 'optional - print commands only, do not execute (default false)', io: 'optional - IO to write to (default $stdout)' ).
-
.help ⇒ Object
Display Usage for this Module.
-
.list_profiles(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::Setup.list_profiles( io: 'optional - IO to write to (default $stdout)' ).
-
.pkg_manager ⇒ Object
- Supported Method Parameters
PWN::Setup.pkg_manager.
Class Method Details
.authors ⇒ Object
- Author(s)
0day Inc. support@0dayinc.com
518 519 520 521 522 |
# File 'lib/pwn/setup.rb', line 518 public_class_method def self. "AUTHOR(S): 0day Inc. <support@0dayinc.com> " end |
.check(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::Setup.check( io: 'optional - IO to write the report to (default $stdout)' )
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 |
# File 'lib/pwn/setup.rb', line 302 public_class_method def self.check(opts = {}) io = opts[:io] || $stdout pm = pkg_manager os = detect_os_attr(attr: :type, fallback: RbConfig::CONFIG['host_os']) arch = detect_os_attr(attr: :arch, fallback: RbConfig::CONFIG['host_cpu']) io.puts "PWN v#{PWN::VERSION} · ruby #{RUBY_VERSION} · #{os} #{arch} · pkg-manager: #{pm[:key]}" io.puts # ~/.pwn ----------------------------------------------------------- pwn_root = File.join(Dir.home, '.pwn') pwn_yaml = File.join(pwn_root, 'pwn.yaml') pwn_dec = File.join(pwn_root, 'pwn.yaml.decryptor') root_ok = File.directory?(pwn_root) root_note = root_ok ? "(#{Dir.children(pwn_root).length} entries)" : '(will be created on first `pwn` launch)' io.puts "#{'~/.pwn/'.ljust(26)} #{root_ok ? OK : MISS} #{root_note}" yaml_ok = File.file?(pwn_yaml) dec_state = File.file?(pwn_dec) ? 'present' : 'MISSING' yaml_note = yaml_ok ? "(encrypted, decryptor #{dec_state})" : '(run `pwn` once, then `pwn-vault` to configure)' io.puts "#{'~/.pwn/pwn.yaml'.ljust(26)} #{yaml_ok ? OK : MISS} #{yaml_note}" engine = ai_active_engine key_ok = ai_key_configured?(engine: engine) engine_note = if engine "#{engine} #{key_ok ? '(key set)' : '(no key — set via `pwn-vault`)'}" else '(no ai.active in pwn.yaml)' end io.puts "#{'AI engine'.ljust(26)} #{key_ok ? OK : MISS} #{engine_note}" io.puts # Native gems ------------------------------------------------------ io.puts 'Ruby extensions' gem_missing = [] NATIVE_GEMS.each do |gem_name, | ok = gem_loadable?(name: gem_name) gem_missing << gem_name unless ok need = ok ? '' : "(needs: #{Array([pm[:key]]).join(' ')})" io.puts " #{gem_name.to_s.ljust(14)} #{ok ? OK : MISS} #{need.ljust(42)} → #{Array([:plugins]).join(', ')}" end io.puts # Toolchain -------------------------------------------------------- io.puts "#{'External toolchain'.ljust(46)} used by" bin_missing = [] TOOLCHAIN.each do |bin, | path = which(name: bin) bin_missing << bin if path.empty? shown_path = path.empty? ? '' : path[0, 24] io.puts " #{bin.to_s.ljust(16)} #{path.empty? ? MISS : OK} #{shown_path.ljust(24)} #{Array([:plugins]).join(', ')}" end io.puts total = NATIVE_GEMS.size + TOOLCHAIN.size missing = gem_missing.size + bin_missing.size io.puts "#{total - missing} / #{total} capabilities usable · #{missing} degraded" io.puts unless missing.zero? io.puts 'Run `pwn setup --deps` to install missing OS headers/tools, or' io.puts ' `pwn setup --profile <name>` for a subset. See `pwn setup --list-profiles`.' end { ok: missing.zero?, native_gems_missing: gem_missing, toolchain_missing: bin_missing, pkg_manager: pm[:key], os: os, arch: arch } rescue StandardError => e raise e end |
.deps(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::Setup.deps( profile: 'optional - one of PROFILES.keys (default :full)', yes: 'optional - non-interactive; assume yes to prompts (default false)', dry_run: 'optional - print commands only, do not execute (default false)', io: 'optional - IO to write to (default $stdout)' )
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 |
# File 'lib/pwn/setup.rb', line 377 public_class_method def self.deps(opts = {}) profile = (opts[:profile] || :full).to_sym yes = opts[:yes] ? true : false dry_run = opts[:dry_run] ? true : false io = opts[:io] || $stdout raise "Unknown profile '#{profile}'. Known: #{PROFILES.keys.join(', ')}" unless PROFILES.key?(profile) pm = pkg_manager raise 'No supported package manager found (apt / dnf / pacman / brew / port).' if pm[:key] == :unknown prof = PROFILES[profile] gems = Array(prof[:gems]) bins = Array(prof[:bins]) os_pkgs = [] gems.each { |g| os_pkgs.concat(Array(NATIVE_GEMS.dig(g, pm[:key]))) } bins.each { |b| os_pkgs.concat(Array(TOOLCHAIN.dig(b, pm[:key]))) } os_pkgs = os_pkgs.reject(&:empty?).uniq io.puts "Profile : #{profile} — #{prof[:desc]}" io.puts "Pkg mgr : #{pm[:key]}" io.puts "OS pkgs : #{os_pkgs.empty? ? '(none)' : os_pkgs.join(' ')}" io.puts "Ruby exts : #{gems.empty? ? '(none)' : gems.join(' ')}" io.puts cmds = [] cmds << "#{pm[:install]} #{os_pkgs.map { |p| Shellwords.escape(p) }.join(' ')}" unless os_pkgs.empty? unless gems.empty? broken = gems.reject { |g| gem_loadable?(name: g) } cmds << "gem pristine #{broken.map { |g| Shellwords.escape(g) }.join(' ')}" unless broken.empty? cmds << "gem install #{broken.map { |g| Shellwords.escape(g) }.join(' ')}" unless broken.empty? end if cmds.empty? io.puts "Nothing to do — profile '#{profile}' is already satisfied." return { profile: profile, ran: [], skipped: true } end io.puts 'Will run:' cmds.each { |c| io.puts " $ #{c}" } io.puts unless yes || dry_run io.print 'Proceed? [y/N] ' ans = $stdin.gets.to_s.strip.downcase return { profile: profile, ran: [], aborted: true } unless %w[y yes].include?(ans) end ran = [] cmds.each do |c| io.puts "→ #{c}" if dry_run ran << { cmd: c, exit: 0, dry_run: true } else ok = system(c) ran << { cmd: c, exit: $CHILD_STATUS && $CHILD_STATUS.exitstatus, ok: ok } io.puts " (exit #{ran.last[:exit]})" unless ok end end io.puts io.puts 'Re-checking…' io.puts result = check(io: io) { profile: profile, ran: ran, check: result } rescue StandardError => e raise e end |
.help ⇒ Object
Display Usage for this Module
526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 |
# File 'lib/pwn/setup.rb', line 526 public_class_method def self.help puts "USAGE: # Read-only doctor — which PWN capabilities are usable on this host? #{self}.check # Install OS headers + rebuild native gems for a capability profile. # profile ∈ #{PROFILES.keys.inspect} #{self}.deps( profile: 'optional - capability profile (default :full)', yes: 'optional - assume yes (non-interactive)', dry_run: 'optional - print commands only' ) # List capability profiles. #{self}.list_profiles # Detected package manager (:apt / :dnf / :pacman / :brew / :port). #{self}.pkg_manager # Data tables — versioned with the gem: #{self}::NATIVE_GEMS # native ext → OS headers → PWN:: constants #{self}::TOOLCHAIN # external bin → OS package → PWN:: constants #{self}::PROFILES # capability profile → gems + bins # From the shell: pwn setup # == check pwn setup --check pwn setup --deps # profile :full pwn setup --profile web pwn setup --profile sdr --yes pwn setup --list-profiles pwn setup --dry-run --profile net #{self}.authors " end |
.list_profiles(opts = {}) ⇒ Object
- Supported Method Parameters
PWN::Setup.list_profiles( io: 'optional - IO to write to (default $stdout)' )
451 452 453 454 455 456 457 458 459 460 461 462 463 464 |
# File 'lib/pwn/setup.rb', line 451 public_class_method def self.list_profiles(opts = {}) io = opts[:io] || $stdout io.puts 'Available capability profiles (`pwn setup --profile <name>`):' io.puts PROFILES.each do |name, | io.puts " #{name.to_s.ljust(10)} #{[:desc]}" io.puts " #{''.ljust(10)} gems: #{[:gems].join(' ')}" unless [:gems].empty? io.puts " #{''.ljust(10)} bins: #{[:bins].join(' ')}" unless [:bins].empty? io.puts end PROFILES.keys rescue StandardError => e raise e end |
.pkg_manager ⇒ Object
- Supported Method Parameters
PWN::Setup.pkg_manager
284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/pwn/setup.rb', line 284 public_class_method def self.pkg_manager return @pkg_manager if defined?(@pkg_manager) && @pkg_manager @pkg_manager = if bin?(name: 'apt-get') then { key: :apt, install: 'sudo apt-get install -y', sudo: true } elsif bin?(name: 'dnf') then { key: :dnf, install: 'sudo dnf install -y', sudo: true } elsif bin?(name: 'pacman') then { key: :pacman, install: 'sudo pacman -S --noconfirm', sudo: true } elsif bin?(name: 'brew') then { key: :brew, install: 'brew install', sudo: false } elsif bin?(name: 'port') then { key: :port, install: 'sudo port -N install', sudo: true } else { key: :unknown, install: nil, sudo: false } end end |