#!/usr/bin/env ruby # frozen_string_literal: false require 'optparse' require 'pwn' require 'time' opts = {} OptionParser.new do |options| options.banner = "USAGE: #{$PROGRAM_NAME} [opts] " options.on('-tRANGE', '--target-range=RANGE', '') do |t| opts[:target_range] = t end options.on('-eFILE', '--target-exclude-file=FILE', '') do |e| opts[:exclude_file] = e end options.on('-iINTERFACE', '--interface=INTERFACE', '') do |r| opts[:results_root] = r end options.on('-T', '--tor', '') do |t| opts[:with_tor] = t end end.parse! if opts.empty? puts `#{$PROGRAM_NAME} --help` exit 1 end ftimestr = '%Y-%m-%d %H:%M:%S.%N%z' started_at = Time.now.strftime(ftimestr) started_at_parse = Time.parse(started_at) banner = '-' * 64 puts "\n\n\n#{banner}" puts "- STARTED: #{started_at} " target_range = opts[:target_range] results_root = opts[:results_root] results_root ||= '/tmp' FileUtils.mkdir_p results_root puts "Results Saved in: #{results_root}" exclude_file = opts[:exclude_file] exclude_file ||= "#{results_root}/nmap_targets_exclude.txt" # We create an exclude file (even if its empty) File.new(exclude_file, 'w') unless File.exist?(exclude_file) interface = opts[:interface] interface ||= 'eth0' with_tor = true if opts[:with_tor] with_tor ||= false if with_tor tor_obj = PWN::Plugins::Tor.start proxy = ["socks4://#{tor_obj[:ip]}:#{tor_obj[:port]}"] end discovery_tcp_ports = { ftp: 21, ssh: 22, telnet: 23, smtp: 25, http: 80, pop3: 110, ident: 113, msrpc: 135, netbios_name_service: 137, netbios_session_service: 139, imap: 143, ldap: 389, https: 443, smb: 445, smtps: 465, remote_process: 512, login: 513, ldaps: 636, rsync: 873, imaps: 993, openvpn: 1194, mssql: 1433, oracle: 1521, pptp: 1723, radius: 1812, nfs: 2049, mysql: 3306, rdp: 3389, meterpreter: 4444, upnp: 5000, postgres: 5432, postgres_alt: 5433, amqp: 5672, vnc: 5900, vncs: 5901, x11: 6000, irc: 6667, http_alt: 8080, https_alt: 8443, http_alt2: 8888, http_alt3: 9090, http_alt4: 9999 } discovery_udp_ports = { dns: 53, dhcp: 67, dhcp_client: 68, tftp: 69, nfs: 111, ntp: 123, snmp: 161, snmp_traps: 162, syslog: 514, rip: 520, iax: 4569, sip: 5060, mdns: 5353 } discovery_sctp_ports = { sigtran: 2905, stl: 5000, sap: 5004, turn_ip: 5766, sicc: 38_412 } target_file = "#{results_root}/nmap_targets.txt" latest_discovery_results = "#{results_root}/nmap_discovery_results.xml" latest_tcp_results = "#{results_root}/nmap_tcp_results" latest_udp_results = "#{results_root}/nmap_udp_results" begin # Per man nmap: # The main effects of T0 are serializing the scan so only one port # is scanned at a time, and waiting five minutes between sending # each probe. # T1 and T2 are similar but they only wait 15 seconds and 0.4 seconds, # respectively, between probes. # T3 is Nmap's default behavior, which includes parallelization. # T4 does the equivalent of --max-rtt-timeout 1250ms --min-rtt-timeout 100ms # --initial-rtt-timeout 500ms --max-retries 6 and sets the maximum TCP and # SCTP scan delay to 10ms. # T5 does the equivalent of --max-rtt-timeout 300ms --min-rtt-timeout 50ms # --initial-rtt-timeout 250ms --max-retries 2 --host-timeout 15m # --script-timeout 10m --max-scan-delay as well as setting the maximum TCP # and SCTP scan delay to 5ms. Maximum UDP scan delay is not set by T4 or T5, # but it can be set with the --max-scan-delay option. # Target Discovery Scan # Using -T5 template to reduce number of # retransmission attempts on filtered ports. puts banner puts '- PHASE 1: Target Discovery' PWN::Plugins::NmapIt.port_scan do |nmap| if with_tor nmap.proxies = proxy nmap.syn_discovery = discovery_tcp_ports.values nmap.ack_discovery = discovery_tcp_ports.values else nmap.ping = true nmap.arp_ping = true nmap.icmp_echo_discovery = true nmap.icmp_timestamp_discovery = true nmap.udp_discovery = discovery_udp_ports.values nmap.sctp_init_ping = discovery_sctp_ports.values end nmap.verbose = true nmap.exclude_file = exclude_file nmap.interface = interface nmap.insane_timing = true nmap.output_xml = latest_discovery_results nmap.targets = target_range nmap.randomize_hosts = true nmap.min_parallelism = 36 nmap.max_retries = 3 nmap.max_scan_delay = 3 end # Generate targets.txt from discovery above # taking into consideration IPs to skip scans File.open(target_file, 'w') do |f| PWN::Plugins::NmapIt.parse_xml_results( xml_file: latest_discovery_results ) do |xml| xml.each_host do |host| next if File.read(exclude_file).include?(host.ip) || host.status.state != :up f.puts host.ip end end end # Produce a good targets.txt redacting duplicates && sorting by IP sorted_ips = File.readlines(target_file).uniq.map do |ip| IPAddr.new(ip.chomp) end sorted_ips = sorted_ips.sort_by(&:hton) # Now Add additional info about the IP as a comment File.open(target_file, 'w') do |f| sorted_ips.each do |ip| PWN::Plugins::NmapIt.parse_xml_results( xml_file: latest_discovery_results ) do |xml| xml.each_host do |host| next unless host.ip.to_s == ip.to_s hosts_arr = host.hostnames.map { |h| h[:name] } f.puts "#{ip} # { \"hostnames\": #{hosts_arr}}, \"mac\": \"#{host.mac}\" }" end end end end phase1_ended_at = Time.now.strftime(ftimestr) phase1_ended_at_parse = Time.parse(phase1_ended_at) elapsed_in_seconds = (phase1_ended_at_parse - started_at_parse).to_f fmt_elapsed_in_seconds = format('%0.2f', elapsed_in_seconds) puts "\n#{banner}" puts "- DISCOVERY COMPLETE! DURATION: #{fmt_elapsed_in_seconds} seconds" puts banner puts "\n\n\n#{banner}" puts '- PHASE 2: TCP Port Scanning' puts banner phase2_started_at = Time.now.strftime(ftimestr) phase2_started_at_parse = Time.parse(phase2_started_at) # Switch Tor Exit Node if with_tor if with_tor puts '- INFO: Switching to Clean Tor Circuit...' PWN::Plugins::Tor.switch_exit_node(tor_obj: tor_obj) end # TCP Scan # Using -T5 template to reduce number of # retransmission attempts on filtered ports. PWN::Plugins::NmapIt.port_scan do |nmap| nmap.proxies = proxy if with_tor nmap.verbose = true nmap.target_file = target_file nmap.randomize_hosts = true nmap.show_reason = true nmap.exclude_file = exclude_file nmap.interface = interface nmap.min_host_group = 9 nmap.host_timeout = '36m' nmap.insane_timing = true nmap.skip_discovery = true nmap.syn_scan = true nmap.default_script = true nmap.update_scriptdb = true nmap.ports = [1..65_535] nmap.output_all = latest_tcp_results nmap.min_parallelism = 36 nmap.max_retries = 3 nmap.max_scan_delay = 3 end FileUtils.cp("#{latest_tcp_results}.nmap", "#{latest_tcp_results}.txt") phase2_ended_at = Time.now.strftime(ftimestr) phase2_ended_at_parse = Time.parse(phase2_ended_at) elapsed_in_seconds = (phase2_ended_at_parse - phase2_started_at_parse).to_f fmt_elapsed_in_seconds = format('%0.2f', elapsed_in_seconds) puts "\n#{banner}" puts "- TCP SCAN COMPLETE! DURATION: #{fmt_elapsed_in_seconds} seconds" puts banner puts "\n\n\n#{banner}" puts '- PHASE 3: UDP Port Scanning' puts banner phase3_started_at = Time.now.strftime(ftimestr) phase3_started_at_parse = Time.parse(phase3_started_at) # Switch Tor Exit Node if with_tor if with_tor puts '- INFO: Switching to Clean Tor Circuit...' PWN::Plugins::Tor.switch_exit_node(tor_obj: tor_obj) end # UDP Scan # Using -T5 template to reduce number of # retransmission attempts on filtered ports. PWN::Plugins::NmapIt.port_scan do |nmap| if with_tor nmap.proxies = proxy else nmap.default_script = true nmap.update_scriptdb = true end nmap.verbose = true nmap.target_file = target_file nmap.fast = true nmap.randomize_hosts = true nmap.show_reason = true nmap.exclude_file = exclude_file nmap.interface = interface nmap.min_host_group = 9 nmap.host_timeout = '3m' nmap.insane_timing = true nmap.skip_discovery = true nmap.udp_scan = true nmap.output_all = latest_udp_results nmap.min_parallelism = 36 nmap.max_retries = 0 nmap.max_scan_delay = 3 nmap.data_length = Random.rand(1..256) end FileUtils.cp("#{latest_udp_results}.nmap", "#{latest_udp_results}.txt") phase3_ended_at = Time.now.strftime(ftimestr) phase3_ended_at_parse = Time.parse(phase3_ended_at) elapsed_in_seconds = (phase3_ended_at_parse - phase3_started_at_parse).to_f fmt_elapsed_in_seconds = format('%0.2f', elapsed_in_seconds) puts "\n#{banner}" puts "- UDP SCAN COMPLETE! DURATION: #{fmt_elapsed_in_seconds} seconds" puts banner rescue SystemExit, Interrupt puts "\nGoodbye." rescue StandardError => e raise e ensure tor_obj = PWN::Plugins::Tor.stop(tor_obj: tor_obj) if with_tor ended_at = Time.now.strftime(ftimestr) ended_at_parse = Time.parse(ended_at) elapsed_in_seconds = (ended_at_parse - started_at_parse).to_f fmt_elapsed_in_seconds = format('%0.2f', elapsed_in_seconds) puts "\n\n\n#{banner}" puts "- ENDED: #{ended_at}" puts "- SCAN COMPLETE! DURATION: #{fmt_elapsed_in_seconds} seconds" puts banner end