├── .gitignore ├── README.md ├── config.json.sample ├── exclusions.txt ├── ranges.txt └── spoonmap.py /.gitignore: -------------------------------------------------------------------------------- 1 | config.json 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpooNMAP 2 | 3 | ## Dependencies 4 | This script is simply a wrapper for NMAP and Masscan. Install them from your 5 | favorite package manager, or install from source. 6 | 7 | The script also utilizes Python's magical f-strings, so Python 3.6 or above 8 | is required. 9 | 10 | ## Usage 11 | Again, make sure that you have Python 3.6 or above installed. Simply executing 12 | the script will prompt you for all of the required scanning options. 13 | 14 | ``` 15 | # ./spoonmap.py 16 | 17 | ________ _____ _______ _________________ 18 | __ ___/______________________ | / /__ |/ /__ |__ __ \ 19 | _____ \___ __ \ __ \ __ \_ |/ /__ /|_/ /__ /| |_ /_/ / 20 | ____/ /__ /_/ / /_/ / /_/ / /| / _ / / / _ ___ | ____/ 21 | /____/ _ .___/\____/\____//_/ |_/ /_/ /_/ /_/ |_/_/ 22 | /_/ 23 | 24 | 25 | Scan Type 26 | (1) Small Port Scan 27 | (2) Medium Port Scan 28 | (3) Large Port Scan 29 | (4) Extra Large Port Scan (Small, Medium, and Large) 30 | (5) Full Port Scan 31 | (6) Custom Port Scan 32 | 33 | What type of scan would you like to perform (default: Small Port Scan)? 34 | 35 | Would you like to enumerate service banners for any identified services (default: Yes)? 36 | 37 | Target Scan 38 | (1) External 39 | (2) Internal 40 | 41 | Is this an internal or external scan (default: External)? 42 | 43 | How fast would you like to scan (default: 20000 packets/second)? 44 | 45 | Example Target File 46 | One CIDR or IP Address per line 47 | 48 | 192.168.0.0/24 49 | 192.168.1.23 50 | 51 | Please enter the full path for the file containing target hosts (default: /opt/spoonmap/ranges.txt): 52 | 53 | Would you like to exclude any hosts? (default: No) 54 | 55 | Scan Type: Small Port Scan 56 | Target Ports: ['80', '443', '8000', '8080', '8008', '8181', '8443'] 57 | Service Banner: False 58 | Source Port: 53 59 | Masscan Max Packet Rate (pps): 2000 60 | Target File: ranges.txt 61 | Exclusions File: exclusions.txt 62 | 63 | Scanning port 80... 64 | ``` 65 | You can also create a configuration file to avoid all of the prompts. Use the 66 | provided 'config.json.sample' as an example. Just make sure that your file 67 | is named 'config.json' 68 | ``` 69 | # cat config.json 70 | { 71 | "__scan_type_choices__" : "Small Port Scan, Medium Port Scan, Large Port Scan, Extra Large Port Scan, Full Port Scan, Custom Port Scan", 72 | "scan_type" : "Custom Port Scan", 73 | "dest_ports" : ["80","443","8000","8080","U:53"], 74 | "__banner_scan_choices__" : "True, False", 75 | "banner_scan" : "True", 76 | "__target_scan_choices__" : "External, Internal", 77 | "target_scan" : "Internal", 78 | "__max_rate_external_recommedation__" : "Single Port = 20000, Full Port = 10000", 79 | "__max_rate_internal_recommedation__" : "Single Port = 2000, Full Port = 1000", 80 | "max_rate" : "2000", 81 | "target_file" : "ranges.txt", 82 | "output_path" : "./", 83 | "exclusions_file" : "exclusions.txt" 84 | } 85 | ``` 86 | Note: To perform UDP scans simply prepend 'U:' to the port you'd like to scan (i.e 'U:53'). 87 | #### config.json Parameters 88 | ##### scan_type 89 | This paramater is used to determine what ports to scan. 90 | * Small Port Scan 91 | * 80, 443, 8000, 8080, 8008, 8181, 8443 92 | * Medium Port Scan 93 | * 7001, 1433, 445, 139, 21, 22, 23, 25, \ 94 | 53, 111, 389, 4243, 3389, 3306, 4786, \ 95 | 5900, 5901, 6379, 6970, 9100 96 | * Large Port Scan 97 | * 1090, 1098, 1099, 10999, 11099, 11111, \ 98 | 3300, 4243, 4444, 4445, 45000, 45001, \ 99 | 47001, 47002, 4786, 4848, 50500, 5555, \ 100 | 5556, 6129, 6379, 6970, 7000, \ 101 | 7002, 7003, 7004, 7070, 7071, \ 102 | 8001, 8002, 8003, 8686, 9000, \ 103 | 9001, 9002, 9003, 9012, 9503 104 | * Extra Large Port Scan 105 | * Small, Medium, and Large Ports Combined 106 | * Full Port Scan 107 | * 1-65,535 108 | * Custom Port Scan 109 | * Dealer's Choice 110 | ##### dest_ports 111 | * Only used if 'Custom Port Scan' is selected. 112 | ##### banner_scan 113 | This parameter is used to determine whether NMAP will be used 114 | to grab service banners. 115 | * True 116 | * False 117 | ##### target_scan 118 | This paramater is used to determine what source port to spoof. 119 | * External Port Scan 120 | * source port = 53 121 | * Internal Port Scan 122 | * source port = 88 123 | ##### max_rate 124 | This parameter is used to determine how fast to scan in masscan. 125 | If it is not set manually, it is determined from the 126 | scan_type and target_scan parameters. 127 | **Note: Selecting a max_rate that is too high can easily create 128 | a denial-of-service. In my testing, the following rates have been 129 | found to be safe. YMMV** 130 | * 'External' and 'Common Port Scan' 131 | * max_rate = 20,000 packets/second 132 | * 'External' and 'Full Port Scan' 133 | * max_rate = 10,000 packets/second 134 | * 'Internal' and 'Common Port Scan' 135 | * max_rate = 2,000 packets/second 136 | * 'Internal' and 'Full Port Scan' 137 | * max_rate = 1,000 packets/second 138 | * Everything else 139 | * max_rate = 2,000 packets/second 140 | 141 | ## Potential Hacks to Look For 142 | 143 | 1090, 1098, 1099, 4444, 11099, 47001, 47002, 10999 144 | Java RMI 145 | https://www.rapid7.com/db/modules/exploit/multi/misc/java_rmi_server 146 | https://medium.com/@afinepl/java-rmi-for-pentesters-structure-recon-and-communication-non-jmx-registries-a10d5c996a79 147 | https://medium.com/@afinepl/java-rmi-for-pentesters-part-two-reconnaissance-attack-against-non-jmx-registries-187a6561314d 148 | 149 | 7000-7004, 8000-8003, 9000-9003, 9503, 7070, 7071 150 | WebLogic 151 | https://www.exploit-db.com/search?q=weblogic 152 | 153 | 45000, 45001 154 | JDWP 155 | https://www.rapid7.com/db/modules/exploit/multi/misc/java_jdwp_debugger 156 | https://github.com/IOActive/jdwp-shellifier 157 | 158 | 8686, 9012, 50500 159 | JMX 160 | https://www.rapid7.com/db/modules/exploit/multi/misc/java_jmx_server 161 | 162 | 4848 163 | GlassFish 164 | https://www.rapid7.com/db/modules/auxiliary/scanner/http/glassfish_traversal 165 | 166 | 11111, 4444, 4445 167 | JBoss 168 | https://www.rapid7.com/db/modules/auxiliary/scanner/http/jboss_vulnscan 169 | https://github.com/joaomatosf/jexboss 170 | 171 | 4786 172 | Cisco Smart Install 173 | https://www.rapid7.com/db/modules/auxiliary/scanner/misc/cisco_smart_install 174 | https://github.com/Sab0tag3d/SIET 175 | 176 | 5555, 5556 177 | HP Data Protector 178 | https://www.rapid7.com/db/modules/exploit/multi/misc/hp_data_protector_exec_integutil 179 | https://www.rapid7.com/db/modules/exploit/windows/misc/hp_dataprotector_cmd_exec 180 | 181 | 3300 182 | SAP 183 | https://github.com/chipik/SAP_GW_RCE_exploit 184 | 185 | 6129 186 | Dameware 187 | https://www.tenable.com/security/research/tra-2019-43 188 | https://github.com/tenable/poc/blob/master/Solarwinds/Dameware/dwrcs_dwDrvInst_rce.py 189 | 190 | 6379 191 | Redis 192 | https://www.rapid7.com/db/modules/exploit/linux/redis/redis_replication_cmd_exec 193 | 194 | 6970 195 | Cisco Unified Communications Manager 196 | https://github.com/trustedsec/SeeYouCM-Thief 197 | http://[CUCM IP Address]:6970/ConfigFileCacheList.txt 198 | 199 | 8080 200 | Adobe CodFusion BlazeDS 201 | https://www.tenable.com/plugins/nessus/99731 202 | 203 | -------------------------------------------------------------------------------- /config.json.sample: -------------------------------------------------------------------------------- 1 | { 2 | "__scan_type_choices__" : "Small Port Scan, Medium Port Scan, Large Port Scan, Extra Large Port Scan, Full Port Scan, Custom Port Scan", 3 | "scan_type" : "Custom Port Scan", 4 | "dest_ports" : ["80","443","8000","8080","U:53"], 5 | "__banner_scan_choices__" : "True, False", 6 | "banner_scan" : "True", 7 | "__target_scan_choices__" : "External, Internal", 8 | "target_scan" : "Internal", 9 | "__max_rate_external_recommedation__" : "Single Port = 20000, Full Port = 10000", 10 | "__max_rate_internal_recommedation__" : "Single Port = 2000, Full Port = 1000", 11 | "max_rate" : "2000", 12 | "target_file" : "ranges.txt", 13 | "output_path" : "./", 14 | "exclusions_file" : "exclusions.txt" 15 | } 16 | -------------------------------------------------------------------------------- /exclusions.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trustedsec/spoonmap/baa4ba0611c55ba1d2b0ff1370912c8d19b969d0/exclusions.txt -------------------------------------------------------------------------------- /ranges.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/trustedsec/spoonmap/baa4ba0611c55ba1d2b0ff1370912c8d19b969d0/ranges.txt -------------------------------------------------------------------------------- /spoonmap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Author: Spoonman (Larry.Spohn@TrustedSec.com) 4 | # QA and Personal Pythonian Consultant: Bandrel (Justin.Bollinger@TrustedSec.com) 5 | 6 | import json 7 | import os 8 | from pathlib import Path 9 | import subprocess 10 | import xml.etree.ElementTree as etree 11 | 12 | 13 | def verify_python_version(): 14 | import sys 15 | if sys.version_info[0] == 2: 16 | print('Python 3.6+ is required') 17 | quit(1) 18 | elif sys.version_info[0] == 3 and sys.version_info[1] < 6: 19 | print('Python 3.6+ is required') 20 | quit(1) 21 | 22 | 23 | def ascii_art(): 24 | print(r''' 25 | ________ _____ _______ _________________ 26 | __ ___/______________________ | / /__ |/ /__ |__ __ \ 27 | _____ \___ __ \ __ \ __ \_ |/ /__ /|_/ /__ /| |_ /_/ / 28 | ____/ /__ /_/ / /_/ / /_/ / /| / _ / / / _ ___ | ____/ 29 | /____/ _ .___/\____/\____//_/ |_/ /_/ /_/ /_/ |_/_/ 30 | /_/ 31 | ''') 32 | 33 | def mass_scan(scan_type, dest_ports, source_port, max_rate, target_file, exclusions_file): 34 | status_summary = '\nSummary' 35 | 36 | if not os.path.exists(f'{output_path}/masscan_results'): 37 | os.makedirs(f'{output_path}/masscan_results') 38 | for dest_port in dest_ports: 39 | 40 | # Commence masscan! 41 | print('\x1b[33m' + f'Scanning port {dest_port}...' + '\x1b[0m') 42 | if exclusions_file: 43 | masscan_process = subprocess.Popen(f'masscan -p {dest_port} --open --max-rate {max_rate} ' \ 44 | f'--source-port {source_port} -iL {target_file} --excludefile {exclusions_file} -oX \"{output_path}/masscan_results/port{dest_port}.xml\"', 45 | shell=True) 46 | else: 47 | masscan_process = subprocess.Popen(f'masscan -p {dest_port} --open --max-rate {max_rate} ' \ 48 | f'--source-port {source_port} -iL {target_file} -oX \"{output_path}/masscan_results/port{dest_port}.xml\"', 49 | shell=True) 50 | try: 51 | masscan_process.wait() 52 | except KeyboardInterrupt: 53 | print(f'Killing PID {str(masscan_process.pid)}...') 54 | if masscan_process.returncode == 1: 55 | quit(1) 56 | 57 | # Parse results from masscan 58 | if os.stat(f'{output_path}/masscan_results/port{dest_port}.xml').st_size == 0: 59 | os.remove(f'{output_path}/masscan_results/port{dest_port}.xml') 60 | print('\x1b[33m' + f'\nHosts Found on Port {dest_port}: 0') 61 | print('Masscan Completion Status: ' + '{:.0%}'.format((dest_ports.index(dest_port) + 1) / len(dest_ports)) + '\x1b[0m') 62 | else: 63 | root = etree.parse(f'{output_path}/masscan_results/port{dest_port}.xml') 64 | hosts = root.findall('host') 65 | for host in hosts: 66 | ip_address = host.findall('address')[0].attrib['addr'] 67 | live_port = host.findall('ports/port')[0].attrib['portid'] 68 | 69 | if 'U:' in dest_port: 70 | live_port = 'U:'+live_port 71 | 72 | # Write live hosts out to file 73 | os.makedirs(output_path+"/live_hosts", exist_ok=True) 74 | if os.path.exists(f'{output_path}/live_hosts/port{live_port}.txt'): 75 | with open(f'{output_path}/live_hosts/port{live_port}.txt') as file: 76 | ip_exists = False 77 | if f'{ip_address}\n' in file.read(): 78 | ip_exists = True 79 | if not ip_exists: 80 | with open(f'{output_path}/live_hosts/port{live_port}.txt', 'a') as file: 81 | file.write(f'{ip_address}\n') 82 | else: 83 | with open(f'{output_path}/live_hosts/port{live_port}.txt', 'w') as file: 84 | file.write(f'{ip_address}\n') 85 | host_count = lineCount(f'{output_path}/live_hosts/port{live_port}.txt') 86 | status_update = f'\nHosts Found on Port {dest_port}: {host_count}' 87 | status_summary += status_update 88 | print('\x1b[33m' + status_update) 89 | print('Masscan Completion Status: ' + '{:.0%}'.format((dest_ports.index(dest_port) + 1) / len(dest_ports)) + '\x1b[0m') 90 | 91 | return status_summary 92 | 93 | def nmap_scan(source_port): 94 | 95 | # Commence NMAP banner grabbing! 96 | os.makedirs(output_path+"/nmap_results", exist_ok=True) 97 | try: 98 | host_files = os.listdir(f'{dir_path}/live_hosts') 99 | for host_file in host_files: 100 | dest_port = ((host_file.split('.')[0])[4:]) 101 | if not os.path.exists(f'{output_path}/nmap_results/port{dest_port}.xml'): 102 | print('\x1b[33m' + f'Grabbing service banners for port {dest_port}...\n' + '\x1b[0m') 103 | 104 | if 'U:' in dest_port: 105 | nmap_process = subprocess.Popen(f'nmap -T4 -sU -sV --version-intensity 0 -Pn -p {dest_port[2:]} --open ' \ 106 | f'--randomize-hosts --source-port {source_port} -iL {output_path}/live_hosts/port{dest_port}.txt ' \ 107 | f'-oX {output_path}/nmap_results/port{dest_port}.xml', 108 | shell=True) 109 | else: 110 | nmap_process = subprocess.Popen(f'nmap -T4 -sS -sV --version-intensity 0 -Pn -p {dest_port} --open ' \ 111 | f'--randomize-hosts --source-port {source_port} -iL {output_path}/live_hosts/port{dest_port}.txt ' \ 112 | f'-oX {output_path}/nmap_results/port{dest_port}.xml', 113 | shell=True) 114 | try: 115 | nmap_process.wait() 116 | print('\x1b[33m' + '\nNMAP Completion Status: ' + \ 117 | '{:.0%}'.format((host_files.index(host_file) + 1) / len(host_files)) + \ 118 | '\x1b[0m') 119 | except KeyboardInterrupt: 120 | print(f'Killing PID {str(nmap_process.pid)}...') 121 | except: 122 | pass 123 | 124 | # Counts the number of lines in a file 125 | def lineCount(file): 126 | try: 127 | with open(file) as outFile: 128 | count = 0 129 | for line in outFile: 130 | count = count + 1 131 | return count 132 | except: 133 | return 0 134 | 135 | 136 | # The Main Guts 137 | def main(): 138 | global dir_path 139 | global output_path 140 | ascii_art() 141 | 142 | scan_type = '' 143 | dest_ports = [] 144 | banner_scan = '' 145 | target_scan = '' 146 | source_port = '53' 147 | max_rate = '' 148 | target_file = '' 149 | exclusions_file = '' 150 | status_summary = '' 151 | output_path = '' 152 | 153 | 154 | # Get options from configuration file if it exists 155 | dir_path = os.path.dirname(os.path.realpath(__file__)) 156 | if os.path.exists(f'{dir_path}/config.json'): 157 | with open(f'{dir_path}/config.json') as config: 158 | config_parser = json.load(config) 159 | 160 | scan_type = config_parser['scan_type'] 161 | dest_ports = config_parser['dest_ports'] 162 | banner_scan = config_parser['banner_scan'] 163 | if banner_scan == 'True': 164 | banner_scan = True 165 | else: 166 | banner_scan = False 167 | target_scan = config_parser['target_scan'] 168 | max_rate = config_parser['max_rate'] 169 | target_file = config_parser['target_file'] 170 | output_path = config_parser['output_path'] 171 | exclusions_file = config_parser['exclusions_file'] 172 | 173 | if scan_type == '': 174 | scan_choice = '1' 175 | while True: 176 | print('\nScan Type') 177 | print('\t(1) Small Port Scan') 178 | print('\t(2) Medium Port Scan') 179 | print('\t(3) Large Port Scan') 180 | print('\t(4) Extra Large Port Scan (Small, Medium, and Large)') 181 | print('\t(5) Full Port Scan') 182 | print('\t(6) Custom Port Scan') 183 | scan_choice = input( 184 | f'\nWhat type of scan would you like to perform (default: Small Port Scan)? ' 185 | ) or scan_choice 186 | if scan_choice == '1': 187 | scan_type = 'Small Port Scan' 188 | break 189 | elif scan_choice == '2': 190 | scan_type = 'Medium Port Scan' 191 | break 192 | elif scan_choice == '3': 193 | scan_type = 'Large Port Scan' 194 | break 195 | elif scan_choice == '4': 196 | scan_type = 'Extra Large Port Scan' 197 | break 198 | elif scan_choice == '5': 199 | scan_type = 'Full Port Scan' 200 | break 201 | elif scan_choice == '6': 202 | scan_type = 'Custom Port Scan' 203 | break 204 | 205 | small_ports = ['80', '443', '8000', '8080', '8008', '8181', '8443'] 206 | medium_ports = ['7001', '1433', '445', '139', '21', '22', '23', '25', 207 | '53', '111', '389', '4243', '3389', '3306', '4786', 208 | '5900', '5901', '5985', '5986', '6379', '6970', '9100'] 209 | large_ports = ['1090', '1098', '1099', '10999', '11099', '11111', 210 | '3300', '4243', '4444', '4445', '45000', '45001', 211 | '47001', '47002', '4786', '4848', '50500', '5555', 212 | '5556', '6129', '6379', '6970', '7000', 213 | '7002', '7003', '7004', '7070', '7071', 214 | '8001', '8002', '8003', '8686', '9000', 215 | '9001', '9002', '9003', '9012', '9503'] 216 | if scan_type == 'Small Port Scan': 217 | dest_ports = small_ports 218 | elif scan_type == 'Medium Port Scan': 219 | dest_ports = medium_ports 220 | elif scan_type == 'Large Port Scan': 221 | dest_ports = large_ports 222 | elif scan_type == 'Extra Large Port Scan': 223 | dest_ports = small_ports + medium_ports + large_ports 224 | elif scan_type == 'Full Port Scan': 225 | dest_ports = ['1-65535'] 226 | elif scan_type == 'Custom Port Scan' and not dest_ports: 227 | dest_ports = input( 228 | '\nWhat ports would you like to scan (separated by space: 80 443)? ').split() 229 | 230 | if banner_scan == '': 231 | banner_choice = 1 232 | banner_choice = input( 233 | f'\nWould you like to enumerate service banners for any identified services ' 234 | f'(default: Yes)? ' 235 | ) or banner_choice 236 | if banner_choice == 1 or banner_choice[0].lower() == 'y': 237 | banner_scan = True 238 | else: 239 | banner_scan = False 240 | 241 | if not target_scan: 242 | source_choice = '1' 243 | while True: 244 | print('\nTarget Scan') 245 | print('\t(1) External') 246 | print('\t(2) Internal') 247 | source_choice = input( 248 | f'\nIs this an internal or external scan ' 249 | f'(default: External)? ' 250 | ) or source_choice 251 | if source_choice == '1': 252 | target_scan = 'External' 253 | source_port = '53' 254 | break 255 | elif source_choice == '2': 256 | target_scan = 'Internal' 257 | source_port = '88' 258 | break 259 | 260 | if not max_rate: 261 | if target_scan == "External" and scan_type == "Small Port Scan": 262 | max_rate = '20000' 263 | elif target_scan == "External" and scan_type == "Full Port Scan": 264 | max_rate = '10000' 265 | elif target_scan == "Internal" and scan_type == "Small Port Scan": 266 | max_rate = '2000' 267 | elif target_scan == "Internal" and scan_type == "Full Port Scan": 268 | max_rate = '1000' 269 | else: 270 | max_rate = '2000' 271 | while True: 272 | try: 273 | rate_choice = input(f'\nHow fast would you like to scan ' 274 | f'(default: {max_rate} packets/second)? ' 275 | ) or max_rate 276 | if int(rate_choice): 277 | max_rate = rate_choice 278 | break 279 | except ValueError: 280 | pass 281 | 282 | if not output_path: 283 | output_path = dir_path 284 | output_path = input(f'\nPlease enter full path for output ' 285 | f'(default: {dir_path}): ' 286 | ) or output_path 287 | os.makedirs(output_path, exist_ok=True) 288 | 289 | if not target_file: 290 | target_file = output_path+"/ranges.txt" 291 | while True: 292 | print(target_file) 293 | print('\nExample Target File') 294 | print('One CIDR or IP Address per line\n') 295 | print('\t192.168.0.0/24') 296 | print('\t192.168.1.23') 297 | target_file = input(f'\nPlease enter the full path for the file ' 298 | f'containing target hosts (default: {target_file}): ' 299 | ) or target_file 300 | 301 | if os.path.exists(target_file): 302 | break 303 | 304 | if not exclusions_file: 305 | exclusions_choice = 'n' 306 | exclusions_choice = input(f'\nWould you like to exclude any hosts? (default: No) ' 307 | ) or exclusions_choice 308 | 309 | if exclusions_choice[0].lower() == 'y': 310 | exclusions_file = 'exclusions.txt' 311 | while True: 312 | print('\nExample Exclusions File') 313 | print('One CIDR or IP Address per line\n') 314 | print('\t192.168.0.0/24') 315 | print('\t192.168.1.23') 316 | exclusions_file = input(f'\nPlease enter the full path for the file ' 317 | f'containing excluded hosts if applicable (default: {dir_path}/{exclusions_file}): ' 318 | ) or exclusions_file 319 | 320 | if os.path.exists(target_file): 321 | break 322 | else: 323 | exclusions_file = None 324 | 325 | print(f'\nScan Type: {scan_type}') 326 | print(f'Target Ports: {dest_ports}') 327 | print(f'Service Banner: {banner_scan}') 328 | print(f'Source Port: {source_port}') 329 | print(f'Masscan Max Packet Rate (pps): {max_rate}') 330 | print(f'Target File: {target_file}') 331 | print(f'Exclusions File: {exclusions_file}\n') 332 | 333 | status_summary = mass_scan(scan_type, dest_ports, source_port, max_rate, target_file, exclusions_file) 334 | 335 | # If service banners requested, send to nmap 336 | if banner_scan or banner_scan == 'Yes': 337 | nmap_scan(source_port) 338 | 339 | # Combine all live hosts into one file 340 | all_ips = set() 341 | if os.path.exists(f'{output_path}/live_hosts'): 342 | host_files = os.listdir(f'{output_path}/live_hosts') 343 | for host_file in host_files: 344 | with open(f'{output_path}/live_hosts/{host_file}') as input_file: 345 | for line in input_file: 346 | all_ips.add(line) 347 | with open(f'{output_path}/all_live_hosts.txt', 'w') as output_file: 348 | for ip in all_ips: 349 | output_file.write(ip) 350 | 351 | # Combine all XML results into one file 352 | if banner_scan : 353 | result_dir = f'{output_path}/nmap_results/' 354 | else: 355 | result_dir = f'{output_path}/masscan_results/' 356 | xml_result = '\n\n\n' 357 | xml_files = os.listdir(result_dir) 358 | for xml_file in xml_files: 359 | root = etree.parse(result_dir + xml_file) 360 | hosts = root.findall('host') 361 | for host in hosts: 362 | xml_result += etree.tostring(host, encoding="unicode", method="xml") 363 | xml_result += '' 364 | with open(f'{output_path}/spoonmap_output.xml', 'w+') as spoonmap_output: 365 | spoonmap_output.write(xml_result) 366 | print('\x1b[33m' + f'\nResults written to {output_path}/spoonmap_output.xml' + '\x1b[0m') 367 | 368 | else: 369 | status_summary += '\nNo hosts found.' 370 | 371 | # Print Summary 372 | print('\x1b[33m' + status_summary + '\x1b[0m') 373 | 374 | # Boilerplate 375 | if __name__ == '__main__': 376 | verify_python_version() 377 | main() 378 | --------------------------------------------------------------------------------