Changeset 1086:fa0c948e7ffa
- Timestamp:
- 09/21/09 19:03:59 (12 months ago)
- Branch:
- default
- Files:
-
- 1 modified
-
scapy/modules/p0f.py (modified) (14 diffs)
Legend:
- Unmodified
- Added
- Removed
-
scapy/modules/p0f.py
r862 r1086 6 6 from scapy.data import KnowledgeBase 7 7 from scapy.config import conf 8 from scapy.layers.inet import IP, TCP, TCPOptions 9 from scapy.packet import NoPayload 8 10 9 11 conf.p0f_base ="/etc/p0f/p0f.fp" 12 conf.p0fa_base ="/etc/p0f/p0fa.fp" 13 conf.p0fr_base ="/etc/p0f/p0fr.fp" 14 conf.p0fo_base ="/etc/p0f/p0fo.fp" 10 15 11 16 … … 26 31 # OS - OS genre 27 32 # details - OS description 28 29 30 33 31 34 class p0fKnowledgeBase(KnowledgeBase): … … 47 50 if len(l) < 8: 48 51 continue 49 li = map(int,l[1:4]) 52 def a2i(x): 53 if x.isdigit(): 54 return int(x) 55 return x 56 li = map(a2i, l[1:4]) 50 57 #if li[0] not in self.ttl_range: 51 58 # self.ttl_range.append(li[0]) … … 58 65 59 66 p0f_kdb = p0fKnowledgeBase(conf.p0f_base) 60 67 p0fa_kdb = p0fKnowledgeBase(conf.p0fa_base) 68 p0fr_kdb = p0fKnowledgeBase(conf.p0fr_base) 69 p0fo_kdb = p0fKnowledgeBase(conf.p0fo_base) 70 71 def p0f_selectdb(flags): 72 # tested flags: S, R, A 73 if flags & 0x16 == 0x2: 74 # SYN 75 return p0f_kdb 76 elif flags & 0x16 == 0x12: 77 # SYN/ACK 78 return p0fa_kdb 79 elif flags & 0x16 in [ 0x4, 0x14 ]: 80 # RST RST/ACK 81 return p0fr_kdb 82 elif flags & 0x16 == 0x10: 83 # ACK 84 return p0fo_kdb 85 else: 86 return None 61 87 62 88 def packet2p0f(pkt): 89 pkt = pkt.copy() 90 pkt = pkt.__class__(str(pkt)) 63 91 while pkt.haslayer(IP) and pkt.haslayer(TCP): 64 92 pkt = pkt.getlayer(IP) … … 66 94 break 67 95 pkt = pkt.payload 68 96 69 97 if not isinstance(pkt, IP) or not isinstance(pkt.payload, TCP): 70 98 raise TypeError("Not a TCP/IP packet") 71 if pkt.payload.flags & 0x13 != 0x02: #S,!A,!F 72 raise TypeError("Not a syn packet") 99 #if pkt.payload.flags & 0x7 != 0x02: #S,!F,!R 100 # raise TypeError("Not a SYN or SYN/ACK packet") 101 102 db = p0f_selectdb(pkt.payload.flags) 73 103 74 104 #t = p0f_kdb.ttl_range[:] … … 77 107 #ttl=t[t.index(pkt.ttl)+1] 78 108 ttl = pkt.ttl 79 109 80 110 df = (pkt.flags & 2) / 2 81 111 ss = len(pkt) 82 112 # from p0f/config.h : PACKET_BIG = 100 83 113 if ss > 100: 84 ss = 0 85 114 if db == p0fr_kdb: 115 # p0fr.fp: "Packet size may be wildcarded. The meaning of 116 # wildcard is, however, hardcoded as 'size > 117 # PACKET_BIG'" 118 ss = '*' 119 else: 120 ss = 0 121 if db == p0fo_kdb: 122 # p0fo.fp: "Packet size MUST be wildcarded." 123 ss = '*' 124 86 125 ooo = "" 87 126 mss = -1 … … 89 128 qqP = False 90 129 #qqBroken = False 91 ilen = (pkt [TCP].dataofs << 2) - 20 # from p0f.c130 ilen = (pkt.payload.dataofs << 2) - 20 # from p0f.c 92 131 for option in pkt.payload.options: 93 132 ilen -= 1 … … 119 158 qqP = True 120 159 else: 121 ooo += "?," 160 if type(option[0]) is str: 161 ooo += "?%i," % TCPOptions[1][option[0]] 162 else: 163 ooo += "?%i," % option[0] 122 164 # FIXME: ilen 123 165 ooo = ooo[:-1] 124 166 if ooo == "": ooo = "." 125 167 126 168 win = pkt.payload.window 127 169 if mss != -1: 128 if win % mss == 0:170 if mss != 0 and win % mss == 0: 129 171 win = "S" + str(win/mss) 130 172 elif win % (mss + 40) == 0: 131 173 win = "T" + str(win/(mss+40)) 132 win = str(win)133 174 win = str(win) 175 134 176 qq = "" 135 177 178 if db == p0fr_kdb: 179 if pkt.payload.flags & 0x10 == 0x10: 180 # p0fr.fp: "A new quirk, 'K', is introduced to denote 181 # RST+ACK packets" 182 qq += "K" 183 # The two next cases should also be only for p0f*r*, but although 184 # it's not documented (or I have not noticed), p0f seems to 185 # support the '0' and 'Q' quirks on any databases (or at the least 186 # "classical" p0f.fp). 187 if pkt.payload.seq == pkt.payload.ack: 188 # p0fr.fp: "A new quirk, 'Q', is used to denote SEQ number 189 # equal to ACK number." 190 qq += "Q" 191 if pkt.payload.seq == 0: 192 # p0fr.fp: "A new quirk, '0', is used to denote packets 193 # with SEQ number set to 0." 194 qq += "0" 136 195 if qqP: 137 196 qq += "P" 138 if pkt [IP].id == 0:197 if pkt.id == 0: 139 198 qq += "Z" 140 if pkt [IP].options != '':199 if pkt.options != []: 141 200 qq += "I" 142 if pkt [TCP].urgptr != 0:201 if pkt.payload.urgptr != 0: 143 202 qq += "U" 144 if pkt [TCP].reserved != 0:203 if pkt.payload.reserved != 0: 145 204 qq += "X" 146 if pkt [TCP].ack != 0:205 if pkt.payload.ack != 0: 147 206 qq += "A" 148 207 if qqT: 149 208 qq += "T" 150 if pkt[TCP].flags & 40 != 0: 151 # U or P 152 qq += "F" 153 if not isinstance(pkt[TCP].payload, NoPayload): 209 if db == p0fo_kdb: 210 if pkt.payload.flags & 0x20 != 0: 211 # U 212 # p0fo.fp: "PUSH flag is excluded from 'F' quirk checks" 213 qq += "F" 214 else: 215 if pkt.payload.flags & 0x28 != 0: 216 # U or P 217 qq += "F" 218 if db != p0fo_kdb and not isinstance(pkt.payload.payload, NoPayload): 219 # p0fo.fp: "'D' quirk is not checked for." 154 220 qq += "D" 155 # FIXME : "!" - broken options segment 221 # FIXME : "!" - broken options segment: not handled yet 156 222 157 223 if qq == "": 158 224 qq = "." 159 225 160 return (win, 161 ttl, 162 df, 163 ss, 164 ooo, 165 qq) 226 return (db, (win, ttl, df, ss, ooo, qq)) 166 227 167 228 def p0f_correl(x,y): 168 229 d = 0 169 # wwww can be "*" or "%nn" 230 # wwww can be "*" or "%nn". "Tnn" and "Snn" should work fine with 231 # the x[0] == y[0] test. 170 232 d += (x[0] == y[0] or y[0] == "*" or (y[0][0] == "%" and x[0].isdigit() and (int(x[0]) % int(y[0][1:])) == 0)) 171 233 # ttl 172 234 d += (y[1] >= x[1] and y[1] - x[1] < 32) 173 for i in [2, 3, 5]: 174 d += (x[i] == y[i]) 235 for i in [2, 5]: 236 d += (x[i] == y[i] or y[i] == '*') 237 # '*' has a special meaning for ss 238 d += x[3] == y[3] 175 239 xopt = x[4].split(",") 176 240 yopt = y[4].split(",") … … 193 257 @conf.commands.register 194 258 def p0f(pkt): 195 """Passive OS fingerprinting: which OS emitted this TCP SYN?259 """Passive OS fingerprinting: which OS emitted this TCP packet ? 196 260 p0f(packet) -> accuracy, [list of guesses] 197 261 """ 198 pb = p0f_kdb.get_base() 262 db, sig = packet2p0f(pkt) 263 if db: 264 pb = db.get_base() 265 else: 266 pb = [] 199 267 if not pb: 200 268 warning("p0f base empty.") 201 269 return [] 202 s = len(pb[0][0])270 #s = len(pb[0][0]) 203 271 r = [] 204 sig = packet2p0f(pkt)205 272 max = len(sig[4].split(",")) + 5 206 273 for b in pb: … … 209 276 r.append((b[6], b[7], b[1] - pkt[IP].ttl)) 210 277 return r 211 212 278 213 279 def prnp0f(pkt): 280 # we should print which DB we use 214 281 try: 215 282 r = p0f(pkt) … … 217 284 return 218 285 if r == []: 219 r = ("UNKNOWN", "[" + ":".join(map(str, packet2p0f(pkt) )) + ":?:?]", None)286 r = ("UNKNOWN", "[" + ":".join(map(str, packet2p0f(pkt)[1])) + ":?:?]", None) 220 287 else: 221 288 r = r[0] … … 229 296 res = pkt.sprintf("%IP.src%:%TCP.sport% - " + r[0] + " " + r[1]) 230 297 if uptime is not None: 231 res += pkt.sprintf(" (up: " + str(uptime/3600) + " hrs)\n -> %IP.dst%:%TCP.dport% ")232 else: 233 res += pkt.sprintf("\n -> %IP.dst%:%TCP.dport% ")298 res += pkt.sprintf(" (up: " + str(uptime/3600) + " hrs)\n -> %IP.dst%:%TCP.dport% (%TCP.flags%)") 299 else: 300 res += pkt.sprintf("\n -> %IP.dst%:%TCP.dport% (%TCP.flags%)") 234 301 if r[2] is not None: 235 302 res += " (distance " + str(r[2]) + ")" … … 238 305 @conf.commands.register 239 306 def pkt2uptime(pkt, HZ=100): 240 """Calculate the date the machine which emitted the packet booted using TCP timestamp 307 """Calculate the date the machine which emitted the packet booted using TCP timestamp 241 308 pkt2uptime(pkt, [HZ=100])""" 242 309 if not isinstance(pkt, Packet): … … 254 321 raise TypeError("No timestamp option") 255 322 256 323 def p0f_impersonate(pkt, osgenre=None, osdetails=None, signature=None, 324 extrahops=0, mtu=1500, uptime=None): 325 """Modifies pkt so that p0f will think it has been sent by a 326 specific OS. If osdetails is None, then we randomly pick up a 327 personality matching osgenre. If osgenre and signature are also None, 328 we use a local signature (using p0f_getlocalsigs). If signature is 329 specified (as a tuple), we use the signature. 330 331 For now, only TCP Syn packets are supported. 332 Some specifications of the p0f.fp file are not (yet) implemented.""" 333 pkt = pkt.copy() 334 #pkt = pkt.__class__(str(pkt)) 335 while pkt.haslayer(IP) and pkt.haslayer(TCP): 336 pkt = pkt.getlayer(IP) 337 if isinstance(pkt.payload, TCP): 338 break 339 pkt = pkt.payload 340 341 if not isinstance(pkt, IP) or not isinstance(pkt.payload, TCP): 342 raise TypeError("Not a TCP/IP packet") 343 344 if uptime is None: 345 uptime = random.randint(120,100*60*60*24*365) 346 347 db = p0f_selectdb(pkt.payload.flags) 348 if osgenre: 349 pb = db.get_base() 350 if pb is None: 351 pb = [] 352 pb = filter(lambda x: x[6] == osgenre, pb) 353 if osdetails: 354 pb = filter(lambda x: x[7] == osdetails, pb) 355 elif signature: 356 pb = [signature] 357 else: 358 pb = p0f_getlocalsigs()[db] 359 if db == p0fr_kdb: 360 # 'K' quirk <=> RST+ACK 361 if pkt.payload.flags & 0x4 == 0x4: 362 pb = filter(lambda x: 'K' in x[5], pb) 363 else: 364 pb = filter(lambda x: 'K' not in x[5], pb) 365 if not pb: 366 raise Scapy_Exception("No match in the p0f database") 367 pers = pb[random.randint(0, len(pb) - 1)] 368 369 # options (we start with options because of MSS) 370 ## TODO: let the options already set if they are valid 371 options = [] 372 if pers[4] != '.': 373 for opt in pers[4].split(','): 374 if opt[0] == 'M': 375 # MSS might have a maximum size because of window size 376 # specification 377 if pers[0][0] == 'S': 378 maxmss = (2L**16-1) / int(pers[0][1:]) 379 else: 380 maxmss = (2L**16-1) 381 # If we have to randomly pick up a value, we cannot use 382 # scapy RandXXX() functions, because the value has to be 383 # set in case we need it for the window size value. That's 384 # why we use random.randint() 385 if opt[1:] == '*': 386 options.append(('MSS', random.randint(1,maxmss))) 387 elif opt[1] == '%': 388 coef = int(opt[2:]) 389 options.append(('MSS', coef*random.randint(1,maxmss/coef))) 390 else: 391 options.append(('MSS', int(opt[1:]))) 392 elif opt[0] == 'W': 393 if opt[1:] == '*': 394 options.append(('WScale', RandByte())) 395 elif opt[1] == '%': 396 coef = int(opt[2:]) 397 options.append(('WScale', coef*RandNum(min=1, 398 max=(2L**8-1)/coef))) 399 else: 400 options.append(('WScale', int(opt[1:]))) 401 elif opt == 'T0': 402 options.append(('Timestamp', (0, 0))) 403 elif opt == 'T': 404 if 'T' in pers[5]: 405 # FIXME: RandInt() here does not work (bug (?) in 406 # TCPOptionsField.m2i often raises "OverflowError: 407 # long int too large to convert to int" in: 408 # oval = struct.pack(ofmt, *oval)" 409 # Actually, this is enough to often raise the error: 410 # struct.pack('I', RandInt()) 411 options.append(('Timestamp', (uptime, random.randint(1,2**32-1)))) 412 else: 413 options.append(('Timestamp', (uptime, 0))) 414 elif opt == 'S': 415 options.append(('SAckOK', '')) 416 elif opt == 'N': 417 options.append(('NOP', None)) 418 elif opt == 'E': 419 options.append(('EOL', None)) 420 elif opt[0] == '?': 421 if int(opt[1:]) in TCPOptions[0]: 422 optname = TCPOptions[0][int(opt[1:])][0] 423 optstruct = TCPOptions[0][int(opt[1:])][1] 424 options.append((optname, 425 struct.unpack(optstruct, 426 RandString(struct.calcsize(optstruct))._fix()))) 427 else: 428 options.append((int(opt[1:]), '')) 429 ## FIXME: qqP not handled 430 else: 431 warning("unhandled TCP option " + opt) 432 pkt.payload.options = options 433 434 # window size 435 if pers[0] == '*': 436 pkt.payload.window = RandShort() 437 elif pers[0].isdigit(): 438 pkt.payload.window = int(pers[0]) 439 elif pers[0][0] == '%': 440 coef = int(pers[0][1:]) 441 pkt.payload.window = coef * RandNum(min=1,max=(2L**16-1)/coef) 442 elif pers[0][0] == 'T': 443 pkt.payload.window = mtu * int(pers[0][1:]) 444 elif pers[0][0] == 'S': 445 ## needs MSS set 446 MSS = filter(lambda x: x[0] == 'MSS', options) 447 if not filter(lambda x: x[0] == 'MSS', options): 448 raise Scapy_Exception("TCP window value requires MSS, and MSS option not set") 449 pkt.payload.window = filter(lambda x: x[0] == 'MSS', options)[0][1] * int(pers[0][1:]) 450 else: 451 raise Scapy_Exception('Unhandled window size specification') 452 453 # ttl 454 pkt.ttl = pers[1]-extrahops 455 # DF flag 456 pkt.flags |= (2 * pers[2]) 457 ## FIXME: ss (packet size) not handled (how ? may be with D quirk 458 ## if present) 459 # Quirks 460 if pers[5] != '.': 461 for qq in pers[5]: 462 ## FIXME: not handled: P, I, X, ! 463 # T handled with the Timestamp option 464 if qq == 'Z': pkt.id = 0 465 elif qq == 'U': pkt.payload.urgptr = RandShort() 466 elif qq == 'A': pkt.payload.ack = RandInt() 467 elif qq == 'F': 468 if db == p0fo_kdb: 469 pkt.payload.flags |= 0x20 # U 470 else: 471 pkt.payload.flags |= RandChoice(8, 32, 40) #P / U / PU 472 elif qq == 'D' and db != p0fo_kdb: 473 pkt /= Raw(load=RandString(random.randint(1, 10))) # XXX p0fo.fp 474 elif qq == 'Q': pkt.payload.seq = pkt.payload.ack 475 #elif qq == '0': pkt.payload.seq = 0 476 #if db == p0fr_kdb: 477 # '0' quirk is actually not only for p0fr.fp (see 478 # packet2p0f()) 479 if '0' in pers[5]: 480 pkt.payload.seq = 0 481 elif pkt.payload.seq == 0: 482 pkt.payload.seq = RandInt() 483 484 while pkt.underlayer: 485 pkt = pkt.underlayer 486 return pkt 487 488 def p0f_getlocalsigs(): 489 """This function returns a dictionary of signatures indexed by p0f 490 db (e.g., p0f_kdb, p0fa_kdb, ...) for the local TCP/IP stack. 491 492 You need to have your firewall at least accepting the TCP packets 493 from/to a high port (30000 <= x <= 40000) on your loopback interface. 494 495 Please note that the generated signatures come from the loopback 496 interface and may (are likely to) be different than those generated on 497 "normal" interfaces.""" 498 pid = os.fork() 499 port = random.randint(30000, 40000) 500 if pid > 0: 501 # parent: sniff 502 result = {} 503 def addresult(res): 504 # TODO: wildcard window size in some cases? and maybe some 505 # other values? 506 if res[0] not in result: 507 result[res[0]] = [res[1]] 508 else: 509 if res[1] not in result[res[0]]: 510 result[res[0]].append(res[1]) 511 # XXX could we try with a "normal" interface using other hosts 512 iface = conf.route.route('127.0.0.1')[0] 513 # each packet is seen twice: S + RA, S + SA + A + FA + A 514 # XXX are the packets also seen twice on non Linux systems ? 515 count=14 516 pl = sniff(iface=iface, filter='tcp and port ' + str(port), count = count, timeout=3) 517 map(addresult, map(packet2p0f, pl)) 518 os.waitpid(pid,0) 519 elif pid < 0: 520 log_runtime.error("fork error") 521 else: 522 # child: send 523 # XXX erk 524 time.sleep(1) 525 s1 = socket.socket(socket.AF_INET, type = socket.SOCK_STREAM) 526 # S & RA 527 try: 528 s1.connect(('127.0.0.1', port)) 529 except socket.error: 530 pass 531 # S, SA, A, FA, A 532 s1.bind(('127.0.0.1', port)) 533 s1.connect(('127.0.0.1', port)) 534 # howto: get an RST w/o ACK packet 535 s1.close() 536 os._exit(0) 537 return result 538