import logging,os,ctypes,sys,argparse,time,re from subprocess import * from datetime import datetime from pkgutil import iter_modules import pkg_resources #Recon-Informer (c) #By John Page (Hyp3rlinx) #ApparitionSec #hyp3rlinx.altervista.org #twitter.com/hyp3rlinx #apparitionsec@gmail.com #PoC Video URL: https://www.youtube.com/watch?v=XM-G9Udbphc #========================================================== # #Recon-Informer is a basic real-time anti-reconnaissance detection tool for offensive #security systems, useful for penetration testers. It runs on Windows/Linux and leverages scapy. # #Purpose: #Recon-Informer is NOT meant for protecting public facing or lan critical enterprise systems whatsoever. #Its purpose is detect possible recon against our attacker system on a LAN to provide us defensive intel. #Therefore, this script is most useful for basic short-term defensive visibility. # #Features: #Attempt to detect and identify typical port scans generated using Nmap including scan type. #-sS, -sC, -F, -sR, -sT, -sA, -sW, -sN, -sF, -sX, -sM, -sZ, -sY, -sO, -sV, -sP, -sn, -f (fragment scan), -D (Decoy). # #FYI, scans such as FIN don't work well on windows OS and firewalls can make scans return incorrect result. #XMAS scans work against systems following RFC 793 for TCP/IP and don’t work against any Windows versions, #NULL is another type that don't work well on Windows. # #However, Fin, Null and Xmas scans can work on Linux machines. Therefore, Recon-Informer checks the OS #its run on and reports on scans that affect that OS, unless the -s "scan_type" flag is supplied. #With -s flag you can add extra scan types to detect that otherwise would be ignored. # #PING SWEEP (-sP, -sn, -sn -PY, -sY -PY) disabled by default. #Not enabled by default as most Nmap scans begin with an ARP who-has request, when using -p flag you #will see this detection preceding most scans. Also, you may see (noise) non-reconaissance related ARP #requests or even ones resulting from your own ICMP pings, this exclusive detection may fail if a scan uses -Pn flag. # #ICMP #Note: If nmap --disable-arp-ping flag is supplied for the scan it will be detected as ICMP ping. # #BLOCK -b offending IP(s) default is no blocking as packets can be spoofed causing DoS. #Firewall rule for blocks are in-bound "ANY" but still allows out-bound. #FW rules are named like ReconInformer_. # #DELETE FW RULE -d to remove FW rules for blocked hosts. # #WHITELIST -w HOST-IP(s) you never want to block on. # #FILTER DEST PORTS -f (filter_dst_port) cut down noisy ports like TCP 2869, NetBIOs 137 etc. #ignore packets destined for specific ports to try reduce false positive probe alerts. # #IGNORE HOST -n don't process packets from specific hosts, e.g. intranet-apps, printers and ACKS #from SMB connected shares to try reduce false positives. # #LOG -l flag, default size limit for writing to disk is 1MB. # #UDP protocol is ignored by default to try reduce false positives from sources like NetBIOS, SNMP etc. #To detect UDP scans use the -u flag, then can also combine with -f port filter #(reduce noise) on specific dest ports like 137,161,1900,2869,7680. # #PCAP saving -s flag, default size limit is also 1MB. # #RESTORE CONSOLE -r focus the console window (Win OS) if console is minimized on port scan detect. # #Private Network range: #Wrote this for basic LAN visibility for my attacker machine, packets from public IP ranges are ignored. # #BYPASS examples --scanflags and custom packet window sizes: #Recon-Informer does not try to detect every case of --scanflags or specially crafted packets. # #These scans can bypass Recon-Informer and correctly report open ports found. #nmap -n -Pn -sS --scanflags PSHSYN x.x.x.x -p139 #nmap -P0 -T4 -sS --scanflags=SYNPSH x.x.x.x # #Therefore, I accounted for some of these in Recon-Informer to report these detections. # #SCANFLAGS #nmap -P0 -T4 -sS --scanflags=SYNURG x.x.x.x -p139 (returns correct) #nmap -P0 -T4 -sS --scanflags=PSHSYNURG x.x.x.x -p21-445 (returns correct) #nmap -P0 -T4 -sS --scanflags=ECE x.x.x.x shows up as NULL scan (nothin useful returned) #nmap -n -Pn -sS --scanflags 0x42 x.x.x.x -p139 (useful) #nmap -n -Pn -sS --scanflags=SYNPSH x.x.x.x -p135 (useful) # #The above scanflag examples, would have bypassed detection if we didn't check packets for them. #Useful scanflags that return open ports and bypassed Recon-Informer prior to scanflag checks: # #10=(0x00a) SYNPSH #34= (0x22) SYNURG #42=(0x02a) SYNPSHURG #66 (0x42) SYNECN #74 (0x04a) SYNPSHECN #98 (0x062) SYNURGECN #106 (0x06a) SYNPSHURGECN #130 (0x082) SYNCWR #138 (0x08a) SYNPSHCWR #162 (0x0a2) SYNURGCWR #170 (0x0aa) SYNPSHURGCWR #194 (0x0c2) SYNECNCWR #202 (0x0ca) SYNPSHECNCWR #226 (0x0e2) SYNURGECNCWR #234 (0x0ea) SYNPSHURGECNCWR # #Custom packet window size from 1024 typical of Nmap SYN scans to a size of 666 for the bypass!. #ip=IP(dst="192.168.1.104") #syn=TCP(sport=54030,dport=139,window=666,flags="S") #send(ip/syn) # #Custom packet tests were tested on Kali to Win7/10 machines. #Recon-Informer trys to inform about most typical out-of-the-box type of scans. # #Service scans -A detection: #nmap -n -Pn -T4 -A x.x.x.x -p22 #If we scan from Kali Linux to Windows machine port 23 using -A we see SYN followed by XMAS #also we see an immediate high port of like 30000 or more. # #But scanning Windows ports 135 - 139 we see FSPU flags set so we can be fairly confident #it is a Service scan -A also it usually is followed by scanning high ports of 30000 or greater. # #However, I found that an easier way to pick up service -A scans is checking the window size. #If the window size is 65535 we can be fairly certain its a service -A scan. #Sometimes -A scan seems only to be detected when certain ports are hit. # #Example, Windows ports 135,139 or Kali Linux ports 1, 22 etc... #If not targeting port 135/139 (windows) -A detect may get missed. #Testing on newest nmap on Kali seemed to be easier to detect -A scan on ports other than 135/139. #Anyway, added this to try get more intel about possible incoming probes. # #DECOY SCAN -D detection set to a threshold of two or more ip-addresses. # #Examples: #capture TCP packets only, restores console on detection, detect ping sweep and ICMP #Recon-Informer.py -i -r -p # #capture UDP, whitelist ips, block, log, restore console, save pcap, detect XMAS,NULL on Win OS box. #Recon-Informer.py -i -u -w -b -l -r -a -s X,N # #capture UDP, filter ports, whitelist ips, block and deletes a previous FW rule #Recon-Informer.py -i -u -f 137,161 -w -b -d # #ignore specific hosts for whatever reason you may have #Recon-Informer.py -i -n host1, host2 # #capture TCP packets block all offending hosts (in-bound only) on detection, filter port 7680 MS WUDO #Recon-Informer.py -i -b -f 7680 # #Dependencies: #npcap or winpcap, scapy, clint and pygetwindow. # #Tested Win7/10/Linux/Kali - Wired Ethernet LAN and Wifi networks. # #Scapy Errors: #If get scapy runtime error "NameError: global name 'log_runtime' is not defined on scapy" #OR you get "ImportError: cannot import name NPCAP_PATH" #Download the latest https://github.com/secdev/scapy #They were bugs in scapy thats been fixed in 2.4.3. # #======================================================================================== #Packet window size tests: # #CONNECT -sT scan window size anomalies and example of port detection bypass. #Whats nice about detecting CONNECT scans is if someone does a telnet x.x.x.x it #should also get flagged by Recon-Informer. FYI, if SYN scan is run as non-root user #it becomes CONNECT scan. # #1) Custom scapy CONNECT scan from Kali to Win7/Win10 box with SYN flag set window size is 8192 #2) Nmap -sT CONNECT Win10 to Win7 used window size of 64240 #3) Nmap -sT CONNECT i686 i386 GNU/Linux box with Nmap v4.11 to Win7/Win10 had window size 5840 #4) Nmap -sT CONNECT Kali to Win7/Win10 used window size of 29200 #5) Nmap -sT CONNECT Win7 to Win10 also window size was 8192 as in case 1) # #Nmap versions 4.11, 7.70 and 7.80 were used for port scan testing: #However, we may not be able to catch them all, like when custom window size is used. # #False positives: #Some ports (MS UPNP Host port 2869) as they show up as CONNECT or MAIMON #scans on some noisy networks. HTTP GET requests can also be flagged as CONNECT scans. #TCP source port 443 can also get picked up from web browsers or webapps. #======================================================================================= # #VM and NAT setups: # #TEST -sZ COOKIE_ECHO: #1) Kali to Win (NAT) we see 3-way handshake and no SCTP packets #2) Win to Win 10. range we see the SCTP packets # #TEST -sT CONNECT #1) Win to Win 10.x.x.x range we see correct packets in wireshark #SYN packet with a large amount of TCP options # #If use NAT mode on VM the machine may perform 3-way handshake #Recon-Informer may report SYN scans as CONNECT scans as they become ambigous. # # #DISCLAIMER: #Author is NOT responsible for any damages whatsoever by using this software, #by using Recon Informer you assume and accept all risk implied or otherwise. #======================================================================================= BANNER=""" ____ ____ ____ / __ \___ _________ ____ / _/___ / __/___ _________ ___ ___ _____ / /_/ / _ \/ ___/ __ \/ __ \ / // __ \/ /_/ __ \/ ___/ __ `__ \/ _ \/ ___/ / _, _/ __/ /__/ /_/ / / / / _/ // / / / __/ /_/ / / / / / / / / __/ / /_/ |_|\___/\___/\____/_/ /_/ /___/_/ /_/_/ \____/_/ /_/ /_/ /_/\___/_/ v1 Intel for offensive systems --------------------------- By Hyp3rlinx ApparitionSec """ local_ip_address="" OS="win32" whitelist_conf="Recon-Whitelist.txt" ip_whitelist=set() attacker_ip_set=set() priv24 = re.compile("^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$") priv20 = re.compile("^192\.168\.\d{1,3}.\d{1,3}$") priv16 = re.compile("^172.(1[6-9]|2[0-9]|3[0-1]).[0-9]{1,3}.[0-9]{1,3}$") recon_log="ReconLog.txt" pcap_file="ReconPcap.pcap" max_log_sz=1024.0 #1MB default log and pcap file size limit service_scan_win_sz=65535 #Detect -A scan ip_proto_scan_lst=[] #Detect -sO scan scan_detect_lst=[] #Deal with OS and scans like FIN,NUL,XMAS #Enforce run as admin. def isAdmin(): try: is_admin = (os.getuid() == 0) except AttributeError: is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 if not is_admin: print("[!] Run me from an elevated command line.") exit() #Check FW rules exist. def getFirewall_rules(IP): global OS try: if OS=="win32": CMD="netsh advfirewall firewall show rule name=ReconInformer_"+IP+" verbose" else: CMD="iptables -L INPUT -v -n" net=Popen(CMD, shell=True, stderr=PIPE, stdout=PIPE ) output, errors = net.communicate() if IP in output: return True else: return False except Exception as e: pass return False #Block IP in-bound, allow out. def firewall_ip(ip): global OS try: if OS=="win32": if not getFirewall_rules(IP): os.system("netsh advfirewall firewall add rule name=ReconInformer_"+ip+" dir=in interface=any action=block remoteip="+ip+ ">nul 2>&1") else: #Block ANY new in-bound connection but allow outbound. if not getFirewall_rules(IP): os.system("iptables -A INPUT -s "+ip+" -m state --state NEW -j DROP") except Exception as e: print(str(e)) #Delete FW rules. def rem_firewall_rule(ip_lst): global OS try: for addr in ip_lst: time.sleep(0.3) if is_ip_private(addr): CMD="netsh advfirewall firewall delete rule name=ReconInformer_"+addr if OS!="win32": CMD="iptables -D INPUT -s "+addr+" -m state --state NEW -j DROP" if getFirewall_rules(addr): os.system(CMD) print(colored.cyan("[!] deleted fw rule: ReconInformer_"+addr)) time.sleep(2) else: print(colored.cyan("[!] Firewall rule: ReconInformer_"+addr+" does not exist.")) else: print(colored.cyan("[!] Invalid or non private ip-address.")) sys.stdout.flush() except Exception as e: print(str(e)) def valid_ip(addr): try: socket.inet_aton(addr) return True except socket.error: return False #Never block on specified hosts def whitelist(): global whitelist_conf, ip_whitelist if os.path.exists(whitelist_conf): if os.stat(whitelist_conf).st_size == 0: print(colored.cyan("[!] Recon_Whitelist.txt is empty.")) exit() wl=open(whitelist_conf, "r") for ip in wl: ip = ip.strip() if not valid_ip(ip): print(colored.cyan("[!] Invalid IP: "+ip)) else: #Check IP is in LAN range. if is_ip_private(ip): ip_whitelist.add(ip) else: print(colored.cyan("[!] Non private IP(s) will not be added: "+ip)) print(colored.cyan("[-] Whitelisting: ")+colored.green(ip)) time.sleep(0.1) wl.close() print("\n") else: print(colored.cyan(whitelist_conf+" does not exist.")) exit() sys.stdout.flush() #Disk write chk. def getsize(log_file): sz=0 try: if os.path.exists(log_file): sz = round(os.path.getsize(log_file)/float(1<<10)) except Exception as e: pass return sz def log(data): global recon_log, max_log_sz try: if getsize(recon_log) < max_log_sz: f=open(recon_log,"a") f.write(data+"\r\n") f.close() else: print(colored.cyan("[!] Log size of "+str(max_log_sz)+" limit reached, logging stopped.")) sys.stdout.flush() except Exception as e: pass def detection_time(): recon_time = str(datetime.now()) recon_time = recon_time.replace(":","-").replace(" ","_") return recon_time #Filter. def capture_filter(udp_capture, ping_sweep): global local_ip_address HOST="(dst net "+local_ip_address+")" WINDOW_SZ="tcp[14:2]==1024||tcp[14:2]==2048||tcp[14:2]==3072||tcp[14:2]==4096||tcp[14:2]==29200||tcp[14:2]==5840||tcp[14:2]==8192||tcp[14:2]==64240" SYN_SCAN="tcp[13]==2 && tcp[13]!=16" NULL_SCAN="tcp[13]==0" XMAS="tcp[13] & 1!=0 && tcp[13] & 32!=0 && tcp[13] & 8!=0" SCTP="sctp" FRAG="ip[6] = 32 or icmp[1]==4" ICMP="icmp" ARP="arp[6:2]==1" #opcode 1 (request) or 2 (reply). if udp_capture and not ping_sweep: return (HOST+"&&"+SYN_SCAN+"||"+XMAS+"||"+NULL_SCAN+"||"+WINDOW_SZ+"||"+SCTP+"||"+"udp"+"&&"+"dst net "+local_ip_address) elif udp_capture and ping_sweep: return (HOST+"&&"+SYN_SCAN+"||"+XMAS+"||"+NULL_SCAN+"||"+WINDOW_SZ+"||"+SCTP+"||"+ARP+"||"+"udp"+"&&"+"dst net "+local_ip_address) elif ping_sweep: return (HOST+"&&"+SYN_SCAN+"||"+XMAS+"||"+NULL_SCAN+"||"+WINDOW_SZ +"||"+SCTP+"||"+ICMP+"||"+FRAG+"||"+ARP+"&&"+"dst net "+local_ip_address) else: return (HOST+"&&"+SYN_SCAN+"||"+XMAS+"||"+NULL_SCAN+"||"+WINDOW_SZ +"||"+SCTP+"||"+ICMP+"||"+FRAG+"&&"+"dst net "+local_ip_address) #Private ip range. def is_ip_private(ip): global priv24,priv20,priv16 res = priv24.match(ip) or priv20.match(ip) or priv16.match(ip) return res is not None def fw_block_inbound(addr): fw_rules = getFirewall_rules(addr) if not fw_rules and addr in ip_whitelist: return colored.cyan("[!] Machine whitelisted.") elif not fw_rules and addr not in ip_whitelist: #Extra network range check if is_ip_private(addr): firewall_ip(addr) return colored.cyan(colored.magenta("[+] Blocking IP: "+addr)) else: return colored.cyan("[!] "+addr+" is blocked at the Firewall.") sys.stdout.flush() def save_pcap(pkt): global pcap_file, max_log_sz if getsize(pcap_file) < max_log_sz: try: wrpcap(pcap_file, pkt, append=True) except Exception as e: pass else: print(colored.cyan("[!] Pcap size of "+str(max_log_sz)+" limit reached, pcap not saved.")) sys.stdout.flush() def restore_console(): global recon_win, OS if recon_win and OS=="win32": #Restore console if minimized try: recon_win.restore() except Exception as e: pass def doit(pkt): global local_ip_address, _args, attacker_ip_set, ip_proto_scan_lst, OS, recon_win global gw, no_report_scan_list, dst_port_whitelist, scan_detect_lst SCAN_TYPE="" scan_flags="" service_scan="" fragmented=False addr="" dest="" mac="" pnum="" lines=60 #Deal with ping sweep -sn -sP try: if pkt.haslayer(ARP): addr = str(pkt[ARP].psrc) mac = str(pkt[Ether].src) print(colored.red("[+] Recon:"+" "*(len("ARP Ping sweep")-1)+"IP:"+" "*(len(addr)+2)+"MAC:"+" "*(len(mac)+1))) print(colored.cyan("[*] ARP Ping sweep" +" | " + addr + " | " + str(mac))) print(colored.red("-"*lines)) sys.stdout.flush() #IP layer, LAN and Check Target if IP not in pkt or not is_ip_private(pkt[0][IP].src) or pkt[0][IP].dst != local_ip_address: return #Ping if str(pkt.haslayer(ICMP)): if str(pkt.getlayer(ICMP).type) == "8": print(colored.cyan("[*] Ping detected from: "+pkt[0][IP].src)) print(colored.red("-"*lines)) sys.stdout.flush() except Exception as e: pass #Handle fragmented packets -f if str(pkt[0][IP].flags)=="MF": fragmented=True try: dest=str(pkt[0][IP].dst) addr=str(pkt[0][IP].src) mac=str(pkt[Ether].src) pnum=str(pkt[IP].dport) win_sz = pkt[0][IP].window #Skip ignored hosts or filtered dest ports. if addr in no_report_scan_list or pnum in dst_port_whitelist: return except Exception as e: pass #Report fragmented packets -f. if fragmented==True: SCAN_TYPE="Fragmented" try: if pnum != "": print(colored.red("[+] Recon:"+" "*(len("Fragmented")-1)+"IP:"+" "*(len(addr)+2)+"MAC:"+" "*(len(mac)+1)+"Port: ")) print(colored.cyan("[*] Fragmented" +" | " + addr + " | " + str(mac)+ " | " + pnum)) else: print(colored.red("[+] Recon:"+" "*(len("Fragmented")-1)+"IP:"+" "*(len(addr)+2)+"MAC:"+" "*(len(mac)+1))) print(colored.cyan("[*] Fragmented" +" | " + addr + " | " + str(mac))) print(colored.red("-"*lines)) sys.stdout.flush() except Exception as e: pass if _args.block_mode: print(fw_block_inbound(addr)) if _args.log_probe: info = "Source: " +addr + " | " + "Dest: "+dest + " | " + mac + " | " + "Fragmented packet | " + detection_time() log(info) if _args.archive: save_pcap(pkt) if recon_win and OS=="win32": restore_console() return #Noisy port if OS == "win32" and pnum == "2869": print(colored.cyan("[!] Port 2869 MS UPNP noise?, see -f flag")) sys.stdout.flush() #Noisy port if pnum == "7680": print(colored.cyan("[!] Port 7680 MS WUDO noise?, see -f flag")) sys.stdout.flush() if UDP in pkt[0]: SCAN_TYPE = "UDP" if TCP in pkt: try: flags = str(pkt[0][TCP].flags) options = str(pkt[0][TCP].options) if (flags=="S" or pkt[0][TCP].flags==0x002) and len(flags)==1: SCAN_TYPE = "SYN" #Handle useful --scanflags 0 - 255 if (flags=="SP") or (pkt[0][TCP].flags==0x00a) and len(flags)==2: SCAN_TYPE = "SYN" scan_flags="SYN, PSH" if (flags=="SU") or (pkt[0][TCP].flags==0x022) and len(flags)==2: SCAN_TYPE = "SYN" scan_flags = "SYN, URG" if (flags=="SPU") or (pkt[0][TCP].flags==0x02a) and len(flags)==3: SCAN_TYPE = "SYN" scan_flags = "SYN, PSH, URG" if (flags=="SE") or (pkt[0][TCP].flags==0x42) and len(flags)==2: SCAN_TYPE = "SYN" scan_flags = "SYN, ECN" if (flags=="SPE") or (pkt[0][TCP].flags==0x04a) and len(flags)==3: SCAN_TYPE = "SYN" scan_flags = "SYN, PSH, ECN" if (flags=="SUE") or (pkt[0][TCP].flags==0x062) and len(flags)==3: SCAN_TYPE = "SYN" scan_flags = "SYN, URG, ECN" if (flags=="SPUE") or (pkt[0][TCP].flags==0x06a) and len(flags)==4: SCAN_TYPE = "SYN" scan_flags = "SYN, PSH, URG, ECN" if (flags=="SC") or (pkt[0][TCP].flags==0x082) and len(flags)==2: SCAN_TYPE = "SYN" scan_flags = "SYN, CWR" if (flags=="SPC") or (pkt[0][TCP].flags==0x08a) and len(flags)==3: SCAN_TYPE = "SYN" scan_flags = "SYN, PSH, CWR" if (flags=="SUC") or (pkt[0][TCP].flags==0x0a2) and len(flags)==3: SCAN_TYPE = "SYN" scan_flags = "SYN, URG, CWR" if (flags=="SPUC") or (pkt[0][TCP].flags==0x0a2) and len(flags)==4: SCAN_TYPE = "SYN" scan_flags = "SYN, PSH, URG, CWR" if (flags=="SPUC") or (pkt[0][TCP].flags==0x0aa) and len(flags)==4: SCAN_TYPE = "SYN" scan_flags = "SYN, PSH, URG, CWR" if (flags=="SEC") or (pkt[0][TCP].flags==0x0c2) and len(flags)==3: SCAN_TYPE = "SYN" scan_flags = "SYN, ECN, CWR" if (flags=="SPEC") or (pkt[0][TCP].flags==0x0ca) and len(flags)==4: SCAN_TYPE = "SYN" scan_flags = "SYN, PSH, ECN, CWR" if (flags=="SUEC") or (pkt[0][TCP].flags==0x0e2) and len(flags)==4: SCAN_TYPE = "SYN" scan_flags = "SYN, URG, ECN, CWR" if (flags=="SPUEC") or (pkt[0][TCP].flags==0x0ea) and len(flags)==5: SCAN_TYPE = "SYN" scan_flags = "SYN, PSH, URG, ECN, CWR" #Handle -A Service scans. if (flags=="SE" or pkt[0][TCP].flags==0x042) and len(flags)==2: #We can miss detects from old systems unless hits port 135/139 (Win OS). service_scan="Service Scan -A" if (flags=="SEC" or pkt[0][TCP].flags==0x8c2) and len(flags)==3: service_scan="Service Scan -A" if (flags=="FSPU" or pkt[0][TCP].flags==0x02b) and len(flags)==4: service_scan="Service Scan -A" if win_sz == service_scan_win_sz: service_scan="Service Scan -A" if (flags=="S" or pkt[0][TCP].flags==0x002) and len(flags)==1 and len(options)>15: SCAN_TYPE = "CONNECT" lines=58 #FW scan -sA if (flags=="A" or pkt[0][TCP].flags==0x010) and len(flags)==1: SCAN_TYPE = "ACK" if "F" in scan_detect_lst or OS != "win32": if (flags=="F" or pkt[0][TCP].flags==0x001) and len(flags)==1: SCAN_TYPE = "FIN" if "N" in scan_detect_lst or OS != "win32": if (flags=="" or pkt[0][TCP].flags==0x000) and len(flags)==0: SCAN_TYPE = "NULL" if "X" in scan_detect_lst or OS != "win32": if (flags=="FPU" or pkt[0][TCP].flags==0x029) and len(flags)==3: SCAN_TYPE = "XMAS" if "M" in scan_detect_lst or OS != "win32": if (flags=="FA" or pkt[0][TCP].flags==0x011) and len(flags)==2: SCAN_TYPE = "MAIMON" lines=58 except Exception as e: pass else: try: if IP in pkt: if "SCTP": if (str(pkt[0][IP].flags)=="" or pkt[0][IP].flags == 0) and pkt[0][IP].len==52 and pkt[0][IP].type==1: SCAN_TYPE = "SCTP" if "SCTP_COOKIE_ECHO": if (str(pkt[0][IP].flags)=="" or pkt[0][IP].flags==0) and pkt[0][IP].type==10: SCAN_TYPE = "SCTP_COOKIE_ECHO" lines=69 except Exception as e: pass #Bail if no scan type. if SCAN_TYPE=="": return #Try detect IP Protocol scan, not full proof as consecutive ACK, SCTP packets will be flagged. if SCAN_TYPE=="ACK" or SCAN_TYPE=="SCTP" and len(ip_proto_scan_lst) < 2: #Don't add same scan type twice. if SCAN_TYPE not in ip_proto_scan_lst: ip_proto_scan_lst.append(SCAN_TYPE) if len(ip_proto_scan_lst)==2: print(colored.cyan("[*] Possible IP Protocol Scan -sO")) sys.stdout.flush() #Reset the list. ip_proto_scan_lst=[] #Clear any old one off ACK or SCTP scan flags hanging around. elif SCAN_TYPE != "ACK" or SCAN_TYPE != "SCTP": ip_proto_scan_lst=[] print(colored.red("[+] Recon:"+" "*(len(SCAN_TYPE)-1)+"IP:"+" "*(len(addr)+2)+"MAC:"+" "*(len(mac)+1)+"Port: ")) print(colored.green("[+] "+SCAN_TYPE + " | " + addr + " | " + str(mac) + " | " + pnum)) if scan_flags != "": print(colored.cyan("[*] --scanflags "+scan_flags)) if service_scan != "": print(colored.cyan("[*] "+service_scan)) if _args.block_mode: print(fw_block_inbound(addr)) if addr not in attacker_ip_set: attacker_ip_set.add(addr) if len(attacker_ip_set) >= 2: print(colored.cyan("[!] Multiple hosts detected, possible -D decoy scan.")) attacker_ip_set=set() print(colored.red("-"*lines)) sys.stdout.flush() #Log if _args.log_probe: try: info = ("Source: "+ addr + " | " + "Dest: "+local_ip_address+" | "+SCAN_TYPE+" | "+ "MAC: "+str(pkt[0][Ether].src)+" | "+ "Port: " + str(pkt[0][IP].dport)+" | "+detection_time()) if scan_flags != "": info = info + " | " + "--scanflags: " + scan_flags elif service_scan != "": info = info + " | " + service_scan elif scan_flags != "" and service_scan != "": info = info + " | " + "--scanflags: " + scan_flags + " | " + service_scan except Exception as e: pass finally: log(info) #Save PCAP if _args.archive: save_pcap(pkt) #Restore console if recon_win and OS=="win32": restore_console() def parse_args(): parser = argparse.ArgumentParser() parser.add_argument("-i", "--ip_addr", required=True, help=".") parser.add_argument("-b", "--block_mode", nargs="?", const="1", help="Block IP at Firewall, default block any in-bound, allow out.") parser.add_argument("-d", "--delete_fw", help="Unblock firewalled IP(s) <-d host1, host2>.") parser.add_argument("-u", "--udp", nargs="?", const="1", help="UDP capture.") parser.add_argument("-s", "--scan_type", help="Report non-workable anomalous (on Windows OS) scan types XMAS,FIN,NULL,MAIMON <-s X, F, N, M>.") parser.add_argument("-p", "--ping_sweep", nargs="?", const="1", help="Detect ping sweeps -sP, -sn, may fail if -Pn is used in the scan.") parser.add_argument("-f", "--filter_dst_port", help="Filter dest ports <-f 53,137,161,2869,..> reduce noise NBNS, DNS etc.") parser.add_argument("-w", "--whitelist", nargs="?", const="1", help="Whitelist IP from FW block.") parser.add_argument("-n", "--no_report", help="Ignore packets from server <-n host1, host2>.") parser.add_argument("-r", "--restore_console", nargs="?", const="1", help="Restores console window if minimized (Window only).") parser.add_argument("-a", "--archive", nargs="?", const="1", help="Save PCAP (appends to pcap) size limit 1MB.") parser.add_argument("-l", "--log_probe", nargs="?", const="1", help="Log detected probes (appends log) size limit set at 1MB.") return parser.parse_args() #Ensure module exists def haslib(lib): if not lib in (name for loader, name, ispkg in iter_modules()): print("[!] "+lib+ " does not exist, pip install "+lib) exit() return True #Try deal with known bugs in some scapy versions so people don't lose their minds. def scapy_ver(): ver = pkg_resources.get_distribution("scapy").version if ver=="2.4.1" or ver=="2.4.2": print("[!] Known bugs in scapy versions 2.4.1 and 2.4.2") print("[!] Scapy version detected is " +ver+" update to 2.4.3 or latest.") return False return True def recon_init(udp, ping_sweep): while True: try: sniff(filter = capture_filter(udp, ping_sweep), prn=doit, count=10, store=0) time.sleep(1) except Exception as e: pass def main(args): global _args, local_ip_address, OS, block_ip, recon_log, dst_port_whitelist global pcap_file, recon_win, gw, no_report_scan_list, scan_detect_lst if len(sys.argv)==1: parser.print_help(sys.stderr) sys.exit(1) #Assign args to global var to ref in other functions. _args = args print(colored.red("[*] Packets can be forged.")) print(colored.red("[*] False positives may occur.")) print(colored.red("[*] Attackers need protection too.")) print(colored.red("[*] Anything can be bypass, use at own risk.")) print(colored.red("[/] Listening...\n")) sys.stdout.flush() _os = sys.platform if _os!="win32": OS="Linux" recon_win=False dst_port_whitelist="" no_report_scan_list="" src_port_whitelist="" if OS=="win32": ctypes.windll.kernel32.SetConsoleTitleA("Recon-Informer v1") else: sys.stdout.write(b'\33]0;Recon-Informer v1\a') sys.stdout.flush() if args.restore_console and OS=="win32": try: import pygetwindow as gw recon_win = gw.getWindowsWithTitle("Recon-Informer v1")[0] except Exception as e: pass elif args.restore_console and OS!="win32": print(colored.cyan("[!] Skipped -r Windows only.")) if args.ip_addr: if not valid_ip(args.ip_addr): print(colored.cyan("[!] Invalid IP.")) exit() else: local_ip_address=args.ip_addr if args.block_mode: print(colored.cyan("[!] Warning -b, spoofing can DoS in-bound.")) if not args.whitelist: print(colored.cyan("[!] No whitelist, all IPs blocked.")) if args.udp: print(colored.cyan("[!] udp equals more noise, see -f or -n flags.")) if args.ping_sweep: print(colored.cyan("[!] I see your using -p, most Nmap scans start with ARP anyway.")) if args.filter_dst_port: dst_port_whitelist=args.filter_dst_port.upper().split(",") if OS=="win32" and args.scan_type: scan_detect_lst=args.scan_type.upper().split(",") elif OS != "win32" and args.scan_type: print(colored.cyan("[!] Ignoring -s flag, Non Windows OS.")) if OS=="win32" and len(scan_detect_lst)==0: print(colored.cyan("[!] FIN,NULL,XMAS,MAIMON scans are ignored on Windows")) print(colored.cyan("[!] Still wish to detect them? use -s flag, see -h.")) if args.whitelist and not args.block_mode: print(colored.cyan("[!] -w has no block mode (-b).")) exit() if args.block_mode and args.whitelist: whitelist() if args.no_report: no_report_scan_list=args.no_report.split(",") if args.log_probe: if os.path.exists(recon_log): if round(os.path.getsize(recon_log)/float(1<<10)) >= max_log_sz: print(colored.cyan("[!] Log file size of "+str(max_log_sz)+" limit reached, delete log file to continue logging.")) exit() if args.archive: if os.path.exists(pcap_file): if round(os.path.getsize(pcap_file)/float(1<<10)) >= max_log_sz: print(colored.cyan("[!] PCAP file size of "+str(max_log_sz)+" limit reached, delete pcap to continue saving.")) exit() if args.delete_fw: rem_firewall_rule(args.delete_fw.split(",")) #Listen for recon attempts. recon_init(args.udp, args.ping_sweep) if __name__=="__main__": isAdmin() try: if haslib("scapy"): from scapy.all import * scapy_ver() except Exception as e: if str(e) == "cannot import name NPCAP_PATH": scapy_ver() try: if haslib("clint"): from clint.textui import colored except Exception as e: print(str(e)) try: print(colored.red(BANNER)) time.sleep(0.2) sys.stdout.flush() except Exception as e: print(str(e)) parser = argparse.ArgumentParser() if len(sys.argv)==1: parser.print_help(sys.stderr) exit() main(parse_args())