// $Id: OSFinger.cc 5857 2008-06-26 23:00:03Z vern $ /* Taken with permission from: p0f - passive OS fingerprinting (GNU LESSER GENERAL PUBLIC LICENSE) ------------------------------------------------------------------- "If you sit down at a poker game and don't see a sucker, get up. You're the sucker." (C) Copyright 2000-2003 by Michal Zalewski */ // To make it easier to upgrade this file to newer releases of p0f, // it remains in the coding style used by p0f rather than Bro. #include "OSFinger.h" #include "net_util.h" #include "util.h" #include "Var.h" #include #include #include void int_delete_func(void* v) { delete (int*) v; } // Initializes data structures for fingerprinting in the given mode. OSFingerprint::OSFingerprint(FingerprintMode arg_mode) { err = 0; mode = arg_mode; sigcnt=gencnt=0; problems=0; char* fname; memset(sig, 0, sizeof(struct fp_entry)*MAXSIGS); memset(bh, 0, sizeof(struct fp_entry*)*OSHSIZE); os_matches.SetDeleteFunc(int_delete_func); if (mode == SYN_FINGERPRINT_MODE) { fname = copy_string(internal_val("passive_fingerprint_file")->AsString()->CheckString()); load_config(fname); delete [] fname; } else if (mode == SYN_ACK_FINGERPRINT_MODE) {//not yet supported load_config("p0fsynack.sig"); } else if (mode == RST_FINGERPRINT_MODE) {//not yet supported load_config("p0frst.sig"); } else { Error("OS fingerprinting: unknown mode!"); } } bool OSFingerprint::CacheMatch(uint32 addr, int id) { HashKey key = HashKey(&addr, 1); int* pid = new int; *pid=id; int* prev = os_matches.Insert(&key, pid); bool ret = (prev ? *prev != id : 1); if (prev) delete prev; return ret; } // Determines whether the signature file had any collisions. void OSFingerprint::collide(uint32 id) { uint32 i,j; uint32 cur; if (sig[id].ttl % 32 && sig[id].ttl != 255 && sig[id].ttl % 30) { problems=1; warn(fmt("OS fingerprinting: [!] Unusual TTL (%d) for signature '%s %s' (line %d).", 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 ) != (int) 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) != (int) 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) Error("OS fingerprinting: Too many TCP options specified in config line",(uint32)ln); /* Skip separators */ do { p++; } while (*p && !isalpha(*p) && *p != '?'); } sig[sigcnt].line = ln; p = quirks; while (*p) switch (toupper(*(p++))) { case 'E': Error("OS fingerprinting: Quirk 'E' is obsolete. Remove it, append E to the options. Line",(uint32)ln); case 'K': if ( mode != RST_FINGERPRINT_MODE ) Error("OS fingerprinting: Quirk 'K' is valid only in RST+ (-R) mode (wrong config file?). Line",(uint32)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: Error("OS fingerprinting: Bad quirk in line",(uint32)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]; } collide(sigcnt); if (++sigcnt >= MAXSIGS) Error("OS fingerprinting: Maximum signature count exceeded.\n"); } fclose(c); if (!sigcnt) Error("OS fingerprinting: no signatures loaded from config file."); } // Does the actual match between the packet and the signature database. // Modifies retval and contains OS Type and other useful information. // Returns config-file line of the matching signature as id. int OSFingerprint::FindMatch(struct os_type* retval, uint16 tot,uint8 df, uint8 ttl,uint16 wss,uint8 ocnt,uint8* op, uint16 mss,uint8 wsc,uint32 tstamp, uint32 quirks,uint8 ecn) const { uint32 j; //used for counter in loops struct fp_entry* p; uint8 orig_df = df; struct fp_entry* fuzzy = 0; uint8 fuzzy_now = 0; int id = 0; //return value: 0 indicates no match. retval->os="UNKNOWN"; retval->desc=NULL; retval->gadgets=0; retval->match=0; retval->uptime=0; re_lookup: p = bh[SIGHASH(tot,ocnt,quirks,df)]; 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 ( mode != RST_FINGERPRINT_MODE )fuzzy = p; p = p->next; continue; } /* Naah... can't happen ;-) */ if (!p->no_detail) if (p->ttl - ttl > MAXDIST) { if (mode != RST_FINGERPRINT_MODE ) fuzzy = p; p = p->next; continue; } continue_fuzzy: /* Match! */ id = p->line; if (mss & wss) { if (p->wsize_mod == MOD_MSS) { if ((wss % mss) && !(wss % 1460)) retval->gadgets|=GADGETNAT; } else if (p->wsize_mod == MOD_MTU) { if ((wss % (mss+40)) && !(wss % 1500)) retval->gadgets|=GADGETNAT2; } } retval->os=p->os; retval->desc=p->desc; retval->dist=p->ttl-ttl; if (ecn) retval->gadgets|=GADGETECN; if (orig_df ^ df) retval->gadgets|=GADGETFIREWALL; if (p->generic) retval->match=MATCHGENERIC; if (fuzzy_now) retval->match=MATCHFUZZY; if (!p->no_detail && tstamp) { retval->uptime=tstamp/360000; retval->gadgets|=GADGETUPTIME; } return id; continue_search: p = p->next; } if (!df) { df = 1; goto re_lookup; } //not found with df=0 do df=1 if (fuzzy) { df = orig_df; fuzzy_now = 1; p = fuzzy; fuzzy = 0; goto continue_fuzzy; } if (mss & wss) { if ((wss % mss) && !(wss % 1460)) retval->gadgets|=GADGETNAT; else if ((wss % (mss+40)) && !(wss % 1500)) retval->gadgets|=GADGETNAT2; } if (ecn) retval->gadgets|=GADGETECN; if (tstamp) { retval->uptime=tstamp/360000; retval->gadgets|=GADGETUPTIME; } return id; }