#!/usr/bin/python #Copyright 2008, William Stearns #Released under the GPL. import sys import re from scapy import sniff #sr1,IP,ICMP #Count=0 #IPCount = { } #How many times have we seen this IP? SynSentToTCPService = { } #Boolean dictionary: have we seen a syn sent to this IP:Port pair yet? LiveTCPService = { } #Boolean dictionary: Have we seen a SYN/ACK come back (true) or a RST (False) from this IP:Port pair? LiveUDPService = { } #Boolean dictionary: we've found a server here ServerDescription = { } #String dictionary: What server is this IP:Port pair? UDPServerDescription = { } #Similar for UDP ClientDescription = { } #String dictionary: What client is on this IP:port? NOTE: the port here is the _server_ port at the other end. So if #Firefox on 1.2.3.4 is making outbound connections to port 80 on remote servers, ClientDescription['1.2.3.4:80'] = "http/firefox" MacAddr = { } #String dictionary: For a given IP (key), what is its mac (value)? MuteWarned = { } #Boolean dictionary: if true for a given key, we've warned that we won't report this object any more. EtherManuf = { } #String dictionary: for a given key of the first three octets of a mac address ("00:01:0F"), who made this card? def LoadMacData(MacFile): """Load Ethernet Mac address prefixes from standard locations (from ettercap, nmap, wireshark, and/or arp-scan).""" global EtherManuf More='' if (len(EtherManuf) > 0): More=' more' LoadCount = 0 try: MacHandle=open(MacFile, 'r') for line in MacHandle: if (len(line) >= 8) and (line[2] == ':') and (line[5] == ':'): MacHeader=line[:8] Manuf=line[8:].strip() #print "==", MacHeader, "==", Manuf, "==" if (not EtherManuf.has_key(MacHeader)): EtherManuf[MacHeader] = Manuf LoadCount += 1 #elif (string.hexdigits.find(line[0]) > -1) and (string.hexdigits.find(line[1]) > -1) ...: #Nah. :-) elif (len(line) >= 7) and (re.search('^[0-9A-F]{6}[ \t]', line) <> None): MacHeader=line[0:2] + ':' + line[2:4] + ':' + line[4:6] Manuf=line[7:].strip() #print "==", MacHeader, "==", Manuf, "==" if (not EtherManuf.has_key(MacHeader)): EtherManuf[MacHeader] = Manuf LoadCount += 1 MacHandle.close() if EtherManuf.has_key('00:00:00'): del EtherManuf['00:00:00'] #Not really Xerox LoadCount -= 1 print str(LoadCount) + More + " mac prefixes loaded from", MacFile return True except: print "Unable to load ", MacFile return False def ReportId(Type, Location, Description): """Print and log a new piece of network information.""" global ServerDescription global UDPServerDescription global ClientDescription global MacAddr global MuteWarned global EtherManuf if (Type == "TCPServer"): ServerDescription[Location] = Description print Type + ":" + Location + "=" + Description elif (Type == "UDPServer"): UDPServerDescription[Location] = Description print Type + ":" + Location + "=" + Description elif (Type == "TCPClient"): ClientDescription[Location] = Description print Type + ":" + Location + "=" + Description elif (Type == "MacAddr"): Found = False for Key in MacAddr.keys(): if (MacAddr[Key] == Description): Found=True break if Found: if (not MuteWarned.has_key(Description)): print "Duplicate IPs found for " + Description + ", no longer printing for it." MuteWarned[Description] = True else: MacAddr[Location] = Description if EtherManuf.has_key(Description[:8]): print Type + ":" + Location + "=" + Description + " (" + EtherManuf[Description[:8]] + ")" else: print Type + ":" + Location + "=" + Description def processpacket(p): """Extract information from a single packet off the wire.""" #p.show() #quit() global Count #global IPCount global SynSentToTCPService global LiveTCPService global LiveUDPService global ServerDescription global ClientDescription global MacAddr #Count=Count+1 #print Count if p['Ethernet.type'] == 0x0806: #ARP pass #print "ARP" #p.show() elif p['Ethernet.type'] == 0x0800: #IP sIP=str(p['IP.src']) dIP=str(p['IP.dst']) if not MacAddr.has_key(sIP): ReportId("MacAddr", sIP, p['Ethernet.src']) if not MacAddr.has_key(dIP): ReportId("MacAddr", dIP, p['Ethernet.dst']) #if not IPCount.has_key(p['IP.src']): # IPCount[p['IP.src']] = 0 #IPCount[p['IP.src']] += 1 #if not IPCount.has_key(p['IP.dst']): # IPCount[p['IP.dst']] = 0 #IPCount[p['IP.dst']] += 1 if p['IP.proto'] == 1: #ICMP pass ##print p['IP.src'], "(" + str(IPCount[p['IP.src']]) + ") -> ", p['IP.dst'], "(" + str(IPCount[p['IP.dst']]) + ") ", ##print "ICMP" #p.show() #print p['ICMP.type'], p['ICMP.code'] ##Nope print p['ICMP.payload.IP.proto'] #print p['Raw.load'] #OK ##print p['Raw.load.IP.proto'] #Nope #print 'portcheck:' + str(p['UDPerror.sport']) + ":", p['IPerror.proto'] #quit() #It doesn't look like IPerror/UDPerror fields actually get values #Stripme once we have UDPerror fields #sport=str(p['UDP.sport']) #dport=str(p['UDP.dport']) #Service = sIP + ":" + sport #if (sport == "53") and (p['DNS.qr'] == 1): #For some reason I can't check: (p.has_key('DNS.qr')) # #FIXME - check return code for dns failure? # if ((not LiveUDPService.has_key(Service)) or (LiveUDPService[Service] == False)): # LiveUDPService[Service] = True # ReportId("UDPServer", Service, "dns/generic") elif p['IP.proto'] == 2: #IGMP pass elif p['IP.proto'] == 6: #TCP sport=str(p['TCP.sport']) dport=str(p['TCP.dport']) #"(" + str(IPCount[p['IP.src']]) + ") "(" + str(IPCount[p['IP.dst']]) + ") #print p['IP.src'] + ":" + sport + " -> ", p['IP.dst'] + ":" + dport, #print "TCP ", if (p['TCP.flags'] & 0x17) == 0x12: #Syn-Ack (rst/fin off) #print "SA" Service = sIP + ":" + sport #If we've seen a syn sent to this port and have either not seen any SA/R, or we've seen a R in the past: #The last test is for a service that was previously closed and is now open; report each transition once. if ( (SynSentToTCPService.has_key(Service)) and ((not LiveTCPService.has_key(Service)) or (LiveTCPService[Service] == False)) ): LiveTCPService[Service] = True print "Live: " + Service #quit() elif (p['TCP.flags'] & 0x17) == 0x02: #Syn (ack/rst/fin off) #print "S" Service = dIP + ":" + dport if not SynSentToTCPService.has_key(Service): SynSentToTCPService[Service] = True #print "SynSentTo: " + Service #quit() elif (p['TCP.flags'] & 0x07) == 0x04: #Rst (syn/fin off) #print "R" Service = sIP + ":" + sport if ( (SynSentToTCPService.has_key(Service)) and ((not LiveTCPService.has_key(Service)) or (LiveTCPService[Service] == True)) ): LiveTCPService[Service] = False print "Dead: " + Service #quit() elif (p['TCP.flags'] & 0x17) == 0x10: #Ack (rst/syn/fin off) FromPort = sIP + ":" + sport ToPort = dIP + ":" + dport Payload = str(p['Raw.load']) if ( (LiveTCPService.has_key(FromPort)) and (LiveTCPService[FromPort] == True) and (LiveTCPService.has_key(ToPort)) and (LiveTCPService[ToPort] == True)): print "Logic failure: both " + FromPort + " and " + ToPort + " are listed as live services." elif ((LiveTCPService.has_key(FromPort)) and (LiveTCPService[FromPort] == True)): if (not ServerDescription.has_key(FromPort)): if (sport == "22") and ( (Payload.find('SSH-1.99-OpenSSH_') > -1) or (Payload.find('SSH-2.0-OpenSSH_') > -1) ): ReportId("TCPServer", FromPort, "ssh/openssh") elif (sport == "22") and (Payload.find('SSH-1.5-') > -1): ReportId("TCPServer", FromPort, "ssh/generic") elif (sport == "25") and (Payload.find(' ESMTP Sendmail ') > -1): ReportId("TCPServer", FromPort, "smtp/sendmail") elif (sport == "25") and (Payload.find(' - Welcome to our SMTP server ESMTP') > -1): ReportId("TCPServer", FromPort, "smtp/generic") elif (sport == "80") and (Payload.find('Server: Apache') > -1): ReportId("TCPServer", FromPort, "http/apache") elif (sport == "80") and (Payload.find('Server: gws') > -1): ReportId("TCPServer", FromPort, "http/gws") elif (sport == "80") and (Payload.find('Server: Microsoft-IIS') > -1): ReportId("TCPServer", FromPort, "http/iis") elif (sport == "80") and (Payload.find('Server: MIIxpc') > -1): ReportId("TCPServer", FromPort, "http/mirrorimage") elif (sport == "80") and (Payload.find('Server: nc -l -p 80') > -1): ReportId("TCPServer", FromPort, "http/nc") elif (sport == "80") and (Payload.find('Server: Sun-ONE-Web-Server/') > -1): ReportId("TCPServer", FromPort, "http/sun-one") elif (sport == "80") and (Payload.find('Server: TrustRank Frontend') > -1): ReportId("TCPServer", FromPort, "http/trustrank") elif (sport == "80") and (Payload.find('Server: YTS/') > -1): ReportId("TCPServer", FromPort, "http/yahoo") elif (sport == "143") and (Payload.find('* OK dovecot ready') > -1): ReportId("TCPServer", FromPort, "imap/dovecot") elif (sport == "143") and (Payload.find(' IMAP4rev1 ') > -1): ReportId("TCPServer", FromPort, "imap/generic") elif (sport == "783") and (Payload.find('SPAMD/1.1 ') > -1): ReportId("TCPServer", FromPort, "spamd/spamd") elif ( (sport == "3128") or (sport == "80") ) and (Payload.find('Via: ') > -1) and (Payload.find(' (squid/') > -1): ReportId("TCPServer", FromPort, "proxy/squid") else: if (not Payload == "None"): print "FromSrv:" + FromPort + "====" + Payload + "=====" elif ((LiveTCPService.has_key(ToPort)) and (LiveTCPService[ToPort] == True)): ClientKey = sIP + ":" + dport #Note: CLIENT ip and SERVER port if (not ClientDescription.has_key(ClientKey)): if (dport == "22") and ( (Payload.find('SSH-2.0-OpenSSH_') > -1) or (Payload.find('SSH-1.5-OpenSSH_') > -1) ): ReportId("TCPClient", ClientKey, "ssh/openssh") elif (dport == "25") and (Payload.find('Message-ID: -1): ReportId("TCPClient", ClientKey, "smtp/pine") elif ( (dport == "80") or (dport == "3128") ) and (Payload.find('User-Agent: libwww-perl/') > -1): ReportId("TCPClient", ClientKey, "http/libwww-perl") elif ( (dport == "80") or (dport == "3128") ) and (Payload.find('User-Agent: Lynx') > -1): ReportId("TCPClient", ClientKey, "http/lynx") elif ( (dport == "80") or (dport == "3128") ) and (Payload.find('User-Agent: Mozilla') > -1) and (Payload.find(' Firefox/') > -1): ReportId("TCPClient", ClientKey, "http/firefox") elif ( (dport == "80") or (dport == "3128") ) and (Payload.find('User-Agent: Wget/') > -1): ReportId("TCPClient", ClientKey, "http/wget") elif (dport == "143") and (Payload.find('A0001 CAPABILITY') > -1): ReportId("TCPClient", ClientKey, "imap/generic") elif (dport == "783") and (Payload.find('PROCESS SPAMC') > -1): ReportId("TCPClient", ClientKey, "spamd/spamc") else: if (not Payload == "None"): print "FromCli:" + ClientKey + "====" + Payload + "=====" else: #Neither port pair is known as a server pass #Following is debugging at best; it should only show up early on as the sniffer listens to conversations for which it didn't hear the SYN/ACK #print "note: neither " + FromPort + " nor " + ToPort + " is listed as a live service." #p.show() #p.show() #quit() elif p['IP.proto'] == 17: #UDP sport=str(p['UDP.sport']) dport=str(p['UDP.dport']) Service = sIP + ":" + sport if (sport == "53") and (p['DNS.qr'] == 1): #For some reason I can't check: (p.has_key('DNS.qr')) #FIXME - check return code for dns failure? if ((not LiveUDPService.has_key(Service)) or (LiveUDPService[Service] == False)): LiveUDPService[Service] = True ReportId("UDPServer", Service, "dns/generic") #print p['IP.src'] + ":" + str(p['UDP.sport']) + " -> ", p['IP.dst'] + ":" + str(p['UDP.dport']), #print "UDP" else: #print p['IP.src'], "(" + str(IPCount[p['IP.src']]) + ") -> ", p['IP.dst'], "(" + str(IPCount[p['IP.dst']]) + ") ", print "Other IP protocol (", p['IP.src'], "->", p['IP.dst'], "): ", p['IP.proto'] p.show() else: print "Unregistered ethernet type:", p['Ethernet.type'] p.show() if len(sys.argv) > 1: bpfilter = sys.argv[1] else: bpfilter = "" LoadMacData('/usr/share/ettercap/etter.finger.mac') LoadMacData('/usr/share/nmap/nmap-mac-prefixes') LoadMacData('/usr/share/wireshark/manuf') LoadMacData('/usr/share/arp-scan/ieee-oui.txt') if (len(EtherManuf) == 0): print "None of the default mac address listings found. Please install ettercap, nmap, wireshark, and/or arp-scan." else: print len(EtherManuf), "mac prefixes loaded." sniff(filter=bpfilter, prn=lambda x: processpacket(x), store=0) #, count=500 #New signatures from: #pads-signature-list #nmap-service-probes #scapy.ls(scapy.Ether)