├── bin ├── arpwatch.py ├── cdpsniff.py ├── dhcpos.py ├── mailsnarf.py ├── ngrep.py ├── p0f2.py └── urlsnarf.py ├── dsniff ├── __init__.py ├── core │ ├── __init__.py │ ├── flow.py │ ├── ip.py │ ├── pkt.py │ └── service.py ├── dsniff.py ├── lib │ ├── __init__.py │ ├── fcap.py │ ├── html.py │ ├── http.py │ ├── io.py │ ├── itree.py │ ├── json.py │ ├── mbox.py │ ├── mime.py │ ├── net.py │ ├── pyparsing.py │ └── reasm.py └── mail │ ├── __init__.py │ ├── _webmail.py │ ├── aolmail.py │ ├── fastmail.py │ ├── gmail.py │ ├── hotmail.py │ ├── pop.py │ ├── smtp.py │ └── yahoomail.py ├── setup.py └── share ├── dhcpos_fingerprints.txt ├── p0f.fp ├── p0fa.fp ├── p0fo.fp └── p0fr.fp /bin/arpwatch.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # $Id$ 4 | 5 | import cPickle, sys 6 | import dnet, dpkt 7 | import dsniff 8 | 9 | class ArpWatch(dsniff.Handler): 10 | name = 'arpwatch' 11 | filename = False 12 | 13 | def setup(self): 14 | try: 15 | self.cache = cPickle.load(open(self.filename)) 16 | print >>sys.stderr, 'loaded %s entries from %s' % (len(self.cache), self.filename) 17 | except IOError: 18 | self.cache = {} 19 | self.subscribe('pcap', 'arp[6:2] = 2', self.recv_pkt) 20 | 21 | def teardown(self): 22 | cPickle.dump(self.cache, open(self.filename, 'wb')) 23 | print >>sys.stderr, 'saved %s entries to %s' % (len(self.cache), self.filename) 24 | 25 | def recv_pkt(self, pc, pkt): 26 | arp = dpkt.ethernet.Ethernet(pkt).arp 27 | try: 28 | old = self.cache[arp.spa] 29 | if old != arp.sha: 30 | self.cache[arp.spa] = arp.sha 31 | print 'CHANGE: %s is-at %s (was-at %s)' % \ 32 | (dnet.ip_ntoa(arp.spa), dnet.eth_ntoa(arp.sha), 33 | dnet.eth_ntoa(old)) 34 | except KeyError: 35 | self.cache[arp.spa] = arp.sha 36 | print 'NEW: %s is-at %s' % (dnet.ip_ntoa(arp.spa), 37 | dnet.eth_ntoa(arp.sha)) 38 | 39 | if __name__ == '__main__': 40 | dsniff.add_option('-f', dest='arpwatch.filename', 41 | default='/var/run/arpwatch.pkl', 42 | help='cache file') 43 | dsniff.main() 44 | -------------------------------------------------------------------------------- /bin/cdpsniff.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # $Id$ 4 | 5 | import pprint 6 | import dnet, dpkt 7 | import dsniff 8 | import sys 9 | 10 | class CDPSniff(dsniff.Handler): 11 | name = 'cdpsniff' 12 | 13 | def setup(self): 14 | self.cache = {} 15 | self.subscribe('pcap', 'ether dst 01:00:0c:cc:cc:cc', self.recv_pkt) 16 | 17 | def teardown(self): 18 | print >>sys.stderr, 'caught %s unique cdp entries' % (len(self.cache), ) 19 | 20 | def recv_pkt(self, pc, pkt): 21 | eth = dpkt.ethernet.Ethernet(pkt) 22 | if eth.src not in self.cache and eth.type == dpkt.ethernet.ETH_TYPE_CDP: 23 | d = dict([ (tlv.type, tlv.data) for tlv in eth.cdp.data ]) 24 | self.cache[eth.src] = d 25 | print "%s [%s] - %s [%s]" % (d[1], dnet.eth_ntoa(eth.src), dnet.ip_ntoa(d[2][0].data), d[3]) 26 | pprint.pprint(d) 27 | print 28 | 29 | if __name__ == '__main__': 30 | dsniff.main() 31 | -------------------------------------------------------------------------------- /bin/dhcpos.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import dnet 4 | import dpkt 5 | import dsniff 6 | 7 | class DHCPOS(dsniff.Handler): 8 | 9 | types = ('discover', 'offer', 'request', 'decline', 'ack', 'nak', 'release', 'inform') 10 | 11 | def setup(self): 12 | self.fpos = {} 13 | f = open('share/dhcpos_fingerprints.txt') 14 | for line in f: 15 | line = line.strip() 16 | if not line or line.startswith('#'): 17 | continue 18 | i = line.rfind(',') 19 | if self.fpos.has_key(line[:i]): 20 | print "duplicate", line[:i], line[i+1:] 21 | self.fpos[line[:i]] = line[i+1:] 22 | f.close() 23 | self.subscribe('pcap', 'udp dst port 67', self.recv_pkt) 24 | 25 | def recv_pkt(self, pc, pkt): 26 | ip = dpkt.ethernet.Ethernet(pkt).ip 27 | msg = dpkt.dhcp.DHCP(ip.udp.data) 28 | opts = dict(msg.opts) 29 | if dpkt.dhcp.DHCP_OPT_PARAM_REQ in opts: 30 | l = map(ord, opts[dpkt.dhcp.DHCP_OPT_PARAM_REQ]) 31 | fp = ','.join(map(str, l)) 32 | print fp 33 | print '%s: %s (%s [%s] - %s):\n%s\n' % (self.types[map(ord, opts[dpkt.dhcp.DHCP_OPT_MSGTYPE])[0]-1], 34 | dnet.eth_ntoa(msg.chaddr), 35 | dnet.ip_ntoa(ip.src), 36 | opts.get(dpkt.dhcp.DHCP_OPT_HOSTNAME, 'unknown'), 37 | opts.get(dpkt.dhcp.DHCP_OPT_VENDOR_ID, 'unknown'), 38 | self.fpos.get(fp, 'UNKNOWN: %s' % fp)) 39 | 40 | if __name__ == '__main__': 41 | dsniff.main() 42 | -------------------------------------------------------------------------------- /bin/mailsnarf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # $Id$ 4 | 5 | import os, time 6 | import dsniff 7 | 8 | class MailSnarf(dsniff.Handler): 9 | def setup(self): 10 | self.subscribe('*', 'email', self.recv_email) 11 | self.__last_hdrs = None 12 | 13 | def recv_email(self, hdrs, msgfile): 14 | """Output mail in BSD mbox format. 15 | """ 16 | flow = self.flow 17 | if hdrs != self.__last_hdrs: 18 | self.__last_hdrs = hdrs 19 | print 'From mailsnarf', time.ctime(flow.etime) 20 | for line in open(msgfile): 21 | line = line.rstrip() 22 | if line.startswith('From '): 23 | print '>%s' % line 24 | else: 25 | print line 26 | os.unlink(msgfile) 27 | 28 | if __name__ == '__main__': 29 | dsniff.main() 30 | -------------------------------------------------------------------------------- /bin/ngrep.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # $Id$ 4 | 5 | import os, re, sys, time 6 | import dpkt, dsniff 7 | 8 | class Ngrep(dsniff.Handler): 9 | name = 'ngrep' 10 | pat = None 11 | hex = kill = quiet = noheader = raw = False 12 | 13 | def setup(self): 14 | if os.isatty(sys.stdout.fileno()): 15 | def _color(s, arrow): 16 | print { '>':'\033[31m%s\033[0m', 17 | '<':'\033[34m%s\033[0m' }[arrow] % s 18 | else: 19 | def _color(s, arrow): 20 | print s 21 | self.color = _color 22 | self.subscribe('flow', '', self.recv_flow) 23 | 24 | def _grep_data(self, flow, buf, arrow): 25 | if self.pat is None or self.pat.search(buf): 26 | if not self.noheader: 27 | print '-----------------' 28 | print time.strftime('%x %X', time.localtime(flow.etime)), 29 | print flow.__str__(arrow) 30 | if self.hex: 31 | self.color(dpkt.hexdump(str(buf)), arrow) 32 | elif self.raw: 33 | flow.save['rawf'].write(buf) 34 | elif not self.quiet: 35 | self.color(repr(buf), arrow) 36 | if self.kill: 37 | flow.kill() 38 | 39 | def recv_flow(self, flow): 40 | if self.raw: 41 | if flow.state == dsniff.FLOW_START: 42 | flow.save['rawf'] = open('/tmp/%s.flow' % id(flow), 'wb') 43 | elif flow.state == dsniff.FLOW_END: 44 | flow.save['rawf'].close() 45 | if flow.client.data: 46 | self._grep_data(flow, flow.client.data, '>') 47 | elif flow.server.data: 48 | self._grep_data(flow, flow.server.data, '<') 49 | 50 | class NgrepProgram(dsniff.Program): 51 | def getopt(self, argv): 52 | super(NgrepProgram, self).getopt(argv) 53 | if self.args: 54 | dsniff.config['ngrep'] = { 'pat':re.compile(self.args.pop(0)) } 55 | 56 | if __name__ == '__main__': 57 | dsniff.set_usage('%prog [options] [pattern [filter]]') 58 | dsniff.add_option('-x', dest='ngrep.hex', action='store_true', 59 | help='hexdump output') 60 | dsniff.add_option('-k', dest='ngrep.kill', action='store_true', 61 | help='kill matching TCP connections') 62 | dsniff.add_option('-q', dest='ngrep.quiet', action='store_true', 63 | help='no content output') 64 | dsniff.add_option('-n', dest='ngrep.noheader', action='store_true', 65 | help='no header output') 66 | dsniff.add_option('-r', dest='ngrep.raw', action='store_true', 67 | help='raw output') 68 | dsniff.main() 69 | -------------------------------------------------------------------------------- /bin/p0f2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | port of Michal Zalewski's p0fv2 passive OS fingerprinting tool 5 | """ 6 | 7 | # $Id$ 8 | 9 | # based on p0f-2.0.6b1 10 | # signatures brought in from 2.0.8 11 | 12 | # XXX - TODO: RST fingerprinting, masquerade/NAT detection, diagnostics 13 | 14 | import struct, time 15 | import dpkt 16 | 17 | PACKET_BIG = 100 18 | 19 | MOD_NONE = 0 20 | MOD_CONST = 1 21 | MOD_MSS = 2 22 | MOD_MTU = 3 23 | 24 | wss2mod = { 25 | '*':(MOD_CONST, lambda x: 1), 26 | 's':(MOD_MSS, lambda x: int(x[1:])), 27 | 't':(MOD_MTU, lambda x: int(x[1:])), 28 | '%':(MOD_CONST, lambda x: int(x[1:])) 29 | } 30 | 31 | qbits = [0] + [2**n for n in range(13)] 32 | qflags = ['.', 'P', 'Z', 'I', 'U', 'X', 'A', 'T', 'F', 'D', '!', 'K', 'Q', '0'] 33 | 34 | quirk2bits = dict(zip(qflags, qbits)) 35 | quirk2flags = dict(zip(qbits, qflags)) 36 | 37 | genre2name = { 38 | '-':'userland', 39 | '*':'no_detail', 40 | '@':'generic' 41 | } 42 | 43 | mtu2link = { 44 | 40:"unspecified", # XXX 45 | 256:"radio modem", 46 | 386:"ethernut", 47 | 552:"SLIP line / encap ppp", 48 | 576:"sometimes modem", 49 | 1280:"gif tunnel", 50 | 1300:"PIX:SMC:sometimes wireless", 51 | 1362:"sometimes DSL (1)", 52 | 1372:"cable modem", 53 | 1400:"(Google/AOL)", # To be investigated 54 | 1415:"sometimes wireless", 55 | 1420:"GPRS:T1:FreeS/WAN", 56 | 1423:"sometimes cable", 57 | 1440:"sometimes DSL (2)", 58 | 1442:"IPIP tunnel", 59 | 1450:"vtun", 60 | 1452:"sometimes DSL (3)", 61 | 1454:"sometimes DSL (4)", 62 | 1456:"ISDN ppp", 63 | 1458:"BT DSL (?)", 64 | 1462:"sometimes DSL (5)", 65 | 1476:"IPSec/GRE", 66 | 1480:"IPv6/IPIP", 67 | 1492:"pppoe (DSL)", 68 | 1496:"vLAN", 69 | 1500:"ethernet/modem", 70 | 1656:"Ericsson HIS", 71 | 2024:"wireless/IrDA", 72 | 2048:"Cyclom X.25 WAN", 73 | 2250:"AiroNet wireless", 74 | 3924:"loopback", 75 | 4056:"token ring (1)", 76 | 4096:"Sangoma X.25 WAN", 77 | 4352:"FDDI", 78 | 4500:"token ring (2)", 79 | 9180:"FORE ATM", 80 | 16384:"sometimes loopback (1)", 81 | 16436:"sometimes loopback (2)", 82 | 18000:"token ring x4" 83 | } 84 | 85 | def string_to_sig(line, ln=0): 86 | """Convert printable fingerprint entry string to a sig dict.""" 87 | try: 88 | w, t, d, s, obuf, quirks, genre, desc = line.split(':') 89 | t = int(t) 90 | d = int(d) 91 | if s[0] != '*': 92 | s = int(s) 93 | else: 94 | s = 0 95 | except ValueError: 96 | raise 'Syntax error in config line %d' % ln 97 | 98 | sig = { 'mss_mod':MOD_NONE, 'mss':0 } 99 | 100 | while genre: 101 | g = genre[0] 102 | try: 103 | sig[genre2name[genre[0]]] = 1 104 | genre = genre[1:] 105 | except KeyError: 106 | break 107 | if not genre: 108 | raise 'Empty OS genre in line %d' % ln 109 | 110 | sig['os'] = genre 111 | sig['desc'] = desc 112 | sig['ttl'] = t 113 | sig['tot'] = s 114 | sig['df'] = d 115 | 116 | wss = w[0].lower() 117 | try: 118 | mod = wss2mod[wss] 119 | sig['wss_mod'], sig['wss'] = mod[0], mod[1](w) 120 | except KeyError: 121 | sig['wss_mod'], sig['wss'] = MOD_NONE, int(w) 122 | except: 123 | raise 'Bad %snn value in WSS in line %d' % (wss, ln) 124 | 125 | sig['wsc_mod'] = MOD_NONE 126 | sig['wsc'] = 0 127 | sig['zero_stamp'] = 1 128 | 129 | sig['opts'] = [] 130 | opts = obuf.split(',') 131 | for opt in opts: 132 | o = opt[0].lower() 133 | if o == '.': 134 | break 135 | elif o == 'n': 136 | sig['opts'].append(dpkt.tcp.TCP_OPT_NOP) 137 | elif o == 'e': 138 | sig['opts'].append(dpkt.tcp.TCP_OPT_EOL) 139 | # XXX check for end 140 | elif o == 's': 141 | sig['opts'].append(dpkt.tcp.TCP_OPT_SACKOK) 142 | elif o == 't': 143 | sig['opts'].append(dpkt.tcp.TCP_OPT_TIMESTAMP) 144 | if not opt[1:]: 145 | sig['zero_stamp'] = 0 146 | elif o == 'w': 147 | sig['opts'].append(dpkt.tcp.TCP_OPT_WSCALE) 148 | if opt[1] == '*': 149 | sig['wsc_mod'], sig['wsc'] = MOD_CONST, 1 150 | elif opt[1] == '%': 151 | try: sig['wsc_mod'], sig['wsc'] = MOD_CONST, int(opt[2:]) 152 | except: raise 'Null modulo for wscale in line %d' % ln 153 | else: 154 | try: sig['wsc_mod'], sig['wsc'] = MOD_NONE, int(opt[1:]) 155 | except: raise 'Incorrect W value in line %d' % ln 156 | elif o == 'm': 157 | sig['opts'].append(dpkt.tcp.TCP_OPT_MSS) 158 | if opt[1] == '*': 159 | sig['mss_mod'], sig['mss'] = MOD_CONST, 1 160 | elif opt[1] == '%': 161 | try: sig['mss_mod'], sig['mss'] = MOD_CONST, int(opt[2:]) 162 | except: raise 'Null modulo for MSS in config line %d' % ln 163 | else: 164 | try: sig['mss_mod'], sig['mss'] = MOD_NONE, int(opt[1:]) 165 | except: raise 'Incorrect M value in line %d' % ln 166 | elif o == '?': 167 | try: sig['opts'].append(int(opt[1:])) 168 | except: raise 'Bogus ?nn value in line %d' % ln 169 | else: 170 | raise 'Unknown TCP option %s in line %d' % (o, ln) 171 | 172 | sig['quirks'] = 0 173 | for c in quirks: 174 | if c == '.': break 175 | try: sig['quirks'] |= quirk2bits[c] 176 | except KeyError: raise 'Bad quirk %s in line %d' % (c, ln) 177 | 178 | return sig 179 | 180 | def load_config(f): 181 | """Load fingerprint entries from a file, and return them in a 182 | fingerprint dict. 183 | 184 | Arguments: 185 | 186 | f -- p0f2 fingerprint file handle 187 | """ 188 | bh = {} 189 | ln = 0 190 | for line in f.readlines(): 191 | ln += 1 192 | # Skip comments and empty lines 193 | line = line.strip() 194 | if line and line[0] != '#': 195 | sig = string_to_sig(line, ln) 196 | key = (sig['tot'], len(sig['opts']), sig['quirks'], sig['df']) 197 | bh.setdefault(key, []).append(sig) 198 | return bh 199 | 200 | def parse_ip(ip): 201 | """Parse fields from a TCP/IP packet into a sig dict to match.""" 202 | tcp = ip.tcp 203 | mss = 0 204 | wsc = 0 205 | quirks = 0 206 | tstamp = 0 207 | 208 | if (ip.v_hl & 0x0f) > 5: 209 | quirks |= quirk2bits['I'] 210 | if (tcp.flags & dpkt.tcp.TH_RST) and (tcp.flags & dpkt.tcp.TH_ACK): 211 | quirks |= quirk2bits['K'] 212 | if tcp.seq == tcp.ack: 213 | quirks |= quirk2bits['Q'] 214 | if tcp.seq == 0: 215 | quirks |= quirk2bits['0'] 216 | if (tcp.flags & ~(dpkt.tcp.TH_SYN|dpkt.tcp.TH_ACK|dpkt.tcp.TH_RST|dpkt.tcp.TH_ECE|dpkt.tcp.TH_CWR)): 217 | quirks |= quirk2bits['F'] 218 | if tcp.data: 219 | quirks |= quirk2bits['D'] 220 | 221 | opts = [] 222 | # XXX - add ilen QUIRK_PAST parsing here 223 | for opt in dpkt.tcp.parse_opts(tcp.opts): 224 | try: 225 | o,d = opt 226 | if len(d) > 32: raise TypeError 227 | except TypeError: 228 | quirks |= quirk2bits['B'] 229 | break 230 | if o == dpkt.tcp.TCP_OPT_MSS: 231 | mss = struct.unpack('>H', d)[0] 232 | elif o == dpkt.tcp.TCP_OPT_WSCALE: 233 | wsc = ord(d) 234 | elif o == dpkt.tcp.TCP_OPT_TIMESTAMP: 235 | tstamp, t2 = struct.unpack('>II', d) 236 | if t2: quirks |= quirk2bits['T'] 237 | opts.append(o) 238 | 239 | if (tcp.flags & dpkt.tcp.TH_ACK): quirks |= quirk2bits['A'] 240 | if (tcp.flags & dpkt.tcp.TH_URG): quirks |= quirk2bits['U'] 241 | if (tcp.off_x2 & 0x0f): quirks |= quirk2bits['X'] 242 | if ip.id == 0: quirks |= quirk2bits['Z'] 243 | 244 | return { 'ttl':ip.ttl, 'tot':ip.len, 245 | 'df':int((ip.off & dpkt.ip.IP_DF) != 0), 246 | 'opts':opts, 'mss':mss, 'wss':tcp.win, 'wsc':wsc, 247 | 'tstamp':tstamp, 'quirks':quirks } 248 | 249 | 250 | def sig_to_string(sig): 251 | """Convert sig dict to printable fingerprint entry string.""" 252 | mss, wss, = sig['mss'], sig['wss'], 253 | l = [] 254 | 255 | if (mss and wss and not (wss % mss) and (wss/mss) <= 1460): # XXX 256 | l.append('S%d' % (wss/mss)) 257 | elif (wss and not (wss % 1460)): 258 | l.append('S%d' % (wss/1460)) 259 | elif (mss and wss and not (wss % (mss+40))): 260 | l.append('T%d' % (wss/(mss+40))) 261 | elif (wss and not (wss % 1500)): 262 | l.append('T%d' % (wss/1500)) 263 | elif wss == 12345: 264 | l.append('*(12345)') 265 | else: 266 | l.append('%d' % wss) 267 | 268 | l.append('%d' % sig['ttl']) 269 | l.append('%d' % sig['df']) 270 | if sig['tot'] < PACKET_BIG: 271 | l.append('%d' % sig['tot']) 272 | else: 273 | l.append('*(%d)' % sig['tot']) 274 | 275 | ol = [] 276 | for o in sig['opts']: 277 | if o == dpkt.tcp.TCP_OPT_NOP: ol.append('N') 278 | elif o == dpkt.tcp.TCP_OPT_WSCALE: 279 | if sig.get('wsc_mod', 0) == MOD_CONST and sig['wsc'] == 1: 280 | ol.append('W*') 281 | else: ol.append('W%d' % sig['wsc']) 282 | elif o == dpkt.tcp.TCP_OPT_MSS: 283 | if sig.get('mss_mod', 0) == MOD_CONST and sig['mss'] == 1: 284 | ol.append('M*') 285 | else: ol.append('M%d' % mss) 286 | elif o == dpkt.tcp.TCP_OPT_TIMESTAMP: 287 | if sig.get('zero_stamp', 0): 288 | ol.append('T0') # XXX 289 | elif sig.get('tstamp', 1): 290 | ol.append('T') 291 | else: 292 | ol.append('T0') 293 | elif o == dpkt.tcp.TCP_OPT_SACKOK: 294 | ol.append('S') 295 | elif o == dpkt.tcp.TCP_OPT_EOL: 296 | ol.append('E') 297 | else: 298 | ol.append('?%d' % o) 299 | if ol: 300 | l.append(','.join(ol)) 301 | else: 302 | l.append('.') 303 | 304 | if sig['quirks']: 305 | quirks = sig['quirks'] 306 | ql = [] 307 | for b in quirk2flags: 308 | if quirks & b: 309 | ql.append(quirk2flags[b]) 310 | l.append(''.join(ql)) 311 | else: 312 | l.append('.') 313 | 314 | if 'os' in sig: 315 | l.append(sig['os']) 316 | l.append(sig['desc']) 317 | else: 318 | l.extend(('?', '?')) 319 | 320 | return ':'.join(l) 321 | 322 | def find_match(bh, **kwargs): 323 | """Return matching fingerprint entry as a sig dict, or None if not found. 324 | 325 | Keyword arguments (e.g. from a sig dict): 326 | 327 | bh -- fingerprint entry dict 328 | ttl -- IP time-to-live 329 | tot -- total IP length 330 | df -- has IP_DF set 331 | opts -- list of TCP_OPT_* values 332 | mss -- TCP_OPT_MSS maximum segment size 333 | wss -- TCP window size 334 | wsc -- TCP_OPT_WSCALE window scaling factor 335 | tstamp -- TCP_OPT_TIMESTAMP timestamp 336 | quirks -- bitmask of QUIRK_* values 337 | """ 338 | ttl, tot, df, opts, mss, wss, wsc, tstamp, quirks = \ 339 | kwargs['ttl'], kwargs['tot'], kwargs['df'], kwargs['opts'], \ 340 | kwargs['mss'], kwargs['wss'], kwargs['wsc'], kwargs['tstamp'], \ 341 | kwargs['quirks'] 342 | try: 343 | sigs = bh[(tot,len(opts),quirks,df)] 344 | except KeyError: 345 | sigs = () 346 | 347 | match = {} 348 | fuzzy = None 349 | 350 | for sig in sigs: 351 | # tot set to zero means >= PACKET_BIG (100) 352 | if sig['tot']: 353 | if (tot ^ sig['tot']): continue 354 | elif tot < 100: continue 355 | 356 | if (len(opts) ^ len(sig['opts'])): continue 357 | 358 | if (sig['zero_stamp'] ^ int(not tstamp)): continue 359 | if (sig['df'] ^ df): continue 360 | if (sig['quirks'] ^ quirks): continue 361 | 362 | # check MSS and WSCALE 363 | if not sig['mss_mod']: 364 | if (mss ^ sig['mss']): continue 365 | elif (mss % sig['mss']): continue 366 | 367 | if not sig['wsc_mod']: 368 | if (wsc ^ sig['wsc']): continue 369 | elif (wsc % sig['wsc']): continue 370 | 371 | # then proceed with the most complex WSS check 372 | mod = sig['wss_mod'] 373 | if mod == 0: 374 | if (wss ^ sig['wss']): continue 375 | elif mod == MOD_CONST: 376 | if (wss % sig['wss']): continue 377 | elif mod == MOD_MSS: 378 | if mss and not (wss % mss): 379 | if ((wss / mss) ^ sig['wss']): continue 380 | elif not (wss % 1460): 381 | if ((wss / 1460) ^ sig['wss']): continue 382 | else: continue 383 | elif mod == MOD_MTU: 384 | if mss and not (wss % (mss+40)): 385 | if ((wss / (mss+40)) ^ sig['wss']): continue 386 | elif not (wss % 1500): 387 | if ((wss / 1500) ^ sig['wss']): continue 388 | else: continue 389 | 390 | # numbers agree, let's check options 391 | if filter(None, [ x ^ y for x, y in zip(sig['opts'], opts) ]): 392 | continue 393 | 394 | # Check TTLs last because we might want to go fuzzy 395 | if sig['ttl'] < ttl: 396 | fuzzy = sig 397 | continue 398 | 399 | if 'no_detail' not in sig: 400 | if sig['ttl'] - ttl > 40: 401 | fuzzy = sig 402 | continue 403 | 404 | # Match! 405 | match.update(sig) 406 | break 407 | 408 | if not match: 409 | if fuzzy: 410 | match.update(fuzzy) 411 | match['fuzzy'] = True 412 | elif not df: 413 | # XXX - GOTO!@#$% 414 | kwargs['df'] = 1 415 | match = find_match(bh, **kwargs) 416 | 417 | if match: 418 | # XXX - move these to reporting? these can be computed 419 | # even for UNKNOWN matches... 420 | if (mss & wss): 421 | if match['wss_mod'] == MOD_MSS: 422 | if ((wss % mss) and not (wss % 1460)): match['nat'] = 1 423 | elif match['wss_mod'] == MOD_MTU: 424 | if ((wss % (mss+40)) and not (wss % 1500)): match['nat'] = 2 425 | 426 | if (df ^ match['df']): match['firewall'] = True 427 | 428 | if tstamp: match['uptime'] = tstamp / 360000 429 | 430 | # XXX - fix below! 431 | try: match['link'] = mtu2link[mss + 40] 432 | except KeyError: match['link'] = 'unknown-%d' % mss 433 | match['distance'] = match['ttl'] - ttl 434 | 435 | return match 436 | 437 | class P0f(object): 438 | def __init__(self, synconf='share/p0f.fp', synackconf='share/p0fa.fp', 439 | rstconf='share/p0fr.fp', match=True, rewrite=False): 440 | self.matchdb = {} 441 | self.rewritedb = {} 442 | if synconf: 443 | bh = load_config(open(synconf)) 444 | if match: 445 | self.matchdb[dpkt.tcp.TH_SYN] = bh 446 | if rewrite: 447 | self.rewritedb[dpkt.tcp.TH_SYN] = self._match_to_rewrite(bh) 448 | if synackconf: 449 | bh = load_config(open(synackconf)) 450 | if match: 451 | self.matchdb[dpkt.tcp.TH_SYN|dpkt.tcp.TH_ACK] = bh 452 | if rewrite: 453 | bh = self._match_to_rewrite(bh) 454 | self.rewritedb[dpkt.tcp.TH_SYN|dpkt.tcp.TH_ACK] = bh 455 | if rstconf: 456 | bh = load_config(open(rstconf)) 457 | if match: 458 | self.matchdb[dpkt.tcp.TH_RST] = bh 459 | self.matchdb[dpkt.tcp.TH_RST|dpkt.tcp.TH_ACK] = bh 460 | if rewrite: 461 | bh = self._match_to_rewrite(bh) 462 | self.rewritedb[dpkt.tcp.TH_RST] = bh 463 | self.rewritedb[dpkt.tcp.TH_RST|dpkt.tcp.TH_ACK] = bh 464 | 465 | def _match_to_rewrite(self, matchdb): 466 | db = {} 467 | for k, sigs in matchdb.iteritems(): 468 | for sig in sigs: 469 | db[(sig['os'], sig['desc'])] = sig 470 | return db 471 | 472 | def match(self, ip): 473 | """Return matching sig dict for TCP/IP packet, or None.""" 474 | return find_match(self.matchdb[ip.tcp.flags & (dpkt.tcp.TH_SYN|dpkt.tcp.TH_RST|dpkt.tcp.TH_ACK)], **parse_ip(ip)) 475 | 476 | def fingerprint(self, ip): 477 | """Return sig string for TCP/IP packet.""" 478 | return sig_to_string(parse_ip(ip)) 479 | 480 | def rewrite(self, ip, os, desc): 481 | """Rewrite TCP/IP packet to match os, desc fingerprint, returning boolean status.""" 482 | # XXX - descriptions differ by fingerprint database! :-( 483 | tcp = ip.data 484 | try: 485 | bh = self.rewritedb[tcp.flags & (dpkt.tcp.TH_SYN|dpkt.tcp.TH_RST|dpkt.tcp.TH_ACK)] 486 | sig = bh[(os, desc)] 487 | except KeyError: 488 | return False 489 | # Set options 490 | mss = 0 491 | ol = [] 492 | for opt in sig['opts']: 493 | if opt == dpkt.tcp.TCP_OPT_NOP or opt == dpkt.tcp.TCP_OPT_EOL: 494 | ol.append(chr(opt)) 495 | elif opt == dpkt.tcp.TCP_OPT_WSCALE: 496 | ol.append(struct.pack('>BBB', opt, 3, sig['wsc'])) 497 | elif opt == dpkt.tcp.TCP_OPT_MSS and \ 498 | tcp.flags & (dpkt.tcp.TH_SYN|dpkt.tcp.TH_ACK) == \ 499 | dpkt.tcp.TH_SYN: 500 | mss = sig['mss'] 501 | if mss == 1: mss = 1460 502 | ol.append(struct.pack('>BBH', opt, 4, mss)) 503 | elif opt == dpkt.tcp.TCP_OPT_SACKOK: 504 | ol.append('\x04\x02') 505 | elif opt == dpkt.tcp.TCP_OPT_TIMESTAMP: 506 | if sig['zero_stamp']: ts = 0 507 | else: ts = int(time.time()) # XXX - should read real ts 508 | ol.append(struct.pack('>BBII', opt, 10, ts, 0)) 509 | tcp.opts = ''.join(ol) 510 | tcp.off = (20 + len(tcp.opts)) >> 2 511 | 512 | # Set window size 513 | mod, win = sig['wss_mod'], sig['wss'] 514 | if mod == MOD_MSS: 515 | if mss: win *= mss 516 | else: win *= 1460 517 | elif mod == MOD_MTU: 518 | if mss: win *= (mss + 40) 519 | else: win *= 1500 520 | tcp.win = win 521 | 522 | # Fix up IP header 523 | ip.off = sig['df'] and dpkt.ip.IP_DF or 0 524 | ip.ttl = sig['ttl'] 525 | ip.len = 20 + len(tcp) 526 | ip.sum = 0 527 | 528 | return True 529 | 530 | import dnet, dsniff 531 | 532 | class P0f2(dsniff.Handler): 533 | name = 'p0f2' 534 | 535 | def setup(self): 536 | self.p0f = P0f() 537 | self.ipcache = { dpkt.tcp.TH_SYN:{}, dpkt.tcp.TH_RST:{} } 538 | self.subscribe('pcap', '(tcp[13] & 0x%x != 0)' % (dpkt.tcp.TH_SYN|dpkt.tcp.TH_RST), self.recv_pkt) 539 | 540 | def recv_pkt(self, pc, pkt): 541 | ip = dpkt.ethernet.Ethernet(pkt).ip 542 | f = ip.tcp.flags & (dpkt.tcp.TH_SYN|dpkt.tcp.TH_RST) 543 | if ip.src not in self.ipcache[f]: 544 | self.ipcache[f][ip.src] = 1 545 | sig = self.p0f.match(ip) 546 | print '%s -' % dnet.ip_ntoa(ip.src), 547 | if sig: 548 | print '%s %s' % (sig['os'], sig['desc']), 549 | else: 550 | print 'UNKNOWN %s' % sig_to_string(parse_ip(ip)), 551 | l = [] 552 | if 'uptime' in sig: 553 | l.append('up: %d hrs' % sig['uptime']) 554 | if 'distance' in sig: 555 | l.append('distance: %d' % sig['distance']) 556 | if 'link' in sig: 557 | l.append('link: %s' % sig['link']) 558 | if l: 559 | print '(%s)' % ', '.join(l) 560 | else: 561 | print 562 | 563 | if __name__ == '__main__': 564 | dsniff.main() 565 | -------------------------------------------------------------------------------- /bin/urlsnarf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # $Id$ 4 | 5 | import base64, time 6 | import dsniff 7 | from dsniff.lib import http 8 | 9 | class UrlParser(http.HttpParser): 10 | def __init__(self, flow): 11 | super(UrlParser, self).__init__(self) 12 | self.flow = flow 13 | 14 | def handle_request(self, method, uri, version): 15 | self.req = { 'method':method, 'uri':uri, 'version':version } 16 | 17 | def _get_http_user(self, hdrs): 18 | if 'authorization' in hdrs: 19 | scheme, auth = hdrs['authorization'].split(None, 1) 20 | if scheme == 'Basic': 21 | return base64.decodestring(auth).split(':')[0] 22 | return '-' 23 | 24 | def handle_headers(self, hdrs): 25 | d = self.req 26 | d['ip'] = self.flow.src 27 | d['user'] = self._get_http_user(hdrs) 28 | if d['uri'].startswith('http'): 29 | d['url'] = d['uri'] 30 | else: 31 | d['url'] = 'http://%s%s' % (hdrs.get('host', self.flow.dst), 32 | d['uri']) 33 | for k in ('referer', 'user-agent'): 34 | d[k] = hdrs.get(k, '-') 35 | d['timestamp'] = \ 36 | time.strftime('%e/%b/%Y:%X', time.gmtime(self.flow.etime)).strip() 37 | print repr('%(ip)s - %(user)s [%(timestamp)s] ' 38 | '"%(method)s %(url)s" - - ' 39 | '"%(referer)s" "%(user-agent)s"' % d).strip("'") 40 | 41 | class URLSnarf(dsniff.Handler): 42 | def setup(self): 43 | self.subscribe('service', 'http', self.recv_flow) 44 | 45 | def recv_flow(self, f): 46 | if f.state == dsniff.FLOW_START: 47 | f.client.save['urlparser'] = UrlParser(f) 48 | elif f.state == dsniff.FLOW_CLIENT_DATA: 49 | try: 50 | f.client.save['urlparser'].feed(f.client.data) 51 | except http.mime.ParseError: 52 | pass 53 | 54 | if __name__ == '__main__': 55 | dsniff.main() 56 | -------------------------------------------------------------------------------- /dsniff/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from dsniff import * 3 | from core.flow import * 4 | 5 | import core, mail 6 | 7 | __version__ = '3.0a' 8 | __author__ = 'Dug Song ' 9 | __url__ = 'http://dsniff.org/' 10 | -------------------------------------------------------------------------------- /dsniff/core/__init__.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | import pkt, ip, flow, service 4 | -------------------------------------------------------------------------------- /dsniff/core/flow.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | import traceback 4 | import dnet, dpkt 5 | import dsniff 6 | from dsniff.lib import fcap, net, reasm 7 | 8 | FLOW_START, FLOW_CLIENT_DATA, FLOW_SERVER_DATA, FLOW_END = range(4) 9 | 10 | class FlowHalf(object): 11 | __slots__ = ('addr', 'port', 'pkts', 'bytes', 'data', 'save') 12 | 13 | def __init__(self, addr, pkts, bytes): 14 | self.addr = addr 15 | self.pkts = pkts 16 | self.bytes = bytes 17 | self.port = self.data = None 18 | self.save = {} 19 | 20 | def readlines(self, keepends=False): # XXX - need max buffer size 21 | """Return list of lines parsed from a flow data stream. 22 | """ 23 | x = '_readlines_buf' 24 | self.save[x] = self.save.get(x, '') + self.data 25 | while self.save[x]: 26 | i = self.save[x].find('\n') 27 | if i < 0: 28 | break 29 | line, self.save[x] = self.save[x][:i+1], self.save[x][i+1:] 30 | if not keepends: 31 | line = line.rstrip() 32 | yield line 33 | 34 | def unpack(self, dpkt_cls, maxsz=1000 * 1000): 35 | """Iterator to return dpkt_cls instances parsed from a flow 36 | data stream. 37 | """ 38 | x = dpkt_cls 39 | buf = self.save[x] = self.save.get(x, '') + self.data 40 | while buf: 41 | try: 42 | p = dpkt_cls(buf) 43 | except dpkt.UnpackError: 44 | break 45 | yield p 46 | buf = p.data 47 | # XXX - on decode error, maybe we should whack any saved buf? 48 | self.save[x] = buf[-maxsz:] 49 | raise StopIteration 50 | 51 | class Flow(object): 52 | __slots__ = ('client', 'server', 'p', 'stime', 'etime', 53 | 'state', 'save', 'half', 'callbacks') 54 | _Half = FlowHalf 55 | 56 | def __init__(self, ts, ip): 57 | def _ip2addr(x): 58 | a = dnet.addr() 59 | if ip.v == 4: a.ip = x 60 | elif ip.v: a.ip6 = x 61 | return a 62 | self.client = self._Half(_ip2addr(ip.src), 1, ip.len) 63 | self.server = self._Half(_ip2addr(ip.dst), 0, 0) 64 | self.half = { ip.src:self.client, ip.dst:self.server } 65 | self.p = ip.p 66 | self.stime = self.etime = ts 67 | self.callbacks = [] 68 | self.save = {} 69 | 70 | src = property(lambda self: self.client.addr) 71 | dst = property(lambda self: self.server.addr) 72 | sport = property(lambda self: self.client.port) 73 | dport = property(lambda self: self.server.port) 74 | 75 | def flip(self): 76 | self.client, self.server = self.server, self.client 77 | 78 | def register(self, callback): 79 | self.callbacks.append(callback) 80 | 81 | def unregister(self, callback): 82 | self.callbacks.remove(callback) 83 | 84 | def publish(self, state): 85 | self.state = state 86 | for cb in self.callbacks: 87 | try: 88 | cb(self) 89 | except: 90 | traceback.print_exc() 91 | 92 | def update(self, ts, ip): 93 | self.etime = ts 94 | half = self.half[ip.src] 95 | half.pkts += 1 96 | half.bytes += ip.len 97 | 98 | def __getitem__(self, k): 99 | try: 100 | return getattr(self, k) 101 | except AttributeError: 102 | raise KeyError 103 | 104 | def __repr__(self): 105 | return 'Flow(%(src)s, %(dst)s, %(p)s, %(sport)s, %(dport)s)' % self 106 | 107 | def __str__(self, arrow='>'): 108 | p = net.proto_ntoa(self.p) 109 | if self.dport is not None: 110 | if self.sport is None: # XXX - ICMP 111 | return '%s %s %s %s:%s' % (p, self.src, arrow, self.dst, self.dport) 112 | return '%s %s:%s %s %s:%s' % \ 113 | (p, self.src, self.sport, arrow, self.dst, self.dport) 114 | return '%s %s %s %s' % (p, self.src, arrow, self.dst) 115 | 116 | class IpFlow(Flow): 117 | __slots__ = Flow.__slots__ + ('data',) 118 | 119 | def update(self, ts, ip): 120 | Flow.update(self, ts, ip) 121 | self.data = str(ip.data) 122 | 123 | # XXX - to map reply to request type 124 | _icmp_typemap = { 0:8, 10:9, 14:13, 16:15, 18:17, 34:33, 36:35, 38:37 } 125 | 126 | class IcmpFlow(Flow): 127 | __slots__ = Flow.__slots__ + ('type', 'code') 128 | 129 | def __init__(self, ts, ip): 130 | Flow.__init__(self, ts, ip) 131 | t = ip.icmp.type 132 | if t in _icmp_typemap: 133 | self.flip() 134 | self.type = self.server.port = _icmp_typemap[t] 135 | else: 136 | self.type = self.server.port = t 137 | self.code = self.client.port = None 138 | 139 | def update(self, ts, ip): 140 | Flow.update(self, ts, ip) 141 | self.half[ip.src].data = str(ip.icmp.data) 142 | 143 | class UdpFlow(Flow): 144 | def __init__(self, ts, ip): 145 | Flow.__init__(self, ts, ip) 146 | self.client.port = ip.udp.sport 147 | self.server.port = ip.udp.dport 148 | 149 | def update(self, ts, ip): 150 | Flow.update(self, ts, ip) 151 | self.half[ip.src].data = str(ip.udp.data) 152 | 153 | class TcpHalf(FlowHalf): 154 | __slots__ = FlowHalf.__slots__ + ('flags', 'reasm') 155 | 156 | class TcpFlow(Flow): 157 | _Half = TcpHalf 158 | 159 | def __init__(self, ts, ip): 160 | Flow.__init__(self, ts, ip) 161 | self.client.port = ip.tcp.sport 162 | self.server.port = ip.tcp.dport 163 | self.client.flags = ip.tcp.flags 164 | self.server.flags = 0 165 | self.client.reasm = self.server.reasm = None 166 | 167 | def update(self, ts, ip): 168 | Flow.update(self, ts, ip) 169 | half = self.half[ip.src] 170 | half.flags &= ip.tcp.flags 171 | # XXX - what about URG data? 172 | if half.reasm is None: 173 | is_syn = int(ip.tcp.flags & 0x02 == 0x02) 174 | half.reasm = reasm.Reassembler(ip.tcp.seq + is_syn, ip.tcp.win) 175 | if ip.tcp.data: 176 | half.data = half.reasm.reassemble(ip.tcp.seq, ip.tcp.data) 177 | 178 | def kill(self, rawsock): 179 | # XXX - should be FlowHalf method, abstracted to .inject()? 180 | # e.g. flow.client.kill(), flow.client.send('foo') 181 | if self.server.reasm is None: 182 | return -1 183 | if self.src.type == dnet.ADDR_TYPE_IP: 184 | ip = dpkt.ip.IP(src=self.src.ip, dst=self.dst.ip, p=self.p) 185 | tcp = dpkt.tcp.TCP(sport=self.dport, dport=self.sport, 186 | seq=self.server.reasm.cur, 187 | flags=dnet.TH_RST) 188 | ip.data = tcp 189 | ip.len += len(tcp) 190 | 191 | for i in range(3): 192 | ip.id = id(ip) 193 | tcp.seq += i * tcp.win 194 | ip.sum = tcp.sum = 0 195 | rawsock.send(str(ip)) 196 | return 0 197 | else: # XXX - TODO IPv6 198 | return -1 199 | 200 | class FlowHandler(dsniff.Handler): 201 | """IPv4/IPv6 flow handler, with basic TCP/IP reassembly 202 | and GRE, IPv6-in-IPv4, and IP-IP decapsulation. 203 | XXX - should we decapsulate SOCKS also? or does a SocksHandler 204 | publish new IP events? 205 | """ 206 | name = 'flow' 207 | max_flows = 5000 208 | flowcls = { 1:IcmpFlow, 6:TcpFlow, 17:UdpFlow } 209 | 210 | def setup(self): 211 | def __lru_flows(cache): 212 | l = [ (v.etime, k) for k, v in cache.iteritems() ] 213 | l.sort() 214 | for ts, k in l[:cache.maxsz / 8]: 215 | cache.pop(k).publish(FLOW_END) 216 | self.cache = reasm.Cache(self.max_flows, reclaimfn=__lru_flows) 217 | self.fcap = fcap.Fcap() 218 | self.pcap_filter = None 219 | 220 | def teardown(self): 221 | self.cache.clear() 222 | 223 | def __resubscribe(self): 224 | s = self.fcap.pcap_filter() 225 | if self.pcap_filter != s: 226 | if self.pcap_filter: 227 | self.unsubscribe('ip', self.pcap_filter, self.recv_ip) 228 | self.pcap_filter = s 229 | self.subscribe('ip', self.pcap_filter, self.recv_ip) 230 | 231 | def _register(self, event, callback): 232 | self.fcap.add(event, event) 233 | self.__resubscribe() 234 | super(FlowHandler, self)._register(event, callback) 235 | 236 | def _unregister(self, event, callback): 237 | self.fcap.delete(event, event) 238 | self.__resubscribe() 239 | super(FlowHandler, self)._unregister(event, callback) 240 | # XXX - should whack cached flow callback 241 | 242 | def _hash_tuple(self, ip): 243 | # XXX - ugly but fast... er 244 | t = ip.data 245 | if ip.p == 6 or ip.p == 17: 246 | if ip.src > ip.dst: 247 | return (ip.dst, ip.src, ip.p, t.dport, t.sport) 248 | return (ip.src, ip.dst, ip.p, t.sport, t.dport) 249 | if ip.p == 1: 250 | if ip.src > ip.dst: 251 | return (ip.dst, ip.src, _icmp_typemap.get(t.type, t.type)) 252 | return (ip.src, ip.dst, _icmp_typemap.get(t.type, t.type)) 253 | if ip.src > ip.dst: 254 | return (ip.dst, ip.src, ip.p) 255 | return (ip.src, ip.dst, ip.p) 256 | 257 | def _set_direction(self, flow, ip): 258 | t = ip.data 259 | if (ip.p == 6 and t.flags & 0x12 == 0x12) or \ 260 | (flow.sport is not None and flow.sport != flow.dport and 261 | flow.sport in self.fcap.matcher.dport): 262 | flow.flip() 263 | 264 | def recv_ip(self, ip): 265 | if ip.v == 6: # XXX - fake the funk 266 | ip.p = ip.nxt 267 | ip.len = 40 + ip.plen 268 | 269 | # XXX - decapsulate tunnel protocols 270 | if ip.p == 47: # GRE 271 | if ip.gre.p == 0x800: # ETH_TYPE_IP 272 | return self.recv_ip(ip.gre.ip) 273 | elif ip.gre.p == 0x880B and ip.gre.ppp.p == 0x21: 274 | return self.recv_ip(ip.gre.ppp.ip) 275 | elif ip.p == 41: # IPv6-in-IPv4 276 | return self.recv_ip(ip.ip6) 277 | elif ip.p == 4: # IPIP 278 | return self.recv_ip(ip.ip) 279 | 280 | t = self._hash_tuple(ip) 281 | if t in self.cache: 282 | flow = self.cache[t] 283 | else: 284 | flow = self.cache[t] = self.flowcls.get(ip.p, IpFlow)(self.ts, ip) 285 | self._set_direction(flow, ip) 286 | # XXX - cache currently registered matching callbacks in flow 287 | for e in self.fcap.match(src=flow.src, dst=flow.dst, p=flow.p, 288 | sport=flow.sport, dport=flow.dport): 289 | for cb in self.callbacks[e]: 290 | flow.register(cb) 291 | flow.publish(FLOW_START) 292 | 293 | dsniff.Handler.flow = flow # XXX 294 | flow.update(self.ts, ip) 295 | 296 | if flow.client.data: 297 | flow.publish(FLOW_CLIENT_DATA) 298 | flow.client.data = None 299 | elif flow.server.data: 300 | flow.publish(FLOW_SERVER_DATA) 301 | flow.server.data = None 302 | -------------------------------------------------------------------------------- /dsniff/core/ip.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | import dpkt, pcap 4 | import dsniff 5 | from dsniff.lib import reasm 6 | 7 | class IPHandler(dsniff.Handler): 8 | """IPv4/IPv6 handler, with basic IP fragment reassembly.""" 9 | name = 'ip' 10 | max_frag_ids = 1000 11 | 12 | def setup(self): 13 | self.defrag = reasm.Defragger(self.max_frag_ids) 14 | 15 | def _register(self, event, callback): 16 | # XXX - pass through events (pcap filters) to pkt handler 17 | self.subscribe('pcap', event, self.recv_pkt) 18 | super(IPHandler, self)._register(event, callback) 19 | 20 | def _unregister(self, event, callback): 21 | self.unsubscribe('pcap', event, self.recv_pkt) 22 | super(IPHandler, self)._unregister(event, callback) 23 | 24 | def recv_pkt(self, pc, pkt): 25 | # Try to handle both IPv4 and IPv6... 26 | dlt = pc.datalink() 27 | if dlt == pcap.DLT_EN10MB: 28 | eth = dpkt.ethernet.Ethernet(pkt) 29 | if eth.type != dpkt.ethernet.ETH_TYPE_IP and \ 30 | eth.type != dpkt.ethernet.ETH_TYPE_IP6: 31 | return 32 | ip = eth.data 33 | elif dlt == pcap.DLT_LOOP or dlt == pcap.DLT_NULL: 34 | loop = dpkt.loopback.Loopback(pkt) 35 | if loop.family > 1500: # XXX - see dpkt.loopback 36 | ip = loop.data.data 37 | else: 38 | ip = loop.data 39 | else: 40 | ip = dpkt.ip.IP(pkt[pc.dloff:]) 41 | 42 | dsniff.Handler.ip = ip # XXX 43 | 44 | if isinstance(ip, dpkt.ip.IP): 45 | if ip.off & 0x3fff: # IP_MF|IP_OFFMASK 46 | ip = self.defrag.defrag(ip) 47 | if ip: 48 | self.publish(pc.event, ip) 49 | else: 50 | self.publish(pc.event, ip) 51 | elif isinstance(ip, dpkt.ip6.IP6): 52 | self.publish(pc.event, ip) 53 | -------------------------------------------------------------------------------- /dsniff/core/pkt.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | import glob, os, sys 4 | import pcap, dnet 5 | import dsniff 6 | 7 | def lookupdev(): 8 | """XXX - better pcap_lookupdev()""" 9 | intf = dnet.intf() 10 | ifent = intf.get_dst(dnet.addr('1.2.3.4')) or \ 11 | [ x for x in intf if x['flags'] & dnet.INTF_FLAG_UP and 12 | x['type'] == dnet.INTF_TYPE_ETH ][0] 13 | return ifent['name'] 14 | 15 | class PcapFactory(object): 16 | def __new__(cls, *args, **kwargs): 17 | try: 18 | import wtap 19 | class Wtap(wtap.wtap): pass 20 | return Wtap(*args, **kwargs) 21 | except (ImportError, IOError): 22 | class Pcap(pcap.pcap): pass 23 | return Pcap(*args, **kwargs) 24 | 25 | class PcapHandler(dsniff.Handler): 26 | """Packet capture handler.""" 27 | name = 'pcap' 28 | 29 | interfaces = [] 30 | snaplen = 31337 31 | prefilter = '' 32 | debug = 0 33 | 34 | def setup(self): 35 | if self.interfaces: 36 | l = [] 37 | for i in self.interfaces: 38 | l.extend(glob.glob(i) or [ i ]) 39 | self.interfaces = l 40 | elif not self.interfaces: 41 | self.interfaces = [ lookupdev() ] 42 | self.pcaps = {} 43 | 44 | def __pcap_open(self, name, **kwargs): 45 | def __recv_pkt(ts, pkt, pc): 46 | dsniff.Handler.ts = ts # XXX 47 | dsniff.Handler.pkt = pkt 48 | dsniff.Handler.pc = pc 49 | self.publish(pc.event, pc, pkt) 50 | def __read_cb(pc, stat): 51 | if pc.dispatch(-1, __recv_pkt, pc) <= stat: 52 | self.abort() 53 | return True 54 | pc = PcapFactory(name, **kwargs) 55 | if not (os.path.isfile(pc.name) or pc.name == '-'): 56 | # FIXME - or only if b0rked BPF 57 | pc.setnonblock() 58 | self.timeout(0.1, __read_cb, pc, -1) 59 | else: 60 | dsniff.event.read(pc, __read_cb, pc, 0) 61 | return pc 62 | 63 | def __pcap_info(self, pc): 64 | if pc.filter: 65 | return '%s (%s, snaplen: %d)' % (pc.name, pc.filter, pc.snaplen) 66 | else: 67 | return '%s (snaplen: %d)' % (pc.name, pc.snaplen) 68 | 69 | def _register(self, event, callback): 70 | # Create new pcap handle as needed for new subscriptions 71 | pcfilter = ' and '.join(filter(None, [ self.prefilter, event ])) 72 | if None in self.pcaps: 73 | # XXX - reuse any cached pcaps 74 | self.pcaps[event] = self.pcaps.pop(None) 75 | for pc in self.pcaps[event]: 76 | pc.setfilter(pcfilter) 77 | pc.event = event 78 | if self.debug > 0: 79 | print >>sys.stderr, 'updated', self.__pcap_info(pc) 80 | elif event not in self.pcaps: 81 | self.pcaps[event] = [] 82 | for dev in self.interfaces: 83 | pc = self.__pcap_open(dev, timeout_ms=0, 84 | snaplen=self.snaplen) 85 | pc.setfilter(pcfilter) 86 | pc.event = event 87 | print >>sys.stderr, 'opened', self.__pcap_info(pc) 88 | self.pcaps[event].append(pc) 89 | super(PcapHandler, self)._register(event, callback) 90 | 91 | def _unregister(self, event, callback): 92 | super(PcapHandler, self)._unregister(event, callback) 93 | pcaps = self.pcaps.pop(event) 94 | if not self.callbacks: 95 | # XXX - cache last set of pcaps 96 | self.pcaps[None] = pcaps 97 | 98 | def teardown(self): 99 | for pcaps in self.pcaps.itervalues(): 100 | for pc in pcaps: 101 | try: 102 | stats = pc.stats() 103 | print >>sys.stderr, \ 104 | 'closed %s: %d packets received, %d dropped' % \ 105 | (self.__pcap_info(pc), stats[0], stats[1]) 106 | except OSError: 107 | print >>sys.stderr, 'closed', self.__pcap_info(pc) 108 | -------------------------------------------------------------------------------- /dsniff/core/service.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | import dsniff 4 | import sys 5 | from dsniff.core import flow 6 | from dsniff.lib import net 7 | 8 | # XXX - override some service definitions 9 | _services = { 10 | 'aolmail':'tcp and dst port 80 and dst net 205.188.0.0/16', 11 | 'http':'tcp and dst port %s' % ' or '.join([ str(p) for p in (80, 98, 280, 591, 3128, 3132, 4480, 5490, 8000, 8080, 11371) ]), 12 | 'fastmail':'tcp and dst port 80 and dst net 66.111.0.0/20', 13 | 'gmail':'tcp and dst port 80 and dst net 64.233.160.0/19 or 66.249.64.0/19 or 72.14.0.0/16 or 64.68.80.0/21', 14 | 'hotmail':'tcp and dst port 80 and dst net 64.4.0.0/18', 15 | 'lycosmail':'tcp and dst port 80 and dst net 208.36.123.0/24', 16 | 'yahoomail':'tcp and dst port 80 and dst net 66.163.160.0/19 or 64.58.76.0/22 or 64.41.224.0/23', 17 | 'dns':'dst port 53', 18 | } 19 | 20 | try: 21 | import appid 22 | appid_loaded = True 23 | except ImportError, e: 24 | appid_loaded = False 25 | pass 26 | 27 | class ServiceHandler(dsniff.Handler): 28 | name = 'service' 29 | auto = False 30 | 31 | def setup(self): 32 | if self.auto: 33 | self.subscribe('flow', 'tcp or udp', self.recv_flow) 34 | else: 35 | self._register = self.__proxy_register 36 | self._unregister = self.__proxy_unregister 37 | 38 | def recv_flow(self, f): 39 | if '_service' in f.save: 40 | self.publish(f.save['_service'], f) 41 | return 42 | if appid_loaded: 43 | if f.state == flow.FLOW_START: 44 | for half in f.half.itervalues(): 45 | half.save['_appid'] = appid.appid() 46 | half.save['_appid_buf'] = half.data or '' 47 | app = (-1, 0) 48 | for half in f.half.itervalues(): 49 | if half.data: 50 | half.save['_appid_buf'] += half.data 51 | print sys.stderr, 'appid about to process %s s:%s d:%s' % (f.p, f.sport, f.dport) 52 | app = half.save['_appid'].process(f.p, f.sport, f.dport, half.data) 53 | print sys.stderr, 'appid returned %s' % (app,) 54 | if app[0] != 0: # APPID_CONTINUE 55 | break 56 | if app[0] not in (65535, -1, 0): # XXX: 65535 is because application is currently unsigned 57 | event = appid.app_to_name(app[0]).lower() 58 | print 'appid match:', event 59 | if event in self.callbacks: 60 | f.save['_service'] = event 61 | f.save['_confidence'] = app[1] 62 | for half in f.half.itervalues(): 63 | half.data = half.save.pop('_appid_buf') 64 | del half.save['_appid'] 65 | f.state = flow.FLOW_START 66 | self.publish(event, f) 67 | if f.client.data: 68 | f.state = flow.FLOW_CLIENT_DATA 69 | self.publish(event, f) 70 | if f.server.data: 71 | f.state = flow.FLOW_SERVER_DATA 72 | self.publish(event, f) 73 | else: 74 | for half in f.half.itervalues(): 75 | del half.save['_appid_buf'] 76 | del half.save['_appid'] 77 | f.unregister(self.recv_flow) 78 | 79 | def __event_to_fcaps(self, event): 80 | if event in _services: 81 | return [ _services[event] ] 82 | svcs = net.serv_aton(event) 83 | l = [] 84 | for p in (1, 6, 17): 85 | ports = [ str(svc[1]) for svc in svcs if svc[0] == p ] 86 | if ports: 87 | l.append('%s and dst port %s' % 88 | (net.proto_ntoa(p), ' or '.join(ports))) 89 | return l 90 | 91 | def __proxy_register(self, event, callback): 92 | # XXX - just proxy to FlowHandler 93 | fcaps = self.__event_to_fcaps(event) 94 | for fcap in fcaps: 95 | self.subscribe('flow', fcap, callback) 96 | 97 | def __proxy_unregister(self, event, callback): 98 | # XXX - just proxy to FlowHandler 99 | fcaps = self.__event_to_fcaps(event) 100 | for fcap in fcaps: 101 | self.unsubscribe('flow', fcap, callback) 102 | -------------------------------------------------------------------------------- /dsniff/dsniff.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | try: 4 | from __builtin__ import set 5 | except ImportError: 6 | from sets import Set as set 7 | import fnmatch, optparse, os, signal, sys, types 8 | import event 9 | 10 | _op = optparse.OptionParser(usage='%prog [options] [filter]') 11 | handlers = {} # name:handler instances 12 | config = {} # name:{var:val} handler configs 13 | 14 | add_option = _op.add_option 15 | set_usage = _op.set_usage 16 | 17 | class _MetaHandler(type): 18 | def __new__(cls, clsname, clsbases, clsdict): 19 | t = type.__new__(cls, clsname, clsbases, clsdict) 20 | if clsdict.get('name'): 21 | t._subclasses[clsdict['name']] = t 22 | return t 23 | 24 | class Handler(object): 25 | """Pub/sub handler interface. 26 | """ 27 | __metaclass__ = _MetaHandler # XXX - stuff class into Handler.subclasses 28 | 29 | name = None # handler name 30 | events = () # list of events published 31 | 32 | _subclasses = {} # name:handler class 33 | 34 | def __init__(self, *args, **kwargs): 35 | global config 36 | if self.name in config: 37 | self.__dict__.update(config[self.name]) # XXX 38 | self.subscriptions = {} 39 | self.callbacks = {} 40 | self.setup() 41 | 42 | def setup(self): 43 | """Override with any setup actions (e.g. subscriptions, etc.).""" 44 | pass 45 | 46 | def subscribe(self, name, event, callback): 47 | """Subscribe to a handler's event. 48 | 49 | Arguments: 50 | name -- name of handler (or match pattern) 51 | event -- name of event published by handler 52 | callback -- callback to be invoked 53 | """ 54 | if '*' in name: 55 | found = False 56 | for x in fnmatch.filter(Handler._subclasses.iterkeys(), name): 57 | if event in Handler._subclasses[x].events: 58 | self.subscribe(x, event, callback) 59 | found = True 60 | if not found: 61 | raise RuntimeError, 'no matching handlers found' 62 | return 63 | if name not in handlers: 64 | if name != self.name: 65 | # XXX - auto-instantiate handler 66 | handlers[name] = Handler._subclasses[name]() 67 | else: 68 | handlers[name] = self 69 | pub = handlers[name] 70 | pub._register(event, callback) 71 | if pub not in self.subscriptions: 72 | self.subscriptions[pub] = set() 73 | self.subscriptions[pub].add((event, callback)) 74 | 75 | def unsubscribe(self, name, event, callback): 76 | """Unsubscribe from a handler's event. 77 | 78 | Arguments: 79 | name -- name of handler (or match pattern) 80 | event -- name of event published by handler 81 | callback -- callback to be invoked 82 | """ 83 | pub = handlers[name] 84 | if pub in self.subscriptions: 85 | pub._unregister(event, callback) 86 | self.subscriptions[pub].remove((event, callback)) 87 | 88 | def _register(self, event, callback): 89 | """Register subscription from another handler.""" 90 | if event not in self.callbacks: 91 | self.callbacks[event] = set() 92 | self.callbacks[event].add(callback) 93 | 94 | def _unregister(self, event, callback): 95 | """Remove subscription from another handler.""" 96 | l = self.callbacks[event] 97 | l.remove(callback) 98 | if not l: 99 | del self.callbacks[event] 100 | 101 | def publish(self, event, *args, **kwargs): 102 | """Send an event to any registered listeners.""" 103 | if event in self.callbacks: 104 | for callback in self.callbacks[event]: 105 | # XXX - unsub from within callback breaks iteration 106 | callback(*args, **kwargs) 107 | else: 108 | print >>sys.stderr, self.name, 'publishing %s to nobody!' % event 109 | 110 | def teardown(self): 111 | """Override to perform any cleanup actions.""" 112 | pass 113 | 114 | def delete(self): 115 | for pub, subscriptions in self.subscriptions.iteritems(): 116 | for event, callback in subscriptions: 117 | pub._unregister(event, callback) 118 | self.subscriptions.clear() 119 | self.teardown() 120 | # XXX - send status to our subscribers? 121 | 122 | # XXX - add event functions 123 | signal = event.signal 124 | timeout = event.timeout 125 | abort = event.abort 126 | 127 | def find_subclasses(cls, module, default=[]): 128 | """Return a list of public subclasses of a class from a module.""" 129 | l = [] 130 | for name in [ x for x in dir(module) if not x.startswith('_') ]: 131 | o = getattr(module, name) 132 | if isinstance(o, (type, types.ClassType)) and issubclass(o, cls): 133 | l.append(o) 134 | if not l: 135 | l = default 136 | return l 137 | 138 | class Program(object): 139 | def __init__(self): 140 | add_option('-i', action='append', dest='pcap.interfaces', 141 | metavar='INPUT', default=[], 142 | help='input device or filename') 143 | add_option('-s', dest='pcap.snaplen', type='int', 144 | metavar='SNAPLEN', default=31337, 145 | help='capture snapshot length') 146 | add_option('-d', action='count', dest='pcap.debug', 147 | help='debug level') 148 | self.opts = None 149 | self.args = () 150 | 151 | def setup(self): 152 | """Override with any setup actions (such as adding options, etc.)""" 153 | pass 154 | 155 | def teardown(self): 156 | """Override with any teardown actions.""" 157 | pass 158 | 159 | def getopt(self, argv): 160 | global _op, config 161 | self.opts, self.args = _op.parse_args(argv) 162 | # XXX - map options to config tree 163 | for k, v in self.opts.__dict__.iteritems(): 164 | name, var = k.split('.') 165 | if name not in config: 166 | config[name] = {} 167 | config[name][var] = v 168 | 169 | def main(self, argv=sys.argv[1:], subclasses=None): 170 | """Run any Handler subclass in __main__ scope. 171 | """ 172 | # XXX - even with only select enabled, BPF immediate doesn't 173 | # work on OSX, and we only get read events on full buffers. 174 | if sys.platform in ('darwin', 'win32'): 175 | os.putenv('EVENT_NOKQUEUE', '1') 176 | os.putenv('EVENT_NOPOLL', '1') 177 | 178 | if not subclasses: 179 | subclasses = find_subclasses(Handler, __import__('__main__')) 180 | if not subclasses: 181 | raise RuntimeError, 'no Handler subclasses found' 182 | 183 | event.init() 184 | self.setup() 185 | self.getopt(argv) 186 | 187 | # XXX - configure pcap filter 188 | global config 189 | config['pcap']['prefilter'] = ' '.join(self.args) 190 | 191 | for cls in subclasses: 192 | handlers[cls.name] = cls() 193 | for sig in (signal.SIGINT, signal.SIGTERM): 194 | event.signal(sig, event.abort) 195 | 196 | event.dispatch() 197 | 198 | for h in handlers.itervalues(): 199 | h.teardown() 200 | self.teardown() 201 | 202 | def main(argv=sys.argv[1:], profile=False): 203 | program = find_subclasses(Program, __import__('__main__'), [ Program ])[0] 204 | if profile: 205 | import hotshot, hotshot.stats 206 | filename = sys.argv[0] + '.prof' 207 | prof = hotshot.Profile(filename) 208 | prof.runcall(program().main, argv) 209 | stats = hotshot.stats.load(filename) 210 | stats.strip_dirs() 211 | stats.sort_stats('time', 'calls') 212 | stats.print_stats(20) 213 | os.unlink(filename) 214 | else: 215 | program().main(argv) 216 | 217 | def test(): 218 | import inspect 219 | subclasses = find_subclasses(Handler, __import__('__main__')) 220 | class TestHandler(Handler): 221 | def setup(self): 222 | for h in subclasses: 223 | for ev in h.events: 224 | print 'SUBSCRIBE:', h.name, ev 225 | self.subscribe(h.name, ev, self.output) 226 | 227 | def output(self, *args): 228 | f = inspect.currentframe(1) 229 | d = inspect.getargvalues(f)[3] 230 | print 'PUBLISH:', d['self'].name, d['event'] 231 | print `args` 232 | 233 | subclasses.append(TestHandler) 234 | Program().main(argv=sys.argv[1:], subclasses=subclasses) 235 | -------------------------------------------------------------------------------- /dsniff/lib/__init__.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | -------------------------------------------------------------------------------- /dsniff/lib/fcap.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | """XXX quick pcap-like flow specification language 4 | should use something like RuleDispatch instead... 5 | """ 6 | 7 | try: 8 | from __builtin__ import set 9 | except ImportError: 10 | from sets import Set as set 11 | import dnet 12 | import itree, net 13 | from pyparsing import * 14 | 15 | class Matcher(object): 16 | """Flow matcher. 17 | 18 | Flow tuple keyword arguments: 19 | 20 | src -- source address as dnet.addr object 21 | dst -- destination address as dnet.addr object 22 | p -- IP protocol as integer 23 | sport -- source port (or ICMP code) as integer 24 | dport -- destination port (or ICMP type) as integer 25 | """ 26 | __keys = ('p', 'sport', 'dport') 27 | 28 | def __init__(self): 29 | self.src, self.src_any = itree.Itree(), set() 30 | self.dst, self.dst_any = itree.Itree(), set() 31 | for k in self.__keys: 32 | setattr(self, k, { None: set() }) 33 | 34 | def add(self, item, **ftuple): 35 | """Add an item to be matched on a flow tuple filter. 36 | Flow tuple values may be lists. 37 | """ 38 | if 'src' in ftuple: 39 | value = ftuple['src'] 40 | # XXX - handle lists of values 41 | if not isinstance(value, list): 42 | value = [ value ] 43 | for v in value: 44 | self.src.add(v.net(), v.bcast(), item) 45 | else: 46 | self.src_any.add(item) 47 | 48 | if 'dst' in ftuple: 49 | value = ftuple['dst'] 50 | # XXX - handle lists of values 51 | if not isinstance(value, list): 52 | value = [ value ] 53 | for v in value: 54 | self.dst.add(v.net(), v.bcast(), item) 55 | else: 56 | self.dst_any.add(item) 57 | 58 | for k in self.__keys: 59 | d = getattr(self, k) 60 | value = ftuple.get(k, None) 61 | # XXX - handle lists of values 62 | if not isinstance(value, list): 63 | value = [ value ] 64 | for v in value: 65 | if v not in d: 66 | d[v] = set() 67 | d[v].add(item) 68 | 69 | def match(self, **ftuple): 70 | """Return a list of matched items for the specified flow tuple.""" 71 | items = [] 72 | items.append(self.src_any.union(self.src.match(ftuple.get('src', None)))) 73 | items.append(self.dst_any.union(self.dst.match(ftuple.get('dst', None)))) 74 | for k in self.__keys: 75 | d = getattr(self, k) 76 | items.append(d[None].union(d.get(ftuple.get(k, None), ()))) 77 | items = list(reduce(lambda x, y: x.intersection(y), items)) 78 | items.sort() 79 | return items 80 | 81 | class Parser(object): 82 | """XXX - crappy pcap-subset flow filter parser.""" 83 | def __init__(self): 84 | self.__fcap_items = {} 85 | self.__parsed = {} 86 | 87 | _set_src = lambda s, l, t: self._set_addr('src', t[:]) 88 | _set_dst = lambda s, l, t: self._set_addr('dst', t[:]) 89 | _set_p = lambda s, l, t: self._set_proto('p', t[:]) 90 | _set_sport = lambda s, l, t: self._set_port('sport', t[:]) 91 | _set_dport = lambda s, l, t: self._set_port('dport', t[:]) 92 | 93 | INT = Word(nums) 94 | IPNAME = Combine(Word(alphanums) + ZeroOrMore('.' + Word(alphanums))) 95 | IPCIDR = Combine(IPNAME + '/' + INT) 96 | 97 | HOST = Optional(Suppress('host')) + IPNAME + \ 98 | ZeroOrMore(Suppress('or') + IPNAME) 99 | NET = Suppress('net') + IPCIDR + ZeroOrMore(Suppress('or') + IPCIDR) 100 | ADDR = NET | HOST 101 | WKP = oneOf("icmp tcp udp") 102 | 103 | SRCADDR = Suppress('src') + ADDR 104 | DSTADDR = Suppress('dst') + ADDR 105 | 106 | PROTO = WKP | (Optional(Suppress('ip')) + Suppress('proto') + INT) 107 | PROTOS = PROTO + ZeroOrMore(Suppress('or') + PROTO) 108 | 109 | PORTS = Suppress('port') + Word(alphanums) + \ 110 | ZeroOrMore(Suppress('or') + Word(alphanums)) 111 | SRCPORT = Suppress('src') + PORTS 112 | DSTPORT = Suppress('dst') + PORTS 113 | 114 | # only allow one src predicate, but src predicate can be a list 115 | # maybe allow a src predicate to be recursive also 116 | PRED = SRCPORT.setParseAction(_set_sport) | \ 117 | DSTPORT.setParseAction(_set_dport) | \ 118 | SRCADDR.setParseAction(_set_src) | \ 119 | DSTADDR.setParseAction(_set_dst) | \ 120 | PROTOS.setParseAction(_set_p) 121 | 122 | EXPR = PRED + ZeroOrMore(Suppress('and') + PRED) + \ 123 | restOfLine.setParseAction(self._error) 124 | 125 | self.parser = EXPR 126 | 127 | def _set_addr(self, k, v): 128 | if k in self.__parsed: 129 | raise ValueError, '%s already set' % k 130 | self.__parsed[k] = map(dnet.addr, v) 131 | 132 | def _set_proto(self, k, v): 133 | if k in self.__parsed: 134 | raise ValueError, '%s already set' % k 135 | def _parse_proto(p): 136 | n = net.proto_aton(p) 137 | if not n: 138 | n = int(p.split()[-1]) 139 | return n 140 | self.__parsed[k] = map(_parse_proto, v) 141 | 142 | def _set_port(self, k, v): 143 | if k in self.__parsed: 144 | raise ValueError, '%s already set' % k 145 | def _parse_port(p): 146 | t = net.serv_aton(p) 147 | if t: 148 | n = t[-1] 149 | else: 150 | n = int(p) 151 | return n 152 | self.__parsed[k] = map(_parse_port, v) 153 | 154 | def _error(self, s, l, t): 155 | if t[0]: 156 | raise SyntaxError, 'invalid input at char %d: %r' % (l, t[0]) 157 | 158 | def parse(self, s): 159 | """Parse filter string into dict.""" 160 | self.__parsed = {} 161 | if s: 162 | self.parser.parseString(s) 163 | return self.__parsed 164 | 165 | class Fcap(object): 166 | def __init__(self): 167 | self.parser = Parser() 168 | self.matcher = Matcher() 169 | self.__fcap_items = {} 170 | self.__fcap_parsed = {} 171 | 172 | def add(self, fcap, item): 173 | """Add item to return on flow filter match.""" 174 | d = self.parser.parse(fcap) 175 | # Save raw filter and item 176 | if fcap not in self.__fcap_items: 177 | self.__fcap_items[fcap] = [] 178 | self.__fcap_parsed[fcap] = d 179 | self.__fcap_items[fcap].append(item) 180 | # Add filter to DAG 181 | self.matcher.add(item, **d) 182 | 183 | def delete(self, fcap, item): 184 | """Delete flow filter match of item.""" 185 | self.__fcap_items[fcap].remove(item) 186 | if not self.__fcap_items[fcap]: 187 | del self.__fcap_items[fcap], self.__fcap_parsed[fcap] 188 | # XXX - recompile DAG 189 | self.matcher = Matcher() 190 | for fcap, items in self.__fcap_items.iteritems(): 191 | d = self.__fcap_parsed[fcap] 192 | for item in items: 193 | self.matcher.add(item, **d) 194 | 195 | def match(self, **ftuple): 196 | return self.matcher.match(**ftuple) 197 | 198 | def pcap_filter(self): 199 | """Return pcap filter expression from compiled flow filter.""" 200 | # XXX - gross hack to get around BPF_MAXINSNS limit on BSD for 201 | # programs like authsnarf. if only the BPF optimizer (or i) 202 | # were a little smarter... 203 | fields = { 'proto':[], 'tcp port':[], 'udp port':[] } 204 | l = [] 205 | for fcap, d in self.__fcap_parsed.iteritems(): 206 | if d.keys() == ['dport', 'p']: 207 | if d['p'] == [6]: 208 | fields['tcp port'].extend(d['dport']) 209 | elif d['p'] == [17]: 210 | fields['udp port'].extend(d['dport']) 211 | else: 212 | raise ValueError 213 | elif d.keys() == ['p']: 214 | fields['proto'].extend(d['p']) 215 | elif fcap: 216 | l.append('(%s)' % fcap.replace('src ', '').replace('dst ', '')) 217 | for k, v in fields.iteritems(): 218 | if v: 219 | v.sort() 220 | l.append('(%s %s)' % (k, ' or '.join(map(str, v)))) 221 | l.sort() 222 | return ' or '.join(l) 223 | 224 | if __name__ == '__main__': 225 | import unittest 226 | 227 | class TestParser(unittest.TestCase): 228 | def test_parse(self): 229 | tests = { 230 | 'tcp':{ 'p':[6] }, 231 | 'tcp or udp':{ 'p':[6,17] }, 232 | 'tcp and dst port 80':{ 'p':[6], 'dport':[80] }, 233 | 'tcp and dst port 22 or 80':{ 'p':[6], 'dport':[22,80] }, 234 | 'dst 1.2.3.4 and tcp and dst port 22': 235 | { 'p':[6], 'dst':[dnet.addr('1.2.3.4')], 'dport':[22] }, 236 | 'dst net 5.6.7.0/24 or 1.2.3.0/24 and tcp and src port 80 or 81': 237 | { 'p':[6], 'sport':[80,81], 238 | 'dst':[dnet.addr('5.6.7.0/24'), dnet.addr('1.2.3.0/24')] }, 239 | } 240 | 241 | parser = Parser() 242 | for k, v in tests.iteritems(): 243 | d = parser.parse(k) 244 | assert d == v, 'expected %r, got %r' % (v, d) 245 | 246 | class TestMatcher(unittest.TestCase): 247 | def test_match(self): 248 | matcher = Matcher() 249 | matcher.add('ping', p=1, dport=8) 250 | matcher.add('ssh', p=6, dport=22) 251 | matcher.add('tcp', p=6) 252 | matcher.add('http', p=6, dport=80) 253 | matcher.add('dns', p=17, dport=53) 254 | matcher.add('gre', p=47) 255 | matcher.add('intranet', dst=dnet.addr('10.0.0.0/8')) 256 | matcher.add('testbed', dst=dnet.addr('10.0.5.0/24')) 257 | assert matcher.match(p=6, dport=22) == ['ssh', 'tcp'] 258 | assert matcher.match(dst=dnet.addr('10.1.2.3'), 259 | p=17, dport=53) == ['dns', 'intranet'] 260 | assert matcher.match(dst=dnet.addr('10.0.5.0'), p=6, dport=23) == [ 'intranet', 'tcp', 'testbed' ] 261 | assert matcher.match(dst=dnet.addr('1.2.3.4'), p=17, dport=80) == [] 262 | assert matcher.match(p=6, dport=80) == ['http', 'tcp'] 263 | assert matcher.match(p=6, dport=666) == ['tcp'] 264 | assert matcher.match(p=50) == [] 265 | assert matcher.match(p=1, dport=8) == ['ping'] 266 | assert matcher.match(p=1, dport=0) == [] 267 | 268 | class TestFcap(unittest.TestCase): 269 | def test_fcap(self): 270 | fcap = Fcap() 271 | fcap.add('tcp and dst port 22', 'ssh') 272 | fcap.add('tcp and dst port 80', 'http') 273 | assert fcap.match(src=1, dst=2, p=6, dport=22) == ['ssh'] 274 | assert fcap.match(src=1, dst=2, p=17, dport=22) == [] 275 | assert fcap.pcap_filter() == '(tcp port 22 or 80)' 276 | fcap.delete('tcp and dst port 22', 'ssh') 277 | assert fcap.match(src=1, dst=2, p=6, dport=22) == [] 278 | assert fcap.pcap_filter() == '(tcp port 80)' 279 | fcap.add('tcp and dst port 80 and dst net 216.239.32.0/19 or 72.14.192.0/19', 'GOGL') 280 | assert fcap.match(dst=dnet.addr('72.14.192.123'), p=6, dport=80) == [ 'GOGL', 'http' ] 281 | 282 | unittest.main() 283 | -------------------------------------------------------------------------------- /dsniff/lib/html.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | import cStringIO, htmlentitydefs, re 4 | from xml.sax.saxutils import unescape as __unescape 5 | 6 | __tag_re = re.compile("|", re.M|re.S) 7 | 8 | def strip(s): 9 | return __unescape(__tag_re.sub('', s)) 10 | 11 | # From Ka-Ping Yee 12 | def decode(text): 13 | """Decode HTML entities in the given text.""" 14 | chunks = text.split('&') 15 | for i in range(1, len(chunks)): 16 | if ';' in chunks[i][:10]: 17 | entity, rest = chunks[i].split(';', 1) 18 | if entity.startswith('#'): 19 | chunks[i] = chr(int(entity[1:])) + rest 20 | elif entity in htmlentitydefs.entitydefs: 21 | chunks[i] = htmlentitydefs.entitydefs[entity] + rest 22 | else: 23 | chunks[i] = '&' + chunks[i] 24 | else: 25 | chunks[i] = '&' + chunks[i] 26 | return ''.join(chunks) 27 | 28 | -------------------------------------------------------------------------------- /dsniff/lib/http.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | """HTTP feedparser.""" 4 | 5 | import cgi, cStringIO, zlib 6 | import mime 7 | 8 | def parse_POST(content_type, buf): 9 | """Return dict of POST variables given the Content-Type header value 10 | and the content buffer. 11 | """ 12 | ctype, pdict = cgi.parse_header(content_type) 13 | if ctype == 'multipart/form-data': 14 | return cgi.parse_multipart(cStringIO.StringIO(buf), pdict) 15 | elif ctype == 'application/x-www-form-urlencoded': 16 | return cgi.parse_qs(buf) 17 | return {} 18 | 19 | class HttpParser(mime.MimeParser): 20 | methods = dict.fromkeys(( 21 | 'GET', 'PUT', 'ICY', 22 | 'COPY', 'HEAD', 'LOCK', 'MOVE', 'POLL', 'POST', 23 | 'BCOPY', 'BMOVE', 'MKCOL', 'TRACE', 'LABEL', 'MERGE', 24 | 'DELETE', 'SEARCH', 'UNLOCK', 'REPORT', 'UPDATE', 'NOTIFY', 25 | 'BDELETE', 'CONNECT', 'OPTIONS', 'CHECKIN', 26 | 'PROPFIND', 'CHECKOUT', 'CCM_POST', 27 | 'SUBSCRIBE', 'PROPPATCH', 'BPROPFIND', 28 | 'BPROPPATCH', 'UNCHECKOUT', 'MKACTIVITY', 29 | 'MKWORKSPACE', 'UNSUBSCRIBE', 'RPC_CONNECT', 30 | 'VERSION-CONTROL', 31 | 'BASELINE-CONTROL' 32 | )) 33 | proto = 'HTTP' 34 | 35 | def reset(self, data=None): 36 | """Reset HTTP parser.""" 37 | super(HttpParser, self).reset(data) 38 | self.body_len = self.chunk_len = self.zlib = self.gzcnt = None 39 | self.headers = {} 40 | 41 | def _parse_start(self): 42 | # XXX - RFC 2616, 4.1 43 | while True: 44 | line = self.getline().strip() 45 | if line: break 46 | 47 | l = line.split(None, 2) 48 | if len(l) == 2: 49 | l.append('') # XXX - empty version 50 | 51 | if l[0].startswith(self.proto): 52 | # HTTP response 53 | version, status, reason = l 54 | status = int(status) 55 | if status == 204 or status == 304 or 100 <= status < 200: 56 | self.body_len = 0 57 | self.handle_response(version, status, reason) 58 | else: 59 | # HTTP request 60 | try: 61 | method, uri, version = l 62 | except ValueError: 63 | return 64 | if method not in self.methods or \ 65 | not version.startswith(self.proto): 66 | return # XXX - be forgiving of mid-stream parsing 67 | if method == 'HEAD': 68 | self.body_len = 0 69 | self.handle_request(method, uri, version) 70 | 71 | super(HttpParser, self)._parse_start() 72 | 73 | def handle_headers(self, headers): 74 | """Overload to handle a dict of HTTP headers.""" 75 | pass 76 | 77 | def handle_request(self, method, uri, version): 78 | """Overload to handle a new HTTP request.""" 79 | pass 80 | 81 | def handle_response(self, version, status, reason): 82 | """Overload to handle a new HTTP response.""" 83 | pass 84 | 85 | def handle_field(self, name, value): 86 | """HTTP header field collector.""" 87 | name = name.lower() 88 | self.headers[name] = value 89 | 90 | def _end_fields(self): 91 | self.handle_headers(self.headers) 92 | self._zlib_setup(self.headers) 93 | if self.headers.get('transfer-encoding', '').lower() == 'chunked': 94 | self._parse_next = self.__parse_body_chunked 95 | elif self.body_len == 0: 96 | self.reset(self._data) 97 | elif 'content-length' in self.headers: 98 | self.body_len = int(self.headers['content-length']) 99 | self._parse_next = self.__parse_body_len 100 | elif self.headers.get('connection', '').lower() == 'keep-alive': 101 | self.reset(self._data) 102 | else: 103 | self._parse_next = self.__parse_body_close 104 | 105 | def _zlib_setup(self, hdrs): 106 | if 'gzip' in hdrs.get('content-encoding', '') or \ 107 | 'gzip' in hdrs.get('transfer-encoding', ''): 108 | self.zlib = zlib.decompressobj(-zlib.MAX_WBITS) 109 | self.gzcnt = 10 # XXX - vanilla gzip hdr len 110 | else: 111 | self.zlib = None 112 | self.gzcnt = 0 113 | 114 | def _zlib_decompress(self, buf): 115 | if self.zlib is not None: 116 | if self.gzcnt: 117 | n = min(self.gzcnt, len(buf)) 118 | self.gzcnt -= n 119 | buf = buf[n:] 120 | if buf: 121 | buf = self.zlib.decompress(buf) 122 | return buf 123 | 124 | def __parse_body_close(self): 125 | self.handle_body(self._zlib_decompress(self._data)) 126 | self._data = '' 127 | # XXX - self.handle_end() never called! 128 | 129 | def __parse_body_len(self): 130 | buf = self._data[:self.body_len] 131 | self.handle_body(self._zlib_decompress(buf)) 132 | self._data = self._data[self.body_len:] 133 | self.body_len -= len(buf) 134 | if not self.body_len: 135 | self.handle_end() 136 | self.reset(self._data) 137 | 138 | def __parse_body_chunked(self): 139 | if self.chunk_len is None: 140 | line = self.getline() 141 | self.chunk_len = int(line.split(None, 1)[0], 16) 142 | if self.chunk_len == 0: 143 | self.chunk_len = -1 144 | elif self.chunk_len > 0: 145 | buf = self._data[:self.chunk_len] 146 | s = self._zlib_decompress(buf) 147 | if s: 148 | self.handle_body(s) 149 | self._data = self._data[self.chunk_len:] 150 | self.chunk_len -= len(buf) 151 | else: 152 | line = self.getline() 153 | if self.chunk_len < 0: 154 | self.handle_end() 155 | self.reset(self._data) 156 | else: 157 | self.chunk_len = None 158 | 159 | if __name__ == '__main__': 160 | import sys 161 | class TestParser(HttpParser): 162 | def handle_request(self, *args): 163 | print 'REQ:', args 164 | 165 | def handle_response(self, *args): 166 | print 'RESPONSE:', args 167 | 168 | def handle_headers(self, headers): 169 | print 'HDRS:', headers 170 | 171 | def handle_body(self, body): 172 | print 'BODY:', len(body), `body`#`body[:50]` 173 | 174 | #buf = 'GET /download.html HTTP/1.1\r\nHost: www.ethereal.com\r\nUser-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.6) Gecko/20040113\r\nAccept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,image/jpeg,image/gif;q=0.2,*/*;q=0.1\r\nAccept-Language: en-us,en;q=0.5\r\nAccept-Encoding: gzip,deflate\r\nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\nKeep-Alive: 300\r\nConnection: keep-alive\r\nReferer: http://www.ethereal.com/development.html\r\n\r\n' 175 | buf = open(sys.argv[1]).read() 176 | TestParser().feed(buf) 177 | -------------------------------------------------------------------------------- /dsniff/lib/io.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | import os, sys, tempfile 4 | 5 | PATH_SHARE='%s:%s:.' % \ 6 | (os.path.join(sys.prefix, 'share', 'dsniff'), 7 | os.path.realpath(os.path.join(__file__, '..', '..', '..', 'etc'))) 8 | 9 | def path_open(filename, path=PATH_SHARE): 10 | try: 11 | return open(filename) 12 | except IOError: 13 | pass 14 | for pdir in path.split(':'): 15 | try: 16 | return open(os.path.join(pdir, filename)) 17 | except IOError: 18 | pass 19 | raise IOError, "couldn't find %s in %s" % (filename, path) 20 | 21 | class Tempfile(object): 22 | """Like tempfile.NamedTemporaryFile minus the implicit unlink on close. 23 | """ 24 | def __init__(self, dir=None, prefix='tmp'): 25 | fd, self.name = tempfile.mkstemp(prefix=prefix, dir=dir) 26 | self.dir = dir or tempfile.gettempdir() 27 | self.f = os.fdopen(fd, 'w') 28 | def __getattr__(self, k): 29 | return getattr(self.f, k) 30 | def rename(self, newname): 31 | oldname = self.name 32 | self.name = os.path.join(self.dir, newname) 33 | return os.rename(oldname, self.name) 34 | -------------------------------------------------------------------------------- /dsniff/lib/itree.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | BLACK, RED = range(2) 4 | LEFT, RIGHT, PARENT = range(3) 5 | 6 | class _ItreeNode(object): 7 | __slots__ = ('low', 'high', 'data', 'max', 'color', 'kids', 'parent') 8 | def __init__(self, low, high, data): 9 | self.low, self.high, self.data = low, high, data 10 | self.max = high 11 | self.color = RED 12 | self.kids = [ None, None ] 13 | self.parent = None 14 | 15 | class Itree(object): 16 | """Simple interval tree. 17 | """ 18 | def __init__(self): 19 | self.root = None 20 | self.count = 0 21 | 22 | def __max_fixup(self, elm): 23 | elm.max = elm.high 24 | for i in range(2): 25 | if elm.kids[i] and elm.kids[i].max > elm.max: 26 | elm.max = elm.kids[i].max 27 | 28 | def __rotate(self, elm, n): 29 | tmp = elm.kids[n ^ 1] 30 | elm.kids[n ^ 1] = tmp.kids[n] 31 | if elm.kids[n ^ 1]: 32 | tmp.kids[n].parent = elm 33 | tmp.parent = elm.parent 34 | if tmp.parent: 35 | if elm == elm.parent.kids[n]: 36 | elm.parent.kids[n] = tmp 37 | else: 38 | elm.parent.kids[n ^ 1] = tmp 39 | else: 40 | self.root = tmp 41 | tmp.kids[n] = elm 42 | elm.parent = tmp 43 | 44 | self.__max_fixup(elm) 45 | self.__max_fixup(tmp) 46 | 47 | def add(self, low, high, item): 48 | """Add interval with item to be returned on match. 49 | """ 50 | elm = _ItreeNode(low, high, item) 51 | parent, tmp = None, self.root 52 | while tmp: 53 | parent = tmp 54 | if tmp.max < elm.max: 55 | tmp.max = elm.max 56 | tmp = tmp.kids[int(elm.low >= tmp.low)] 57 | if parent: 58 | parent.kids[int(elm.low >= parent.low)] = elm 59 | elm.parent = parent 60 | else: 61 | self.root = elm 62 | self.count += 1 63 | 64 | # rebalance and color 65 | parent = elm.parent 66 | while parent and parent.color == RED: 67 | gparent = parent.parent 68 | n = int(parent != gparent.kids[LEFT]) 69 | tmp = gparent.kids[n ^ 1] 70 | if tmp and tmp.color == RED: 71 | tmp.color, parent.color, gparent.color = BLACK, BLACK, RED 72 | elm = gparent 73 | else: 74 | if parent.kids[n ^ 1] == elm: 75 | self.__rotate(parent, n) 76 | tmp, parent, elm = parent, elm, tmp 77 | parent.color, gparent.color = BLACK, RED 78 | self.__rotate(gparent, n ^ 1) 79 | self.root.color = BLACK 80 | 81 | def __match(self, elm, low, high, matches): 82 | if not elm: 83 | return 84 | if low <= elm.high and elm.low <= high: 85 | matches.append(elm.data) 86 | if elm.kids[LEFT] and low <= elm.kids[LEFT].max: 87 | self.__match(elm.kids[LEFT], low, high, matches) 88 | if elm.kids[RIGHT] and high >= elm.low and low <= elm.kids[RIGHT].max: 89 | self.__match(elm.kids[RIGHT], low, high, matches) 90 | 91 | def match(self, low, high=None): 92 | """Perform stabbing query for all overlapping intervals 93 | returning a list of matched items. 94 | """ 95 | if high is None: 96 | high = low 97 | elm = self.root 98 | matches = [] 99 | self.__match(elm, low, high, matches) 100 | return matches 101 | 102 | def __len__(self): 103 | return self.count 104 | 105 | if __name__ == '__main__': 106 | import unittest 107 | import dnet 108 | 109 | class ItreeTestCase(unittest.TestCase): 110 | def test_itree(self): 111 | it = Itree() 112 | it.add(0, 10, 'dec') 113 | it.add(0, 16, 'hex') 114 | it.add(0, 8, 'oct') 115 | it.add('a', 'f', 'a-f') 116 | a = dnet.addr('10.0.0.0/8') 117 | it.add(a.net(), a.bcast(), '10/8') 118 | assert it.match(-5, -5) == it.match(33, 33) == [] 119 | assert it.match(-10, 0) == ['hex', 'dec', 'oct'] 120 | assert it.match(5, 8) == ['hex', 'dec', 'oct'] 121 | assert it.match(9, 10) == ['hex', 'dec'] 122 | assert it.match(16, 23) == ['hex'] 123 | assert it.match('c') == ['a-f'] 124 | assert it.match('b0rked', 'dugsong') == ['a-f'] 125 | assert it.match('z') == [] 126 | assert it.match(dnet.addr('10.0.0.1')) == ['10/8'] 127 | assert it.match(dnet.addr('10.0.0.0'), dnet.addr('10.255.255.255')) == ['10/8'] 128 | assert it.match(dnet.addr('10.0.1.0'), dnet.addr('10.0.1.255')) == ['10/8'] 129 | assert it.match(dnet.addr('1.0.0.10')) == [] 130 | 131 | unittest.main() 132 | -------------------------------------------------------------------------------- /dsniff/lib/json.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | """XXX quick JSON parser, based on Michael Spencer's safe eval(). 4 | should use simplejson instead... 5 | """ 6 | 7 | import compiler 8 | 9 | class AbstractVisitor(object): 10 | def __init__(self): 11 | self._cache = {} # dispatch table 12 | 13 | def visit(self, node,**kw): 14 | cls = node.__class__ 15 | meth = self._cache.setdefault( 16 | cls, getattr(self, 'visit' + cls.__name__, self.default)) 17 | return meth(node, **kw) 18 | 19 | def default(self, node, **kw): 20 | for child in node.getChildNodes(): 21 | return self.visit(child, **kw) 22 | visitExpression = default 23 | 24 | class SafeEval(AbstractVisitor): 25 | def visitConst(self, node, **kw): 26 | return node.value 27 | 28 | def visitDict(self, node, **kw): 29 | return dict([ (self.visit(k), self.visit(v)) for k,v in node.items ]) 30 | 31 | def visitTuple(self, node, **kw): 32 | return tuple([ self.visit(i) for i in node.nodes ]) 33 | 34 | def visitList(self, node, **kw): 35 | return [ self.visit(i) for i in node.nodes ] 36 | 37 | def parse(s): 38 | try: 39 | ast = compiler.parse(s, 'eval') 40 | except SyntaxError, err: 41 | raise 42 | try: 43 | return SafeEval().visit(ast) 44 | except ValueError, err: 45 | raise 46 | 47 | -------------------------------------------------------------------------------- /dsniff/lib/mbox.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | import mime 4 | 5 | class MboxParser(mime.MimeParser): 6 | def _parse_start(self): 7 | line = self.getline() 8 | if line.startswith('From '): 9 | self.handle_mbox_from(line) 10 | else: 11 | raise ParseError, 'expected From mbox line, got %r' % line 12 | self._parse_next = self._parse_field 13 | 14 | def handle_mbox_from(self, line): 15 | pass 16 | 17 | def _parse_body(self, body): 18 | # XXX - replace '\n>From' with '\nFrom' 19 | super(MboxParser, self)._parse_body(body) 20 | -------------------------------------------------------------------------------- /dsniff/lib/mime.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | """Streaming MIME parser, ala sgmllib.""" 4 | 5 | class NeedInput(Exception): pass 6 | class ParseError(Exception): pass 7 | 8 | class MimeParser(object): 9 | def __init__(self, *args, **kwargs): 10 | self.reset() 11 | 12 | def reset(self, data=None): 13 | self._field = None 14 | self._data = data 15 | self._parse_next = self._parse_start 16 | 17 | def getline(self): 18 | i = self._data.find('\n') 19 | if i < 0: 20 | raise NeedInput 21 | line, self._data = self._data[:i+1], self._data[i+1:] 22 | return line 23 | 24 | def feed(self, data): 25 | if self._data: 26 | self._data += data 27 | else: 28 | self._data = data 29 | while self._data: 30 | try: 31 | self._parse_next() 32 | except NeedInput: 33 | break 34 | 35 | def _parse_start(self): 36 | self.handle_start() 37 | self._parse_next = self._parse_field 38 | 39 | def _parse_field(self): 40 | line = self.getline() 41 | if line.startswith(' ') or line.startswith('\t'): 42 | # line continuation 43 | self._field = '%s %s' % (self._field, line.strip()) 44 | else: 45 | if self._field: 46 | # if we had a previous field, parse it 47 | name, value = self._field.split(':', 1) 48 | value = value.strip() 49 | self.handle_field(name, value) 50 | """ 51 | try: 52 | m = getattr(self, 'do_%s' % name.lower().replace('-', '_')) 53 | m(name, value) 54 | except AttributeError: 55 | pass 56 | """ 57 | self._field = None 58 | line = line.strip() 59 | if line: 60 | self._field = line 61 | else: 62 | self._end_fields() 63 | 64 | def _end_fields(self): 65 | self._parse_next = self._parse_body 66 | 67 | def _parse_body(self): 68 | self.handle_body(self._data) 69 | self.handle_end() 70 | self.reset() 71 | 72 | def handle_start(self): 73 | """Override to handle start of a message.""" 74 | pass 75 | 76 | def handle_end(self): 77 | """Override to handle the end of a message.""" 78 | pass 79 | 80 | def handle_field(self, name, value): 81 | """Override to handle header field.""" 82 | pass 83 | 84 | def handle_body(self, body): 85 | """Override to handle some body data (not necessarily all of it).""" 86 | pass 87 | 88 | -------------------------------------------------------------------------------- /dsniff/lib/net.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | proto2num = { 4 | 'ip':0, 'icmp':1, 'igmp':2, 'tcp':6, 'udp':17, 'ddp':37, 'ip6':46, 5 | 'gre':47, 'esp':50, 'ah':51, 'icmp6':58, 'ospf':89, 'pim':103, 6 | 'vrrp':112, 'isis':124 7 | } 8 | proto2name = dict(zip(proto2num.itervalues(), proto2num.iterkeys())) 9 | 10 | # XXX - diverge from both IANA and nmap service names here 11 | # XXX - multiple values can be returned as a list 12 | serv2num = { 13 | 'echo_reply':[(1,0),(58,129)], 'dst_unreach':[(1,3),(58,1)], 14 | 'src_quench':(1,4), 'redirect':(1,5), 'echo':[(1,8),(58,128)], 15 | 'time_exceeded':[(1,11),(58,3)], 'param_prob':[(1,12),(58,4)], 16 | 17 | 'dns':(17,53), 'dhcp':[(17,67),(17,68)], 'tftp':(17,69), 18 | 'krb5':[(17,88),(6,88)], 'portmap':(17,111), 'ntp':(17,123), 19 | 'netbios-ns':(17,137), 'netbios-dgm':(17,138), 'snmp':(17,161), 20 | 'slp':(17,427), 'ike':(17,500), 'syslog':(17,514), 'rip':(17,520), 21 | 'hsrp':(17,1985), 'nfs':[(17,2049),(6,2049)], 22 | 'rendezvous':(17,5353), 23 | 24 | 'ftp':(6,21), 'ssh':(6,22), 'telnet':(6,23), 'smtp':[(6,25),(6,587)], 25 | 'http':(6,80), 'pop':[(6,110), (6,109)], 'ident':(6,113), 'nntp':(6,119), 26 | 'ms-rpc':[(6,135),(17,135),(6,1025)], 'netbios-ssn':[(6,139),(6,445)], 27 | 'imap':(6,143), 'bgp':(6,179), 'fw-1':(6,256), 'ldap':[(6,389),(6,3268)], 28 | 'https':(6,443), 'dantz':(6,497), 'rlogin':(6,513), 'rsh':(6,514), 29 | 'lpr':(6,515), 'rtsp':(6,554), 'ipp':[(6,631),(17,631)], 30 | 'ldap-ssl':(6,636), 'imap-ssl':(6,993), 'pop-ssl':(6,995), 31 | 'socks':(6,1080), 'kazaa':(6,1214), 'citrix':(6,1494), 'oracle':(6,1521), 32 | 'pptp':(6,1723), 'ms-winmedia':(6,1755), 'ms-msgs':(6,1863), 33 | 'slsk':[(6,2234),(6,5534)], 'cvs':(6,2401), 34 | 'http-proxy':[(6,3128),(6,8080)], 'mysql':(6,3306), 35 | 'ms-term-serv':(6,3389), 'edonkey2000':(6,4662), 'upnp':(6,5000), 36 | 'aim':(6,5190), 'yahoo':(6,5050), 'jabber':(6,5222), 37 | 'postgres':(6,5432), 'gnutella':(6,6346), 'irc':(6,6667), 38 | 'napster':(6,6699), 'bittorrent':(6,6881), 'icb':(6,7326), 39 | 'jetdirect':(6,9100) 40 | } 41 | serv2name = {} 42 | 43 | prog2num = { 44 | 'portmapper': 100000, 'rstatd': 100001, 'rusersd': 100002, 45 | 'nfs': 100003, 'ypserv': 100004, 'mountd': 100005, 'ypbind': 100007, 46 | 'walld': 100008, 'yppasswdd': 100009, 'etherstatd': 100010, 47 | 'rquotad': 100011, 'sprayd': 100012, '3270_mapper': 100013, 48 | 'rje_mapper': 100014, 'selection_svc': 100015, 'database_svc': 100016, 49 | 'rexd': 100017, 'alis': 100018, 'sched': 100019, 'llockmgr': 100020, 50 | 'nlockmgr': 100021, 'x25.inr': 100022, 'statmon': 100023, 51 | 'status': 100024, 'bootparamd': 100026, 'ypupdated': 100028, 52 | 'keyserv': 100029, 'tfsd': 100037, 'nsed': 100038, 'nsemntd': 100039, 53 | 'cmsd': 100068, 'ttdbserver': 100083, 'pcnfsd': 150001, 'amd': 300019, 54 | 'netinfo': 200100000, 'netinfobind': 200100001, 55 | } 56 | prog2name = dict(zip(prog2num.itervalues(), prog2num.iterkeys())) 57 | 58 | def __init_serv2name(): 59 | for value, key in serv2num.iteritems(): 60 | if isinstance(key[0], int): 61 | serv2name[key] = value 62 | else: 63 | for k in key: 64 | serv2name[k] = value 65 | 66 | __init_serv2name() 67 | 68 | def proto_ntoa(proto, default=None): 69 | """Return name for given protocol number.""" 70 | return proto2name.get(proto, default) 71 | 72 | def proto_aton(proto, default=None): 73 | """Return number for given protocol name.""" 74 | return proto2num.get(proto.lower(), default) 75 | 76 | def serv_ntoa(proto, port, default=None): 77 | """Return service name for given (proto, port) tuple.""" 78 | return serv2name.get((proto, port), default) 79 | 80 | def serv_aton(serv, default=None): 81 | """Return (proto, port) tuple for given service name.""" 82 | return serv2num.get(serv.lower(), default) 83 | 84 | def rpcprog_ntoa(prog, default=None): 85 | """Return RPC program name for given program number.""" 86 | return prog2name.get(prog, default) 87 | 88 | def rpcprog_aton(prog, default=None): 89 | """Return RPC program number for given program name.""" 90 | return prog2num.get(prog.lower(), default) 91 | 92 | def proto_load(filename='/etc/protocols'): 93 | """Load internal table of IP protocols from a file.""" 94 | f = open(filename) 95 | for line in f.readlines(): 96 | if line[0] not in '# \t\r\n': 97 | l = line.split(None, 2) 98 | name = l[0].lower() 99 | proto = int(l[1]) 100 | proto2name[proto] = name 101 | proto2num[name] = proto 102 | 103 | def serv_load(filename='/etc/services'): 104 | """Load internal table of services from a file.""" 105 | f = open(filename) 106 | for line in f.readlines(): 107 | if line[0] not in '# \t\r\n': 108 | l = line.split(None, 2) 109 | name = l[0].lower() 110 | port, proto = l[1].split('/') 111 | serv = (proto_aton(proto), int(port)) 112 | 113 | serv2name[serv] = name 114 | 115 | if name in serv2num: 116 | s = serv2num[name] 117 | if isinstance(s, list): 118 | if serv not in s: 119 | s.append(serv) 120 | else: 121 | serv2num[name] = [ s, serv ] 122 | else: 123 | serv2num[name] = serv 124 | 125 | if __name__ == '__main__': 126 | import unittest 127 | 128 | class netTestCase(unittest.TestCase): 129 | def test_proto_ntoa(self): 130 | self.failUnless(proto_ntoa(1) == 'icmp') 131 | def test_proto_aton(self): 132 | self.failUnless(proto_aton('icmp') == 1) 133 | def test_serv_ntoa(self): 134 | self.failUnless(serv_ntoa(6, 22) == 'ssh') 135 | def test_serv_aton(self): 136 | self.failUnless(serv_aton('ssh') == (6, 22)) 137 | 138 | unittest.main() 139 | -------------------------------------------------------------------------------- /dsniff/lib/reasm.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | """Reassembly helpers. 4 | XXX - only implements 'BSD' algorithm 5 | """ 6 | 7 | import copy, heapq 8 | 9 | class Cache(dict): 10 | """Fixed-size dict. 11 | 12 | Arguments: 13 | maxsz -- maximum number of entries 14 | 15 | Keyword arguments: 16 | reclaimfn -- callback to be executed on cache fill, to clear 17 | at least one entry. 18 | """ 19 | def __init__(self, maxsz, 20 | reclaimfn=lambda d: d.popitem()): 21 | self.maxsz = maxsz 22 | self._reclaimfn = reclaimfn 23 | 24 | def __setitem__(self, k, v): 25 | if self.__len__() >= self.maxsz: 26 | self._reclaimfn(self) 27 | dict.__setitem__(self, k, v) 28 | 29 | def clear(self): 30 | while self.__len__() > 0: 31 | self._reclaimfn(self) 32 | 33 | class Reassembler(object): 34 | """Quick-n-dirty TCP stream reassembler. 35 | """ 36 | def __init__(self, isn=0, win=0): 37 | self.cur = isn 38 | self.win = win 39 | self.q = [] # heapq of (seq, buf) 40 | 41 | def reassemble(self, seq, buf): 42 | """Given a sequence number and buffer, return sequenced data. 43 | XXX - half-duplex, doesn't require ACK of reassembled data 44 | XXX - need to limit buffering, implement windowing, sequence healing 45 | """ 46 | # XXX - fastpath properly sequenced data. 47 | if seq == self.cur and not self.q: 48 | self.cur += len(buf) 49 | return buf 50 | # XXX - favor newer data 51 | heapq.heappush(self.q, (seq, buf)) 52 | l = [] 53 | while self.q: 54 | if self.q[0][0] <= self.cur: 55 | seq, buf = heapq.heappop(self.q) 56 | if seq != self.cur: 57 | # Reverse overlap. Trim left (empty string on rexmit)... 58 | buf = buf[self.cur-seq:] 59 | l.append(buf) 60 | self.cur += len(buf) 61 | else: 62 | break 63 | return ''.join(l) 64 | 65 | class Defragger(object): 66 | """Quick-n-dirty IP fragment reassembler. 67 | """ 68 | def __init__(self, maxfragids=1000): 69 | self.pkts = Cache(maxfragids) 70 | 71 | def defrag(self, ip): 72 | """Given an IP fragment, try to return the reassembled packet.""" 73 | t = (ip.src, ip.dst, ip.p, ip.id) 74 | try: 75 | ipq = self.pkts[t] 76 | except KeyError: 77 | ipq = self.pkts[t] = Reassembler() 78 | ipq.totlen = 0 79 | ipq.bufs = [] 80 | 81 | off = (ip.off & 0x1fff) << 3 # IP_OFFMASK 82 | buf = ipq.reassemble(off, str(ip.data)) 83 | if buf: 84 | ipq.bufs.append(buf) 85 | 86 | if ipq.totlen == 0: 87 | # Check for last frag. 88 | if ip.off & 0x2000 == 0: # IP_MF 89 | ipq.totlen = off + len(ip.data) 90 | 91 | if ipq.totlen != 0 and sum(map(len, ipq.bufs)) == ipq.totlen: 92 | ip2 = copy.copy(ip) 93 | ip2.off = ip2.sum = 0 94 | ip2.data = ''.join(ipq.bufs) 95 | ip2.len = ipq.totlen 96 | ip2.unpack(str(ip2)) 97 | del self.pkts[t] 98 | return ip2 99 | 100 | if __name__ == '__main__': 101 | import unittest 102 | 103 | class ReasmTest(unittest.TestCase): 104 | def test_reasm(self): 105 | # Shankar and Paxson "Active Mapping" test, pinched from fragtest 106 | icmpecho = 'tcssidsq' 107 | off_data = ( 108 | (0, icmpecho + '1'*3*8), 109 | (40, '2'*2*8), 110 | (56, '3'*3*8), 111 | (16, '4'*4*8), 112 | (56, '5'*3*8), 113 | (80, '6'*3*8) 114 | ) 115 | data_policy = { 116 | '1'*3*8 + '4'*2*8 + '2'*8 + '3'*3*8 + '6'*3*8 : 'BSD', 117 | '1'*8 + '4'*3*8 + '2'*2*8 + '5'*3*8 + '6'*3*8 : 'BSD-right', 118 | '1'*3*8 + '4'*2*8 + '2'*8 + '5'*3*8 + '6'*3*8 : 'Linux', 119 | '1'*3*8 + '4'*8 + '2'*2*8 + '3'*3*8 + '6'*3*8 : 'First', 120 | '1'*8 + '4'*4*8 + '2'*8 + '5'*3*8 + '6'*3*8 : 'Last', 121 | } 122 | asm = Reassembler() 123 | l = [] 124 | for off, data in off_data: 125 | s = asm.reassemble(off, data) 126 | if s: 127 | l.append(s) 128 | s = ''.join(l)[len(icmpecho):] 129 | assert data_policy[s] == 'BSD' 130 | 131 | unittest.main() 132 | -------------------------------------------------------------------------------- /dsniff/mail/__init__.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | import aolmail, fastmail, gmail, hotmail, pop, smtp, yahoomail 4 | -------------------------------------------------------------------------------- /dsniff/mail/_webmail.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | import os, rfc822 4 | import dsniff 5 | from dsniff.lib import http, io 6 | 7 | class Parser(http.HttpParser): 8 | def __init__(self, handler): 9 | super(Parser, self).__init__() 10 | self.__handler = handler 11 | self.__f = self.__get_response = None 12 | 13 | def collect_request(self, callback): 14 | self.__f = io.Tempfile() 15 | self.__callback = callback 16 | 17 | def collect_response(self, callback): 18 | self.__get_response = 1 19 | self.__callback = callback 20 | 21 | def handle_headers(self, hdrs): 22 | self.__ctype = hdrs.get('content-type', '') 23 | 24 | def handle_response(self, version, status, reason): 25 | if self.__get_response: 26 | self.__f = io.Tempfile() 27 | 28 | def handle_body(self, buf): 29 | if self.__f: 30 | self.__f.write(buf) 31 | 32 | def handle_end(self): 33 | if self.__f: 34 | self.__f.close() 35 | self.__callback(self.__handler.flow, open(self.__f.name).read()) 36 | os.unlink(self.__f.name) 37 | self.__f = self.__get_response = None 38 | 39 | def get_postvars(self, buf): 40 | return http.parse_POST(self.__ctype, buf) 41 | 42 | def publish_email(self, hdrs, body): 43 | f = io.Tempfile(prefix='email') 44 | hdrs = dict(hdrs) 45 | if 'Date' not in hdrs: 46 | hdrs['Date'] = rfc822.formatdate(self.__handler.flow.etime) 47 | for k, v in hdrs.iteritems(): 48 | f.write('%s: %s\n' % (k, v)) 49 | f.write('\n') 50 | f.write(body) 51 | f.close() 52 | self.__handler.publish('email', dict(hdrs), f.name) 53 | 54 | class Handler(dsniff.Handler): 55 | events = ('email', ) 56 | 57 | def setup(self): 58 | self.subscribe('service', self.name, self.recv_flow) 59 | 60 | def recv_flow(self, f): 61 | if f.state == dsniff.FLOW_START: 62 | f.save['http'] = self.parser(self) 63 | elif f.state == dsniff.FLOW_CLIENT_DATA: 64 | f.save['http'].feed(f.client.data) 65 | elif f.state == dsniff.FLOW_SERVER_DATA: 66 | f.save['http'].feed(f.server.data) 67 | -------------------------------------------------------------------------------- /dsniff/mail/aolmail.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | import re 4 | import dsniff 5 | from dsniff.lib import json 6 | import _webmail 7 | 8 | class AOLMailParser(_webmail.Parser): 9 | hdr_map = { 10 | 'sentTime':'Date', 'displayTo':'To', 'displayFrom':'From', 11 | 'displayCc':'Cc', 'messageID':'Message-Id', 'subject':'Subject' 12 | } 13 | msg_re = re.compile(r'^message\.(?P.*?) = (?P.*);$') 14 | 15 | def handle_request(self, method, uri, version): 16 | if method == 'GET' and 'GetMessage.aspx' in uri: 17 | self.collect_response(self.__parse_get) 18 | elif method == 'POST': 19 | self.collect_request(self.__parse_post) 20 | 21 | def __parse_post(self, flow, buf): 22 | d = self.get_postvars(buf) 23 | hdrs = [ (k, d[k][0]) for k in ('From', 'To', 'Cc', 'Subject') 24 | if k in d and d[k][0]] 25 | body = d['PlainBody'][0] 26 | if hdrs and body: 27 | self.publish_email(hdrs, body) 28 | 29 | def __parse_get(self, flow, buf): 30 | hdrs, body = [], None 31 | for line in buf.splitlines(): 32 | m = self.msg_re.match(line.strip()) 33 | if m: 34 | k, v = m.group('k'), m.group('v') 35 | if k in self.hdr_map: 36 | v = json.parse(v) 37 | if v: hdrs.append((self.hdr_map[k], v)) 38 | elif k == 'body': 39 | body = json.parse(v) 40 | if hdrs and body: 41 | self.publish_email(hdrs, body) 42 | 43 | class AOLMailHandler(_webmail.Handler): 44 | name = 'aolmail' 45 | parser = AOLMailParser 46 | 47 | if __name__ == '__main__': 48 | dsniff.test() 49 | -------------------------------------------------------------------------------- /dsniff/mail/fastmail.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | import re 4 | import dsniff 5 | from dsniff.lib import html 6 | import _webmail 7 | 8 | class FastmailParser(_webmail.Parser): 9 | hdr_re = re.compile(r'(?P.*?) (?P.*?)', re.DOTALL) 10 | body_re = re.compile(r'\n\n(?P.*?)', re.DOTALL) 11 | 12 | def handle_request(self, method, uri, version): 13 | if method == 'GET' and uri.startswith('/mail/?'): 14 | self.collect_response(self.__parse_get) 15 | elif method == 'POST' and uri.startswith('/mail/?'): 16 | self.collect_request(self.__parse_post) 17 | 18 | def __parse_post(self, flow, buf): 19 | d = self.get_postvars(buf) 20 | if 'FMC-MsgMessage' in d: 21 | hdrs = [ ('To', d['FMC-MsgTo'][0]), 22 | ('Subject', d['FMC-MsgSubject'][0]), 23 | ('Cc', d['FMC-MsgCc'][0]) ] 24 | body = d['FMC-MsgMessage'][0] 25 | if hdrs and body: 26 | self.publish_email(hdrs, body) 27 | 28 | def __parse_get(self, flow, buf): 29 | hdrs = [] 30 | for k, v in self.hdr_re.findall(buf): 31 | k = k.strip(' ') 32 | if ' ' not in k: 33 | v = html.decode(html.strip(v)) 34 | v = v.strip(' [Add]') # XXX - addrs 35 | v = v.split(' \xa0')[0] # XXX - date 36 | hdrs.append((k, v)) 37 | m = self.body_re.search(buf) 38 | if hdrs and m: 39 | self.publish_email(hdrs, m.group('body')) 40 | 41 | class FastmailHandler(_webmail.Handler): 42 | name = 'fastmail' 43 | parser = FastmailParser 44 | 45 | if __name__ == '__main__': 46 | dsniff.test() 47 | -------------------------------------------------------------------------------- /dsniff/mail/gmail.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | import re 4 | import dsniff 5 | from dsniff.lib import json 6 | import _webmail 7 | 8 | class GmailParser(_webmail.Parser): 9 | data_re = re.compile(r'D\((.*?)\);\n', re.DOTALL) 10 | 11 | def handle_request(self, method, uri, version): 12 | if method == 'GET' and uri.startswith('/mail/') and 'view=cv' in uri: 13 | self.collect_response(self.__parse_get) 14 | elif method == 'POST' and uri.startswith('/mail/') and 'ik=' in uri: 15 | self.collect_request(self.__parse_post) 16 | 17 | def __parse_post(self, flow, buf): 18 | d = self.get_postvars(buf) 19 | if d: 20 | hdrs = [ (h.capitalize(), d[h][0].rstrip(', ')) 21 | for h in ('from', 'to', 'subject', 'cc') if d[h][0] ] 22 | self.publish_email(hdrs, '\n'.join(d['msgbody'])) 23 | 24 | def __parse_get(self, flow, buf): 25 | hdrs, body = [], [] 26 | for s in self.data_re.findall(buf): 27 | s = s.replace('\n', '') 28 | for i in range(2): # XXX - lame 29 | s = s.replace(',,', ',None,') 30 | l = json.parse(s) 31 | if l[0] == 'mi': 32 | if hdrs and body: 33 | # XXX - handle message threads 34 | self.publish_email(hdrs, '\n'.join(body)) 35 | hdrs, body = [], [] 36 | hdrs.append(('From', '%s <%s>' % (l[6], l[8]))) 37 | hdrs.append(('To', ', '.join(l[11]))) 38 | if l[12]: 39 | hdrs.append(('Cc', ', '.join(l[12]))) 40 | if l[14]: 41 | hdrs.append(('Reply-To', l[14][0])) 42 | hdrs.append(('Date', l[15])) 43 | hdrs.append(('Subject', l[16])) 44 | hdrs.append(('Message-Id', l[-1])) 45 | elif l[0] == 'mb': 46 | body.append(l[1]) 47 | if hdrs and body: 48 | self.publish_email(hdrs, '\n'.join(body)) 49 | 50 | class Gmail(_webmail.Handler): 51 | name = 'gmail' 52 | parser = GmailParser 53 | 54 | if __name__ == '__main__': 55 | dsniff.test() 56 | -------------------------------------------------------------------------------- /dsniff/mail/hotmail.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | import re 4 | import dsniff 5 | from dsniff.lib import html 6 | import _webmail 7 | 8 | class HotmailParser(_webmail.Parser): 9 | hdrs_body_re = re.compile(r'(?P.*?)
.*?.*?
\n \n
(?P.*?)
\n
(?P.*?)(?P.*?)', re.DOTALL) 11 | 12 | def handle_request(self, method, uri, version): 13 | if method == 'GET' and uri.startswith('/cgi-bin/getmsg'): 14 | self.collect_response(self.__parse_get) 15 | elif method == 'POST' and uri.startswith('/cgi-bin/premail'): 16 | self.collect_request(self.__parse_post) 17 | 18 | def __parse_post(self, flow, buf): 19 | d = self.get_postvars(buf) 20 | hdrs = [ ('From', '%s@hotmail.com' % d['login'][0]), 21 | ('To', d['to'][0]), ('Subject', d['subject'][0]) ] 22 | body = d['body'][0] 23 | if hdrs and body: 24 | self.publish_email(hdrs, body) 25 | 26 | def __parse_get(self, flow, buf): 27 | m = self.hdrs_body_re.search(buf) 28 | body = m.group('body') 29 | hdrs = [ (k.replace(' ', '').strip(':'), html.decode(v)) 30 | for k, v in self.hdr_re.findall(m.group('hdrs')) ] 31 | self.publish_email(hdrs, body) 32 | 33 | class HotmailHandler(_webmail.Handler): 34 | name = 'hotmail' 35 | parser = HotmailParser 36 | 37 | if __name__ == '__main__': 38 | dsniff.test() 39 | 40 | -------------------------------------------------------------------------------- /dsniff/mail/pop.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | import base64, email.Parser 4 | import dsniff 5 | from dsniff.lib import io 6 | 7 | POP_NONE, POP_RETR, POP_DATA = range(3) 8 | 9 | class POPHandler(dsniff.Handler): 10 | name = 'pop' 11 | events = ('auth', 'email') 12 | 13 | def setup(self): 14 | self.subscribe('service', 'pop', self.recv_flow) 15 | 16 | def recv_flow(self, flow): 17 | if self in flow.save: 18 | d = flow.save[self] 19 | else: 20 | d = flow.save[self] = { 'state':POP_NONE } 21 | 22 | if flow.server.data: 23 | if d['state'] == POP_RETR: 24 | if self.callbacks.get('email') and \ 25 | flow.server.data.startswith('+OK'): 26 | d['state'] = POP_DATA 27 | d['msgfile'] = io.Tempfile() 28 | d['msgfile'].write(flow.server.data.split('\n', 1)[1]) 29 | else: 30 | d['state'] = POP_NONE 31 | elif d['state'] == POP_DATA: 32 | if flow.server.data.endswith('\r\n.\r\n'): 33 | d['msgfile'].write(flow.server.data[:-5]) 34 | d['msgfile'].close() 35 | # XXX - don't litter 36 | d['msgfile'].rename('pop.msg') 37 | parser = email.Parser.Parser() 38 | msg = parser.parse(open(d['msgfile'].name), 39 | headersonly=True) 40 | self.publish('email', msg._headers, 41 | d['msgfile'].name) 42 | d['state'] = POP_NONE 43 | else: 44 | d['msgfile'].write(flow.server.data) 45 | elif flow.client.data: 46 | for line in flow.client.readlines(): 47 | if d['state'] == POP_NONE: 48 | if line.startswith('RETR'): 49 | d['state'] = POP_RETR 50 | elif line.startswith('USER'): 51 | d['username'] = line.split(' ', 1)[1] 52 | elif line.startswith('PASS') and d.get('username'): 53 | password = line.split(' ', 1)[1] 54 | if self.callbacks.get('auth'): 55 | self.publish('auth', d.pop('username'), password) 56 | elif line.startswith('AUTH') and \ 57 | self.callbacks.get('auth'): 58 | l = line.split() 59 | if l[1] in ('PLAIN', 'LOGIN'): 60 | username, password = \ 61 | base64.decodestring(l[2]).split('\x00')[-2:] 62 | self.publish('auth', username, password) 63 | 64 | if __name__ == '__main__': 65 | dsniff.test() 66 | -------------------------------------------------------------------------------- /dsniff/mail/smtp.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | import base64, email.Parser 4 | import dsniff 5 | from dsniff.lib import io 6 | 7 | SMTP_NONE, SMTP_AUTH, SMTP_HELO, SMTP_MAIL, SMTP_RCPT, SMTP_DATA = range(6) 8 | 9 | class SMTPHandler(dsniff.Handler): 10 | name = 'smtp' 11 | events = ('auth', 'email') 12 | 13 | def setup(self): 14 | self.subscribe('service', 'smtp', self.recv_flow) 15 | 16 | def recv_flow(self, flow): 17 | if not flow.client.data: 18 | return 19 | if self in flow.save: 20 | d = flow.save[self] 21 | else: 22 | d = flow.save[self] = { 'state':SMTP_NONE } 23 | 24 | for line in flow.client.readlines(): 25 | if d['state'] == SMTP_DATA: 26 | if line == '.': 27 | if 'email' in self.callbacks: 28 | d['msgfile'].close() 29 | # XXX - don't litter 30 | d['msgfile'].rename('smtp.msg') 31 | parser = email.Parser.Parser() 32 | msg = parser.parse(open(d['msgfile'].name), 33 | headersonly=True) 34 | self.publish('email', msg._headers, 35 | d['msgfile'].name) 36 | d['state'] = SMTP_HELO 37 | elif 'email' in self.callbacks: 38 | d['msgfile'].write(line) 39 | d['msgfile'].write('\r\n') 40 | elif line.strip(): 41 | cmd = line.split()[0] 42 | if cmd == 'RSET': 43 | d['state'] = SMTP_HELO 44 | elif line.startswith('AUTH LOGIN'): 45 | l = line.split() 46 | d['user'] = base64.decodestring(l[2]) 47 | d['state'] = SMTP_AUTH 48 | elif d['state'] == SMTP_AUTH: 49 | if 'auth' in self.callbacks: 50 | self.publish('auth', d.pop('user'), 51 | base64.decodestring(line), {}) 52 | d['state'] = SMTP_HELO 53 | elif d['state'] == SMTP_NONE and cmd in ('HELO', 'EHLO'): 54 | d['state'] = SMTP_HELO 55 | elif d['state'] == SMTP_HELO and \ 56 | cmd in ('MAIL', 'SEND', 'SAML'): 57 | #d['from'] = line.split('<', 1)[1].split('>', 1)[0] 58 | d['state'] = SMTP_MAIL 59 | elif d['state'] == SMTP_MAIL and cmd == 'RCPT': 60 | d['state'] = SMTP_RCPT 61 | elif d['state'] == SMTP_RCPT and cmd == 'DATA': 62 | d['state'] = SMTP_DATA 63 | if 'email' in self.callbacks: 64 | d['msgfile'] = io.Tempfile() 65 | 66 | if __name__ == '__main__': 67 | dsniff.test() 68 | -------------------------------------------------------------------------------- /dsniff/mail/yahoomail.py: -------------------------------------------------------------------------------- 1 | # $Id$ 2 | 3 | import re 4 | import dsniff 5 | from dsniff.lib import html 6 | import _webmail 7 | 8 | class YmailParser(_webmail.Parser): 9 | hdrs_re = re.compile(r'(?P.*?)
', re.DOTALL) 10 | hdr_re = re.compile(r'(?P.*?)(?P.*?)', re.DOTALL) 11 | body_re = re.compile(r'
(?P.*?)
\n\n', re.DOTALL) 12 | client_addrs = {} 13 | 14 | def handle_request(self, method, uri, version): 15 | if method == 'GET' and uri.startswith('/ym/ShowLetter'): 16 | self.collect_response(self.__parse_get) 17 | elif method == 'POST' and uri.startswith('/ym/Compose'): 18 | self.collect_request(self.__parse_post) 19 | 20 | def __parse_get(self, flow, buf): 21 | hdrs = self.hdrs_re.search(buf).group('hdrs') 22 | hdrs = [ (k.strip(':'), 23 | # XXX - hack around HREFs in From: value 24 | html.decode(html.strip(v.split('  ')[0]))) 25 | for k, v in self.hdr_re.findall(buf) ] 26 | d = dict(hdrs) 27 | # XXX - track client addrs by IP - should use cookie instead? 28 | self.client_addrs[flow.client.addr] = d.get('To', '') 29 | body = self.body_re.search(buf).group('body') 30 | if hdrs and body: 31 | self.publish_email(hdrs, body) 32 | 33 | def __parse_post(self, flow, buf): 34 | d = self.get_postvars(buf) 35 | hdrs = [ ('From', self.client_addrs.get(flow.client.addr, '')), 36 | ('To', d.get('To', [''])[0]), 37 | ('Subject', d.get('Subj', [''])[0]) ] 38 | body = d.get('Body', [''])[0] 39 | if hdrs and body: 40 | self.publish_email(hdrs, body) 41 | 42 | class YmailHandler(_webmail.Handler): 43 | name = 'yahoomail' 44 | parser = YmailParser 45 | 46 | if __name__ == '__main__': 47 | dsniff.test() 48 | 49 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # $Id$ 4 | 5 | from distutils.core import setup 6 | 7 | # setup vars 8 | dist_name = 'dsniff' 9 | ver = '3.0a' 10 | desc = 'dsniff is a simple Python application framework for network monitoring.' 11 | auth = 'Dug Song' 12 | email = 'dugsong@monkey.org' 13 | license = 'GNU General Public License 2.0' 14 | website = 'http://code.google.com/p/dsniff' 15 | platform = 'linux' 16 | dist_modules = ['dsniff','dsniff.core','dsniff.lib','dsniff.mail'] 17 | scripts = [] 18 | # setup command 19 | setup(name = dist_name, 20 | version = ver, 21 | description = desc, 22 | maintainer = auth, 23 | maintainer_email = email, 24 | license = license, 25 | url = website, 26 | platforms = platform, 27 | packages = dist_modules, 28 | scripts = scripts, 29 | ) 30 | 31 | -------------------------------------------------------------------------------- /share/dhcpos_fingerprints.txt: -------------------------------------------------------------------------------- 1 | 1,15,3,6,44,46,47,31,33,249,43,Windows 2003 or XP 2 | 1,15,3,6,44,46,47,31,33,249,43,252,Windows XP 3 | 1,15,3,6,44,46,47,31,33,249,43,252,12,Windows XP 4 | 15,3,6,44,46,47,31,33,249,43,252,Windows XP 5 | 15,3,6,44,46,47,31,33,249,43,252,12,Windows XP 6 | 28,2,3,15,6,12,44,47,Windows XP 7 | 1,15,3,6,44,46,47,31,33,43,Windows 2000 8 | 1,15,3,6,44,46,47,31,33,43,252,Windows 2000 or Netgear Wireless 9 | 1,15,3,6,44,46,47,31,33,43,252,12,Windows 2000 10 | 15,3,6,44,46,47,31,33,43,Windows 2000 11 | 15,3,6,44,46,47,31,33,43,252,Windows 2000 12 | 1,15,3,44,46,47,6,Windows NT 4 13 | 1,2,3,6,12,15,26,28,85,86,87,88,44,45,46,47,70,69,78,79,Windows NT 4 14 | 1,15,3,6,44,46,47,31,33,43,77,Windows ME 15 | 15,3,6,44,46,47,31,33,43,77,Windows ME 16 | 1,15,3,6,44,46,47,43,77,Windows 98 SE 17 | 1,15,3,6,44,46,47,43,77,252,Windows 98 SE 18 | 15,3,6,44,46,47,43,77,252,Windows 98 SE 19 | 15,3,6,44,46,47,43,77,Windows 98 SE 20 | 1,3,6,15,44,46,47,57,Windows 98 or PocketPC or SlingBox 21 | 1,3,15,6,44,46,47,Windows 95 22 | 78,79,85,Novell Netware Client 23 | 85,86,87,Novell Netware Client 24 | 100,Novell Netware Client 25 | 85,Novell Netware Client 26 | 85,86,Novell Netware Client 27 | 78,79,Novell Netware Client 28 | 1,3,6,15,12,BeOS 4.x or BATM VOIP 29 | 1,3,6,15,12,19,BeOS 5.0 30 | 1,3,6,15,112,113,78,79,95,MacOS X 31 | 1,3,6,15,112,113,78,79,95,252,MacOS X 32 | 3,6,15,112,113,78,79,95,MacOS X 33 | 3,6,15,112,113,78,79,95,252,MacOS X 34 | 1,3,6,15,112,113,78,79,MacOS X 35 | 1,3,6,15,112,113,78,79,95,44,47,MacOS X 36 | 3,6,15,112,113,78,79,95,44,47,MacOS X 37 | 1,3,6,15,112,113,78,79,95,252,44,47,MacOS X 38 | 60,43,MacOS X 39 | 1,3,6,15,33,42,44,45,46,47,69,70,71,74,78,79,Mac OS 9 40 | 1,3,43,60,Mac ? 41 | 1,28,2,3,15,6,12,46,44,Lindows 4.x 42 | 1,3,6,12,15,23,28,29,31,33,40,41,42,Linux 2.4.8-pre4 i568 43 | 3,6,12,15,17,23,28,29,31,33,40,41,42,9,7,200,44,Linux 2.4.21-202-default48 i686 44 | 1,3,6,12,15,23,28,29,31,33,40,41,42,9,7,200,44,Linux 2.6.4 45 | 1,3,6,15,28,12,7,9,42,48,49,Linux (Knoppix or old RedHat) 46 | 1,3,6,12,15,17,23,28,29,31,33,40,41,42,Gentoo (2005 or older) 47 | 1,3,6,12,15,17,23,28,29,31,33,40,41,42,119,Gentoo 2006+ 48 | 1,28,2,3,15,6,12,44,47,Debian Based Linux 49 | 3,6,15,28,12,7,9,42,48,49,Debian Based Linux 50 | 3,6,12,15,17,23,28,29,31,33,40,41,42,119,Generic Linux 51 | 28,2,3,15,6,12,40,41,42,RedHat/Fedora based Linux 52 | 1,3,6,15,28,12,7,9,42,48,49,26,RedHat/Fedora based Linux 53 | 1,3,6,12,15,17,23,28,29,31,33,40,41,42,9,7,200,44,SuSE Linux/Novell Desktop 54 | 1,3,6,12,15,17,23,28,29,31,33,40,41,42,9,7,44,45,46,47,SuSE Linux/Novell Desktop 55 | 1,28,2,3,15,6,12,40,41,SLED 10 56 | 1,28,3,15,6,12,OpenBSD 57 | 1,66,6,3,67,12,150,Cisco 2900 Catalyst XL 58 | 1,66,6,3,67,Cisco 2900 Catalyst XL 59 | 1,66,6,3,67,150,43,Cisco 2900 Catalyst XL or Cisco Aironet 1240 60 | 1,6,15,44,3,33,Cisco 3550 61 | 1,6,15,44,52,Cisco 3560 62 | 1,3,12,43,Etherboot or Sun Blade 63 | 1,2,3,5,6,11,12,13,15,16,17,18,43,54,60,67,128,129,130,131,132,133,134,135,PXE Client 64 | 1,3,3,5,6,11,12,13,15,16,17,18,43,54,60,67,128,129,130,131,132,133,134,135,PXE Client 65 | 1,3,6,15,28,33,IBM WARP 4.1 or Nintendo Wii 66 | 1,3,6,12,15,28,Linksys or Deliberant Router 67 | 1,3,6,15,28,54,Linksys Router 68 | 1,28,2,3,15,6,12,4,7,23,26,43,50,51,54,55,60,61,Linksys Router 69 | 1,28,2,3,15,6,12,4,7,23,26,43,50,51,54,55,60,72,Linksys Router 70 | 1,3,6,12,15,44,46,47,Linksys Router 71 | 1,15,3,6,44,46,47,Linksys Router 72 | 1,15,3,6,44,46,47,1,3,6,15,44,46,47,Linksys Router 73 | 1,15,3,6,44,46,47,66,1,3,6,15,44,46,47,Linksys Router 74 | 1,3,6,12,15,28,44,Linksys Router 75 | 15,3,6,44,46,47,Linksys Router 76 | 1,3,6,15,12,69,70,88,42,NetBotz WallBotz 400C 77 | 3,6,15,Netgear Router 78 | 3,1,6,15,Netgear Router 79 | 3,1,6,15,12,Netgear Router 80 | 3,1,6,12,15,67,66,43,Sunrocket VoIP Gizmo 81 | 1,3,42,6,7,15,58,59,66,Linksys PAP VoIP 82 | 1,3,6,12,15,42,43,Mediatrix VoIP Adapter 83 | 1,2,3,6,15,42,Uniden DTA VoIP Adapter 84 | 54,51,58,59,1,3,6,15,28,139,UniData IP Phone 85 | 1,3,6,15,66,69,43,176,Avaya IP Telephone 86 | 1,3,7,6,15,66,69,43,176,Avaya IP Telephone 87 | 1,3,6,15,42,66,150,Cisco IP Phone 88 | 1,66,6,3,15,150,35,Cisco 7940/60 IP Phone 89 | 1,66,6,3,15,150,35,151,Cisco 7940/60 IP Phone 90 | 1,28,3,6,15,67,4,7,Cisco Wireless Access Point 91 | 1,6,15,44,3,33,150,43,Cisco Wireless Access Point 92 | 1,66,6,15,44,3,67,33,150,43,Cisco Wireless Access Point 93 | 1,66,6,15,44,3,67,12,33,150,43,Cisco Wireless Access Point 94 | 1,3,15,6,43,77,Compex Wireless Access Point 95 | 1,3,6,15,OEMed Wireless Router or Airport Express or PalmOS 96 | 28,3,6,15,Apple Airport 97 | 1,28,3,6,15,Apple Airport 98 | 1,3,6,12,15,43,66,67,128,129,130,131,132,133,134,135,Symbol Wireless Access Point 99 | 3,6,XBox 100 | 1,3,6,XBox or XBox 360 or Nintendo DS/DS Lite 101 | 3,15,6,PlayStation 2 102 | 3,6,26,28,58,59,Gamecube 103 | 28,2,3,15,6,12,TiVo or Debian Unstable 3 104 | 1,2,3,4,7,6,15,Replay TV 105 | 1,2,3,6,12,15,17,23,28,29,31,33,40,41,42,43,Amino Aminet Set Top Box 106 | 1,3,7,44,51,54,58,59,12,15,144,18,Hewlett-Packard JetDirect Generic 107 | 1,3,44,6,7,12,15,22,54,58,59,69,18,144,Hewlett-Packard JetDirect Generic 108 | 3,44,6,7,12,15,22,54,58,59,69,18,144,Hewlett-Packard JetDirect Generic 109 | 3,44,6,81,7,12,15,22,54,58,59,69,18,144,Hewlett-Packard JetDirect Generic 110 | 3,7,44,51,54,58,59,12,15,144,18,Hewlett-Packard JetDirect Generic 111 | 1,3,44,6,7,12,15,22,54,58,59,18,144,Hewlett-Packard JetDirect Generic 112 | 1,3,44,6,81,7,12,15,22,54,58,59,69,18,144,Hewlett-Packard JetDirect Generic 113 | 1,3,7,44,51,54,58,59,12,144,18,Hewlett-Packard JetDirect Generic 114 | 3,6,15,44,47,Canon Printer 115 | 1,3,6,15,44,47,Canon Printer 116 | 51,1,3,58,59,12,44,54,6,15,144,Xerox Printer 117 | 1,28,3,15,6,12,44,78,79,Xerox Printer 118 | 12,Xerox Printer 119 | 1,2,3,6,12,15,26,28,88,44,45,46,47,70,69,78,79,Dell Printer 120 | 1,3,6,12,15,28,42,40,44,46,Brother Printer 121 | 1,3,6,58,59,44,46,47,Tally Printer 122 | 1,3,6,15,28,Hotway LanDrive 123 | 56,6,1,3,15,RIM Blackberry 124 | 1,3,6,15,28,33,43,44,58,59,HP iLo Agent 125 | 1,3,6,12,15,120,242,Tandberg 1000 126 | 6,1,3,12,44,Polycom ViewStation 127 | # not in fingerbank 128 | 1,28,2,3,15,6,12,40,41,42,Linux 129 | 1,28,2,3,15,6,12,ISC-DHCP client 130 | 1,63,78,79,85,86,87,Microsoft Windows 98 131 | 1,3,6,26,28,58,59,Nintendo Game Cube 132 | 1,3,6,12,15,28,40,41,42,OpenWRT Kamikaze 133 | 1,15,3,6,44,46,47,31,33,249,43,252,121,15,3,6,44,46,47,31,33,249,43,252,Microsoft Windows XP 134 | 1,3,6,15,119,95,252,44,46,47,MacOSX Leopard or Apple iPhone 135 | 1,15,3,6,44,46,47,31,33,121,249,43,Microsoft Windows Vista 136 | 1,15,3,6,44,46,47,31,33,121,249,43,252,Microsoft Windows Vista 137 | 1,3,28,6,15,Sony PSP 138 | 1,2,3,6,12,15,28,42,44,Ruckus CPE 139 | 1,3,6,15,44,46,47,Slingbox or Windows Mobile 140 | 1,3,6,12,15,28,42,40,38,23,37,38,39,19,26,Weathergoose Monitor 141 | 1,3,15,6,Sony PS2 or PS3 142 | 58,59,1,28,121,33,3,12,119,15,6,40,41,42,26,System Rescue CD 143 | 6,Systm Rescue CD 144 | -------------------------------------------------------------------------------- /share/p0f.fp: -------------------------------------------------------------------------------- 1 | # 2 | # p0f - SYN fingerprints 3 | # ---------------------- 4 | # 5 | # .-------------------------------------------------------------------------. 6 | # | The purpose of this file is to cover signatures for incoming TCP/IP | 7 | # | connections (SYN packets). This is the default mode of operation for | 8 | # | p0f. This is also the biggest and most up-to-date set of signatures | 9 | # | shipped with this project. The file also contains a detailed discussion | 10 | # | of all metrics examined by p0f, and some practical notes on how to | 11 | # | add new signatures. | 12 | # `-------------------------------------------------------------------------' 13 | # 14 | # (C) Copyright 2000-2006 by Michal Zalewski 15 | # 16 | # Each line in this file specifies a single fingerprint. Please read the 17 | # information below carefully before attempting to append any signatures 18 | # reported by p0f as UNKNOWN to this file to avoid mistakes. Note that 19 | # this file is compatible only with the default operation mode, and not 20 | # with -R or -A options (SYN+ACK and RST+ modes). 21 | # 22 | # We use the following set metrics for fingerprinting: 23 | # 24 | # - Window size (WSS) - a highly OS dependent setting used for TCP/IP 25 | # performance control (max. amount of data to be sent without ACK). 26 | # Some systems use a fixed value for initial packets. On other 27 | # systems, it is a multiple of MSS or MTU (MSS+40). In some rare 28 | # cases, the value is just arbitrary. 29 | # 30 | # NEW SIGNATURE: if p0f reported a special value of 'Snn', the number 31 | # appears to be a multiple of MSS (MSS*nn); a special value of 'Tnn' 32 | # means it is a multiple of MTU ((MSS+40)*nn). Unless you notice the 33 | # value of nn is not fixed (unlikely), just copy the Snn or Tnn token 34 | # literally. If you know this device has a simple stack and a fixed 35 | # MTU, you can however multiply S value by MSS, or T value by MSS+40, 36 | # and put it instead of Snn or Tnn. One system may exhibit several T 37 | # or S values. In some situations, this might be a source of some 38 | # additional information about the setup if you have some time to dig 39 | # thru the kernel sources; in some other cases, like Windows, there seem 40 | # to be a multitude of variants and WSS selection algorithms, but it's 41 | # rather difficult to find a pattern without having the source. 42 | # 43 | # If WSS looks like a regular fixed value (for example is a power of two), 44 | # or if you can confirm the value is fixed by looking at several 45 | # fingerprints, please quote it literaly. If there's no apparent pattern 46 | # in WSS chosen, you should consider wildcarding this value - but this 47 | # should be the last option. 48 | # 49 | # NOTE: Some NAT devices, such as Linux iptables with --set-mss, will 50 | # modify MSS, but not WSS. As a result, MSS is changed to reflect 51 | # the MTU of the NAT device, but WSS remains a multiple of the original 52 | # MSS. Fortunately for us, the source device would almost always be 53 | # hooked up to Ethernet. P0f handles it automatically for the original 54 | # MSS of 1460, by adding "NAT!" tag to the result. 55 | # 56 | # In certain configurations, Linux erratically (?) uses MTU from another 57 | # interface on the default gw interface. This only happens on systems with 58 | # two network interfaces. Thus, some Linux systems that do not go thru NAT, 59 | # but have multiple interfaces instead, will be also tagged this way. 60 | # 61 | # P0f recognizes and automatically wildcards WSS of 12345, as generated 62 | # by sendack and sendsyn utilities shipped with the program, when 63 | # reporting a new signature. See test/sendack.c and test/sendsyn.c for more 64 | # information about this. 65 | # 66 | # - Overall packet size - a function of all IP and TCP options and bugs. 67 | # While this is partly redundant in the real world, we record this value 68 | # to capture rare cases when there are IP options (which we do not currently 69 | # examine) or packet data past the headers. Both situations are rare. 70 | # 71 | # Packet size MAY be wildcarded, but the meaning of the wildcard is 72 | # very special, and means the packet must be larger than PACKET_BIG 73 | # (defined in config.h as 100). This is usually not necessary, except 74 | # for some really broken implementations in RST+ mode. For more information, 75 | # see p0fr.fp. P0f automatically wildcards big packets when reporting 76 | # new signatures. 77 | # 78 | # NEW SIGNATURE: Copy this value literally. 79 | # 80 | # - Initial TTL - We check the actual TTL of a received packet. It can't 81 | # be higher than the initial TTL, and also shouldn't be dramatically 82 | # lower (maximum distance is defined in config.h as 40 hops). 83 | # 84 | # NEW SIGNATURE: *Never* copy TTL from a p0f-reported signature literally. 85 | # You need to determine the initial TTL. The best way to do it is to 86 | # check the documentation for a remote system, or check its settings. 87 | # A fairly good method is to simply round the observed TTL up to 88 | # 32, 64, 128, or 255, but it should be noted that some obscure devices 89 | # might not use round TTLs (in particular, some shoddy appliances and 90 | # IRIX and Tru64 are known to use "original" initial TTL settings). If not 91 | # sure, use traceroute or mtr to see how far you are from the host. 92 | # 93 | # Note that -F option overrides this check if no signature can be found. 94 | # 95 | # - Don't fragment flag (DF) - some modern OSes set this to implement PMTU 96 | # discovery. Others do not bother. 97 | # 98 | # NEW SIGNATURE: Copy this value literally. Note: this setting is 99 | # sometimes cleared by firewalls and/or certain connectivity clients. 100 | # Try to find out what's the actual state for a given OS if you see both, 101 | # and add the right one. P0f will automatically detect a case when a 102 | # firewall removed the DF flag and will append "(firewall!)" suffix to 103 | # the signature, so if the DF version is the right one, don't add no-DF 104 | # variant, unless it has a different meaning. 105 | # 106 | # - Maximum segment size (MSS) - this setting is usually link-dependent. P0f 107 | # uses it to determine link type of the remote host. 108 | # 109 | # NEW SIGNATURE: Always wildcard this value, except for rare cases when 110 | # you have an appliance with a fixed value, know the system supports only 111 | # a very limited number of network interface types, or know the system 112 | # is using a value it pulled out of nowhere. I use specific unique MSS 113 | # to tell Google crawlbots from the rest of Linux population, for example. 114 | # 115 | # If a specific MSS/MTU is unique to a certain link type, be sure to 116 | # add it to mtu.h instead of creating several variants of each signature. 117 | # 118 | # - Window scaling (WSCALE) - this feature is used to scale WSS. 119 | # It extends the size of a TCP/IP window to 32 bits, of sorts. Some modern 120 | # systems implement this feature. 121 | # 122 | # NEW SIGNATURE: Observe several signatures. Initial WSCALE is often set 123 | # to zero or other low value. There's usually no need to wildcard this 124 | # parameter. 125 | # 126 | # - Timestamp - some systems that implement timestamps set them to 127 | # zero in the initial SYN. This case is detected and handled appropriately. 128 | # 129 | # NEW SIGNATURE: Copy T or T0 option literally. 130 | # 131 | # - Selective ACK permitted - a flag set by systems that implement 132 | # selective ACK functionality, 133 | # 134 | # NEW SIGNATURE: copy S option literally. 135 | # 136 | # - NOP option - its presence, count and sequence is a useful OS-dependent 137 | # characteristic, 138 | # 139 | # NEW SIGNATURE: copy N options literally. 140 | # 141 | # - Other and unrecognized options (TTCP-related and such) - implemented by 142 | # some eccentric or very buggy TCP/IP stacks ;-), 143 | # 144 | # NEW SIGNATURE: copy ? options literally. 145 | # 146 | # - EOL option. Contrary to the popular belief, the presence of EOL 147 | # option is actually quite rare, most systems just NOP-pad to the 148 | # packet boundary. 149 | # 150 | # NEW SIGNATURE: copy E option literally. 151 | # 152 | # - The sequence of TCP all options mentioned above - this is very 153 | # specific to the implementation, 154 | # 155 | # NEW SIGNATURE: Copy the sequence literally. 156 | # 157 | # - Quirks. Some buggy stacks set certain values that should be zeroed in a 158 | # TCP packet to non-zero values. This has no effect as of today, but is 159 | # a valuable source of information. Some systems actually seem to leak 160 | # memory there. Other systems just exhibit harmful but very specific 161 | # behavior. This section captures all unusual yes-no properties not 162 | # related to the main and expected header layout. We detect the following: 163 | # 164 | # - Data past the headers. Neither SYN nor SYN+ACK packets are supposed 165 | # to carry any payload. If they do, we should take notice. The actual 166 | # payload is not examined, but will be displayed if use the -X option. 167 | # Note that payload is not unusual in RST+ mode (see p0fr.fp), very 168 | # rare otherwise. 169 | # 170 | # - Options past EOL. Some systems have some trailing data past EOL 171 | # in the options section of TCP/IP headers. P0f does not examine this 172 | # data as of today, simply detects its presence. If there is a 173 | # confirmed sizable population of systems that have data past EOL, it 174 | # might be a good idea to look at it. Until then, you have to recompile 175 | # p0f with DEBUG_EXTRAS set or use -x to display this data, 176 | # 177 | # - Zero IP ID. This again is a (mostly) harmless setting to use a fixed 178 | # IP ID for packets with DF set. Some systems reportedly use zero ID, 179 | # most OSes do not. There is a very slight probability of a false 180 | # positive when IP ID is "naturally" chosen to be zero on a system 181 | # that otherwise does set proper values, but the probability is 182 | # neglible (if it becomes a problem, recompile p0f with IGNORE_ZEROID 183 | # set in the sources). 184 | # 185 | # - IP options specified. Usually, packets do not have any IP options 186 | # set, but there can be some. Until there is a confirmed sizable 187 | # population of systems that do have IP options in a packet, p0f 188 | # does not examine those in detail, but it might change (use 189 | # DEBUG_EXTRAS or -x to display IP options if any found), 190 | # 191 | # - URG pointer value. SYN packets do not have URG flag set, so the 192 | # value in URG pointer in TCP header is ignored. Most systems set it 193 | # to zero, but some OSes (some versions of Windows, for example) do 194 | # not zero this field or even simply leak memory; the actual value is 195 | # not examined, because most cases seem to be just random garbage 196 | # (you can use DEBUG_EXTRAS or -x to report this information though); 197 | # see doc/win-memleak.txt for more information, 198 | # 199 | # - "Unused" field value. This should be always zero, but some systems 200 | # forget to clear it. This might result in some funny issues in the 201 | # future. P0f checks for non-zero value (and will display it if 202 | # DEBUG_EXTRAS is set, or you can use -x), 203 | # 204 | # - ACK number non-zero. ACK value in SYN packets with no ACK flag 205 | # is disregarded and is usually set to zero (just like with URG 206 | # pointer), but some systems forget to do it. The exact value is 207 | # not examined (but will be displayed with DEBUG_EXTRAS, or you can 208 | # use -x). Note that this is not an anomaly in SYN+ACK and RST+ modes, 209 | # 210 | # - Non-zero second timestamp. The initial SYN packet should have the 211 | # second timestamp always zeroed. SYN+ACK and RST+ may "legally" have 212 | # this quirk though, 213 | # 214 | # - Unusual flags. If, in addition to SYN (or SYN+ACK), there are some 215 | # auxilinary flags that do not modify the very meaning of a packet, 216 | # p0f records this (this can be URG, PUSH, or something else). 217 | # 218 | # Note: ECN flags (ECE and CWR) are ignored and denoted in a separate 219 | # way. ECN is never by default, because some systems can't handle it, 220 | # and it probably does not make much sense to include it in signatures 221 | # right now. 222 | # 223 | # - TCP option segment parsing problems. If p0f fails to decode options 224 | # because of a badly broken packet, it records this fact. 225 | # 226 | # There are several other quirks valid only in RST+ mode, see p0fr.fp for 227 | # more information. Those quirks are unheard of in SYN and SYN+ACK 228 | # modes. 229 | # 230 | # NEW SIGNATURE: Copy "quirks" section literally. 231 | # 232 | # We DO NOT use ToS for fingerprinting. While the original TCP/IP 233 | # fingerprinting research believed this value would be useful for this 234 | # purpose, it is not. The setting is way too often tweaked by network 235 | # devices. 236 | # 237 | # To wildcard MSS, WSS or WSCALE, replace it with '*'. You can also use a 238 | # modulo operator to match any values that divide by nnn - '%nnn' (and, 239 | # as stated above, WSS also supports special values Snn and Tnn). 240 | # 241 | # Fingerprint entry format: 242 | # 243 | # wwww:ttt:D:ss:OOO...:QQ:OS:Details 244 | # 245 | # wwww - window size (can be * or %nnn or Sxx or Txx) 246 | # "Snn" (multiple of MSS) and "Tnn" (multiple of MTU) are allowed. 247 | # ttt - initial TTL 248 | # D - don't fragment bit (0 - not set, 1 - set) 249 | # ss - overall SYN packet size (* has a special meaning) 250 | # OOO - option value and order specification (see below) 251 | # QQ - quirks list (see below) 252 | # OS - OS genre (Linux, Solaris, Windows) 253 | # details - OS description (2.0.27 on x86, etc) 254 | # 255 | # If OS genre starts with '*', p0f will not show distance, link type 256 | # and timestamp data. It is useful for userland TCP/IP stacks of 257 | # network scanners and so on, where many settings are randomized or 258 | # bogus. 259 | # 260 | # If OS genre starts with @, it denotes an approximate hit for a group 261 | # of operating systems (signature reporting still enabled in this case). 262 | # Use this feature at the end of this file to catch cases for which 263 | # you don't have a precise match, but can tell it's Windows or FreeBSD 264 | # or whatnot by looking at, say, flag layout alone. 265 | # 266 | # If OS genre starts with - (which can prefix @ or *), the entry is 267 | # not considered to be a real operating system (but userland stack 268 | # instead). It is important to mark all scanners and so on with -, 269 | # so that they are not used for masquerade detection (also add this 270 | # prefix for signatures of application-induced behavior, such as 271 | # increased window size with Opera browser). 272 | # 273 | # Option block description is a list of comma or space separated 274 | # options in the order they appear in the packet: 275 | # 276 | # N - NOP option 277 | # E - EOL option 278 | # Wnnn - window scaling option, value nnn (or * or %nnn) 279 | # Mnnn - maximum segment size option, value nnn (or * or %nnn) 280 | # S - selective ACK OK 281 | # T - timestamp 282 | # T0 - timestamp with zero value 283 | # ?n - unrecognized option number n. 284 | # 285 | # P0f can sometimes report ?nn among the options. This means it couldn't 286 | # recognize this option (option number nn). It's either a bug in p0f, or 287 | # a faulty TCP/IP stack, or, if the number is listed here: 288 | # 289 | # http://www.iana.org/assignments/tcp-parameters 290 | # 291 | # ...the stack might be simply quite exotic. 292 | # 293 | # To denote no TCP options, use a single '.'. 294 | # 295 | # Quirks section is usually an empty list ('.') of oddities or bugs of this 296 | # particular stack. List items are not separated in any way. Possible values: 297 | # 298 | # P - options past EOL, 299 | # Z - zero IP ID, 300 | # I - IP options specified, 301 | # U - urg pointer non-zero, 302 | # X - unused (x2) field non-zero, 303 | # A - ACK number non-zero, 304 | # T - non-zero second timestamp, 305 | # F - unusual flags (PUSH, URG, etc), 306 | # D - data payload, 307 | # ! - broken options segment. 308 | # 309 | # WARNING WARNING WARNING 310 | # ----------------------- 311 | # 312 | # Do not add a system X as OS Y just because NMAP says so. It is often 313 | # the case that X is a NAT firewall. While nmap is talking to the 314 | # device itself, p0f is fingerprinting the guy behind the firewall 315 | # instead. 316 | # 317 | # When in doubt, use common sense, don't add something that looks like 318 | # a completely different system as Linux or FreeBSD or LinkSys router. 319 | # Check DNS name, establish a connection to the remote host and look 320 | # at SYN+ACK (p0f -A -S should do) - does it look similar? 321 | # 322 | # Some users tweak their TCP/IP settings - enable or disable RFC1323, 323 | # RFC1644 or RFC2018 support, disable PMTU discovery, change MTU, initial 324 | # TTL and so on. Always compare a new rule to other fingerprints for 325 | # this system, and verify the system isn't "customized". It is OK to 326 | # add signature variants caused by commonly used software (PFs, security 327 | # packages, etc), but it makes no sense to try to add every single 328 | # possible /proc/sys/net/ipv4/* tweak on Linux or so. 329 | # 330 | # KEEP IN MIND: Some packet firewalls configured to normalize outgoing 331 | # traffic (OpenBSD pf with "scrub" enabled, for example) will, well, 332 | # normalize packets. Signatures will not correspond to the originating 333 | # system (and probably not quite to the firewall either). 334 | # 335 | # NOTE: Try to keep this file in some reasonable order, from most to 336 | # least likely systems. This will speed up operation. Also keep most 337 | # generic and broad rules near ehe end. 338 | # 339 | # Still decided to add signature? Let us know - mail a copy of your discovery 340 | # to lcamtuf@coredump.cx. You can help make p0f better, and I can help you 341 | # make your signature more accurate. 342 | # 343 | 344 | ########################## 345 | # Standard OS signatures # 346 | ########################## 347 | 348 | # ----------------- AIX --------------------- 349 | 350 | # AIX is first because its signatures are close to NetBSD, MacOS X and 351 | # Linux 2.0, but it uses a fairly rare MSSes, at least sometimes... 352 | # This is a shoddy hack, though. 353 | 354 | 45046:64:0:44:M*:.:AIX:4.3 355 | 356 | 16384:64:0:44:M512:.:AIX:4.3.2 and earlier 357 | 358 | 16384:64:0:60:M512,N,W%2,N,N,T:.:AIX:4.3.3-5.2 (1) 359 | 32768:64:0:60:M512,N,W%2,N,N,T:.:AIX:4.3.3-5.2 (2) 360 | 65535:64:0:60:M512,N,W%2,N,N,T:.:AIX:4.3.3-5.2 (3) 361 | 362 | 65535:64:0:64:M*,N,W1,N,N,T,N,N,S:.:AIX:5.3 ML1 363 | 364 | # ----------------- Linux ------------------- 365 | 366 | S1:64:0:44:M*:A:Linux:1.2.x 367 | 512:64:0:44:M*:.:Linux:2.0.3x (1) 368 | 16384:64:0:44:M*:.:Linux:2.0.3x (2) 369 | 370 | # Endian snafu! Nelson says "ha-ha": 371 | 2:64:0:44:M*:.:Linux:2.0.3x (MkLinux) on Mac (1) 372 | 64:64:0:44:M*:.:Linux:2.0.3x (MkLinux) on Mac (2) 373 | 374 | S4:64:1:60:M1360,S,T,N,W0:.:Linux:2.4 (Google crawlbot) 375 | S4:64:1:60:M1430,S,T,N,W0:.:Linux:2.4-2.6 (Google crawlbot) 376 | 377 | S2:64:1:60:M*,S,T,N,W0:.:Linux:2.4 (large MTU?) 378 | S3:64:1:60:M*,S,T,N,W0:.:Linux:2.4 (newer) 379 | S4:64:1:60:M*,S,T,N,W0:.:Linux:2.4-2.6 380 | 381 | S3:64:1:60:M*,S,T,N,W1:.:Linux:2.6, seldom 2.4 (older, 1) 382 | S4:64:1:60:M*,S,T,N,W1:.:Linux:2.6, seldom 2.4 (older, 2) 383 | S3:64:1:60:M*,S,T,N,W2:.:Linux:2.6, seldom 2.4 (older, 3) 384 | S4:64:1:60:M*,S,T,N,W2:.:Linux:2.6, seldom 2.4 (older, 4) 385 | T4:64:1:60:M*,S,T,N,W2:.:Linux:2.6 (older, 5) 386 | 387 | S4:64:1:60:M*,S,T,N,W5:.:Linux:2.6 (newer, 1) 388 | S4:64:1:60:M*,S,T,N,W6:.:Linux:2.6 (newer, 2) 389 | S4:64:1:60:M*,S,T,N,W7:.:Linux:2.6 (newer, 3) 390 | T4:64:1:60:M*,S,T,N,W7:.:Linux:2.6 (newer, 4) 391 | 392 | 393 | S20:64:1:60:M*,S,T,N,W0:.:Linux:2.2 (1) 394 | S22:64:1:60:M*,S,T,N,W0:.:Linux:2.2 (2) 395 | S11:64:1:60:M*,S,T,N,W0:.:Linux:2.2 (3) 396 | 397 | # Popular cluster config scripts disable timestamps and 398 | # selective ACK: 399 | 400 | S4:64:1:48:M1460,N,W0:.:Linux:2.4 in cluster 401 | 402 | # This happens only over loopback, but let's make folks happy: 403 | 32767:64:1:60:M16396,S,T,N,W0:.:Linux:2.4 (loopback) 404 | 32767:64:1:60:M16396,S,T,N,W2:.:Linux:2.6 (newer, loopback) 405 | S8:64:1:60:M3884,S,T,N,W0:.:Linux:2.2 (loopback) 406 | 407 | # Opera visitors: 408 | 16384:64:1:60:M*,S,T,N,W0:.:-Linux:2.2 (Opera?) 409 | 32767:64:1:60:M*,S,T,N,W0:.:-Linux:2.4 (Opera?) 410 | 411 | # Some fairly common mods & oddities: 412 | S22:64:1:52:M*,N,N,S,N,W0:.:Linux:2.2 (tstamp-) 413 | S4:64:1:52:M*,N,N,S,N,W0:.:Linux:2.4 (tstamp-) 414 | S4:64:1:52:M*,N,N,S,N,W2:.:Linux:2.6 (tstamp-) 415 | S4:64:1:44:M*:.:Linux:2.6? (barebone, rare!) 416 | T4:64:1:60:M1412,S,T,N,W0:.:Linux:2.4 (rare!) 417 | 418 | # ----------------- FreeBSD ----------------- 419 | 420 | 16384:64:1:44:M*:.:FreeBSD:2.0-4.2 421 | 16384:64:1:60:M*,N,W0,N,N,T:.:FreeBSD:4.4 (1) 422 | 423 | 1024:64:1:60:M*,N,W0,N,N,T:.:FreeBSD:4.4 (2) 424 | 425 | 57344:64:1:44:M*:.:FreeBSD:4.6-4.8 (RFC1323-) 426 | 57344:64:1:60:M*,N,W0,N,N,T:.:FreeBSD:4.6-4.9 427 | 428 | 32768:64:1:60:M*,N,W0,N,N,T:.:FreeBSD:4.8-5.1 (or MacOS X 10.2-10.3) 429 | 65535:64:1:60:M*,N,W0,N,N,T:.:FreeBSD:4.7-5.2 (or MacOS X 10.2-10.4) (1) 430 | 65535:64:1:60:M*,N,W1,N,N,T:.:FreeBSD:4.7-5.2 (or MacOS X 10.2-10.4) (2) 431 | 432 | 65535:64:1:60:M*,N,W0,N,N,T:Z:FreeBSD:5.1 (1) 433 | 65535:64:1:60:M*,N,W1,N,N,T:Z:FreeBSD:5.1 (2) 434 | 65535:64:1:60:M*,N,W2,N,N,T:Z:FreeBSD:5.1 (3) 435 | 65535:64:1:64:M*,N,N,S,N,W1,N,N,T:.:FreeBSD:5.3-5.4 436 | 65535:64:1:64:M*,N,W1,N,N,T,S,E:P:FreeBSD:6.x (1) 437 | 65535:64:1:64:M*,N,W0,N,N,T,S,E:P:FreeBSD:6.x (2) 438 | 439 | 65535:64:1:44:M*:Z:FreeBSD:5.2 (RFC1323-) 440 | 441 | # 16384:64:1:60:M*,N,N,N,N,N,N,T:.:FreeBSD:4.4 (tstamp-) 442 | 443 | # ----------------- NetBSD ------------------ 444 | 445 | 16384:64:0:60:M*,N,W0,N,N,T:.:NetBSD:1.3 446 | 65535:64:0:60:M*,N,W0,N,N,T0:.:-NetBSD:1.6 (Opera) 447 | 16384:64:1:60:M*,N,W0,N,N,T0:.:NetBSD:1.6 448 | 65535:64:1:60:M*,N,W1,N,N,T0:.:NetBSD:1.6W-current (DF) 449 | 65535:64:1:60:M*,N,W0,N,N,T0:.:NetBSD:1.6X (DF) 450 | 32768:64:1:60:M*,N,W0,N,N,T0:.:NetBSD:1.6Z or 2.0 (DF) 451 | 32768:64:1:64:M1416,N,W0,S,N,N,N,N,T0:.:NetBSD:2.0G (DF) 452 | 32768:64:1:64:M*,N,W0,S,N,N,N,N,T0:.:NetBSD:3.0 (DF) 453 | 454 | # ----------------- OpenBSD ----------------- 455 | 456 | 16384:64:1:64:M*,N,N,S,N,W0,N,N,T:.:OpenBSD:3.0-3.9 457 | 57344:64:1:64:M*,N,N,S,N,W0,N,N,T:.:OpenBSD:3.3-3.4 458 | 16384:64:0:64:M*,N,N,S,N,W0,N,N,T:.:OpenBSD:3.0-3.4 (scrub) 459 | 65535:64:1:64:M*,N,N,S,N,W0,N,N,T:.:-OpenBSD:3.0-3.4 (Opera?) 460 | 32768:64:1:64:M*,N,N,S,N,W0,N,N,T:.:OpenBSD:3.7 461 | 462 | # ----------------- Solaris ----------------- 463 | 464 | S17:64:1:64:N,W3,N,N,T0,N,N,S,M*:.:Solaris:8 (RFC1323 on) 465 | S17:64:1:48:N,N,S,M*:.:Solaris:8 (1) 466 | S17:255:1:44:M*:.:Solaris:2.5-7 (1) 467 | 468 | # Sometimes, just sometimes, Solaris feels like coming up with 469 | # rather arbitrary MSS values ;-) 470 | 471 | S6:255:1:44:M*:.:Solaris:2.5-7 (2) 472 | S23:64:1:48:N,N,S,M*:.:Solaris:8 (2) 473 | S34:64:1:48:M*,N,N,S:.:Solaris:9 474 | S34:64:1:48:M*,N,N,N,N:.:Solaris:9 (no sack) 475 | S44:255:1:44:M*:.:Solaris:7 476 | 477 | 4096:64:0:44:M1460:.:SunOS:4.1.x 478 | 479 | S34:64:1:52:M*,N,W0,N,N,S:.:Solaris:10 (beta) 480 | 32850:64:1:64:M*,N,N,T,N,W1,N,N,S:.:Solaris:10 (1203?) 481 | 32850:64:1:64:M*,N,W1,N,N,T,N,N,S:.:Solaris:9.1 482 | 483 | # ----------------- IRIX -------------------- 484 | 485 | 49152:60:0:44:M*:.:IRIX:6.2-6.4 486 | 61440:60:0:44:M*:.:IRIX:6.2-6.5 487 | 49152:60:0:52:M*,N,W2,N,N,S:.:IRIX:6.5 (RFC1323+) (1) 488 | 49152:60:0:52:M*,N,W3,N,N,S:.:IRIX:6.5 (RFC1323+) (2) 489 | 490 | 61440:60:0:48:M*,N,N,S:.:IRIX:6.5.12-6.5.21 (1) 491 | 49152:60:0:48:M*,N,N,S:.:IRIX:6.5.12-6.5.21 (2) 492 | 493 | 49152:60:0:64:M*,N,W2,N,N,T,N,N,S:.:IRIX:6.5 IP27 494 | 495 | # ----------------- Tru64 ------------------- 496 | # Tru64 and OpenVMS share the same stack on occassions. 497 | # Relax. 498 | 499 | 32768:60:1:48:M*,N,W0:.:Tru64:4.0 (or OS/2 Warp 4) 500 | 32768:60:0:48:M*,N,W0:.:Tru64:5.0 (or OpenVMS 7.x on Compaq 5.0 stack) 501 | 8192:60:0:44:M1460:.:Tru64:5.1 (no RFC1323) (or QNX 6) 502 | 61440:60:0:48:M*,N,W0:.:Tru64:v5.1a JP4 (or OpenVMS 7.x on Compaq 5.x stack) 503 | 504 | # ----------------- OpenVMS ----------------- 505 | 506 | 6144:64:1:60:M*,N,W0,N,N,T:.:OpenVMS:7.2 (Multinet 4.3-4.4 stack) 507 | 508 | # ----------------- MacOS ------------------- 509 | 510 | S2:255:1:48:M*,W0,E:.:MacOS:8.6 classic 511 | 512 | 16616:255:1:48:M*,W0,E:.:MacOS:7.3-8.6 (OTTCP) 513 | 16616:255:1:48:M*,N,N,N,E:.:MacOS:8.1-8.6 (OTTCP) 514 | 32768:255:1:48:M*,W0,N:.:MacOS:9.0-9.2 515 | 516 | 32768:255:1:48:M1380,N,N,N,N:.:MacOS:9.1 (OT 2.7.4) (1) 517 | 65535:255:1:48:M*,N,N,N,N:.:MacOS:9.1 (OT 2.7.4) (2) 518 | 519 | # ----------------- Windows ----------------- 520 | 521 | # Windows TCP/IP stack is a mess. For most recent XP, 2000 and 522 | # even 98, the pathlevel, not the actual OS version, is more 523 | # relevant to the signature. They share the same code, so it would 524 | # seem. Luckily for us, almost all Windows 9x boxes have an 525 | # awkward MSS of 536, which I use to tell one from another 526 | # in most difficult cases. 527 | 528 | 8192:32:1:44:M*:.:Windows:3.11 (Tucows) 529 | S44:64:1:64:M*,N,W0,N,N,T0,N,N,S:.:Windows:95 530 | 8192:128:1:64:M*,N,W0,N,N,T0,N,N,S:.:Windows:95b 531 | 532 | # There were so many tweaking tools and so many stack versions for 533 | # Windows 98 it is no longer possible to tell them from each other 534 | # without some very serious research. Until then, there's an insane 535 | # number of signatures, for your amusement: 536 | 537 | S44:32:1:48:M*,N,N,S:.:Windows:98 (low TTL) (1) 538 | 8192:32:1:48:M*,N,N,S:.:Windows:98 (low TTL) (2) 539 | %8192:64:1:48:M536,N,N,S:.:Windows:98 (13) 540 | %8192:128:1:48:M536,N,N,S:.:Windows:98 (15) 541 | S4:64:1:48:M*,N,N,S:.:Windows:98 (1) 542 | S6:64:1:48:M*,N,N,S:.:Windows:98 (2) 543 | S12:64:1:48:M*,N,N,S:.:Windows:98 (3 544 | T30:64:1:64:M1460,N,W0,N,N,T0,N,N,S:.:Windows:98 (16) 545 | 32767:64:1:48:M*,N,N,S:.:Windows:98 (4) 546 | 37300:64:1:48:M*,N,N,S:.:Windows:98 (5) 547 | 46080:64:1:52:M*,N,W3,N,N,S:.:Windows:98 (RFC1323+) 548 | 65535:64:1:44:M*:.:Windows:98 (no sack) 549 | S16:128:1:48:M*,N,N,S:.:Windows:98 (6) 550 | S16:128:1:64:M*,N,W0,N,N,T0,N,N,S:.:Windows:98 (7) 551 | S26:128:1:48:M*,N,N,S:.:Windows:98 (8) 552 | T30:128:1:48:M*,N,N,S:.:Windows:98 (9) 553 | 32767:128:1:52:M*,N,W0,N,N,S:.:Windows:98 (10) 554 | 60352:128:1:48:M*,N,N,S:.:Windows:98 (11) 555 | 60352:128:1:64:M*,N,W2,N,N,T0,N,N,S:.:Windows:98 (12) 556 | 557 | # What's with 1414 on NT? 558 | T31:128:1:44:M1414:.:Windows:NT 4.0 SP6a (1) 559 | 64512:128:1:44:M1414:.:Windows:NT 4.0 SP6a (2) 560 | 8192:128:1:44:M*:.:Windows:NT 4.0 (older) 561 | 562 | # Windows XP and 2000. Most of the signatures that were 563 | # either dubious or non-specific (no service pack data) 564 | # were deleted and replaced with generics at the end. 565 | 566 | 65535:128:1:48:M*,N,N,S:.:Windows:2000 SP4, XP SP1+ 567 | %8192:128:1:48:M*,N,N,S:.:Windows:2000 SP2+, XP SP1+ (seldom 98) 568 | S20:128:1:48:M*,N,N,S:.:Windows:SP3 569 | S45:128:1:48:M*,N,N,S:.:Windows:2000 SP4, XP SP1+ (2) 570 | 40320:128:1:48:M*,N,N,S:.:Windows:2000 SP4 571 | 572 | S6:128:1:48:M*,N,N,S:.:Windows:XP, 2000 SP2+ 573 | S12:128:1:48:M*,N,N,S:.:Windows:XP SP1+ (1) 574 | S44:128:1:48:M*,N,N,S:.:Windows:XP SP1+, 2000 SP3 575 | 64512:128:1:48:M*,N,N,S:.:Windows:XP SP1+, 2000 SP3 (2) 576 | 32767:128:1:48:M*,N,N,S:.:Windows:XP SP1+, 2000 SP4 (3) 577 | 578 | # Windows 2003 & Vista 579 | 580 | 8192:128:1:52:M*,W8,N,N,N,S:.:Windows:Vista (beta) 581 | 32768:32:1:52:M1460,N,W0,N,N,S:.:Windows:2003 AS 582 | 65535:64:1:52:M1460,N,W2,N,N,S:.:Windows:2003 (1) 583 | 65535:64:1:48:M1460,N,N,S:.:Windows:2003 (2) 584 | 585 | # Odds, ends, mods: 586 | 587 | S52:128:1:48:M1260,N,N,S:.:Windows:XP/2000 via Cisco 588 | 65520:128:1:48:M*,N,N,S:.:Windows:XP bare-bone 589 | 16384:128:1:52:M536,N,W0,N,N,S:.:Windows:2000 w/ZoneAlarm? 590 | 2048:255:0:40:.:.:Windows:.NET Enterprise Server 591 | 44620:64:0:48:M*,N,N,S:.:Windows:ME no SP (?) 592 | S6:255:1:48:M536,N,N,S:.:Windows:95 winsock 2 593 | 32000:128:0:48:M*,N,N,S:.:Windows:XP w/Winroute? 594 | 16384:64:1:48:M1452,N,N,S:.:Windows:XP w/Sygate? (1) 595 | 17256:64:1:48:M1460,N,N,S:.:Windows:XP w/Sygate? (2) 596 | 597 | # No need to be more specific, it passes: 598 | *:128:1:48:M*,N,N,S:U:-Windows:XP/2000 while downloading (leak!) 599 | 600 | # ----------------- HP/UX ------------------- 601 | 602 | 32768:64:1:44:M*:.:HP-UX:B.10.20 603 | 32768:64:1:48:M*,W0,N:.:HP-UX:11.00-11.11 604 | 605 | # Whoa. Hardcore WSS. 606 | 0:64:0:48:M*,W0,N:.:HP-UX:B.11.00 A (RFC1323+) 607 | 608 | # ----------------- RiscOS ------------------ 609 | 610 | 16384:64:1:68:M1460,N,W0,N,N,T,N,N,?12:.:RISC OS:3.70-4.36 (inet 5.04) 611 | 12288:32:0:44:M536:.:RISC OS:3.70 inet 4.10 612 | 4096:64:1:56:M1460,N,N,T:T:RISC OS:3.70 freenet 2.00 613 | 614 | # ----------------- BSD/OS ------------------ 615 | 616 | 8192:64:1:60:M1460,N,W0,N,N,T:.:BSD/OS:3.1-4.3 (or MacOS X 10.2) 617 | 618 | # ---------------- NetwonOS ----------------- 619 | 620 | 4096:64:0:44:M1420:.:NewtonOS:2.1 621 | 622 | # ---------------- NeXTSTEP ----------------- 623 | 624 | S8:64:0:44:M512:.:NeXTSTEP:3.3 (1) 625 | S4:64:0:44:M1024:.:NeXTSTEP:3.3 (2) 626 | 627 | # ------------------ BeOS ------------------- 628 | 629 | 1024:255:0:48:M*,N,W0:.:BeOS:5.0-5.1 630 | 12288:255:0:44:M*:.:BeOS:5.0.x 631 | 632 | # ------------------ OS/400 ----------------- 633 | 634 | 8192:64:1:60:M1440,N,W0,N,N,T:.:OS/400:V4R4/R5 635 | 8192:64:0:44:M536:.:OS/400:V4R3/M0 636 | 4096:64:1:60:M1440,N,W0,N,N,T:.:OS/400:V4R5 + CF67032 637 | 638 | 28672:64:0:44:M1460:A:OS/390:? 639 | 640 | # ------------------ ULTRIX ----------------- 641 | 642 | 16384:64:0:40:.:.:ULTRIX:4.5 643 | 644 | # ------------------- QNX ------------------- 645 | 646 | S16:64:0:44:M512:.:QNX:demodisk 647 | 16384:64:0:60:M1460,N,W0,N,N,T0:.:QNX:6.x 648 | 649 | # ------------------ Novell ----------------- 650 | 651 | 16384:128:1:44:M1460:.:Novell:NetWare 5.0 652 | 6144:128:1:44:M1460:.:Novell:IntranetWare 4.11 653 | 6144:128:1:44:M1368:.:Novell:BorderManager ? 654 | 655 | # According to rfp: 656 | 6144:128:1:52:M*,W0,N,S,N,N:.:Novell:Netware 6 SP3 657 | 658 | # -------------- SCO UnixWare --------------- 659 | 660 | S3:64:1:60:M1460,N,W0,N,N,T:.:SCO:UnixWare 7.1 661 | S17:64:1:60:M*,N,W0,N,N,T:.:SCO:UnixWare 7.1.x 662 | S23:64:1:44:M1380:.:SCO:OpenServer 5.0 663 | 664 | # ------------------- DOS ------------------- 665 | 666 | 2048:255:0:44:M536:.:DOS:Arachne via WATTCP/1.05 667 | T2:255:0:44:M984:.:DOS:Arachne via WATTCP/1.05 (eepro) 668 | 16383:64:0:44:M536:.:DOS:Unknown via WATTCP (epppd) 669 | 670 | # ------------------ OS/2 ------------------- 671 | 672 | S56:64:0:44:M512:.:OS/2:4 673 | 28672:64:0:44:M1460:.:OS/2:Warp 4.0 674 | 675 | # ----------------- TOPS-20 ----------------- 676 | 677 | # Another hardcore MSS, one of the ACK leakers hunted down. 678 | 0:64:0:44:M1460:A:TOPS-20:version 7 679 | 680 | # ------------------ AMIGA ------------------ 681 | 682 | S32:64:1:56:M*,N,N,S,N,N,?12:.:AMIGA:3.9 BB2 with Miami stack 683 | 684 | # ------------------ Minix ------------------ 685 | 686 | # Not quite sure. 687 | # 8192:210:0:44:M1460:X:@Minix:? 688 | 689 | # ------------------ Plan9 ------------------ 690 | 691 | 65535:255:0:48:M1460,W0,N:.:Plan9:edition 4 692 | 693 | # ----------------- AMIGAOS ----------------- 694 | 695 | 16384:64:1:48:M1560,N,N,S:.:AMIGAOS:3.9 BB2 MiamiDX 696 | 697 | # ----------------- FreeMiNT ---------------- 698 | 699 | S44:255:0:44:M536:.:FreeMiNT:1 patch 16A (Atari) 700 | 701 | ########################################### 702 | # Appliance / embedded / other signatures # 703 | ########################################### 704 | 705 | # ---------- Firewalls / routers ------------ 706 | 707 | S12:64:1:44:M1460:.:@Checkpoint:(unknown 1) 708 | S12:64:1:48:N,N,S,M1460:.:@Checkpoint:(unknown 2) 709 | 4096:32:0:44:M1460:.:ExtremeWare:4.x 710 | 711 | S32:64:0:68:M512,N,W0,N,N,T,N,N,?12:.:Nokia:IPSO w/Checkpoint NG FP3 712 | S16:64:0:68:M1024,N,W0,N,N,T,N,N,?12:.:Nokia:IPSO 3.7 build 026 713 | 714 | S4:64:1:60:W0,N,S,T,M1460:.:FortiNet:FortiGate 50 715 | 716 | 8192:64:1:44:M1460:.:@Eagle:Secure Gateway 717 | 718 | # ------- Switches and other stuff ---------- 719 | 720 | 4128:255:0:44:M*:Z:Cisco:7200, Catalyst 3500, etc 721 | S8:255:0:44:M*:.:Cisco:12008 722 | S4:255:0:44:M536:Z:Cisco:IOS 11.0 723 | 60352:128:1:64:M1460,N,W2,N,N,T,N,N,S:.:Alteon:ACEswitch 724 | 64512:128:1:44:M1370:.:Nortel:Contivity Client 725 | 726 | # ---------- Caches and whatnots ------------ 727 | 728 | 8190:255:0:44:M1428:.:Google:Wireless Transcoder (1) 729 | 8190:255:0:44:M1460:.:Google:Wireless Transcoder (2) 730 | 8192:64:1:64:M1460,N,N,S,N,W0,N,N,T:.:NetCache:5.2 731 | 16384:64:1:64:M1460,N,N,S,N,W0,N:.:NetCache:5.3 732 | 65535:64:1:64:M1460,N,N,S,N,W*,N,N,T:.:NetCache:5.3-5.5 (or FreeBSD 5.4) 733 | 20480:64:1:64:M1460,N,N,S,N,W0,N,N,T:.:NetCache:4.1 734 | S44:64:1:64:M1460,N,N,S,N,W0,N,N,T:.:NetCache:5.5 735 | 736 | 32850:64:1:64:N,W1,N,N,T,N,N,S,M*:.:NetCache:Data OnTap 5.x 737 | 738 | 65535:64:0:60:M1460,N,W0,N,N,T:.:CacheFlow:CacheOS 4.1 739 | 8192:64:0:60:M1380,N,N,N,N,N,N,T:.:CacheFlow:CacheOS 1.1 740 | 741 | S4:64:0:48:M1460,N,N,S:.:Cisco:Content Engine 742 | 743 | 27085:128:0:40:.:.:Dell:PowerApp cache (Linux-based) 744 | 745 | 65535:255:1:48:N,W1,M1460:.:Inktomi:crawler 746 | S1:255:1:60:M1460,S,T,N,W0:.:LookSmart:ZyBorg 747 | 748 | 16384:255:0:40:.:.:Proxyblocker:(what's this?) 749 | 750 | 65535:255:0:48:M*,N,N,S:.:Redline: T|X 2200 751 | 752 | # ----------- Embedded systems -------------- 753 | 754 | S9:255:0:44:M536:.:PalmOS:Tungsten T3/C 755 | S5:255:0:44:M536:.:PalmOS:3/4 756 | S4:255:0:44:M536:.:PalmOS:3.5 757 | 2948:255:0:44:M536:.:PalmOS:3.5.3 (Handera) 758 | S29:255:0:44:M536:.:PalmOS:5.0 759 | 16384:255:0:44:M1398:.:PalmOS:5.2 (Clie) 760 | S14:255:0:44:M1350:.:PalmOS:5.2.1 (Treo) 761 | 16384:255:0:44:M1400:.:PalmOS:5.2 (Sony) 762 | 763 | S23:64:1:64:N,W1,N,N,T,N,N,S,M1460:.:SymbianOS:7 764 | 8192:255:0:44:M1460:.:SymbianOS:6048 (Nokia 7650?) 765 | 8192:255:0:44:M536:.:SymbianOS:(Nokia 9210?) 766 | S22:64:1:56:M1460,T,S:.:SymbianOS:? (SE P800?) 767 | S36:64:1:56:M1360,T,S:.:SymbianOS:60xx (Nokia 6600?) 768 | S36:64:1:60:M1360,T,S,W0,E:.:SymbianOS:60xx 769 | 770 | 32768:32:1:44:M1460:.:Windows:CE 3 771 | 772 | # Perhaps S4? 773 | 5840:64:1:60:M1452,S,T,N,W1:.:Zaurus:3.10 774 | 775 | 32768:128:1:64:M1460,N,W0,N,N,T0,N,N,S:.:PocketPC:2002 776 | 777 | S1:255:0:44:M346:.:Contiki:1.1-rc0 778 | 779 | 4096:128:0:44:M1460:.:Sega:Dreamcast Dreamkey 3.0 780 | T5:64:0:44:M536:.:Sega:Dreamcast HKT-3020 (browser disc 51027) 781 | S22:64:1:44:M1460:.:Sony:Playstation 2 (SOCOM?) 782 | 783 | S12:64:0:44:M1452:.:AXIS:Printer Server 5600 v5.64 784 | 785 | 3100:32:1:44:M1460:.:Windows:CE 2.0 786 | 787 | #################### 788 | # Fancy signatures # 789 | #################### 790 | 791 | 1024:64:0:40:.:.:-*NMAP:syn scan (1) 792 | 2048:64:0:40:.:.:-*NMAP:syn scan (2) 793 | 3072:64:0:40:.:.:-*NMAP:syn scan (3) 794 | 4096:64:0:40:.:.:-*NMAP:syn scan (4) 795 | 796 | 1024:64:0:40:.:A:-*NMAP:TCP sweep probe (1) 797 | 2048:64:0:40:.:A:-*NMAP:TCP sweep probe (2) 798 | 3072:64:0:40:.:A:-*NMAP:TCP sweep probe (3) 799 | 4096:64:0:40:.:A:-*NMAP:TCP sweep probe (4) 800 | 801 | 1024:64:0:60:W10,N,M265,T,E:P:-*NMAP:OS detection probe (1) 802 | 2048:64:0:60:W10,N,M265,T,E:P:-*NMAP:OS detection probe (2) 803 | 3072:64:0:60:W10,N,M265,T,E:P:-*NMAP:OS detection probe (3) 804 | 4096:64:0:60:W10,N,M265,T,E:P:-*NMAP:OS detection probe (4) 805 | 806 | 1024:64:0:60:W10,N,M265,T,E:PF:-*NMAP:OS detection probe w/flags (1) 807 | 2048:64:0:60:W10,N,M265,T,E:PF:-*NMAP:OS detection probe w/flags (2) 808 | 3072:64:0:60:W10,N,M265,T,E:PF:-*NMAP:OS detection probe w/flags (3) 809 | 4096:64:0:60:W10,N,M265,T,E:PF:-*NMAP:OS detection probe w/flags (4) 810 | 811 | 32767:64:0:40:.:.:-*NAST:syn scan 812 | 813 | 12345:255:0:40:.:A:-p0f:sendsyn utility 814 | 815 | # UFO - see tmp/*: 816 | 56922:128:0:40:.:A:-@Mysterious:port scanner (?) 817 | 5792:64:1:60:M1460,S,T,N,W0:T:-@Mysterious:NAT device (2nd tstamp) 818 | S12:128:1:48:M1460,E:P:@Mysterious:Chello proxy (?) 819 | S23:64:1:64:N,W1,N,N,T,N,N,S,M1380:.:@Mysterious:GPRS gateway (?) 820 | 821 | ##################################### 822 | # Generic signatures - just in case # 823 | ##################################### 824 | 825 | *:128:1:52:M*,N,W0,N,N,S:.:@Windows:XP/2000 (RFC1323+, w, tstamp-) 826 | *:128:1:52:M*,N,W*,N,N,S:.:@Windows:XP/2000 (RFC1323+, w+, tstamp-) 827 | *:128:1:52:M*,N,N,T0,N,N,S:.:@Windows:XP/2000 (RFC1323+, w-, tstamp+) 828 | *:128:1:64:M*,N,W0,N,N,T0,N,N,S:.:@Windows:XP/2000 (RFC1323+, w, tstamp+) 829 | *:128:1:64:M*,N,W*,N,N,T0,N,N,S:.:@Windows:XP/2000 (RFC1323+, w+, tstamp+) 830 | 831 | *:128:1:48:M536,N,N,S:.:@Windows:98 832 | *:128:1:48:M*,N,N,S:.:@Windows:XP/2000 833 | 834 | 835 | -------------------------------------------------------------------------------- /share/p0fa.fp: -------------------------------------------------------------------------------- 1 | # 2 | # p0f - SYN+ACK fingerprints 3 | # -------------------------- 4 | # 5 | # .-------------------------------------------------------------------------. 6 | # | The purpose of this file is to cover signatures for outgoing TCP/IP | 7 | # | connections (SYN+ACK packets). This mode of operation can be enabled | 8 | # | with -A option. Please refer to p0f.fp for information on the metrics | 9 | # | used to create a signature, and for a guide on adding new entries to | 10 | # | those files. This database is somewhat neglected, and is looking for a | 11 | # | caring maintainer. | 12 | # `-------------------------------------------------------------------------' 13 | # 14 | # (C) Copyright 2000-2006 by Michal Zalewski 15 | # 16 | # Plenty of signatures contributed in bulk by rain forest puppy, Paul Woo and 17 | # Michael Bauer. 18 | # 19 | # Submit all additions to the authors. Read p0f.fp before adding any 20 | # signatures. Run p0f -A -C after making any modifications. This file is 21 | # NOT compatible with SYN, RST+, or stray ACK modes. Use only with -A option. 22 | # 23 | # Feel like contributing? You can run p0f -A -K, then test/tryid -iR nnn... 24 | # 25 | # IMPORTANT INFORMATION ABOUT THE INTERDEPENDENCY OF SYNs AND SYN+ACKs 26 | # -------------------------------------------------------------------- 27 | # 28 | # Some systems would have different SYN+ACK fingerprints depending on 29 | # the system that sent SYN. More specifically, RFC1323, RFC2018 and 30 | # RFC1644 extensions sometimes show up only if SYN had them enabled. 31 | # 32 | # Also, some silly systems may copy WSS from the SYN packet you've sent, 33 | # in which case, you need to wildcard the value. Use test/sendsyn.c, which 34 | # uses a distinct WSS of 12345, to test for this condition if unsure. 35 | # 36 | # IMPORTANT INFORMATION ABOUT DIFFERENCES IN COMPARISON TO p0f.fp: 37 | # ---------------------------------------------------------------- 38 | # 39 | # - 'A' quirk would be present on almost every signature here. ACK number 40 | # is unusual for SYN packets, but is a commonplace in SYN+ACK packets, 41 | # of course. It is still possible to have a signature without 'A', when 42 | # the ACK flag is present but the value is zero - this, however, is 43 | # very uncommon. 44 | # 45 | # - 'T' quirk would show up on almost all signatures for systems implementing 46 | # RFC1323. The second timestamp is only unusual for SYN packets. SYN+ACK 47 | # are expected to have it set. 48 | # 49 | 50 | ########################## 51 | # Standard OS signatures # 52 | ########################## 53 | 54 | # ---------------- Linux ------------------- 55 | 56 | 32736:64:0:44:M*:A:Linux:2.0 57 | S22:64:1:60:M*,S,T,N,W0:AT:Linux:2.2 58 | S22:64:1:52:M*,N,N,S,N,W0:A:Linux:2.2 w/o timestamps 59 | 60 | 5792:64:1:60:M*,S,T,N,W0:AT:Linux:older 2.4 61 | 5792:64:1:60:M*,S,T,N,W0:ZAT:Linux:recent 2.4 (1) 62 | S4:64:1:44:M*:ZA:Linux:recent 2.4 (2) 63 | 5792:64:1:44:M*:ZA:Linux:recent 2.4 (3) 64 | 65 | S4:64:1:52:M*,N,N,S,N,W0:ZA:Linux:2.4 w/o timestamps 66 | 67 | # --------------- Windows ------------------ 68 | 69 | 65535:128:1:64:M*,N,W0,N,N,T0,N,N,S:A:Windows:2000 SP4 70 | S44:128:1:64:M*,N,W0,N,N,T0,N,N,S:A:Windows:XP SP1 71 | S12:128:1:64:M*,N,W0,N,N,T0,N,N,S:A:Windows:2000 (SP1+) 72 | S6:128:1:44:M*:A:Windows:NT 4.0 SP1+ 73 | 65535:128:1:48:M*,N,N,S:A:Windows:98 (SE) 74 | 65535:128:1:44:M*:A:Windows:2000 (1) 75 | 16616:128:1:44:M*:A:Windows:2003 76 | 16384:128:1:44:M*:A:Windows:2000 (2) 77 | S16:128:1:44:M*:A:Windows:2000 (3) 78 | 79 | # ------------------- OpenBSD -------------- 80 | 81 | 17376:64:1:64:M*,N,N,S,N,W0,N,N,T:AT:OpenBSD:3.3 82 | 83 | # ------------------- NetBSD ---------------- 84 | 85 | 16384:64:0:60:M*,N,W0,N,N,T0:AT:NetBSD:1.6 86 | 87 | # ----------------- HP/UX ------------------ 88 | 89 | 32768:64:1:44:M*:A:HPUX:10.20 90 | 91 | # ----------------- Tru64 ------------------ 92 | 93 | S23:60:0:48:M*,N,W0:A:Tru64:5.0 (1) 94 | 65535:64:0:44:M*:A:Tru64:5.0 (2) 95 | 96 | # ----------------- Novell ----------------- 97 | 98 | 6144:128:1:52:M*,W0,N,S,N,N:A:Novell:Netware 6.0 (SP3) 99 | 32768:128:1:44:M*:A:Novell:Netware 5.1 100 | 101 | # ------------------ IRIX ------------------ 102 | 103 | 60816:60:1:60:M*,N,W0,N,N,T:AT:IRIX:6.5.0 104 | 105 | # ----------------- Solaris ---------------- 106 | 107 | 49232:64:1:64:N,N,T,M*,N,W0,N,N,S:AT:Solaris:9 (1) 108 | S1:255:1:60:N,N,T,N,W0,M*:AT:Solaris:7 109 | 24656:64:1:44:M*:A:Solaris:8 110 | 33304:64:1:60:N,N,T,M*,N,W1:AT:Solaris:9 (2) 111 | 112 | # ----------------- FreeBSD ---------------- 113 | 114 | 65535:64:1:60:M*,N,W1,N,N,T:AT:FreeBSD:5.0 115 | 57344:64:1:44:M*:A:FreeBSD:4.6-4.8 116 | 65535:64:1:44:M*:A:FreeBSD:4.4 117 | 118 | 57344:64:1:48:M1460,N,W0:A:FreeBSD:4.6-4.8 (wscale) 119 | 57344:64:1:60:M1460,N,W0,N,N,T:AT:FreeBSD:4.6-4.8 (RFC1323) 120 | 121 | # ------------------- AIX ------------------ 122 | 123 | S17:255:1:44:M536:A:AIX:4.2 124 | 125 | S12:64:0:44:M1460:A:AIX:5.2 ML04 (1) 126 | S42:64:0:44:M1460:A:AIX:5.2 ML04 (2) 127 | 128 | # ------------------ BSD/OS ---------------- 129 | 130 | S6:64:1:60:M1460,N,W0,N,N,T:AT:BSD/OS:4.0.x 131 | 132 | # ------------------ OS/390 ---------------- 133 | 134 | 2048:64:0:44:M1460:A:OS/390:? 135 | 136 | # ------------------ Novell ---------------- 137 | 138 | 6144:128:1:44:M1400:A:Novell:iChain 2.2 139 | 140 | # ------------------ MacOS ----------------- 141 | 142 | 33304:64:1:60:M*,N,W0,N,N,T:AT:MacOS:X 10.2.6 143 | 144 | ################################################################# 145 | # Contributed by Ryan Kruse - trial run # 146 | ################################################################# 147 | 148 | # S4:255:0:44:M1024:A:Cisco:LocalDirector 149 | # 1024:255:0:44:M536:A:Cisco,3COM,Nortel:CatIOS,SuperStack,BayStack 150 | # S16:64:0:44:M512:A:Nortel:Contivity 151 | # 8192:64:0:44:M1460:A:Cisco,Nortel,SonicWall,Tasman:Aironet,BayStack Switch,Soho,1200 152 | # 4096:255:0:44:M1460:A:Cisco:PIX,CatOS 153 | # 8192:128:0:44:M1460:A:Cisco:VPN Concentrator 154 | # 8192:128:0:60:M1460,N,W0,N,N,T:AT:Cisco:VPN Concentrator 155 | # 4096:32:0:44:M1460:A:Cisco,3COM,Extreme,Nortel:Catalyst Switch CatOS,CoreBuilder,Summit,Passport 156 | # S4:255:0:44:M536:ZA:Cisco:IOS 157 | # 1024:32:0:44:M1480:UA:Nortel:BayStack Switch 158 | # 4096:60:0:44:M1460:A:Adtran:NetVanta 159 | # 4096:64:0:44:M1008:A:Adtran:TSU 160 | # S4:32:0:44:M1024:A:Alcatel:Switch 161 | # S8:255:0:44:M536:ZA:Cisco:IOS 162 | # 50:255:0:44:M536:ZA:Cisco:CatIOS 163 | # 512:64:0:40:.:A:Dell:Switch 164 | # 4096:64:0:40:.:A:Enterasys:Vertical Horizon Switch 165 | # 17640:64:1:44:M1460:A:F5,Juniper,RiverStone:BigIP,Juniper OS,Router 7.0+ 166 | # 16384:64:0:44:M1460:A:Foundry,SonicWall:BigIron,TZ 167 | # 4096:64:0:44:M1452:A:HP:ProCurve Switch 168 | # 1024:64:0:44:M1260:A:Marconi:ES 169 | # 10240:30:0:44:M1460:A:Milan:Switch 170 | # 4096:64:0:44:M1380:A:NetScreen:Firewall 171 | # S32:64:0:44:M512:A:Nokia:CheckPoint 172 | # 1024:64:0:44:M536:A:Nortel:BayStack Switch 173 | # 4128:255:0:44:M*:ZA:Cisco:IOS 174 | # 1024:16:0:44:M536:A:Nortel:BayStack Switch 175 | # 1024:30:0:44:M1480:A:Nortel:BayStack Switch 176 | # S4:64:0:44:M1460:A:Symbol:Spectrum Access Point 177 | # S2:255:0:44:M512:A:ZyXEL:Prestige 178 | # S16:255:0:44:M1024:A:ZyXEL:ZyAI 179 | 180 | ########################################### 181 | # Appliance / embedded / other signatures # 182 | ########################################### 183 | 184 | 16384:64:1:44:M1460:A:F5:BigIP LB 4.1.x (sometimes FreeBSD) 185 | 4128:255:0:44:M*:ZA:Cisco:Catalyst 2900 12.0(5) 186 | 4096:60:0:44:M*:A:Brother:HL-1270N 187 | S1:30:0:44:M1730:A:Cyclades:PR3000 188 | 8192:64:1:44:M1460:A:NetApp:Data OnTap 6.x 189 | 5792:64:1:60:W0,N,N,N,T,M1460:ZAT:FortiNet:FortiGate 50 190 | S1:64:1:44:M1460:A:NetCache:5.3.1 191 | S1:64:0:44:M512:A:Printer:controller (?) 192 | 4096:128:0:40:.:A:Sequent:DYNIX 4.2.x 193 | S16:64:0:44:M512:A:3Com:NBX PBX (BSD/OS 2.1) 194 | 16000:64:0:44:M1442:A:CastleNet:DSL router 195 | S2:64:0:44:M32728:A:D-Link:DSL-500 196 | S4:60:0:44:M1460:A:HP:JetDirect A.05.32 197 | 8576:64:1:44:M*:A:Raptor:firewall 198 | S12:64:1:44:M1400:A:Cequrux Firewall:4.x 199 | 2048:255:0:44:M1400:A:Netgear:MR814 200 | 16384:128:0:64:M1460,N,W0,N,N,T0,N,N,S:A:Akamai:??? (1) 201 | 16384:128:0:60:M1460,N,W0,N,N,T0:A:Akamai:??? (2) 202 | 203 | 8190:255:0:44:M1452:A:Citrix:Netscaler 6.1 204 | 205 | # Whatever they run. EOL boys... 206 | S6:128:1:48:M1460,E:PA:@Slashdot:or BusinessWeek (???) 207 | 208 | 209 | -------------------------------------------------------------------------------- /share/p0fo.fp: -------------------------------------------------------------------------------- 1 | # 2 | # p0f - stray ACK signatures 3 | # -------------------------- 4 | # 5 | # .-------------------------------------------------------------------------. 6 | # | The purpose of this file is to cover signatures for stray ACK packets | 7 | # | (established session data). This mode of operation is enabled with -O | 8 | # | option and is HIGHLY EXPERIMENTAL. Please refer to p0f.fp for more | 9 | # | information on the metrics used and for a guide on adding new entries | 10 | # | to this file. This database is looking for a caring maintainer. | 11 | # `-------------------------------------------------------------------------' 12 | # 13 | # (C) Copyright 2000-2006 by Michal Zalewski 14 | # 15 | # Submit all additions to the authors. Read p0f.fp before adding any 16 | # signatures. Run p0f -O -C after making any modifications. This file is 17 | # NOT compatible with SYN, SYN+ACK or RST+ modes. Use only with -O option. 18 | # 19 | # IMPORTANT INFORMATION ABOUT THE INTERDEPENDENCY OF SYNs AND ACKs 20 | # ---------------------------------------------------------------- 21 | # 22 | # Some systems would have different ACK fingerprints depending on the initial 23 | # SYN or SYN+ACK received from the other party. More specifically, RFC1323, 24 | # RFC2018 and RFC1644 extensions sometimes show up only if the other party had 25 | # them enabled. Hence, the reliability of ACK fingerprints may be affected. 26 | # 27 | # IMPORTANT INFORMATION ABOUT DIFFERENCES IN COMPARISON TO p0f.fp: 28 | # ---------------------------------------------------------------- 29 | # 30 | # - Packet size MUST be wildcarded. ACK packets, by their nature, have 31 | # variable sizes, depending on the amount of data carried as a payload. 32 | # 33 | # - Similarly, 'D' quirk is not checked for, and is not allowed in signatures 34 | # in this file. A good number of ACK packets have payloads. 35 | # 36 | # - PUSH flag is excluded from 'F' quirk checks in this mode. 37 | # 38 | # - 'A' quirk is not a bug; all AC packets should have it set; also, 39 | # 'T' quirk is not an anomaly; its absence on systems with T option is. 40 | # 41 | 42 | 32767:64:1:*:N,N,T:AT:Linux:2.4.2x (local?) 43 | *:64:1:*:.:A:Linux:2.4.2x 44 | 32736:64:0:*:.:A:Linux:2.0.3x 45 | 46 | 57600:64:1:*:N,N,T:AT:FreeBSD:4.8 47 | %12:128:1:*:.:A:Windows:XP 48 | S44:128:1:*:.:A:Windows:XP 49 | -------------------------------------------------------------------------------- /share/p0fr.fp: -------------------------------------------------------------------------------- 1 | # 2 | # p0f - RST+ signatures 3 | # --------------------- 4 | # 5 | # .-------------------------------------------------------------------------. 6 | # | The purpose of this file is to cover signatures for reset packets | 7 | # | (RST and RST+ACK). This mode of operation can be enabled with -A option | 8 | # | and is considered to be least accurate. Please refer to p0f.fp for more | 9 | # | information on the metrics used and for a guide on adding new entries | 10 | # | to this file. This database is looking for a caring maintainer. | 11 | # `-------------------------------------------------------------------------' 12 | # 13 | # (C) Copyright 2000-2006 by Michal Zalewski 14 | # 15 | # Submit all additions to the authors. Read p0f.fp before adding any 16 | # signatures. Run p0f -R -C after making any modifications. This file is 17 | # NOT compatible with SYN, SYN+ACK, or stray ACK modes. Use only with -R 18 | # option. 19 | # 20 | # IMPORTANT INFORMATION ABOUT THE INTERDEPENDENCY OF SYNs AND RST+ACKs 21 | # -------------------------------------------------------------------- 22 | # 23 | # Some silly systems may copy WSS from the SYN packet you've sent, 24 | # in which case, you need to wildcard the value. Use test/sendsyn.c for 25 | # "connection refused" and test/sendack.c for "connection dropped" signatures 26 | # - both tools use a distinct WSS of 12345, which is an easy way to tell 27 | # if WSS should be wildcarded. 28 | # 29 | # IMPORTANT INFORMATION ABOUT COMMON IMPLEMENTATION FLAWS 30 | # ------------------------------------------------------- 31 | # 32 | # There are several types of RST packets you will surely encounter. 33 | # Some systems, including most reputable ones, are severily brain-damaged 34 | # and generate some illegal combinations from time to time. This is WAY 35 | # more common than with other packet types, because a broken RST does not 36 | # have any immediately noticable consequences; besides, the RFC793 is fairly 37 | # difficult to comprehend when it comes to this type of responses. 38 | # 39 | # P0f will give you a hint on new RST signatures, but it is your duty to 40 | # diagnose the problem and append the proper description when adding the 41 | # signature. Below is a list of valid and invalid states: 42 | # 43 | # - "Connection refused" message: this is a RST+ACK packet, SEQ number 44 | # set to zero, ACK number non-zero. This is a valid response and 45 | # is denoted by p0f as "refused" (quirk combination: K, 0, A). 46 | # 47 | # There are some very cases when this is incorrectly sent in response 48 | # to an unexpected ACK packet. 49 | # 50 | # - Illegal combination: RST+ACK packet, SEQ number set to zero, ACK 51 | # number zero. This is denoted by p0f as "invalid-K0" (quirk combination: 52 | # K and 0, no A). 53 | # 54 | # - Illegal combination: RST+ACK, SEQ number non-zero, ACK number zero 55 | # or non-zero. This is denoted by p0f as "invalid-K" and 56 | # "invalid-KA", respectively (quirk combinations, K, sometimes A, no 0). 57 | # 58 | # This combination is frequently generated by Cisco routers in certain 59 | # configurations in response to ACK (!). Brain dead, by all means, and 60 | # usually a result of (incorrectly) setting ACK flag on a valid RST packet. 61 | # 62 | # - "Connection dropped": RST, sequence number non-zero, ACK zero or 63 | # non-zero. This is denoted as "dropped" and "dropped 2" respectively 64 | # (quirk combinations: no K, sometimes A, no 0). While the ACK value should 65 | # be zeroed, it is not strictly against the RFC, and some systems either 66 | # leak memory there or set it to the value of SEQ. 67 | # 68 | # The latter variant, with non-zero ACK, is particularly common on 69 | # Windows. 70 | # 71 | # - Ilegal combination: RST, SEQ number zero, ACK zero or non-zero. 72 | # Denoted as "invalid-0" and "invalid-0A". Obviously incorrect, and 73 | # will not have the desired effect. 74 | # 75 | # Ok. That's it. RFC793 does not get much respect nowadays. 76 | # 77 | # IMPORTANT INFORMATION ABOUT DIFFERENCES IN COMPARISON TO p0f.fp: 78 | # ---------------------------------------------------------------- 79 | # 80 | # - Packet size may be wildcarded. The meaning of wildcard is, however, 81 | # hardcoded as 'size > PACKET_BIG' (defined as 100 in config.h). This is 82 | # because some stupid devices (including Ciscos) tend to send back RST 83 | # packets quoting anything you have sent them in ACK packet previously. 84 | # Use sparingly, only if -X confirms the device actually bounces back 85 | # whatever you send. 86 | # 87 | # - A new quirk, 'K', is introduced to denote RST+ACK packets (as opposed 88 | # to plain RST). This quirk is only compatible with this mode. 89 | # 90 | # - A new quirk, 'Q', is used to denote SEQ number equal to ACK number. 91 | # This happens from time to time in RST and RST+ACK packets, but 92 | # is practically unheard of in other modes. 93 | # 94 | # - A new quirk, '0', is used to denote packets with SEQ number set to 0. 95 | # This happens on some RSTs, and is once again unheard of in other modes. 96 | # 97 | # - 'D' quirk is not a bug; some devices send verbose text messages 98 | # describing why a connection got dropped; it's actually suggested 99 | # by RFC1122. Of course, some systems have their own standards, and 100 | # put all kinds of crap in their RST responses (including FreeBSD and 101 | # Cisco). Use -X to examine those values. 102 | # 103 | # - 'A' and 'T' quirks are not an anomaly in certain cases for the reasons 104 | # described in p0fa.fp. 105 | # 106 | 107 | ################################ 108 | # Connection refused - RST+ACK # 109 | ################################ 110 | 111 | 0:255:0:40:.:K0A:Linux:2.0/2.2 (refused) 112 | 0:64:1:40:.:K0A:FreeBSD:4.8 (refused) 113 | 0:64:1:40:.:K0ZA:Linux:recent 2.4 (refused) 114 | 115 | 0:128:0:40:.:K0A:Windows:XP/2000 (refused) 116 | 0:128:0:40:.:K0UA:-Windows:XP/2000 while browsing (refused) 117 | 118 | ###################################### 119 | # Connection dropped / timeout - RST # 120 | ###################################### 121 | 122 | 0:64:1:40:.:.:FreeBSD:4.8 (dropped) 123 | 0:255:0:40:.:.:Linux:2.0/2.2 or IOS 12.x (dropped) 124 | 0:64:1:40:.:Z:Linux:recent 2.4 (dropped) 125 | 0:255:1:40:.:Z:Linux:early 2.4 (dropped) 126 | 0:32:0:40:.:.:Xylan:OmniSwitch / Linksys WAP11 AP (dropped) 127 | 0:64:1:40:.:U:NetIron:load balancer (dropped) 128 | 129 | 0:128:1:40:.:QA:Windows:XP/2000 (dropped 2) 130 | 0:128:1:40:.:A:-Windows:XP/2000 while browsing (1) (dropped 2) 131 | 0:128:1:40:.:QUA:-Windows:XP/2000 while browsing (2) (dropped 2) 132 | 0:128:1:40:.:UA:-Windows:XP/2000 while browsing a lot (dropped 2) 133 | 0:128:1:40:.:.:@Windows:98 (?) (dropped) 134 | 135 | 0:64:0:40:.:A:Ascend:TAOS or BayTech (dropped 2) 136 | 137 | *:255:0:40:.:QA:Cisco:LocalDirector (dropped 2) 138 | 139 | 0:64:1:40:.:A:Hasbani:WindWeb (dropped 2) 140 | S23:255:1:40:.:.:Solaris:2.5 (dropped) 141 | 142 | ####################################################### 143 | # Connection dropped / timeout - RST with description # 144 | ####################################################### 145 | 146 | 0:255:1:58:.:D:MacOS:9.x "No TCP/No listener" (seldom SunOS 5.x) (dropped) 147 | 0:255:1:53:.:D:MacOS:8.5 "no tcp, reset" (dropped) 148 | 0:255:1:65:.:D:MacOS:X "tcp_close, during connect" (dropped) 149 | 0:255:1:54:.:D:MacOS:X "tcp_disconnect" (dropped) 150 | 0:255:1:62:.:D:HP/UX:? "tcp_fin_wait_2_timeout" (dropped) 151 | 32768:255:1:54:.:D:MacOS:8.5 "tcp_disconnect" (dropped) 152 | 0:255:1:63:.:D:@Unknown: "Go away" device (dropped) 153 | 154 | 0:255:0:62:.:D:SunOS:5.x "new data when detached" (1) (dropped) 155 | 32768:255:1:62:.:D:SunOS:5.x "new data when detached" (2) (dropped) 156 | 0:255:1:67:.:D:SunOS:5.x "tcp_lift_anchor, can't wait" (dropped) 157 | 158 | 0:255:0:46:.:D:HP/UX:11.00 "No TCP" (dropped) 159 | 160 | # More obscure ones: 161 | # 648:255:1:54:.:D:MacOS:??? "tcp_disconnect" (dropped) 162 | # 0:45:1:53:.:D:MacOS:7.x "no tcp, reset" (dropped) 163 | 164 | ############################################## 165 | # Connection dropped / timeout - broken RSTs # 166 | ############################################## 167 | 168 | S12:255:1:58:.:KAD:Solaris:2.x "tcp_disconnect" (dropped, lame) 169 | S43:64:1:40:.:KA:AOL:proxy (dropped, lame) 170 | *:64:1:40:.:KA:FreeBSD:4.8 (dropped, lame) 171 | *:64:1:52:N,N,T:KAT:Linux:2.4 (?) (dropped, lame) 172 | 0:255:0:40:.:KAF:3Com:SuperStack II (dropped, lame) 173 | *:255:0:40:.:KA:Intel:Netport print server (dropped, lame) 174 | *:150:0:40:.:KA:Linksys:BEF router (dropped, lame) 175 | 176 | *:32:0:44:.:KZD:@NetWare:??? "ehnc" (dropped, lame) 177 | 0:64:0:40:.:KQ0:BayTech:RPC-3 telnet host (dropped, lame) 178 | 179 | ############################################# 180 | # Connection dropped / timeout - extra data # 181 | ############################################# 182 | 183 | *:255:0:*:.:KAD:Cisco:IOS/PIX NAT + data (1) (dropped, lame) 184 | 0:255:0:*:.:D:Windows:NT 4.0 SP6a + data (dropped) 185 | 0:255:0:*:.:K0AD:Isolation:Infocrypt accelerator + data (dropped, lame) 186 | 187 | *:255:0:*:.:AD:Cisco:IOS/PIX NAT + data (2) (dropped) 188 | 189 | *:64:1:*:N,N,T:KATD:Linux:2.4 (?) + data (dropped, lame) 190 | *:64:1:*:.:KAD:FreeBSD:4.8 + data (dropped, lame) 191 | 192 | 193 | 194 | --------------------------------------------------------------------------------