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.
631fc764a07667ba55ccff741ea4c5d703fb716cdd19dbee4f7067779fe7db39
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) v1.2
#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
#==========================================================
#v1.2 fixed: window title bug, removed pygetwindow module.
#
#Recon Informer is a basic real-time anti-reconnaissance (nmap) 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_<HOST-IP>.
#
#DELETE FW RULE -d <IP-ADDR> 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 <ATTACKER-BOX> -r -p
#
#capture UDP, whitelist ips, block, log, restore console, save pcap, detect XMAS,NULL on Win OS box.
#Recon-Informer.py -i <ATTACKER-BOX> -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 <ATTACKER-BOX> -u -f 137,161 -w -b -d <HOST-IP>
#
#ignore specific hosts for whatever reason you may have
#Recon-Informer.py -i <ATTACKER-BOX> -n host1, host2
#
#capture TCP packets block all offending hosts (in-bound only) on detection, filter port 7680 MS WUDO
#Recon-Informer.py -i <ATTACKER-BOX> -b -f 7680
#
#Dependencies:
#npcap or winpcap, scapy, clint.
#
#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 <port> 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.2
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:
ctypes.windll.user32.ShowWindow( ctypes.windll.kernel32.GetConsoleWindow(), 9)
except Exception as e:
print(str(e))
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="<ATTACKER-IP-ADDR>.")
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":
from os import system
system("title Recon Informer v1.2")
else:
sys.stdout.write(str('\33]0;Recon Informer v1.2\a'))
sys.stdout.flush()
if args.restore_console and OS=="win32":
recon_win = True
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())