#!/usr/bin/python #Copyright 2008, William Stearns #Passer is a PASsive SERvice sniffer. #Home site http://www.stearns.org/passer/ #Dedicated to Mae Anne Laroche. #Released under the GPL. #Version 1.1 #======== Imports ======== import sys import re from scapy import sniff, p0f #sr1,IP,ICMP import os #======== Global arrays ======== #These two are used to discover servers. If we've seen a SYN go to a port, and a SYN/ACK back from it, #that's a pretty good sign it's a server. Not truly stateful, but a generally good guess. SynSentToTCPService = { } #Boolean dictionary: Have we seen a syn sent to this "IP,Proto_Port" pair yet? LiveTCPService = { } #Boolean dictionary: Have we seen a SYN/ACK come back (true) or a RST (False) from this "IP,Proto_Port" pair? #Next two are used to discover clients. If we've seen a SYN/ACK going to what appears to be a client port, and it #later responds with a FIN, we'll call that a live TCP client. SynAckSentToTCPClient = { } #Boolean dictionary: Have we seen a SYN/ACK sent to this "IP,Proto_Port" pair yet? LiveTCPClient = { } #Boolean dictionary: Have we seen a FIN from this client, indicating a 3 way handshake and successful conversation? #And finally UDP. The only port we work with is DNS at the moment. LiveUDPService = { } #Boolean dictionary: we've found a server here at "IP,Proto_Port" ServerDescription = { } #String dictionary: What server is this "IP,Proto_Port" pair? ClientDescription = { } #String dictionary: What client is on this "IP,Proto_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,TCP_80'] = "http/firefox" OSDescription = { } #String dictionary: What OS is this IP key? MacAddr = { } #String dictionary: For a given IP (key), what is its mac (value)? EtherManuf = { } #String dictionary: for a given key of the first three uppercase octets of a mac address ("00:01:0F"), who made this card? LogFileName = '/var/tmp/passer-log' ServiceFPs = { } #Dictionary of service fingerprints. Keys are straight int port numbers (no TCP or UDP), or 'all' for strings that need #to be matched against all ports. These are loaded from nmap's "nmap-service-probes", ignoring the probes since we're passive. #Values are lists of tuples, ala: [("Apache *server ready.", "Apache web"), ("VSFTPD FTP at your service", "vsftpd ftp")] #Note that the first object in a tuple is a _compiled regex_ rather than the printable strings I've used above. #A sample (non-compiled) version looks like: {80: [('^Server: Apache/', 'http/apachewebserver')]} Devel = False #For my internal use to look for new service strings #This payload logging is disabled when Devel == False #Quite likely a security risk, I don't recommend enabling it. ServerPayloadDir = '/var/tmp/passer-server/' ClientPayloadDir = '/var/tmp/passer-client/' #======== Functions ======== def Debug(DebugStr): """Prints a note to stderr""" sys.stderr.write(DebugStr + '\n') def LogNewPayload(PayloadDir, PayloadFile, Payload): """Saves the payload from an ack packet to a file named after the server or client port involved.""" global Devel if (Devel == True): if os.path.isdir(PayloadDir): if (not Payload == "None"): pfile=open(PayloadFile, 'a') pfile.write(Payload) pfile.close() 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 if os.path.isfile(MacFile): try: MacHandle=open(MacFile, 'r') for line in MacHandle: if (len(line) >= 8) and (line[2] == ':') and (line[5] == ':'): #uppercase incoming strings just in case one of the files uses lowercase MacHeader=line[:8].upper() Manuf=line[8:].strip() 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=str.upper(line[0:2] + ':' + line[2:4] + ':' + line[4:6]) Manuf=line[7:].strip() 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 Debug(str(LoadCount) + More + " mac prefixes loaded from" + str(MacFile)) return True except: Debug("Unable to load " + str(MacFile)) return False else: Debug("Unable to load " + str(MacFile)) return False def LoadNmapServiceFP(ServiceFileName): """Load nmap fingerprints from nmap-service-probes, usually in /usr/share/nmap.""" global ServiceFPs LoadCount = 0 CompileSuccess = 0 CompileFail = 0 PortList = "" PortArray = [ ] if os.path.isfile(ServiceFileName): try: ServiceHandle = open(ServiceFileName, "r") for line in ServiceHandle: if (len(line) >= 5) and (line[0:6] == "Probe "): #print "==== PROBE ====" PortArray = [ ] #print len(PortArray), PortArray #len of empty array is 0 elif (len(line) >= 5) and (line[0:6] == "match "): #print "match" #print line #Sample line: # match srun m|^X\0\0\0$| p/Caucho Resin JSP Engine srun/ Remainder=line[6:].strip() # srun m|^X\0\0\0$| p/Caucho Resin JSP Engine srun/ MatchStart=Remainder.find(" m") # 4 ProtoString=Remainder[:MatchStart].replace(',', ';') # srun Separator=Remainder[MatchStart+2:MatchStart+3] # | MatchEnd=Remainder.find(Separator,MatchStart+3) # 16 MatchString=Remainder[MatchStart+3:MatchEnd] # ^X\0\0\0$ ServerDescription=Remainder[MatchEnd+2:].replace(',', ';') # p/Caucho Resin JSP Engine srun/ #Substitute ; for , in ProtoString and ServerDescription since we're using commas as field delimiters in output #print MatchString, " ", ProtoString + "://" + ServerDescription try: #We try to compile the MatchString now before inserting into ServiceFPs so the work only needs to be #done once. If this fails - and I don't know why 413 out of 3671 match lines fail to compile - #we fall down to the except and simply don't use the tuple. Hmmm. SearchTuple=(re.compile(MatchString, re.M), ProtoString + "://" + ServerDescription) CompileSuccess += 1 if (len(PortArray) == 0): #No ports declared yet; we'll place this search pair under the special port "all" if (not(ServiceFPs.has_key('all'))): ServiceFPs['all'] = [ ] ServiceFPs['all'].append(SearchTuple) LoadCount += 1 else: #Register this search pair for every port requested for OnePort in PortArray: if (not(ServiceFPs.has_key(int(OnePort)))): ServiceFPs[int(OnePort)] = [ ] ServiceFPs[int(OnePort)].append(SearchTuple) LoadCount += 1 except: #FIXME - figure out why the failures #print "Failed to compile " + MatchString CompileFail += 1 #if (LoadCount > 10): # print ServiceFPs # quit() elif (len(line) >= 5) and (line[0:6] == "ports "): PortArray = [ ] RawPortsString=line[6:].strip() #print "ports are ", RawPortsString for PortBlock in RawPortsString.split(","): #Each PortBlock is either an individual port or port range if (PortBlock.find("-") > -1): #We have a port range PortRange=PortBlock.split("-") for OnePort in range(int(PortRange[0]), int(PortRange[1]) + 1): PortArray.append(OnePort) else: PortArray.append(PortBlock) #print len(PortArray), PortArray elif (len(line) >= 9) and (line[0:10] == "softmatch "): pass #softmatches look very weak at the moment; none give a productname. Skip for the moment. #print "softmatch" ServiceHandle.close() Debug(str(CompileSuccess) + " successful compiles, " + str(CompileFail) + " failures.") return True except: Debug("Failed to load " + ServiceFileName) return False else: Debug("Unable to find " + ServiceFileName) return False def ReportId(Type, IPAddr, Proto, State, Description): """Print and log a new piece of network information.""" #Can't use : for separator, IPv6, similarly '.' for ipv4 #Can't use "/" because of filesystem #Don't want to use space because of filesystem # Type, IPAddr, Proto State Optional description (may be empty) # 'IP', IPaddr, 'IP', dead or live, p0f OS description # 'MA', IPaddr, 'Ethernet', MacAddr, ManufDescription # 'TC', IPaddr, 'TCP_'Port, closed or open, client description # 'TS', IPaddr, 'TCP_'Port, closed or listening, server description # 'UD', IPaddr, 'UDP_'Port, open or closed, udp port description global ServerDescription global ClientDescription global MacAddr global EtherManuf global LogFile global OSDescription #No longer needed #global MuteWarned Location = IPAddr + "," + Proto if (Type == "TS"): #Only assign this (and the others in this function) if Description non-null if (Description != ''): ServerDescription[Location] = Description elif (Type == "UD"): if (Description != ''): ServerDescription[Location] = Description elif (Type == "TC"): if (Description != ''): ClientDescription[Location] = Description elif (Type == "IP"): if (Description != ''): OSDescription[IPAddr] = Description elif (Type == "MA"): State = State.upper() MacAddr[IPAddr] = State if EtherManuf.has_key(State[:8]): Description = EtherManuf[State[:8]] OutString = Type + "," + IPAddr + "," + Proto + "," + State + "," + Description print OutString LogFile.write(OutString + '\n') LogFile.flush() def processpacket(p): """Extract information from a single packet off the wire.""" global SynSentToTCPService global SynAckSentToTCPClient global LiveTCPService global LiveUDPService global LiveTCPClient global ServerDescription global ClientDescription global MacAddr global OSDescription global ServiceFPs if p['Ethernet.type'] == 0x0806: #ARP #pull arp data from here instead of tcp/udp packets, as these are all local if (p['ARP.op'] == 2): #1 is request ("who-has"), 2 is reply ("is-at") IPAddr=p['ARP.psrc'] MyMac=p['ARP.hwsrc'].upper() if (not MacAddr.has_key(IPAddr)) or (MacAddr[IPAddr] != MyMac): ReportId("MA", IPAddr, 'Ethernet', MyMac, '') elif p['Ethernet.type'] == 0x0800: #IP sIP=str(p['IP.src']) dIP=str(p['IP.dst']) #Best to get these from arps instead; if we get them from here, we get router macs for foreign addresses. #if not MacAddr.has_key(sIP): # ReportId("MA", sIP, "Ethernet", p['Ethernet.src'], '') #if not MacAddr.has_key(dIP): # ReportId("MA", dIP, "Ethernet", p['Ethernet.dst'], '') if p['IP.proto'] == 1: #ICMP pass ##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'] #It doesn't look like IPerror/UDPerror fields actually get values. Hmmmmm. #But oddly enough, p.show does seem to _show_ the values. Hmmmm. #Stripme once we have UDPerror fields #sport=str(p['UDP.sport']) #dport=str(p['UDP.dport']) #Service = sIP + ",UDP_" + sport #if (sport == "53") and (p['DNS.qr'] == 1): #For some reason I can't check: (p.has_key('DNS.qr')) # if ((not LiveUDPService.has_key(Service)) or (LiveUDPService[Service] == False)): # LiveUDPService[Service] = True # ReportId("UD", sIP, "UDP_" + sport, 'open', "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']) #print p['IP.src'] + ":" + sport + " -> ", p['IP.dst'] + ":" + dport, if (p['TCP.flags'] & 0x17) == 0x12: #SYN/ACK (RST and FIN off) CliService = dIP + ",TCP_" + sport if not SynAckSentToTCPClient.has_key(CliService): SynAckSentToTCPClient[CliService] = True #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. Service = sIP + ",TCP_" + sport if ( (SynSentToTCPService.has_key(Service)) and ((not LiveTCPService.has_key(Service)) or (LiveTCPService[Service] == False)) ): LiveTCPService[Service] = True ReportId("TS", sIP, "TCP_" + sport, "listening", '') elif (p['TCP.flags'] & 0x17) == 0x02: #SYN (ACK, RST, and FIN off) Service = dIP + ",TCP_" + dport if not SynSentToTCPService.has_key(Service): SynSentToTCPService[Service] = True #Debug("trying to fingerprint " + sIP) try: if (len(p0f(p)) >=1): PDescription = p0f(p)[0][0] + " " + p0f(p)[0][1] #FIXME - Grabbing just the first candidate, may need to compare correlation values; provided? PDescription = PDescription.replace(',', ';') #Commas are delimiters in output if (not(OSDescription.has_key(sIP))) or (OSDescription[sIP] != PDescription): OSDescription[sIP] = PDescription ReportId("IP", sIP, "IP", "live", PDescription) except: PDescription = 'p0f failure' if (not(OSDescription.has_key(sIP))) or (OSDescription[sIP] != PDescription): Debug("P0f failure in " + sIP + ":" + sport + " -> " + dIP + ":" + dport) OSDescription[sIP] = PDescription ReportId("IP", sIP, "IP", "live", PDescription) elif (p['TCP.flags'] & 0x07) == 0x01: #FIN (SYN/RST off) CliService = sIP + ",TCP_" + dport if ( (SynAckSentToTCPClient.has_key(CliService)) and ((not LiveTCPClient.has_key(CliService)) or (LiveTCPClient[CliService] == False)) ): LiveTCPClient[CliService] = True ReportId("TC", sIP, "TCP_" + dport, "open", '') elif (p['TCP.flags'] & 0x07) == 0x04: #RST (SYN and FIN off) #FIXME - handle rst going in the other direction? Service = sIP + ",TCP_" + sport if ( (SynSentToTCPService.has_key(Service)) and ((not LiveTCPService.has_key(Service)) or (LiveTCPService[Service] == True)) ): LiveTCPService[Service] = False ReportId("TS", sIP, "TCP_" + sport, "closed", '') elif (p['TCP.flags'] & 0x17) == 0x10: #ACK (RST, SYN, and FIN off) FromPort = sIP + ",TCP_" + sport ToPort = dIP + ",TCP_" + 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 the "From" side is a known TCP server: if (not ServerDescription.has_key(FromPort)): #Stub code to test ServiceFPs regex matching #if (sport == "80"): # print "In regex block" # MatchTuple=ServiceFPs[80][0] # #CompiledRegex=re.compile(MatchTuple[0]) # #Above works just fine with non-compiled. Let's precompile at insert time to save time - works fine :-D : # CompiledRegex=MatchTuple[0] # #MatchObject=CompiledRegex.search(Payload) # #if (MatchObject.start > -1): #Nope, "NoneType has no attribute "start"" # #if (MatchObject != None): # #Simpler than above and works: # if (CompiledRegex.search(Payload) != None): #re.M flag only works at compile time, not here # print "Test match found", MatchTuple[1] #Check nmap fingerprint strings for this server port if (ServiceFPs.has_key(int(sport))): #Debug("Checking specific port FPs") for OneTuple in ServiceFPs[int(sport)]: if (OneTuple[0].search(Payload) != None): #Debug("Found specific port FP") ReportId("TS", sIP, "TCP_" + sport, "listening", OneTuple[1]) break #Exit for loop, no need to check any more fingerprints now that we've found a match if (not ServerDescription.has_key(FromPort)): #If the above loop didn't find a server description if (ServiceFPs.has_key('all')): #Now recheck against the 'all' port #Debug("Checking generic FPs") for OneTuple in ServiceFPs['all']: if (OneTuple[0].search(Payload) != None): #Debug("Found generic FP") ReportId("TS", sIP, "TCP_" + sport, "listening", OneTuple[1]) break if (sport == "22") and (Payload.find('SSH-') > -1): if ( (Payload.find('SSH-1.99-OpenSSH_') > -1) or (Payload.find('SSH-2.0-OpenSSH_') > -1) ): ReportId("TS", sIP, "TCP_" + sport, "listening", "ssh/openssh") elif (Payload.find('SSH-1.5-') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "ssh/generic") #LogNewPayload(ServerPayloadDir, FromPort, Payload) else: LogNewPayload(ServerPayloadDir, FromPort, Payload) elif (sport == "25") and (Payload.find(' ESMTP Sendmail ') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "smtp/sendmail") elif (sport == "25") and (Payload.find(' - Welcome to our SMTP server ESMTP') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "smtp/generic") LogNewPayload(ServerPayloadDir, FromPort, Payload) #Check for port 80 and search for "Server: " once elif (sport == "80") and (Payload.find('Server: ') > -1): if (Payload.find('Server: Apache') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "http/apache") elif (Payload.find('Server: Embedded HTTP Server') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "http/embedded") elif (Payload.find('Server: gws') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "http/gws") elif (Payload.find('Server: KFWebServer') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "http/kfwebserver") elif (Payload.find('Server: micro_httpd') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "http/micro-httpd") elif (Payload.find('Server: Microsoft-IIS') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "http/iis") elif (Payload.find('Server: lighttpd') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "http/lighttpd") elif (Payload.find('Server: MIIxpc') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "http/mirrorimage") elif (Payload.find('Server: mini_httpd') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "http/mini-httpd") elif (Payload.find('Server: nc -l -p 80') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "http/nc") elif (Payload.find('Server: nginx/') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "http/nginx") elif (Payload.find('Server: Nucleus') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "http/nucleus") elif (Payload.find('Server: RomPager') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "http/rompager") elif (Payload.find('Server: Server') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "http/server") elif (Payload.find('Server: Sun-ONE-Web-Server/') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "http/sun-one") elif (Payload.find('Server: TrustRank Frontend') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "http/trustrank") elif (Payload.find('Server: YTS/') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "http/yahoo") elif (Payload.find('HTTP/1.0 404 Not Found') > -1) or (Payload.find('HTTP/1.1 200 OK') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "http/generic") LogNewPayload(ServerPayloadDir, FromPort, Payload) else: LogNewPayload(ServerPayloadDir, FromPort, Payload) elif (sport == "143") and (Payload.find('* OK dovecot ready') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "imap/dovecot") elif (sport == "143") and (Payload.find(' IMAP4rev1 ') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "imap/generic") LogNewPayload(ServerPayloadDir, FromPort, Payload) elif (sport == "783") and (Payload.find('SPAMD/1.1 ') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "spamd/spamd") elif ( (sport == "3128") or (sport == "80") ) and (Payload.find('Via: ') > -1) and (Payload.find(' (squid/') > -1): ReportId("TS", sIP, "TCP_" + sport, "listening", "proxy/squid") else: LogNewPayload(ServerPayloadDir, FromPort, Payload) elif ((LiveTCPService.has_key(ToPort)) and (LiveTCPService[ToPort] == True)): #If the "To" side is a known TCP server: ClientKey = sIP + ",TCP_" + 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("TC", sIP, "TCP_" + dport, "open", "ssh/openssh") elif (dport == "25") and (Payload.find('Message-ID: -1): ReportId("TC", sIP, "TCP_" + dport, "open", "smtp/pine") elif ( (dport == "80") or (dport == "3128") ) and (Payload.find('User-Agent: libwww-perl/') > -1): ReportId("TC", sIP, "TCP_" + dport, "open", "http/libwww-perl") elif ( (dport == "80") or (dport == "3128") ) and (Payload.find('User-Agent: Lynx') > -1): ReportId("TC", sIP, "TCP_" + dport, "open", "http/lynx") elif ( (dport == "80") or (dport == "3128") ) and (Payload.find('User-Agent: Mozilla') > -1) and (Payload.find(' Firefox/') > -1): ReportId("TC", sIP, "TCP_" + dport, "open", "http/firefox") elif ( (dport == "80") or (dport == "3128") ) and (Payload.find('User-Agent: Wget/') > -1): ReportId("TC", sIP, "TCP_" + dport, "open", "http/wget") elif (dport == "143") and (Payload.find('A0001 CAPABILITY') > -1): ReportId("TC", sIP, "TCP_" + dport, "open", "imap/generic") #LogNewPayload(ClientPayloadDir, ClientKey, Payload) elif (dport == "783") and (Payload.find('PROCESS SPAMC') > -1): ReportId("TC", sIP, "TCP_" + dport, "open", "spamd/spamc") else: LogNewPayload(ClientPayloadDir, 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." #else: #Test for other TCP flag combinations here elif p['IP.proto'] == 17: #UDP #FIXME - possibly run udp packets through ServiceFPs as well? sport=str(p['UDP.sport']) #dport=str(p['UDP.dport']) Service = sIP + ",UDP_" + sport #FIXME - clean these up if not needed any more 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 #Also report the TLD from one of the query answers to show what it's willing to answer for? ReportId("UD", sIP, "UDP_" + sport, "open", "dns/generic") else: print "Other IP protocol (", p['IP.src'], "->", p['IP.dst'], "): ", p['IP.proto'] p.show() elif p['Ethernet.type'] == 0x86DD: #IPv6 pass else: print "Unregistered ethernet type:", p['Ethernet.type'] p.show() #======== Start of main code. ======== #FIXME - put in try/except, and CLP if os.path.isfile(LogFileName): LogFile=open(LogFileName, 'a') if (not(os.path.isfile("/etc/p0f/p0f.fp"))): Debug("/etc/p0f/p0f.fp not found; please install p0f version 2 to enable OS fingerprinting.") 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): Debug("None of the default mac address listings found. Please install ettercap, nmap, wireshark, and/or arp-scan.") else: Debug(str(len(EtherManuf)) + " mac prefixes loaded.") LoadNmapServiceFP('/usr/share/nmap/nmap-service-probes') if (len(ServiceFPs) == 0): Debug("Can't locate /usr/share/nmap/nmap-service-probes. Please install nmap to support more server descriptions.") else: Debug("Fingerprints for " + str(len(ServiceFPs)) + " ports loaded.") #To force offline mode (reading a pcap file), uncomment the next line, put in the pcap file name, and comment out the line after that. #sniff(store=0, offline='/home/wstearns/med/pcap/perimeter_class.cap', filter=bpfilter, prn=lambda x: processpacket(x)) sniff(store=0, filter=bpfilter, prn=lambda x: processpacket(x)) #To limit to the first 500 packets, add ", count=500" at the end of the "sniff" command inside the last paren #Bill's notes to himself. #To debug: #p.show() #quit() #scapy.ls(scapy.Ether) #No longer needed #MuteWarned = { } #Boolean dictionary: if true for a given key, we've warned that we won't report this object any more. #Former code to mute multiple IP's for a single mac: # Found = False # for Key in MacAddr.keys(): # if (MacAddr[Key] == State): # Found=True # break # if Found: # if (not MuteWarned.has_key(State)): # Debug("Duplicate IPs found for " + State + ", no longer printing for it.") # MuteWarned[State] = True # else: #if not IPCount.has_key(p['IP.src']): # IPCount[p['IP.src']] = 0 #IPCount[p['IP.src']] += 1 #TODO Identify routers/nics with >1 IP #TODO Use unreachables #TODO: fingerprint OS based on echo payloads #New signatures from: #pads-signature-list (appears to be an import of the nmap-service-probes anyways) #nmap-service-probes (done) #Temporary code to load up a stub ServiceFPs #ServiceFPs[80] = [ ] #MatchTuple=('Apache', "http/apachewebserver") #Above works just fine, but lets precompile regexes as they're being stored: #"M" is needed so "^" and "$" work in a multiline payload block #MatchTuple=(re.compile('^Server: Apache/', re.M), "http/apachewebserver") #ServiceFPs[80].append(MatchTuple) #print ServiceFPs #Result: {80: [('Apache', 'http/apachewebserver')]}