Jump to content
Kev

Recon Informer

Recommended Posts

Posted

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.

 

Recon-Informer.py

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_<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 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 <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
    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="<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":
        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())

Source

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.



×
×
  • Create New...