/* p0f - passive OS fingerprinting ------------------------------- "If you sit down at a poker game and don't see a sucker, get up. You're the sucker." (C) Copyright 2000-2004 by Michal Zalewski WIN32 port (C) Copyright 2003-2004 by Michael A. Davis (C) Copyright 2003-2004 by Kirby Kuehl */ #include #include #include #ifndef WIN32 # include # include # include # include # include # include # include # include #else # include "getopt.h" # include # pragma comment (lib, "wpcap.lib") #endif /* ^WIN32 */ #include #include #include #include #include #include /* #define DEBUG_HASH - display signature hash table stats */ #include "config.h" #include "types.h" #include "tcp.h" #include "mtu.h" #include "tos.h" #include "fpentry.h" #include "p0f-query.h" #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif /* ! MSG_NOSIGNAL */ static pcap_dumper_t *dumper; #ifdef WIN32 static inline void debug(_u8* format, ...) { _u8 buff[1024]; va_list args; va_start(args, format); memset(buff, 0, sizeof(buff)); _vsnprintf( buff, sizeof(buff) - 1, format, args); fprintf(stderr, buff); va_end(args); } static inline void fatal(_u8* format, ...) { _u8 buff[1024]; va_list args; va_start(args, format); memset(buff, 0, sizeof(buff)); vsnprintf( buff, sizeof(buff) - 1, format, args); fprintf(stderr, "[-] ERROR: %s", buff); va_end(args); exit(1); } #else # define debug(x...) fprintf(stderr,x) # define fatal(x...) do { debug("[-] ERROR: " x); exit(1); } while (0) #endif /* ^WIN32 */ #define pfatal(x) do { debug("[-] ERROR: "); perror(x); exit(1); } while (0) static struct fp_entry sig[MAXSIGS]; static _u32 sigcnt,gencnt; /* By hash */ static struct fp_entry* bh[16]; #define SIGHASH(tsize,optcnt,q,df) \ (( (_u8) (((tsize) << 1) ^ ((optcnt) << 1) ^ (df) ^ (q) )) & 0x0f) static _u8 *config_file, *use_iface, *use_dump, *write_dump, *use_cache, #ifndef WIN32 *set_user, #endif /* !WIN32 */ *use_rule = "tcp[13] & 0x17 == 2"; static _u32 query_cache = DEFAULT_QUERY_CACHE; static _s32 masq_thres; static _u8 no_extra, find_masq, masq_flags, no_osdesc, no_known, no_unknown, no_banner, use_promisc, add_timestamp, header_len, ack_mode, rst_mode, go_daemon, use_logfile, mode_oneline, always_sig, do_resolve, check_collide, full_dump, use_fuzzy, payload_dump; static pcap_t *pt; static struct bpf_program flt; static _u32 st_time,pkcnt; static void die_nicely(_s32 sig) { if (sig) debug("+++ Exiting on signal %d +++\n",sig); if (pt) pcap_close(pt); if (dumper) pcap_dump_close(dumper); if (!no_banner && pkcnt) { float r = pkcnt * 60; r /= (time(0) - st_time); debug("[+] Average packet ratio: %0.2f per minute",r); if (use_cache || find_masq) debug(" (cache: %0.2f seconds).\n",query_cache * 60 / r); else debug(".\n"); } exit(sig); } static void set_header_len(_u32 type) { switch(type) { case DLT_SLIP: case DLT_RAW: break; case DLT_NULL: header_len=4; break; case DLT_EN10MB: header_len=14; break; #ifdef DLT_LOOP case DLT_LOOP: #endif #ifdef DLT_PPP_SERIAL case DLT_PPP_SERIAL: /* NetBSD oddity */ #endif #ifdef DLT_PPP_ETHER case DLT_PPP_ETHER: /* PPPoE on NetBSD */ header_len=8; break; #endif case DLT_PPP: header_len=4; break; case DLT_IEEE802: header_len=22; break; #ifdef DLT_IEEE802_11 case DLT_IEEE802_11: header_len=32; break; #endif #ifdef DLT_PFLOG case DLT_PFLOG: header_len=28; break; #endif #ifdef DLT_LINUX_SLL case DLT_LINUX_SLL: header_len=16; break; #endif default: debug("[!] WARNING: Unknown datalink type %d, assuming no header.\n",type); break; } } static void usage(_u8* name) { fprintf(stderr, "\nUsage: %s [ -f file ] [ -i device ] [ -s file ] [ -o file ]\n" #ifndef WIN32 " [ -w file ] [ -Q sock ] [ -u user ] [ -FXVNDUKASCMRqtpdlr ]\n" " [ -c size ] [ -T nn ] [ 'filter rule' ]\n" #else " [ -w file ] [ -XVNDUKASCMLRqtpdlrx ]\n" " [ -c size] [ -T nn ] [ 'filter rule' ]\n" #endif /* ^WIN32 */ " -f file - read fingerprints from file\n" " -i device - listen on this device\n" " -s file - read packets from tcpdump snapshot\n" " -o file - write to this logfile (implies -t)\n" " -w file - save packets to tcpdump snapshot\n" #ifndef WIN32 " -u user - chroot and setuid to this user\n" " -Q sock - listen on local socket for queries\n" #endif /* !WIN32 */ " -c size - cache size for -Q and -M options\n" " -M - run masquerade detection\n" " -T nn - set masquerade detection threshold (1-200)\n" " -V - verbose masquerade flags reporting\n" " -F - use fuzzy matching (do not combine with -R)\n" " -N - do not report distances and link media\n" " -D - do not report OS details (just genre)\n" " -U - do not display unknown signatures\n" " -K - do not display known signatures (for tests)\n" " -S - report signatures even for known systems\n" " -A - go into SYN+ACK mode (semi-supported)\n" " -R - go into RST/RST+ACK mode (semi-supported)\n" " -r - resolve host names (not recommended)\n" " -q - be quiet - no banner\n" " -p - switch card to promiscuous mode\n" " -d - daemon mode (fork into background)\n" " -l - use single-line output (easier to grep)\n" " -x - include full packet dump (for debugging)\n" " -X - display payload string (useful in RST mode)\n" " -C - run signature collision check\n" #ifdef WIN32 " -L - list all available interfaces\n" #endif /* ^WIN32 */ " -t - add timestamps to every entry\n\n" " 'Filter rule' is an optional pcap-style BPF expression (man tcpdump).\n\n",name); exit(1); } static _u8 problems; static void collide(_u32 id) { _u32 i,j; _u32 cur; if (sig[id].ttl % 32 && sig[id].ttl != 255 && sig[id].ttl % 30) { problems=1; debug("[!] Unusual TTL (%d) for signature '%s %s' (line %d).\n", sig[id].ttl,sig[id].os,sig[id].desc,sig[id].line); } for (i=0;i 25) continue; if (sig[id].df ^ sig[i].df) continue; if (sig[id].zero_stamp ^ sig[i].zero_stamp) continue; /* Zero means >= PACKET_BIG */ if (sig[id].size) { if (sig[id].size ^ sig[i].size) continue; } else if (sig[i].size < PACKET_BIG) continue; if (sig[id].optcnt ^ sig[i].optcnt) continue; if (sig[id].quirks ^ sig[i].quirks) continue; switch (sig[id].wsize_mod) { case 0: /* Current: const */ cur=sig[id].wsize; do_const: switch (sig[i].wsize_mod) { case 0: /* Previous is also const */ /* A problem if values match */ if (cur ^ sig[i].wsize) continue; break; case MOD_CONST: /* Current: const, prev: modulo (or *) */ /* A problem if current value is a multiple of that modulo */ if (cur % sig[i].wsize) continue; break; case MOD_MSS: /* Current: const, prev: mod MSS */ if (sig[i].mss_mod || sig[i].wsize * (sig[i].mss ? sig[i].mss : 1460 ) != cur) continue; break; case MOD_MTU: /* Current: const, prev: mod MTU */ if (sig[i].mss_mod || sig[i].wsize * ( (sig[i].mss ? sig[i].mss : 1460 )+40) != cur) continue; break; } break; case 1: /* Current signature is modulo something */ /* A problem only if this modulo is a multiple of the previous modulo */ if (sig[i].wsize_mod != MOD_CONST) continue; if (sig[id].wsize % sig[i].wsize) continue; break; case MOD_MSS: /* Current is modulo MSS */ /* There's likely a problem only if the previous one is close to '*'; we do not check known MTUs, because this particular signature can be made with some uncommon MTUs in mind. The problem would also appear if current signature has a fixed MSS. */ if (sig[i].wsize_mod != MOD_CONST || sig[i].wsize >= 8) { if (!sig[id].mss_mod) { cur = (sig[id].mss ? sig[id].mss : 1460 ) * sig[id].wsize; goto do_const; } continue; } break; case MOD_MTU: /* Current is modulo MTU */ if (sig[i].wsize_mod != MOD_CONST || sig[i].wsize <= 8) { if (!sig[id].mss_mod) { cur = ( (sig[id].mss ? sig[id].mss : 1460 ) +40) * sig[id].wsize; goto do_const; } continue; } break; } /* Same for wsc */ switch (sig[id].wsc_mod) { case 0: /* Current: const */ cur=sig[id].wsc; switch (sig[i].wsc_mod) { case 0: /* Previous is also const */ /* A problem if values match */ if (cur ^ sig[i].wsc) continue; break; case 1: /* Current: const, prev: modulo (or *) */ /* A problem if current value is a multiple of that modulo */ if (cur % sig[i].wsc) continue; break; } break; case MOD_CONST: /* Current signature is modulo something */ /* A problem only if this modulo is a multiple of the previous modulo */ if (!sig[i].wsc_mod) continue; if (sig[id].wsc % sig[i].wsc) continue; break; } /* Same for mss */ switch (sig[id].mss_mod) { case 0: /* Current: const */ cur=sig[id].mss; switch (sig[i].mss_mod) { case 0: /* Previous is also const */ /* A problem if values match */ if (cur ^ sig[i].mss) continue; break; case 1: /* Current: const, prev: modulo (or *) */ /* A problem if current value is a multiple of that modulo */ if (cur % sig[i].mss) continue; break; } break; case MOD_CONST: /* Current signature is modulo something */ /* A problem only if this modulo is a multiple of the previous modulo */ if (!sig[i].mss_mod) continue; if ((sig[id].mss ? sig[id].mss : 1460 ) % (sig[i].mss ? sig[i].mss : 1460 )) continue; break; } /* Now check option sequence */ for (j=0;j= MAXOPT) fatal("Too many TCP options specified in config line %d.\n",ln); /* Skip separators */ do { p++; } while (*p && !isalpha(*p) && *p != '?'); } sig[sigcnt].line = ln; p = quirks; while (*p) switch (toupper(*(p++))) { case 'E': fatal("Quirk 'E' (line %d) is obsolete. Remove it, append E to the " "options.\n",ln); case 'K': if (!rst_mode) fatal("Quirk 'K' (line %d) is valid only in RST+ (-R)" " mode (wrong config file?).\n",ln); sig[sigcnt].quirks |= QUIRK_RSTACK; break; case 'Q': sig[sigcnt].quirks |= QUIRK_SEQEQ; break; case '0': sig[sigcnt].quirks |= QUIRK_SEQ0; break; case 'P': sig[sigcnt].quirks |= QUIRK_PAST; break; case 'Z': sig[sigcnt].quirks |= QUIRK_ZEROID; break; case 'I': sig[sigcnt].quirks |= QUIRK_IPOPT; break; case 'U': sig[sigcnt].quirks |= QUIRK_URG; break; case 'X': sig[sigcnt].quirks |= QUIRK_X2; break; case 'A': sig[sigcnt].quirks |= QUIRK_ACK; break; case 'T': sig[sigcnt].quirks |= QUIRK_T2; break; case 'F': sig[sigcnt].quirks |= QUIRK_FLAGS; break; case 'D': sig[sigcnt].quirks |= QUIRK_DATA; break; case '!': sig[sigcnt].quirks |= QUIRK_BROKEN; break; case '.': break; default: fatal("Bad quirk '%c' in line %d.\n",*(p-1),ln); } e = bh[SIGHASH(s,sig[sigcnt].optcnt,sig[sigcnt].quirks,d)]; if (!e) { bh[SIGHASH(s,sig[sigcnt].optcnt,sig[sigcnt].quirks,d)] = sig + sigcnt; } else { while (e->next) e = e->next; e->next = sig + sigcnt; } if (check_collide) collide(sigcnt); if (++sigcnt >= MAXSIGS) fatal("Maximum signature count exceeded.\n"); } fclose(c); #ifdef DEBUG_HASH { int i; struct fp_entry* p; printf("Hash table layout: "); for (i=0;i<16;i++) { int z=0; p = bh[i]; while (p) { p=p->next; z++; } printf("%d ",z); } putchar('\n'); } #endif /* DEBUG_HASH */ if (check_collide && !problems) debug("[+] Signature collision check successful.\n"); if (!sigcnt) debug("[!] WARNING: no signatures loaded from config file.\n"); } static _u8* lookup_link(_u16 mss,_u8 txt) { _u32 i; static _u8 tmp[32]; if (!mss) return txt ? "unspecified" : 0; mss += 40; for (i=0;i ",x); break; case 3: /* seconds since the epoch */ printf("<%u.%06u> ", (_u32)tval.tv_sec, (_u32)tval.tv_usec); break; case 4: /* RFC3339 */ default: tmval = gmtime(&tval.tv_sec); printf("<%04u-%02u-%02uT%02u:%02u:%02u.%06uZ> ", tmval->tm_year + 1900, tmval->tm_mon + 1, tmval->tm_mday, tmval->tm_hour, tmval->tm_min, tmval->tm_sec, (_u32)tval.tv_usec); break; } } #define MY_MAXDNS 32 static inline _u8* grab_name(_u8* a) { struct hostent* r; static _u8 rbuf[MY_MAXDNS+6] = "/"; _u32 j; _u8 *s,*d = rbuf+1; if (!do_resolve) return ""; r = gethostbyaddr(a,4,AF_INET); if (!r || !(s = r->h_name) || !(j = strlen(s))) return ""; if (j > MY_MAXDNS) return ""; while (j--) { if (isalnum(*s) || *s == '-' || *s == '.') *d = *s; else *d = '?'; d++; s++; } *d=0; return rbuf; } static inline void display_signature(_u8 ttl,_u16 tot,_u8 df,_u8* op,_u8 ocnt, _u16 mss,_u16 wss,_u8 wsc,_u32 tstamp, _u32 quirks) { _u32 j; _u8 d=0; if (mss && wss && !(wss % mss)) printf("S%d",wss/mss); else if (wss && !(wss % 1460)) printf("S%d",wss/1460); else if (mss && wss && !(wss % (mss+40))) printf("T%d",wss/(mss+40)); else if (wss && !(wss % 1500)) printf("T%d",wss/1500); else if (wss == 12345) printf("*(12345)"); else printf("%d",wss); if (tot < PACKET_BIG) printf(":%d:%d:%d:",ttl,df,tot); else printf(":%d:%d:*(%d):",ttl,df,tot); for (j=0;j PKT_MAXPAY ? PKT_MAXPAY : dlen; if (!dlen) return; for (i=0;i PKT_MAXPAY ? "..." : ""); } static inline void find_match(_u16 tot,_u8 df,_u8 ttl,_u16 wss,_u32 src, _u32 dst,_u16 sp,_u16 dp,_u8 ocnt,_u8* op,_u16 mss, _u8 wsc,_u32 tstamp,_u8 tos,_u32 quirks,_u8 ecn, _u8* pkt,_u8 plen,_u8* pay, struct timeval pts) { _u32 j; _u8* a; _u8 nat=0; struct fp_entry* p; _u8 orig_df = df; _u8* tos_desc = 0; struct fp_entry* fuzzy = 0; _u8 fuzzy_now = 0; re_lookup: p = bh[SIGHASH(tot,ocnt,quirks,df)]; if (tos) tos_desc = lookup_tos(tos); while (p) { /* Cheap and specific checks first... */ /* psize set to zero means >= PACKET_BIG */ if (p->size) { if (tot ^ p->size) { p = p->next; continue; } } else if (tot < PACKET_BIG) { p = p->next; continue; } if (ocnt ^ p->optcnt) { p = p->next; continue; } if (p->zero_stamp ^ (!tstamp)) { p = p->next; continue; } if (p->df ^ df) { p = p->next; continue; } if (p->quirks ^ quirks) { p = p->next; continue; } /* Check MSS and WSCALE... */ if (!p->mss_mod) { if (mss ^ p->mss) { p = p->next; continue; } } else if (mss % p->mss) { p = p->next; continue; } if (!p->wsc_mod) { if (wsc ^ p->wsc) { p = p->next; continue; } } else if (wsc % p->wsc) { p = p->next; continue; } /* Then proceed with the most complex WSS check... */ switch (p->wsize_mod) { case 0: if (wss ^ p->wsize) { p = p->next; continue; } break; case MOD_CONST: if (wss % p->wsize) { p = p->next; continue; } break; case MOD_MSS: if (mss && !(wss % mss)) { if ((wss / mss) ^ p->wsize) { p = p->next; continue; } } else if (!(wss % 1460)) { if ((wss / 1460) ^ p->wsize) { p = p->next; continue; } } else { p = p->next; continue; } break; case MOD_MTU: if (mss && !(wss % (mss+40))) { if ((wss / (mss+40)) ^ p->wsize) { p = p->next; continue; } } else if (!(wss % 1500)) { if ((wss / 1500) ^ p->wsize) { p = p->next; continue; } } else { p = p->next; continue; } break; } /* Numbers agree. Let's check options */ for (j=0;jopt[j] ^ op[j]) goto continue_search; /* Check TTLs last because we might want to go fuzzy. */ if (p->ttl < ttl) { if (use_fuzzy) fuzzy = p; p = p->next; continue; } /* Naah... can't happen ;-) */ if (!p->no_detail) if (p->ttl - ttl > MAXDIST) { if (use_fuzzy) fuzzy = p; p = p->next; continue; } continue_fuzzy: /* Match! */ if (mss & wss) { if (p->wsize_mod == MOD_MSS) { if ((wss % mss) && !(wss % 1460)) nat=1; } else if (p->wsize_mod == MOD_MTU) { if ((wss % (mss+40)) && !(wss % 1500)) nat=2; } } if (!no_known) { if (add_timestamp) put_date(pts); a=(_u8*)&src; printf("%d.%d.%d.%d%s:%d - %s ",a[0],a[1],a[2],a[3],grab_name(a), sp,p->os); if (!no_osdesc) printf("%s ",p->desc); if (nat == 1) printf("(NAT!) "); else if (nat == 2) printf("(NAT2!) "); if (ecn) printf("(ECN) "); if (orig_df ^ df) printf("(firewall!) "); if (tos) { if (tos_desc) printf("[%s] ",tos_desc); else printf("[tos %d] ",tos); } if (p->generic) printf("[GENERIC] "); if (fuzzy_now) printf("[FUZZY] "); if (p->no_detail) printf("* "); else if (tstamp) printf("(up: %d hrs) ",tstamp/360000); if (always_sig || (p->generic && !no_unknown)) { if (!mode_oneline) printf("\n "); printf("Signature: ["); display_signature(ttl,tot,orig_df,op,ocnt,mss,wss,wsc,tstamp,quirks); if (p->generic) printf(":%s:?] ",p->os); else printf("] "); } if (!no_extra && !p->no_detail) { a=(_u8*)&dst; if (!mode_oneline) printf("\n "); if (fuzzy_now) printf("-> %d.%d.%d.%d%s:%d (link: %s)", a[0],a[1],a[2],a[3],grab_name(a),dp, lookup_link(mss,1)); else printf("-> %d.%d.%d.%d%s:%d (distance %d, link: %s)", a[0],a[1],a[2],a[3],grab_name(a),dp,p->ttl - ttl, lookup_link(mss,1)); } if (pay && payload_dump) dump_payload(pay,plen - (pay - pkt)); putchar('\n'); if (full_dump) dump_packet(pkt,plen); } if (find_masq && !p->userland) { _s16 sc = p0f_findmasq(src,p->os,(p->no_detail || fuzzy_now) ? -1 : (p->ttl - ttl), mss, nat, orig_df ^ df,p-sig, tstamp ? tstamp / 360000 : -1); a=(_u8*)&src; if (sc > masq_thres) { printf(">> Masquerade at %u.%u.%u.%u%s: indicators at %d%%.", a[0],a[1],a[2],a[3],grab_name(a),sc); if (!mode_oneline) putchar('\n'); else printf(" -- "); if (masq_flags) { printf(" Flags: "); p0f_descmasq(); putchar('\n'); } } } if (use_cache || find_masq) p0f_addcache(src,dst,sp,dp,p->os,p->desc,(p->no_detail || fuzzy_now) ? -1 : (p->ttl - ttl),p->no_detail ? 0 : lookup_link(mss,0), tos_desc, orig_df ^ df, nat, !p->userland, mss, p-sig, tstamp ? tstamp / 360000 : -1); fflush(0); return; continue_search: p = p->next; } if (!df) { df = 1; goto re_lookup; } if (use_fuzzy && fuzzy) { df = orig_df; fuzzy_now = 1; p = fuzzy; fuzzy = 0; goto continue_fuzzy; } if (mss & wss) { if ((wss % mss) && !(wss % 1460)) nat=1; else if ((wss % (mss+40)) && !(wss % 1500)) nat=2; } if (!no_unknown) { if (add_timestamp) put_date(pts); a=(_u8*)&src; printf("%d.%d.%d.%d%s:%d - UNKNOWN [",a[0],a[1],a[2],a[3],grab_name(a),sp); display_signature(ttl,tot,orig_df,op,ocnt,mss,wss,wsc,tstamp,quirks); printf(":?:?] "); if (rst_mode) { /* Display a reasonable diagnosis of the RST+ACK madness! */ switch (quirks & (QUIRK_RSTACK | QUIRK_SEQ0 | QUIRK_ACK)) { /* RST+ACK, SEQ=0, ACK=0 */ case QUIRK_RSTACK | QUIRK_SEQ0: printf("(invalid-K0) "); break; /* RST+ACK, SEQ=0, ACK=n */ case QUIRK_RSTACK | QUIRK_ACK | QUIRK_SEQ0: printf("(refused) "); break; /* RST+ACK, SEQ=n, ACK=0 */ case QUIRK_RSTACK: printf("(invalid-K) "); break; /* RST+ACK, SEQ=n, ACK=n */ case QUIRK_RSTACK | QUIRK_ACK: printf("(invalid-KA) "); break; /* RST, SEQ=n, ACK=0 */ case 0: printf("(dropped) "); break; /* RST, SEQ=m, ACK=n */ case QUIRK_ACK: printf("(dropped 2) "); break; /* RST, SEQ=0, ACK=0 */ case QUIRK_SEQ0: printf("(invalid-0) "); break; /* RST, SEQ=0, ACK=n */ case QUIRK_ACK | QUIRK_SEQ0: printf("(invalid-0A) "); break; } } if (nat == 1) printf("(NAT!) "); else if (nat == 2) printf("(NAT2!) "); if (ecn) printf("(ECN) "); if (tos) { if (tos_desc) printf("[%s] ",tos_desc); else printf("[tos %d] ",tos); } if (tstamp) printf("(up: %d hrs) ",tstamp/360000); if (!no_extra) { a=(_u8*)&dst; if (!mode_oneline) printf("\n "); printf("-> %d.%d.%d.%d%s:%d (link: %s)",a[0],a[1],a[2],a[3], grab_name(a),dp,lookup_link(mss,1)); } if (use_cache) p0f_addcache(src,dst,sp,dp,0,0,-1,lookup_link(mss,0),tos_desc, 0,nat,0 /* not real, we're not sure */ ,mss,(_u32)-1, tstamp ? tstamp / 360000 : -1); if (pay && payload_dump) dump_payload(pay,plen - (pay - pkt)); putchar('\n'); if (full_dump) dump_packet(pkt,plen); fflush(0); } } #define GET16(p) \ ((_u16) *((_u8*)(p)+0) << 8 | \ (_u16) *((_u8*)(p)+1) ) static void parse(_u8* none, struct pcap_pkthdr *pph, _u8* packet) { struct ip_header *iph; struct tcp_header *tcph; struct timeval pts; _u8* end_ptr; _u8* opt_ptr; _u8* pay = 0; _s32 ilen,olen; _u8 op[MAXOPT]; _u8 ocnt = 0; _u16 mss_val = 0, wsc_val = 0; _u32 tstamp = 0; _u32 quirks = 0; pkcnt++; if (dumper) pcap_dump((_u8*)dumper,pph,packet); /* Paranoia! */ if (pph->len <= PACKET_SNAPLEN) end_ptr = packet + pph->len; else end_ptr = packet + PACKET_SNAPLEN; iph = (struct ip_header*)(packet+header_len); /* Whoops, IP header ends past end_ptr */ if ((_u8*)(iph + 1) > end_ptr) return; if ( ((iph->ihl & 0x40) != 0x40) || iph->proto != IPPROTO_TCP) { debug("[!] WARNING: Non-IP packet received. Bad header_len!\n"); return; } /* If the declared length is shorter than the snapshot (etherleak or such), truncate this bad boy. */ opt_ptr = (_u8*)iph + htons(iph->tot_len); if (end_ptr > opt_ptr) end_ptr = opt_ptr; ilen = iph->ihl & 15; pts = pph->ts; /* Borken packet */ if (ilen < 5) return; if (ilen > 5) { #ifdef DEBUG_EXTRAS _u8 i; printf(" -- EXTRA IP OPTIONS (packet below): "); for (i=0;i end_ptr) return; if (rst_mode && (tcph->flags & TH_ACK)) quirks |= QUIRK_RSTACK; if (tcph->seq == tcph->ack) quirks |= QUIRK_SEQEQ; if (!tcph->seq) quirks |= QUIRK_SEQ0; if (tcph->flags & ~(TH_SYN|TH_ACK|TH_RST|TH_ECE|TH_CWR)) quirks |= QUIRK_FLAGS; ilen=((tcph->doff) << 2) - sizeof(struct tcp_header); if ( (_u8*)opt_ptr + ilen < end_ptr) { #ifdef DEBUG_EXTRAS _u32 i; printf(" -- EXTRA PAYLOAD (packet below): "); for (i=0;i< (_u32)end_ptr - ilen - (_u32)opt_ptr;i++) printf("%02x ",*(opt_ptr + ilen + i)); putchar('\n'); fflush(0); #endif /* DEBUG_EXTRAS */ quirks |= QUIRK_DATA; pay = opt_ptr + ilen; } while (ilen > 0) { ilen--; switch (*(opt_ptr++)) { case TCPOPT_EOL: /* EOL */ op[ocnt] = TCPOPT_EOL; ocnt++; if (ilen) { quirks |= QUIRK_PAST; #ifdef DEBUG_EXTRAS printf(" -- EXTRA TCP OPTIONS (packet below): "); while (ilen) { ilen--; if (opt_ptr >= end_ptr) { printf("..."); break; } printf("%02x ",*(opt_ptr++)); } putchar('\n'); fflush(0); #endif /* DEBUG_EXTRAS */ } /* This goto will be probably removed at some point. */ goto end_parsing; case TCPOPT_NOP: /* NOP */ op[ocnt] = TCPOPT_NOP; ocnt++; break; case TCPOPT_SACKOK: /* SACKOK LEN */ op[ocnt] = TCPOPT_SACKOK; ocnt++; ilen--; opt_ptr++; break; case TCPOPT_MAXSEG: /* MSS LEN D0 D1 */ if (opt_ptr + 3 > end_ptr) { borken: quirks |= QUIRK_BROKEN; goto end_parsing; } op[ocnt] = TCPOPT_MAXSEG; mss_val = GET16(opt_ptr+1); ocnt++; ilen -= 3; opt_ptr += 3; break; case TCPOPT_WSCALE: /* WSCALE LEN D0 */ if (opt_ptr + 2 > end_ptr) goto borken; op[ocnt] = TCPOPT_WSCALE; wsc_val = *(_u8 *)(opt_ptr + 1); ocnt++; ilen -= 2; opt_ptr += 2; break; case TCPOPT_TIMESTAMP: /* TSTAMP LEN T0 T1 T2 T3 A0 A1 A2 A3 */ if (opt_ptr + 9 > end_ptr) goto borken; op[ocnt] = TCPOPT_TIMESTAMP; memcpy(&tstamp, opt_ptr+5, 4); if (tstamp) quirks |= QUIRK_T2; memcpy(&tstamp, opt_ptr+1, 4); tstamp = ntohl(tstamp); ocnt++; ilen -= 9; opt_ptr += 9; break; default: /* Hrmpf... */ if (opt_ptr + 1 > end_ptr) goto borken; op[ocnt] = *(opt_ptr-1); olen = *(_u8*)(opt_ptr)-1; if (olen > 32 || (olen < 0)) goto borken; ocnt++; ilen -= olen; opt_ptr += olen; break; } if (ocnt >= MAXOPT-1) goto borken; /* Whoops, we're past end_ptr */ if (ilen > 0) if (opt_ptr >= end_ptr) goto borken; } end_parsing: if (tcph->ack) quirks |= QUIRK_ACK; if (tcph->urg) quirks |= QUIRK_URG; if (tcph->_x2) quirks |= QUIRK_X2; if (!iph->id) quirks |= QUIRK_ZEROID; find_match( /* total */ ntohs(iph->tot_len), /* DF */ (ntohs(iph->off) & IP_DF) != 0, /* TTL */ iph->ttl, /* WSS */ ntohs(tcph->win), /* src */ iph->saddr, /* dst */ iph->daddr, /* sp */ ntohs(tcph->sport), /* dp */ ntohs(tcph->dport), /* ocnt */ ocnt, /* op */ op, /* mss */ mss_val, /* wsc */ wsc_val, /* tst */ tstamp, /* TOS */ iph->tos, /* Q? */ quirks, /* ECN */ tcph->flags & (TH_ECE|TH_CWR), /* pkt */ (_u8*)iph, /* len */ end_ptr - (_u8*)iph, /* pay */ pay, /* ts */ pts ); #ifdef DEBUG_EXTRAS if (quirks & QUIRK_FLAGS || tcph->ack || tcph->_x2 || tcph->urg) printf(" -- EXTRA TCP VALUES: ACK=0x%x, UNUSED=%d, URG=0x%x " "(flags = %x)\n",tcph->ack,tcph->_x2,tcph->urg,tcph->flags); fflush(0); #endif /* DEBUG_EXTRAS */ } int main(int argc,char** argv) { _u8 buf[MAXLINE*4]; _s32 r; _u8 errbuf[PCAP_ERRBUF_SIZE]; #ifdef WIN32 _u8 ebuf[PCAP_ERRBUF_SIZE]; pcap_if_t *alldevs, *d; _s32 adapter, i; while ((r = getopt(argc, argv, "f:i:s:o:w:c:T:XNVFDxKUqtpArRlSdCLM")) != -1) #else _s32 lsock=0; if (getuid() != geteuid()) fatal("This program is not intended to be setuid.\n"); while ((r = getopt(argc, argv, "f:i:s:o:Q:u:w:c:T:XFNVDxKUqtRpArlSdCM")) != -1) #endif /* ^WIN32 */ switch (r) { case 'f': config_file = optarg; break; case 'i': use_iface = optarg; break; case 's': use_dump = optarg; break; case 'w': write_dump = optarg; break; case 'c': query_cache = atoi(optarg); break; case 'o': if (!freopen(optarg,"a",stdout)) pfatal(optarg); use_logfile = 1; break; case 'V': masq_flags = 1; break; case 'M': find_masq = 1; break; case 'T': masq_thres = atoi(optarg); if (masq_thres <= 0 || masq_thres > 200) fatal("Invalid -T value.\n"); break; #ifndef WIN32 case 'Q': use_cache = optarg; break; case 'u': set_user = optarg; break; #endif /* !WIN32 */ case 'r': do_resolve = 1; break; case 'S': always_sig = 1; break; case 'N': no_extra = 1; break; case 'D': no_osdesc = 1; break; case 'U': no_unknown = 1; break; case 'K': no_known = 1; break; case 'q': no_banner = 1; break; case 'p': use_promisc = 1; break; case 't': add_timestamp++; break; case 'd': go_daemon = 1; break; case 'l': mode_oneline = 1; break; case 'C': check_collide = 1; break; case 'x': full_dump = 1; break; case 'X': payload_dump = 1; break; case 'F': use_fuzzy = 1; break; case 'A': use_rule = "tcp[13] & 0x17 == 0x12"; ack_mode = 1; break; case 'R': use_rule = "tcp[13] & 0x17 == 0x4 or tcp[13] & 0x17 == 0x14"; rst_mode = 1; break; #ifdef WIN32 case 'L': if (pcap_findalldevs(&alldevs, ebuf) == -1) fatal("pcap_findalldevs: %s\n", ebuf); debug("\nInterface\tDevice\t\tDescription\n" "-------------------------------------------\n"); for(i=1,d=alldevs;d;d=d->next,i++) { debug("%d %s",i, d->name); if (d->description) debug("\t%s",d->description); debug("\n"); } exit(1); break; #endif /* WIN32 */ default: usage(argv[0]); } if (use_logfile && !add_timestamp) add_timestamp = 1; if (use_iface && use_dump) fatal("-s and -i are mutually exclusive.\n"); if (full_dump && mode_oneline) fatal("-x and -l are mutually exclusive.\n"); if (ack_mode && rst_mode) fatal("-A and -R are mutually exclusive.\n"); #ifdef DEBUG_EXTRAS if (mode_oneline || no_known || no_unknown || no_extra) debug("[!] WARNING: compiled with DEBUG_EXTRAS, -l, -K, -U, -N not " "compatible.\n"); #endif if (find_masq || use_cache) p0f_initcache(query_cache); if (!use_cache && !find_masq && no_known && no_unknown) fatal("-U and -K are mutually exclusive (except with -Q or -M).\n"); if (!use_logfile && go_daemon) fatal("-d requires -o.\n"); if (!no_banner) { debug("p0f - passive os fingerprinting utility, version " VER "\n" "(C) M. Zalewski , W. Stearns \n"); #ifdef WIN32 debug("WIN32 port (C) M. Davis , K. Kuehl \n"); #endif /* WIN32 */ if (use_fuzzy && rst_mode) debug("[!] WARNING: It is a bad idea to combine -F and -R.\n"); } load_config(config_file); if (argv[optind] && *(argv[optind])) { sprintf(buf,"(%s) and (%3000s)",use_rule,argv[optind]); use_rule = buf; } signal(SIGINT,&die_nicely); signal(SIGTERM,&die_nicely); #ifndef WIN32 signal(SIGHUP,&die_nicely); signal(SIGQUIT,&die_nicely); if (use_cache) { struct sockaddr_un x; lsock = socket(PF_UNIX,SOCK_STREAM,0); if (lsock < 0) pfatal("socket"); x.sun_family = AF_UNIX; strncpy(x.sun_path,use_cache,63); unlink(use_cache); if (bind(lsock,(struct sockaddr*)&x,sizeof(x))) pfatal(use_cache); if (listen(lsock,10)) pfatal("listen"); } #endif /* !WIN32 */ if (use_dump) { if (!(pt=pcap_open_offline(use_dump, errbuf))) fatal("pcap_open_offline failed: %s\n",errbuf); } else { #ifdef WIN32 if (pcap_findalldevs(&alldevs, ebuf) == -1) fatal("pcap_findalldevs: %s\n", ebuf); if (!use_iface) { d = alldevs; } else { adapter = atoi(use_iface); for(i=1, d=alldevs; adapter && i < adapter && d; i++, d=d->next); if (!d) fatal("Unable to find adapter %d\n", adapter); } use_iface = d->name; #else if (!use_iface) use_iface=pcap_lookupdev(errbuf); #endif /* ^WIN32 */ if (!use_iface) use_iface = "lo"; /* We do not rely on pcap timeouts - they suck really bad. Of course, the documentation sucks, and if you use the timeout of zero, things will break. */ if (!(pt=pcap_open_live(use_iface,PACKET_SNAPLEN,use_promisc,1,errbuf))) fatal("pcap_open_live failed: %s\n",errbuf); } set_header_len(pcap_datalink(pt)); if (pcap_compile(pt, &flt, use_rule, 1, 0)) if (strchr(use_rule,'(')) { pcap_perror(pt,"pcap_compile"); debug("See man tcpdump or p0f README for help on bpf filter expressions.\n"); exit(1); } if (!no_banner) { debug("p0f: listening (%s) on '%s', %d sigs (%d generic), rule: '%s'.\n", ack_mode ? "SYN+ACK" : rst_mode ? "RST+" : "SYN", use_dump?use_dump:use_iface,sigcnt,gencnt, argv[optind]?argv[optind]:"all"); if (use_cache) debug("[*] Accepting queries at socket %s (timeout: %d s).\n",use_cache,QUERY_TIMEOUT); if (find_masq) debug("[*] Masquerade detection enabled at threshold %d%%.\n",masq_thres); } pcap_setfilter(pt, &flt); if (write_dump) { if (!(dumper=pcap_dump_open(pt, write_dump))) { pcap_perror(pt,"pcap_dump_open"); exit(1); } } #ifndef WIN32 if (set_user) { struct passwd* pw; if (geteuid()) fatal("only root can use -u.\n"); pw = getpwnam(set_user); if (!pw) fatal("user %s not found.\n",set_user); if (chdir(pw->pw_dir)) pfatal(pw->pw_dir); if (chroot(pw->pw_dir)) pfatal("chroot"); chdir("/"); if (initgroups(pw->pw_name,pw->pw_gid)) pfatal("initgroups"); if (setgid(pw->pw_gid)) pfatal("setgid"); if (setuid(pw->pw_uid)) pfatal("setuid"); if (getegid() != pw->pw_gid || geteuid() != pw->pw_uid) fatal("failed to setuid/setgid to the desired UID/GID.\n"); } #endif /* !WIN32 */ if (go_daemon) { #ifndef WIN32 _s32 f; struct timeval tv; fflush(0); f = fork(); if (f<0) pfatal("fork() failed"); if (f) exit(0); dup2(1,2); close(0); chdir("/"); setsid(); signal(SIGHUP,SIG_IGN); printf("--- p0f " VER " resuming operations at "); gettimeofday(&tv, (struct timezone*)0); put_date(tv); printf("---\n"); fflush(0); #else fatal("daemon mode is not support in the WIN32 version.\n"); #endif /* ^WIN32 */ } st_time = time(0); #ifndef WIN32 if (use_cache) { _s32 mfd,max; mfd = pcap_fileno(pt); max = 1 + (mfd > lsock ? mfd : lsock); while (1) { fd_set f,e; FD_ZERO(&f); FD_SET(mfd,&f); FD_SET(lsock,&f); FD_ZERO(&e); FD_SET(mfd,&e); FD_SET(lsock,&e); /* This is the neat way to do it; pcap timeouts are broken on many platforms, Linux always resumes recvfrom() on the raw socket, even with no SA_RESTART, it's a mess... select() is rather neutral. */ select(max,&f,0,&e,0); if (FD_ISSET(mfd, &f) || FD_ISSET(mfd,&e)) if (pcap_dispatch(pt,-1,(pcap_handler)&parse,0) < 0) break; if (FD_ISSET(lsock,&f)) { struct timeval tv; struct p0f_query q; _s32 c; if ((c=accept(lsock,0,0))<0) continue; FD_ZERO(&f); FD_SET(c,&f); tv.tv_sec = QUERY_TIMEOUT; tv.tv_usec = 0; if (select(c+1,&f,0,&f,&tv)>0) if (recv(c,&q,sizeof(q),MSG_NOSIGNAL) == sizeof(q)) p0f_handlequery(c,&q); shutdown(c,2); close(c); } if (FD_ISSET(lsock,&e)) fatal("Query socket error.\n"); } } else #endif /* !WIN32 */ pcap_loop(pt,-1,(pcap_handler)&parse,0); pcap_close(pt); if (dumper) pcap_dump_close(dumper); if (use_dump) debug("[+] End of input file.\n"); else fatal("Network is down.\n"); return 0; }