#!/usr/bin/env python3 """ generate_bot_ip.py — Generate bot_ip.csv from known scanner networks + Tor exit nodes. Sources: • Tor exit nodes: downloaded list or hardcoded fallback • Shodan: known scanner ranges (census.shodan.io, 2024) • Censys: known scanner ranges (censys.io, 2024) • Binaryedge, SecurityTrails, ZoomEye, Stretchoid: known ranges • GreyNoise: top mass-scanner IPs (manually curated) Output format (no header): , """ import argparse import ipaddress import sys # --- Known scanner networks (public, well-documented) --- KNOWN_SCANNERS = { # Shodan — https://wiki.ipfire.org/dns/public-servers (census.shodan.io) "Shodan_Scanner": [ "66.240.192.0/24", "66.240.205.0/24", "66.240.236.0/24", "71.6.135.0/24", "71.6.146.0/24", "71.6.158.0/24", "71.6.165.0/24", "80.82.77.0/24", "80.82.78.0/24", "82.221.105.0/24", "82.221.106.0/24", "85.25.43.0/24", "85.25.103.0/24", "93.120.27.0/24", "94.102.49.0/24", "188.138.9.0/24", "198.20.69.0/24", "198.20.70.0/24", "198.20.87.0/24", "198.20.99.0/24", "209.126.110.0/24", ], # Censys — https://support.censys.io/hc/en-us/articles/360043177092 "Censys_Scanner": [ "162.142.125.0/24", "167.248.133.0/24", "167.94.138.0/24", "167.94.145.0/24", "167.94.146.0/24", "192.35.168.0/23", ], # BinaryEdge — https://docs.binaryedge.io/ "BinaryEdge_Scanner": [ "154.89.5.0/24", "45.143.200.0/22", ], # Stretchoid — persistent scanner botnet "Stretchoid_Scanner": [ "198.235.24.0/24", "205.210.31.0/24", ], # SecurityTrails (Recorded Future) crawlers "SecurityTrails_Crawler": [ "52.250.0.0/16", ], # ZoomEye (Knownsec) "ZoomEye_Scanner": [ "106.75.0.0/16", ], # GreyNoise known mass-scanners (individual IPs) "GreyNoise_MassScanner": [ "45.155.205.233/32", "45.155.205.220/32", "45.155.205.205/32", "45.155.205.190/32", "45.155.205.175/32", "45.155.205.160/32", "45.155.205.146/32", "45.155.205.131/32", "193.32.162.10/32", "193.32.162.11/32", "193.32.162.25/32", "193.32.162.30/32", "193.32.162.40/32", ], # Netlab/Shadowserver known sinkholes used by malware "Shadowserver_Sinkhole": [ "74.82.47.0/24", "184.105.139.0/24", "184.105.247.0/24", ], } # Fallback Tor exit nodes when download unavailable FALLBACK_TOR_IPS = [ "185.220.101.34", "185.220.101.35", "185.220.101.36", "185.220.101.37", "185.220.101.38", "185.220.101.39", "185.220.101.40", "185.220.101.41", "185.220.101.42", "185.220.101.43", "185.220.101.44", "185.220.101.45", "185.220.101.46", "185.220.101.47", "185.220.101.48", "185.220.101.49", "185.220.101.50", "185.220.101.51", "185.220.101.52", "185.220.101.53", "185.220.101.54", "185.220.101.55", "185.220.101.56", "185.220.101.57", "185.220.101.58", "185.220.101.59", "185.220.101.60", "185.220.101.61", "185.220.101.62", "185.220.101.63", "185.220.101.64", "185.220.101.65", "185.220.101.66", "185.220.101.67", "185.220.101.68", "185.220.101.69", "185.220.101.70", "185.220.101.71", "185.220.101.72", "185.220.101.73", "185.220.101.74", "185.220.101.75", "185.220.101.76", "185.220.101.77", "185.220.101.78", "185.220.101.79", "185.220.101.80", "185.220.101.81", "185.220.101.82", "185.220.101.83", "185.220.101.84", "185.220.101.85", "185.220.101.86", "185.220.101.87", "185.220.101.88", "185.220.101.89", "185.220.101.90", "185.220.101.91", "185.220.101.92", "185.220.101.93", "185.220.101.94", "185.220.101.95", "185.220.101.96", "185.220.101.97", "185.220.100.240", "185.220.100.241", "185.220.100.242", "185.220.100.243", "185.220.100.244", "185.220.100.245", "185.220.100.246", "185.220.100.247", "185.220.100.248", "185.220.100.249", "185.220.100.250", "185.220.100.251", "185.220.100.252", "185.220.100.253", "185.220.100.254", "185.220.100.255", "178.20.55.16", "178.20.55.18", "178.20.55.182", "23.129.64.130", "23.129.64.131", "23.129.64.132", "23.129.64.133", "23.129.64.134", "23.129.64.135", "23.129.64.136", "23.129.64.137", "23.129.64.138", "23.129.64.139", "23.129.64.140", "23.129.64.141", "23.129.64.142", "23.129.64.143", "23.129.64.144", "23.129.64.145", "23.129.64.146", "23.129.64.147", "23.129.64.148", "23.129.64.149", "23.129.64.150", "23.129.64.151", "23.129.64.152", "23.129.64.153", "104.244.76.13", "104.244.76.14", "104.244.76.15", "104.244.76.16", "104.244.76.17", "104.244.76.18", "104.244.76.19", "104.244.76.20", "199.249.230.64", "199.249.230.65", "199.249.230.66", "199.249.230.67", "199.249.230.68", "199.249.230.69", "199.249.230.70", "199.249.230.71", "199.249.230.72", "199.249.230.73", "199.249.230.74", "199.249.230.75", "199.249.230.76", "199.249.230.77", "199.249.230.78", "199.249.230.79", "199.249.230.80", "199.249.230.81", "199.249.230.82", "199.249.230.83", "199.249.230.84", "199.249.230.85", "199.249.230.86", "199.249.230.87", "199.249.230.88", "199.249.230.89", ] def load_tor_ips(tor_file): """Load Tor exit node IPs from downloaded file.""" ips = set() try: with open(tor_file) as f: for line in f: line = line.strip() if not line or line.startswith("#"): continue try: ipaddress.ip_address(line) ips.add(line) except ValueError: pass except FileNotFoundError: pass return ips def main(): parser = argparse.ArgumentParser(description="Generate bot_ip.csv") parser.add_argument("--output", default="bot_ip.csv") parser.add_argument("--tor-file", help="Path to downloaded Tor exit node list") args = parser.parse_args() entries = [] seen = set() # Add known scanner networks for bot_name, networks in KNOWN_SCANNERS.items(): for net in networks: key = net if key not in seen: seen.add(key) entries.append((net, bot_name)) # Add Tor exit nodes if args.tor_file: tor_ips = load_tor_ips(args.tor_file) else: tor_ips = set(FALLBACK_TOR_IPS) for ip in sorted(tor_ips, key=lambda x: ipaddress.ip_address(x)): key = f"{ip}/32" if key not in seen: seen.add(key) entries.append((key, "Tor_Exit_Node")) with open(args.output, "w") as f: for net, name in entries: f.write(f"{net},{name}\n") print(f"[bot_ip] Generated {len(entries)} entries " f"({len(tor_ips)} Tor nodes, " f"{len(entries) - len(tor_ips)} scanner ranges)") if __name__ == "__main__": main()