mirror of
				https://github.com/telekom-security/tpotce.git
				synced 2025-10-30 20:12:53 +00:00 
			
		
		
		
	
		
			
	
	
		
			1342 lines
		
	
	
	
		
			31 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			1342 lines
		
	
	
	
		
			31 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /*
 | ||
|  |    p0f - TCP/IP packet matching | ||
|  |    ---------------------------- | ||
|  | 
 | ||
|  |    Copyright (C) 2012 by Michal Zalewski <lcamtuf@coredump.cx> | ||
|  | 
 | ||
|  |    Distributed under the terms and conditions of GNU LGPL. | ||
|  | 
 | ||
|  |  */ | ||
|  | 
 | ||
|  | #include <stdio.h>
 | ||
|  | #include <stdlib.h>
 | ||
|  | #include <unistd.h>
 | ||
|  | 
 | ||
|  | #include <netinet/in.h>
 | ||
|  | #include <sys/types.h>
 | ||
|  | #include <ctype.h>
 | ||
|  | 
 | ||
|  | #include "types.h"
 | ||
|  | #include "config.h"
 | ||
|  | #include "debug.h"
 | ||
|  | #include "alloc-inl.h"
 | ||
|  | #include "process.h"
 | ||
|  | #include "hash.h"
 | ||
|  | #include "tcp.h"
 | ||
|  | #include "readfp.h"
 | ||
|  | #include "p0f.h"
 | ||
|  | 
 | ||
|  | #include "fp_tcp.h"
 | ||
|  | 
 | ||
|  | /* TCP signature buckets: */ | ||
|  | 
 | ||
|  | static struct tcp_sig_record* sigs[2][SIG_BUCKETS]; | ||
|  | static u32 sig_cnt[2][SIG_BUCKETS]; | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Figure out what the TTL distance might have been for an unknown sig. */ | ||
|  | 
 | ||
|  | static u8 guess_dist(u8 ttl) { | ||
|  |   if (ttl <= 32) return 32 - ttl; | ||
|  |   if (ttl <= 64) return 64 - ttl; | ||
|  |   if (ttl <= 128) return 128 - ttl; | ||
|  |   return 255 - ttl; | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Figure out if window size is a multiplier of MSS or MTU. We don't take window
 | ||
|  |    scaling into account, because neither do TCP stack developers. */ | ||
|  | 
 | ||
|  | static s16 detect_win_multi(struct tcp_sig* ts, u8* use_mtu, u16 syn_mss) { | ||
|  | 
 | ||
|  |   u16 win = ts->win; | ||
|  |   s32 mss = ts->mss, mss12 = mss - 12; | ||
|  | 
 | ||
|  |   if (!win || mss < 100 || ts->win_type != WIN_TYPE_NORMAL) | ||
|  |     return -1; | ||
|  | 
 | ||
|  | #define RET_IF_DIV(_div, _use_mtu, _desc) do { \
 | ||
|  |     if ((_div) && !(win % (_div))) { \ | ||
|  |       *use_mtu = (_use_mtu); \ | ||
|  |       DEBUG("[#] Window size %u is a multiple of %s [%u].\n", win, _desc, _div); \ | ||
|  |       return win / (_div); \ | ||
|  |     } \ | ||
|  |   } while (0) | ||
|  | 
 | ||
|  |   RET_IF_DIV(mss, 0, "MSS"); | ||
|  | 
 | ||
|  |   /* Some systems will sometimes subtract 12 bytes when timestamps are in use. */ | ||
|  | 
 | ||
|  |   if (ts->ts1) RET_IF_DIV(mss12, 0, "MSS - 12"); | ||
|  | 
 | ||
|  |   /* Some systems use MTU on the wrong interface, so let's check for the most
 | ||
|  |      common case. */ | ||
|  | 
 | ||
|  |   RET_IF_DIV(1500 - MIN_TCP4, 0, "MSS (MTU = 1500, IPv4)"); | ||
|  |   RET_IF_DIV(1500 - MIN_TCP4 - 12, 0, "MSS (MTU = 1500, IPv4 - 12)"); | ||
|  | 
 | ||
|  |   if (ts->ip_ver == IP_VER6) { | ||
|  | 
 | ||
|  |     RET_IF_DIV(1500 - MIN_TCP6, 0, "MSS (MTU = 1500, IPv6)"); | ||
|  |     RET_IF_DIV(1500 - MIN_TCP6 - 12, 0, "MSS (MTU = 1500, IPv6 - 12)"); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /* Some systems use MTU instead of MSS: */ | ||
|  | 
 | ||
|  |   RET_IF_DIV(mss + MIN_TCP4, 1, "MTU (IPv4)"); | ||
|  |   RET_IF_DIV(mss + ts->tot_hdr, 1, "MTU (actual size)"); | ||
|  |   if (ts->ip_ver == IP_VER6) RET_IF_DIV(mss + MIN_TCP6, 1, "MTU (IPv6)"); | ||
|  |   RET_IF_DIV(1500, 1, "MTU (1500)"); | ||
|  | 
 | ||
|  |   /* On SYN+ACKs, some systems use of the peer: */ | ||
|  | 
 | ||
|  |   if (syn_mss) { | ||
|  | 
 | ||
|  |     RET_IF_DIV(syn_mss, 0, "peer MSS"); | ||
|  |     RET_IF_DIV(syn_mss - 12, 0, "peer MSS - 12"); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  | #undef RET_IF_DIV
 | ||
|  | 
 | ||
|  |   return -1; | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | 
 | ||
|  | /* See if any of the p0f.fp signatures matches the collected data. */ | ||
|  | 
 | ||
|  | static void tcp_find_match(u8 to_srv, struct tcp_sig* ts, u8 dupe_det, | ||
|  |                            u16 syn_mss) { | ||
|  | 
 | ||
|  |   struct tcp_sig_record* fmatch = NULL; | ||
|  |   struct tcp_sig_record* gmatch = NULL; | ||
|  | 
 | ||
|  |   u32 bucket = ts->opt_hash % SIG_BUCKETS; | ||
|  |   u32 i; | ||
|  | 
 | ||
|  |   u8  use_mtu = 0; | ||
|  |   s16 win_multi = detect_win_multi(ts, &use_mtu, syn_mss); | ||
|  | 
 | ||
|  |   CP(sigs[to_srv][bucket]); | ||
|  | 
 | ||
|  |   for (i = 0; i < sig_cnt[to_srv][bucket]; i++) { | ||
|  | 
 | ||
|  |     struct tcp_sig_record* ref = sigs[to_srv][bucket] + i; | ||
|  |     struct tcp_sig* refs = CP(ref->sig); | ||
|  | 
 | ||
|  |     u8 fuzzy = 0; | ||
|  |     u32 ref_quirks = refs->quirks; | ||
|  | 
 | ||
|  |     if (ref->sig->opt_hash != ts->opt_hash) continue; | ||
|  | 
 | ||
|  |     /* If the p0f.fp signature has no IP version specified, we need
 | ||
|  |        to remove IPv6-specific quirks from it when matching IPv4 | ||
|  |        packets, and vice versa. */ | ||
|  | 
 | ||
|  |     if (refs->ip_ver == -1) | ||
|  |        ref_quirks &= ((ts->ip_ver == IP_VER4) ? ~(QUIRK_FLOW) : | ||
|  |         ~(QUIRK_DF | QUIRK_NZ_ID | QUIRK_ZERO_ID)); | ||
|  | 
 | ||
|  |     if (ref_quirks != ts->quirks) { | ||
|  | 
 | ||
|  |       u32 deleted = (ref_quirks ^ ts->quirks) & ref_quirks, | ||
|  |           added = (ref_quirks ^ ts->quirks) & ts->quirks; | ||
|  | 
 | ||
|  |       /* If there is a difference in quirks, but it amounts to 'df' or 'id+'
 | ||
|  |          disappearing, or 'id-' or 'ecn' appearing, allow a fuzzy match. */ | ||
|  | 
 | ||
|  |       if (fmatch || (deleted & ~(QUIRK_DF | QUIRK_NZ_ID)) || | ||
|  |           (added & ~(QUIRK_ZERO_ID | QUIRK_ECN))) continue; | ||
|  | 
 | ||
|  |       fuzzy = 1; | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |     /* Fixed parameters. */ | ||
|  | 
 | ||
|  |     if (refs->opt_eol_pad != ts->opt_eol_pad || | ||
|  |         refs->ip_opt_len != ts->ip_opt_len) continue; | ||
|  | 
 | ||
|  |     /* TTL matching, with a provision to allow fuzzy match. */ | ||
|  | 
 | ||
|  |     if (ref->bad_ttl) { | ||
|  | 
 | ||
|  |       if (refs->ttl < ts->ttl) continue; | ||
|  | 
 | ||
|  |     } else { | ||
|  | 
 | ||
|  |       if (refs->ttl < ts->ttl || refs->ttl - ts->ttl > MAX_DIST) fuzzy = 1; | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |     /* Simple wildcards. */ | ||
|  | 
 | ||
|  |     if (refs->mss != -1 && refs->mss != ts->mss) continue; | ||
|  |     if (refs->wscale != -1 && refs->wscale != ts->wscale) continue; | ||
|  |     if (refs->pay_class != -1 && refs->pay_class != ts->pay_class) continue; | ||
|  | 
 | ||
|  |     /* Window size. */ | ||
|  | 
 | ||
|  |     if (ts->win_type != WIN_TYPE_NORMAL) { | ||
|  | 
 | ||
|  |       /* Comparing two p0f.fp signatures. */ | ||
|  | 
 | ||
|  |       if (refs->win_type != ts->win_type || refs->win != ts->win) continue; | ||
|  | 
 | ||
|  |     } else { | ||
|  | 
 | ||
|  |       /* Comparing real-world stuff. */ | ||
|  | 
 | ||
|  |       switch (refs->win_type) { | ||
|  | 
 | ||
|  |         case WIN_TYPE_NORMAL: | ||
|  |        | ||
|  |           if (refs->win != ts->win) continue; | ||
|  |           break; | ||
|  | 
 | ||
|  |         case WIN_TYPE_MOD: | ||
|  |        | ||
|  |           if (ts->win % refs->win) continue; | ||
|  |           break; | ||
|  | 
 | ||
|  |         case WIN_TYPE_MSS: | ||
|  | 
 | ||
|  |           if (use_mtu || refs->win != win_multi) continue; | ||
|  |           break; | ||
|  | 
 | ||
|  |         case WIN_TYPE_MTU: | ||
|  | 
 | ||
|  |           if (!use_mtu || refs->win != win_multi) continue; | ||
|  |           break; | ||
|  | 
 | ||
|  |         /* WIN_TYPE_ANY */ | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |     /* Got a match? If not fuzzy, return. If fuzzy, keep looking. */ | ||
|  | 
 | ||
|  |     if (!fuzzy) { | ||
|  | 
 | ||
|  |       if (!ref->generic) { | ||
|  | 
 | ||
|  |         ts->matched = ref; | ||
|  |         ts->fuzzy   = 0; | ||
|  |         ts->dist    = refs->ttl - ts->ttl; | ||
|  |         return; | ||
|  | 
 | ||
|  |       } else if (!gmatch) gmatch = ref; | ||
|  | 
 | ||
|  |     } else if (!fmatch) fmatch = ref; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /* OK, no definitive match so far... */ | ||
|  | 
 | ||
|  |   if (dupe_det) return; | ||
|  | 
 | ||
|  |   /* If we found a generic signature, and nothing better, let's just use
 | ||
|  |      that. */ | ||
|  | 
 | ||
|  |   if (gmatch) { | ||
|  | 
 | ||
|  |     ts->matched = gmatch; | ||
|  |     ts->fuzzy   = 0; | ||
|  |     ts->dist    = gmatch->sig->ttl - ts->ttl; | ||
|  |     return; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /* No fuzzy matching for userland tools. */ | ||
|  | 
 | ||
|  |   if (fmatch && fmatch->class_id == -1) return; | ||
|  | 
 | ||
|  |   /* Let's try to guess distance if no match; or if match TTL out of
 | ||
|  |      range. */ | ||
|  | 
 | ||
|  |   if (!fmatch || fmatch->sig->ttl < ts->ttl || | ||
|  |        (!fmatch->bad_ttl && fmatch->sig->ttl - ts->ttl > MAX_DIST)) | ||
|  |     ts->dist = guess_dist(ts->ttl); | ||
|  |   else | ||
|  |     ts->dist = fmatch->sig->ttl - ts->ttl; | ||
|  | 
 | ||
|  |   /* Record the outcome. */ | ||
|  | 
 | ||
|  |   ts->matched = fmatch; | ||
|  | 
 | ||
|  |   if (fmatch) ts->fuzzy = 1; | ||
|  |    | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Parse TCP-specific bits and register a signature read from p0f.fp. This
 | ||
|  |    function is too long. */ | ||
|  | 
 | ||
|  | void tcp_register_sig(u8 to_srv, u8 generic, s32 sig_class, u32 sig_name, | ||
|  |                       u8* sig_flavor, u32 label_id, u32* sys, u32 sys_cnt, | ||
|  |                       u8* val, u32 line_no) { | ||
|  | 
 | ||
|  |   s8  ver, win_type, pay_class; | ||
|  |   u8  opt_layout[MAX_TCP_OPT]; | ||
|  |   u8  opt_cnt = 0, bad_ttl = 0; | ||
|  | 
 | ||
|  |   s32 ittl, olen, mss, win, scale, opt_eol_pad = 0; | ||
|  |   u32 quirks = 0, bucket, opt_hash; | ||
|  | 
 | ||
|  |   u8* nxt; | ||
|  | 
 | ||
|  |   struct tcp_sig* tsig; | ||
|  |   struct tcp_sig_record* trec; | ||
|  | 
 | ||
|  |   /* IP version */ | ||
|  | 
 | ||
|  |   switch (*val) { | ||
|  |     case '4': ver = IP_VER4; break; | ||
|  |     case '6': ver = IP_VER6; break; | ||
|  |     case '*': ver = -1; break; | ||
|  |     default: FATAL("Unrecognized IP version in line %u.", line_no); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (val[1] != ':') FATAL("Malformed signature in line %u.", line_no); | ||
|  | 
 | ||
|  |   val += 2; | ||
|  | 
 | ||
|  |   /* Initial TTL (possibly ttl+dist or ttl-) */ | ||
|  | 
 | ||
|  |   nxt = val; | ||
|  |   while (isdigit(*nxt)) nxt++; | ||
|  | 
 | ||
|  |   if (*nxt != ':' && *nxt != '+' && *nxt != '-') | ||
|  |     FATAL("Malformed signature in line %u.", line_no); | ||
|  | 
 | ||
|  |   ittl = atol((char*)val); | ||
|  |   if (ittl < 1 || ittl > 255) FATAL("Bogus initial TTL in line %u.", line_no); | ||
|  |   val = nxt + 1; | ||
|  | 
 | ||
|  |   if (*nxt == '-' && nxt[1] == ':') { | ||
|  | 
 | ||
|  |     bad_ttl = 1; | ||
|  |     val += 2; | ||
|  | 
 | ||
|  |   } else if (*nxt == '+') { | ||
|  | 
 | ||
|  |     s32 ittl_add; | ||
|  | 
 | ||
|  |     nxt++; | ||
|  |     while (isdigit(*nxt)) nxt++; | ||
|  |     if (*nxt != ':') FATAL("Malformed signature in line %u.", line_no); | ||
|  | 
 | ||
|  |     ittl_add = atol((char*)val); | ||
|  | 
 | ||
|  |     if (ittl_add < 0 || ittl + ittl_add > 255) | ||
|  |       FATAL("Bogus initial TTL in line %u.", line_no); | ||
|  | 
 | ||
|  |     ittl += ittl_add; | ||
|  |     val = nxt + 1; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /* Length of IP options */ | ||
|  | 
 | ||
|  |   nxt = val; | ||
|  |   while (isdigit(*nxt)) nxt++; | ||
|  |   if (*nxt != ':') FATAL("Malformed signature in line %u.", line_no); | ||
|  | 
 | ||
|  |   olen = atol((char*)val); | ||
|  |   if (olen < 0 || olen > 255) | ||
|  |     FATAL("Bogus IP option length in line %u.", line_no); | ||
|  | 
 | ||
|  |   val = nxt + 1; | ||
|  | 
 | ||
|  |   /* MSS */ | ||
|  | 
 | ||
|  |   if (*val == '*' && val[1] == ':') { | ||
|  | 
 | ||
|  |     mss = -1; | ||
|  |     val += 2; | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     nxt = val; | ||
|  |     while (isdigit(*nxt)) nxt++; | ||
|  |     if (*nxt != ':') FATAL("Malformed signature in line %u.", line_no); | ||
|  | 
 | ||
|  |     mss = atol((char*)val); | ||
|  |     if (mss < 0 || mss > 65535) FATAL("Bogus MSS in line %u.", line_no); | ||
|  |     val = nxt + 1; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /* window size, followed by comma */ | ||
|  | 
 | ||
|  |   if (*val == '*' && val[1] == ',') { | ||
|  | 
 | ||
|  |     win_type = WIN_TYPE_ANY; | ||
|  |     win = 0; | ||
|  |     val += 2; | ||
|  | 
 | ||
|  |   } else if (*val == '%') { | ||
|  | 
 | ||
|  |     win_type = WIN_TYPE_MOD; | ||
|  | 
 | ||
|  |     val++; | ||
|  | 
 | ||
|  |     nxt = val; | ||
|  |     while (isdigit(*nxt)) nxt++; | ||
|  |     if (*nxt != ',') FATAL("Malformed signature in line %u.", line_no); | ||
|  | 
 | ||
|  |     win = atol((char*)val); | ||
|  |     if (win < 2 || win > 65535) FATAL("Bogus '%%' value in line %u.", line_no); | ||
|  |     val = nxt + 1; | ||
|  | 
 | ||
|  |   } else if (!strncmp((char*)val, "mss*", 4) || | ||
|  |              !strncmp((char*)val, "mtu*", 4)) { | ||
|  | 
 | ||
|  |     win_type = (val[1] == 's') ? WIN_TYPE_MSS : WIN_TYPE_MTU; | ||
|  | 
 | ||
|  |     val += 4; | ||
|  | 
 | ||
|  |     nxt = val; | ||
|  |     while (isdigit(*nxt)) nxt++; | ||
|  |     if (*nxt != ',') FATAL("Malformed signature in line %u.", line_no); | ||
|  | 
 | ||
|  |     win = atol((char*)val); | ||
|  |     if (win < 1 || win > 1000) | ||
|  |       FATAL("Bogus MSS/MTU multiplier in line %u.", line_no); | ||
|  | 
 | ||
|  |     val = nxt + 1; | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     win_type = WIN_TYPE_NORMAL; | ||
|  | 
 | ||
|  |     nxt = val; | ||
|  |     while (isdigit(*nxt)) nxt++; | ||
|  |     if (*nxt != ',') FATAL("Malformed signature in line %u.", line_no); | ||
|  | 
 | ||
|  |     win = atol((char*)val); | ||
|  |     if (win < 0 || win > 65535) FATAL("Bogus window size in line %u.", line_no); | ||
|  |     val = nxt + 1; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /* Window scale */ | ||
|  | 
 | ||
|  |   if (*val == '*' && val[1] == ':') { | ||
|  | 
 | ||
|  |     scale = -1; | ||
|  |     val += 2; | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     nxt = val; | ||
|  |     while (isdigit(*nxt)) nxt++; | ||
|  |     if (*nxt != ':') FATAL("Malformed signature in line %u.", line_no); | ||
|  | 
 | ||
|  |     scale = atol((char*)val); | ||
|  |     if (scale < 0 || scale > 255) | ||
|  |       FATAL("Bogus window scale in line %u.", line_no); | ||
|  | 
 | ||
|  |     val = nxt + 1; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /* Option layout */ | ||
|  | 
 | ||
|  |   memset(opt_layout, 0, sizeof(opt_layout));   | ||
|  | 
 | ||
|  |   while (*val != ':') { | ||
|  | 
 | ||
|  |     if (opt_cnt >= MAX_TCP_OPT) | ||
|  |       FATAL("Too many TCP options in line %u.", line_no); | ||
|  | 
 | ||
|  |     if (!strncmp((char*)val, "eol", 3)) { | ||
|  | 
 | ||
|  |       opt_layout[opt_cnt++] = TCPOPT_EOL; | ||
|  |       val += 3; | ||
|  | 
 | ||
|  |       if (*val != '+') | ||
|  |         FATAL("Malformed EOL option in line %u.", line_no); | ||
|  | 
 | ||
|  |       val++; | ||
|  |       nxt = val; | ||
|  |       while (isdigit(*nxt)) nxt++; | ||
|  | 
 | ||
|  |       if (!*nxt) FATAL("Truncated options in line %u.", line_no); | ||
|  | 
 | ||
|  |       if (*nxt != ':') | ||
|  |         FATAL("EOL must be the last option in line %u.", line_no); | ||
|  | 
 | ||
|  |       opt_eol_pad = atol((char*)val); | ||
|  | 
 | ||
|  |       if (opt_eol_pad < 0 || opt_eol_pad > 255) | ||
|  |         FATAL("Bogus EOL padding in line %u.", line_no); | ||
|  | 
 | ||
|  |       val = nxt; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "nop", 3)) { | ||
|  | 
 | ||
|  |       opt_layout[opt_cnt++] = TCPOPT_NOP; | ||
|  |       val += 3; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "mss", 3)) { | ||
|  | 
 | ||
|  |       opt_layout[opt_cnt++] = TCPOPT_MAXSEG; | ||
|  |       val += 3; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "ws", 2)) { | ||
|  | 
 | ||
|  |       opt_layout[opt_cnt++] = TCPOPT_WSCALE; | ||
|  |       val += 2; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "sok", 3)) { | ||
|  | 
 | ||
|  |       opt_layout[opt_cnt++] = TCPOPT_SACKOK; | ||
|  |       val += 3; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "sack", 4)) { | ||
|  | 
 | ||
|  |       opt_layout[opt_cnt++] = TCPOPT_SACK; | ||
|  |       val += 4; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "ts", 2)) { | ||
|  | 
 | ||
|  |       opt_layout[opt_cnt++] = TCPOPT_TSTAMP; | ||
|  |       val += 2; | ||
|  | 
 | ||
|  |     } else if (*val == '?') { | ||
|  | 
 | ||
|  |       s32 optno; | ||
|  | 
 | ||
|  |       val++; | ||
|  |       nxt = val; | ||
|  |       while (isdigit(*nxt)) nxt++; | ||
|  | 
 | ||
|  |       if (*nxt != ':' && *nxt != ',') | ||
|  |         FATAL("Malformed '?' option in line %u.", line_no); | ||
|  | 
 | ||
|  |       optno = atol((char*)val); | ||
|  | 
 | ||
|  |       if (optno < 0 || optno > 255) | ||
|  |           FATAL("Bogus '?' option in line %u.", line_no); | ||
|  | 
 | ||
|  |       opt_layout[opt_cnt++] = optno; | ||
|  | 
 | ||
|  |       val = nxt; | ||
|  | 
 | ||
|  |     } else { | ||
|  | 
 | ||
|  |       FATAL("Unrecognized TCP option in line %u.", line_no); | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |     if (*val == ':') break; | ||
|  | 
 | ||
|  |     if (*val != ',') | ||
|  |       FATAL("Malformed TCP options in line %u.", line_no); | ||
|  | 
 | ||
|  |     val++; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   val++; | ||
|  | 
 | ||
|  |   opt_hash = hash32(opt_layout, opt_cnt, hash_seed); | ||
|  | 
 | ||
|  |   /* Quirks */ | ||
|  | 
 | ||
|  |   while (*val != ':') { | ||
|  | 
 | ||
|  |     if (!strncmp((char*)val, "df", 2)) { | ||
|  | 
 | ||
|  |       if (ver == IP_VER6) | ||
|  |         FATAL("'df' is not valid for IPv6 in line %d.", line_no); | ||
|  | 
 | ||
|  |       quirks |= QUIRK_DF; | ||
|  |       val += 2; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "id+", 3)) { | ||
|  | 
 | ||
|  |       if (ver == IP_VER6) | ||
|  |         FATAL("'id+' is not valid for IPv6 in line %d.", line_no); | ||
|  | 
 | ||
|  |       quirks |= QUIRK_NZ_ID; | ||
|  |       val += 3; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "id-", 3)) { | ||
|  | 
 | ||
|  |       if (ver == IP_VER6) | ||
|  |         FATAL("'id-' is not valid for IPv6 in line %d.", line_no); | ||
|  | 
 | ||
|  |       quirks |= QUIRK_ZERO_ID; | ||
|  |       val += 3; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "ecn", 3)) { | ||
|  | 
 | ||
|  |       quirks |= QUIRK_ECN; | ||
|  |       val += 3; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "0+", 2)) { | ||
|  | 
 | ||
|  |       if (ver == IP_VER6) | ||
|  |         FATAL("'0+' is not valid for IPv6 in line %d.", line_no); | ||
|  | 
 | ||
|  |       quirks |= QUIRK_NZ_MBZ; | ||
|  |       val += 2; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "flow", 4)) { | ||
|  | 
 | ||
|  |       if (ver == IP_VER4) | ||
|  |         FATAL("'flow' is not valid for IPv4 in line %d.", line_no); | ||
|  | 
 | ||
|  |       quirks |= QUIRK_FLOW; | ||
|  |       val += 4; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "seq-", 4)) { | ||
|  | 
 | ||
|  |       quirks |= QUIRK_ZERO_SEQ; | ||
|  |       val += 4; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "ack+", 4)) { | ||
|  | 
 | ||
|  |       quirks |= QUIRK_NZ_ACK; | ||
|  |       val += 4; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "ack-", 4)) { | ||
|  | 
 | ||
|  |       quirks |= QUIRK_ZERO_ACK; | ||
|  |       val += 4; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "uptr+", 5)) { | ||
|  | 
 | ||
|  |       quirks |= QUIRK_NZ_URG; | ||
|  |       val += 5; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "urgf+", 5)) { | ||
|  | 
 | ||
|  |       quirks |= QUIRK_URG; | ||
|  |       val += 5; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "pushf+", 6)) { | ||
|  | 
 | ||
|  |       quirks |= QUIRK_PUSH; | ||
|  |       val += 6; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "ts1-", 4)) { | ||
|  | 
 | ||
|  |       quirks |= QUIRK_OPT_ZERO_TS1; | ||
|  |       val += 4; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "ts2+", 4)) { | ||
|  | 
 | ||
|  |       quirks |= QUIRK_OPT_NZ_TS2; | ||
|  |       val += 4; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "opt+", 4)) { | ||
|  | 
 | ||
|  |       quirks |= QUIRK_OPT_EOL_NZ; | ||
|  |       val += 4; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "exws", 4)) { | ||
|  | 
 | ||
|  |       quirks |= QUIRK_OPT_EXWS; | ||
|  |       val += 4; | ||
|  | 
 | ||
|  |     } else if (!strncmp((char*)val, "bad", 3)) { | ||
|  | 
 | ||
|  |       quirks |= QUIRK_OPT_BAD; | ||
|  |       val += 3; | ||
|  | 
 | ||
|  |     } else { | ||
|  | 
 | ||
|  |       FATAL("Unrecognized quirk in line %u.", line_no); | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |     if (*val == ':') break; | ||
|  | 
 | ||
|  |     if (*val != ',') | ||
|  |       FATAL("Malformed quirks in line %u.", line_no); | ||
|  | 
 | ||
|  |     val++; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   val++; | ||
|  | 
 | ||
|  |   /* Payload class */ | ||
|  | 
 | ||
|  |   if (!strcmp((char*)val, "*")) pay_class = -1; | ||
|  |   else if (!strcmp((char*)val, "0")) pay_class = 0; | ||
|  |   else if (!strcmp((char*)val, "+")) pay_class = 1; | ||
|  |   else FATAL("Malformed payload class in line %u.", line_no); | ||
|  | 
 | ||
|  |   /* Phew, okay, we're done. Now, create tcp_sig... */ | ||
|  | 
 | ||
|  |   tsig = DFL_ck_alloc(sizeof(struct tcp_sig)); | ||
|  | 
 | ||
|  |   tsig->opt_hash    = opt_hash; | ||
|  |   tsig->opt_eol_pad = opt_eol_pad; | ||
|  | 
 | ||
|  |   tsig->quirks      = quirks; | ||
|  | 
 | ||
|  |   tsig->ip_opt_len  = olen; | ||
|  |   tsig->ip_ver      = ver; | ||
|  |   tsig->ttl         = ittl; | ||
|  | 
 | ||
|  |   tsig->mss         = mss; | ||
|  |   tsig->win         = win; | ||
|  |   tsig->win_type    = win_type; | ||
|  |   tsig->wscale      = scale; | ||
|  |   tsig->pay_class   = pay_class; | ||
|  | 
 | ||
|  |   /* No need to set ts1, recv_ms, match, fuzzy, dist */ | ||
|  | 
 | ||
|  |   tcp_find_match(to_srv, tsig, 1, 0); | ||
|  | 
 | ||
|  |   if (tsig->matched) | ||
|  |     FATAL("Signature in line %u is already covered by line %u.", | ||
|  |           line_no, tsig->matched->line_no); | ||
|  | 
 | ||
|  |   /* Everything checks out, so let's register it. */ | ||
|  | 
 | ||
|  |   bucket = opt_hash % SIG_BUCKETS; | ||
|  | 
 | ||
|  |   sigs[to_srv][bucket] = DFL_ck_realloc(sigs[to_srv][bucket], | ||
|  |     (sig_cnt[to_srv][bucket] + 1) * sizeof(struct tcp_sig_record)); | ||
|  | 
 | ||
|  |   trec = sigs[to_srv][bucket] + sig_cnt[to_srv][bucket]; | ||
|  | 
 | ||
|  |   sig_cnt[to_srv][bucket]++; | ||
|  | 
 | ||
|  |   trec->generic  = generic; | ||
|  |   trec->class_id = sig_class; | ||
|  |   trec->name_id  = sig_name; | ||
|  |   trec->flavor   = sig_flavor; | ||
|  |   trec->label_id = label_id; | ||
|  |   trec->sys      = sys; | ||
|  |   trec->sys_cnt  = sys_cnt; | ||
|  |   trec->line_no  = line_no; | ||
|  |   trec->sig      = tsig; | ||
|  |   trec->bad_ttl  = bad_ttl; | ||
|  | 
 | ||
|  |   /* All done, phew. */ | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Convert struct packet_data to a simplified struct tcp_sig representation
 | ||
|  |    suitable for signature matching. Compute hashes. */ | ||
|  | 
 | ||
|  | static void packet_to_sig(struct packet_data* pk, struct tcp_sig* ts) { | ||
|  | 
 | ||
|  |   ts->opt_hash = hash32(pk->opt_layout, pk->opt_cnt, hash_seed); | ||
|  | 
 | ||
|  |   ts->quirks      = pk->quirks; | ||
|  |   ts->opt_eol_pad = pk->opt_eol_pad; | ||
|  |   ts->ip_opt_len  = pk->ip_opt_len; | ||
|  |   ts->ip_ver      = pk->ip_ver; | ||
|  |   ts->ttl         = pk->ttl; | ||
|  |   ts->mss         = pk->mss; | ||
|  |   ts->win         = pk->win; | ||
|  |   ts->win_type    = WIN_TYPE_NORMAL; /* Keep as-is. */ | ||
|  |   ts->wscale      = pk->wscale; | ||
|  |   ts->pay_class   = !!pk->pay_len; | ||
|  |   ts->tot_hdr     = pk->tot_hdr; | ||
|  |   ts->ts1         = pk->ts1; | ||
|  |   ts->recv_ms     = get_unix_time_ms(); | ||
|  |   ts->matched     = NULL; | ||
|  |   ts->fuzzy       = 0; | ||
|  |   ts->dist        = 0; | ||
|  | 
 | ||
|  | }; | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Dump unknown signature. */ | ||
|  | 
 | ||
|  | static u8* dump_sig(struct packet_data* pk, struct tcp_sig* ts, u16 syn_mss) { | ||
|  | 
 | ||
|  |   static u8* ret; | ||
|  |   u32 rlen = 0; | ||
|  | 
 | ||
|  |   u8  win_mtu; | ||
|  |   s16 win_m; | ||
|  |   u32 i; | ||
|  |   u8  dist = guess_dist(pk->ttl); | ||
|  | 
 | ||
|  | #define RETF(_par...) do { \
 | ||
|  |     s32 _len = snprintf(NULL, 0, _par); \ | ||
|  |     if (_len < 0) FATAL("Whoa, snprintf() fails?!"); \ | ||
|  |     ret = DFL_ck_realloc_kb(ret, rlen + _len + 1); \ | ||
|  |     snprintf((char*)ret + rlen, _len + 1, _par); \ | ||
|  |     rlen += _len; \ | ||
|  |   } while (0) | ||
|  | 
 | ||
|  |   if (dist > MAX_DIST) { | ||
|  | 
 | ||
|  |     RETF("%u:%u+?:%u:", pk->ip_ver, pk->ttl, pk->ip_opt_len); | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     RETF("%u:%u+%u:%u:", pk->ip_ver, pk->ttl, dist, pk->ip_opt_len); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /* Detect a system echoing back MSS from p0f-sendsyn queries, suggest using
 | ||
|  |      a wildcard in such a case. */ | ||
|  | 
 | ||
|  |   if (pk->mss == SPECIAL_MSS && pk->tcp_type == (TCP_SYN|TCP_ACK)) RETF("*:"); | ||
|  |   else RETF("%u:", pk->mss); | ||
|  | 
 | ||
|  |   win_m = detect_win_multi(ts, &win_mtu, syn_mss); | ||
|  | 
 | ||
|  |   if (win_m > 0) RETF("%s*%u", win_mtu ? "mtu" : "mss", win_m); | ||
|  |   else RETF("%u", pk->win); | ||
|  | 
 | ||
|  |   RETF(",%u:", pk->wscale); | ||
|  | 
 | ||
|  |   for (i = 0; i < pk->opt_cnt; i++) { | ||
|  | 
 | ||
|  |     switch (pk->opt_layout[i]) { | ||
|  | 
 | ||
|  |       case TCPOPT_EOL: | ||
|  |         RETF("%seol+%u", i ? "," : "", pk->opt_eol_pad); break; | ||
|  | 
 | ||
|  |       case TCPOPT_NOP: | ||
|  |         RETF("%snop", i ? "," : ""); break; | ||
|  | 
 | ||
|  |       case TCPOPT_MAXSEG: | ||
|  |         RETF("%smss", i ? "," : ""); break; | ||
|  | 
 | ||
|  |       case TCPOPT_WSCALE: | ||
|  |         RETF("%sws", i ? "," : ""); break; | ||
|  | 
 | ||
|  |       case TCPOPT_SACKOK: | ||
|  |         RETF("%ssok", i ? "," : ""); break; | ||
|  | 
 | ||
|  |       case TCPOPT_SACK: | ||
|  |         RETF("%ssack", i ? "," : ""); break; | ||
|  | 
 | ||
|  |       case TCPOPT_TSTAMP: | ||
|  |         RETF("%sts", i ? "," : ""); break; | ||
|  | 
 | ||
|  |       default: | ||
|  |         RETF("%s?%u", i ? "," : "", pk->opt_layout[i]); | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   RETF(":"); | ||
|  | 
 | ||
|  |   if (pk->quirks) { | ||
|  | 
 | ||
|  |     u8 sp = 0; | ||
|  | 
 | ||
|  | #define MAYBE_CM(_str) do { \
 | ||
|  |     if (sp) RETF("," _str); else RETF(_str); \ | ||
|  |     sp = 1; \ | ||
|  |   } while (0) | ||
|  | 
 | ||
|  |     if (pk->quirks & QUIRK_DF)      MAYBE_CM("df"); | ||
|  |     if (pk->quirks & QUIRK_NZ_ID)   MAYBE_CM("id+"); | ||
|  |     if (pk->quirks & QUIRK_ZERO_ID) MAYBE_CM("id-"); | ||
|  |     if (pk->quirks & QUIRK_ECN)     MAYBE_CM("ecn"); | ||
|  |     if (pk->quirks & QUIRK_NZ_MBZ)  MAYBE_CM("0+"); | ||
|  |     if (pk->quirks & QUIRK_FLOW)    MAYBE_CM("flow"); | ||
|  | 
 | ||
|  |     if (pk->quirks & QUIRK_ZERO_SEQ) MAYBE_CM("seq-"); | ||
|  |     if (pk->quirks & QUIRK_NZ_ACK)   MAYBE_CM("ack+"); | ||
|  |     if (pk->quirks & QUIRK_ZERO_ACK) MAYBE_CM("ack-"); | ||
|  |     if (pk->quirks & QUIRK_NZ_URG)   MAYBE_CM("uptr+"); | ||
|  |     if (pk->quirks & QUIRK_URG)      MAYBE_CM("urgf+"); | ||
|  |     if (pk->quirks & QUIRK_PUSH)     MAYBE_CM("pushf+"); | ||
|  | 
 | ||
|  |     if (pk->quirks & QUIRK_OPT_ZERO_TS1) MAYBE_CM("ts1-"); | ||
|  |     if (pk->quirks & QUIRK_OPT_NZ_TS2)   MAYBE_CM("ts2+"); | ||
|  |     if (pk->quirks & QUIRK_OPT_EOL_NZ)   MAYBE_CM("opt+"); | ||
|  |     if (pk->quirks & QUIRK_OPT_EXWS)     MAYBE_CM("exws"); | ||
|  |     if (pk->quirks & QUIRK_OPT_BAD)      MAYBE_CM("bad"); | ||
|  | 
 | ||
|  | #undef MAYBE_CM
 | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   if (pk->pay_len) RETF(":+"); else RETF(":0"); | ||
|  | 
 | ||
|  |   return ret; | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Dump signature-related flags. */ | ||
|  | 
 | ||
|  | static u8* dump_flags(struct packet_data* pk, struct tcp_sig* ts) { | ||
|  | 
 | ||
|  |   static u8* ret; | ||
|  |   u32 rlen = 0; | ||
|  | 
 | ||
|  |   RETF(""); | ||
|  | 
 | ||
|  |   if (ts->matched) { | ||
|  | 
 | ||
|  |     if (ts->matched->generic) RETF(" generic"); | ||
|  |     if (ts->fuzzy) RETF(" fuzzy"); | ||
|  |     if (ts->matched->bad_ttl) RETF(" random_ttl"); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   if (ts->dist > MAX_DIST) RETF(" excess_dist"); | ||
|  |   if (pk->tos) RETF(" tos:0x%02x", pk->tos); | ||
|  | 
 | ||
|  |   if (*ret) return ret + 1; else return (u8*)"none"; | ||
|  | 
 | ||
|  | #undef RETF
 | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Compare current signature with historical data, draw conclusions. This
 | ||
|  |    is called only for OS sigs. */ | ||
|  | 
 | ||
|  | static void score_nat(u8 to_srv, struct tcp_sig* sig, struct packet_flow* f) { | ||
|  | 
 | ||
|  |   struct host_data* hd; | ||
|  |   struct tcp_sig* ref; | ||
|  |   u8  score = 0, diff_already = 0; | ||
|  |   u16 reason = 0; | ||
|  |   s32 ttl_diff; | ||
|  | 
 | ||
|  |   if (to_srv) {  | ||
|  | 
 | ||
|  |     hd = f->client; | ||
|  |     ref = hd->last_syn; | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     hd = f->server; | ||
|  |     ref = hd->last_synack; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  | 
 | ||
|  |   if (!ref) { | ||
|  | 
 | ||
|  |     /* No previous signature of matching type at all. We can perhaps still check
 | ||
|  |        if class / name is the same as on file, as that data might have been | ||
|  |        obtained from other types of sigs. */ | ||
|  | 
 | ||
|  |     if (sig->matched && hd->last_class_id != -1) { | ||
|  | 
 | ||
|  |       if (hd->last_name_id != sig->matched->name_id) { | ||
|  | 
 | ||
|  |         DEBUG("[#] New TCP signature different OS type than host data.\n"); | ||
|  | 
 | ||
|  |         reason |= NAT_OS_SIG; | ||
|  |         score  += 8; | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |     goto log_and_update; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /* We have some previous data. */ | ||
|  | 
 | ||
|  |   if (!sig->matched || !ref->matched) { | ||
|  | 
 | ||
|  |     /* One or both of the signatures are unknown. Let's see if they differ.
 | ||
|  |        The scoring here isn't too strong, because we don't know if the  | ||
|  |        unrecognized signature isn't originating from userland tools. */ | ||
|  | 
 | ||
|  |     if ((sig->quirks ^ ref->quirks) & ~(QUIRK_ECN|QUIRK_DF|QUIRK_NZ_ID| | ||
|  |         QUIRK_ZERO_ID)) { | ||
|  | 
 | ||
|  |       DEBUG("[#] Non-fuzzy quirks changed compared to previous sig.\n"); | ||
|  | 
 | ||
|  |       reason |= NAT_UNK_DIFF; | ||
|  |       score  += 2; | ||
|  | 
 | ||
|  |     } else if (to_srv && sig->opt_hash != ref->opt_hash) { | ||
|  | 
 | ||
|  |       /* We only match option layout for SYNs; it may change on SYN+ACK,
 | ||
|  |          and the user may have gaps in SYN+ACK sigs if he ignored our | ||
|  |          advice on using p0f-sendsyn. */ | ||
|  | 
 | ||
|  |       DEBUG("[#] SYN option layout changed compared to previous sig.\n"); | ||
|  | 
 | ||
|  |       reason |= NAT_UNK_DIFF; | ||
|  |       score  += 1; | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |     /* Progression from known to unknown is also of interest for SYNs. */ | ||
|  | 
 | ||
|  |     if (to_srv && sig->matched != ref->matched) { | ||
|  | 
 | ||
|  |       DEBUG("[#] SYN signature changed from %s.\n", | ||
|  |             sig->matched ? "unknown to known" : "known to unknown"); | ||
|  | 
 | ||
|  |       score += 1; | ||
|  |       reason |= NAT_TO_UNK; | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     /* Both signatures known! */ | ||
|  | 
 | ||
|  |     if (ref->matched->name_id != sig->matched->name_id) { | ||
|  | 
 | ||
|  |       DEBUG("[#] TCP signature different OS type on previous sig.\n"); | ||
|  |       score += 8; | ||
|  |       reason |= NAT_OS_SIG; | ||
|  | 
 | ||
|  |       diff_already = 1; | ||
|  | 
 | ||
|  |     } else if (to_srv) { | ||
|  | 
 | ||
|  |       /* SYN signatures match superficially, but... */ | ||
|  | 
 | ||
|  |       if (ref->matched->label_id != sig->matched->label_id) { | ||
|  | 
 | ||
|  |         /* SYN label changes are a weak but useful signal. SYN+ACK signatures
 | ||
|  |            may need less intuitive groupings, so we don't check that. */ | ||
|  | 
 | ||
|  |         DEBUG("[#] SYN signature label different on previous sig.\n"); | ||
|  |         score += 2; | ||
|  |         reason |= NAT_OS_SIG; | ||
|  | 
 | ||
|  |       } else if (ref->matched->line_no != sig->matched->line_no) { | ||
|  | 
 | ||
|  |         /* Change in line number is an extremely weak but still noteworthy
 | ||
|  |            signal. */ | ||
|  | 
 | ||
|  |         DEBUG("[#] SYN signature changes within the same label.\n"); | ||
|  |         score += 1; | ||
|  |         reason |= NAT_OS_SIG; | ||
|  | 
 | ||
|  |       } else if (sig->fuzzy != ref->fuzzy) { | ||
|  | 
 | ||
|  |         /* Fuzziness change on a perfectly matched signature? */ | ||
|  | 
 | ||
|  |         DEBUG("[#] SYN signature fuzziness changes.\n"); | ||
|  |         score += 1; | ||
|  |         reason |= NAT_FUZZY; | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /* Unless the signatures are already known to differ radically, mismatch
 | ||
|  |      between host data and current sig is of additional note. */ | ||
|  | 
 | ||
|  |   if (!diff_already && sig->matched && hd->last_class_id != -1 && | ||
|  |       hd->last_name_id != sig->matched->name_id) { | ||
|  | 
 | ||
|  |     DEBUG("[#] New OS signature different OS type than host data.\n"); | ||
|  |     score += 8; | ||
|  |     reason |= NAT_OS_SIG; | ||
|  |     diff_already = 1; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /* TTL differences in absence of major signature mismatches is also
 | ||
|  |      interesting, unless the signatures are tagged as "bad TTL", or unless | ||
|  |      the difference is barely 1 and the host is distant. */ | ||
|  | 
 | ||
|  | #define ABS(_x) ((_x) < 0 ? -(_x) : (_x))
 | ||
|  | 
 | ||
|  |   ttl_diff = ((s16)sig->ttl) - ref->ttl; | ||
|  | 
 | ||
|  |   if (!diff_already && ttl_diff && (!sig->matched || !sig->matched->bad_ttl) && | ||
|  |       (!ref->matched || !ref->matched->bad_ttl) && (sig->dist <= NEAR_TTL_LIMIT || | ||
|  |        ttl_diff > 1)) { | ||
|  | 
 | ||
|  |     DEBUG("[#] Signature TTL differs by %d (dist = %u).\n", ttl_diff, sig->dist); | ||
|  | 
 | ||
|  |     if (sig->dist > LOCAL_TTL_LIMIT && ABS(ttl_diff) <= SMALL_TTL_CHG) | ||
|  |       score += 1; else score += 4; | ||
|  | 
 | ||
|  |     reason |= NAT_TTL; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /* Source port going back frequently is of some note, although it will happen
 | ||
|  |      spontaneously every now and then. Require the drop to be by at least | ||
|  |      few dozen, to account for simple case of several simultaneously opened | ||
|  |      connections arriving in odd order. */ | ||
|  | 
 | ||
|  |   if (to_srv && hd->last_port && f->cli_port < hd->last_port && | ||
|  |       hd->last_port - f->cli_port >= MIN_PORT_DROP) { | ||
|  | 
 | ||
|  |     DEBUG("[#] Source port drops from %u to %u.\n", hd->last_port, f->cli_port); | ||
|  | 
 | ||
|  |     score += 1; | ||
|  |     reason |= NAT_PORT; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /* Change of MTU is always sketchy. */ | ||
|  | 
 | ||
|  |   if (sig->mss != ref->mss) { | ||
|  | 
 | ||
|  |     DEBUG("[#] MSS for signature changed from %u to %u.\n", ref->mss, sig->mss); | ||
|  | 
 | ||
|  |     score += 1; | ||
|  |     reason |= NAT_MSS; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   /* Check timestamp progression to possibly adjust current score. Don't rate
 | ||
|  |      on TS alone, because some systems may be just randomizing that. */ | ||
|  | 
 | ||
|  |   if (score && sig->ts1 && ref->ts1) { | ||
|  |   | ||
|  |     u64 ms_diff = sig->recv_ms - ref->recv_ms; | ||
|  | 
 | ||
|  |     /* Require a timestamp within the last day; if the apparent TS progression
 | ||
|  |        is much higher than 1 kHz, complain. */ | ||
|  | 
 | ||
|  |     if (ms_diff < MAX_NAT_TS) { | ||
|  | 
 | ||
|  |       u64 use_ms  = (ms_diff < TSTAMP_GRACE) ? TSTAMP_GRACE : ms_diff; | ||
|  |       u64 max_ts  = use_ms * MAX_TSCALE / 1000; | ||
|  | 
 | ||
|  |       u32 ts_diff  = sig->ts1 - ref->ts1; | ||
|  | 
 | ||
|  |       if (ts_diff > max_ts && (ms_diff >= TSTAMP_GRACE || ~ts_diff > max_ts)) { | ||
|  | 
 | ||
|  |         DEBUG("[#] Dodgy timestamp progression across signatures (%d " | ||
|  |               "in %llu ms).\n", ts_diff, ms_diff); | ||
|  |         score += 4; | ||
|  |         reason |= NAT_TS; | ||
|  | 
 | ||
|  |       } else { | ||
|  | 
 | ||
|  |         DEBUG("[#] Timestamp consistent across signatures (%d in %llu ms), "  | ||
|  |               "reducing score.\n", ts_diff, ms_diff); | ||
|  |         score /= 2; | ||
|  | 
 | ||
|  |       } | ||
|  | 
 | ||
|  |     } else DEBUG("[#] Timestamps available, but with bad interval (%llu ms).\n", | ||
|  |                  ms_diff); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  | log_and_update: | ||
|  | 
 | ||
|  |   add_nat_score(to_srv, f, reason, score); | ||
|  | 
 | ||
|  |   /* Update some of the essential records. */ | ||
|  | 
 | ||
|  |   if (sig->matched) { | ||
|  | 
 | ||
|  |     hd->last_class_id = sig->matched->class_id; | ||
|  |     hd->last_name_id  = sig->matched->name_id; | ||
|  |     hd->last_flavor   = sig->matched->flavor; | ||
|  | 
 | ||
|  |     hd->last_quality  = (sig->fuzzy * P0F_MATCH_FUZZY) | | ||
|  |       (sig->matched->generic * P0F_MATCH_GENERIC); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   hd->last_port = f->cli_port; | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Fingerprint SYN or SYN+ACK. */ | ||
|  | 
 | ||
|  | struct tcp_sig* fingerprint_tcp(u8 to_srv, struct packet_data* pk, | ||
|  |                                 struct packet_flow* f) { | ||
|  | 
 | ||
|  |   struct tcp_sig* sig; | ||
|  |   struct tcp_sig_record* m; | ||
|  | 
 | ||
|  |   sig = ck_alloc(sizeof(struct tcp_sig)); | ||
|  |   packet_to_sig(pk, sig); | ||
|  | 
 | ||
|  |   /* Detect packets generated by p0f-sendsyn; they require special
 | ||
|  |      handling to provide the user with response fingerprints, but not | ||
|  |      interfere with NAT scores and such. */ | ||
|  | 
 | ||
|  |   if (pk->tcp_type == TCP_SYN && pk->win == SPECIAL_WIN && | ||
|  |       pk->mss == SPECIAL_MSS) f->sendsyn = 1; | ||
|  | 
 | ||
|  |   if (to_srv)  | ||
|  |     start_observation(f->sendsyn ? "sendsyn probe" : "syn", 4, 1, f); | ||
|  |   else | ||
|  |     start_observation(f->sendsyn ? "sendsyn response" : "syn+ack", 4, 0, f); | ||
|  | 
 | ||
|  |   tcp_find_match(to_srv, sig, 0, f->syn_mss); | ||
|  | 
 | ||
|  |   if ((m = sig->matched)) { | ||
|  | 
 | ||
|  |     OBSERVF((m->class_id == -1 || f->sendsyn) ? "app" : "os", "%s%s%s", | ||
|  |             fp_os_names[m->name_id], m->flavor ? " " : "", | ||
|  |             m->flavor ? m->flavor : (u8*)""); | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     add_observation_field("os", NULL); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   if (m && m->bad_ttl) { | ||
|  | 
 | ||
|  |     OBSERVF("dist", "<= %u", sig->dist); | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     if (to_srv) f->client->distance = sig->dist; | ||
|  |     else f->server->distance = sig->dist; | ||
|  |      | ||
|  |     OBSERVF("dist", "%u", sig->dist); | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   add_observation_field("params", dump_flags(pk, sig)); | ||
|  | 
 | ||
|  |   add_observation_field("raw_sig", dump_sig(pk, sig, f->syn_mss)); | ||
|  | 
 | ||
|  |   if (pk->tcp_type == TCP_SYN) f->syn_mss = pk->mss; | ||
|  | 
 | ||
|  |   /* That's about as far as we go with non-OS signatures. */ | ||
|  | 
 | ||
|  |   if (m && m->class_id == -1) { | ||
|  |     verify_tool_class(to_srv, f, m->sys, m->sys_cnt); | ||
|  |     ck_free(sig); | ||
|  |     return NULL; | ||
|  |   } | ||
|  | 
 | ||
|  |   if (f->sendsyn) { | ||
|  |     ck_free(sig); | ||
|  |     return NULL; | ||
|  |   } | ||
|  | 
 | ||
|  |   score_nat(to_srv, sig, f); | ||
|  | 
 | ||
|  |   return sig; | ||
|  | 
 | ||
|  | } | ||
|  | 
 | ||
|  | 
 | ||
|  | /* Perform uptime detection. This is the only FP function that gets called not
 | ||
|  |    only on SYN or SYN+ACK, but also on ACK traffic. */ | ||
|  | 
 | ||
|  | void check_ts_tcp(u8 to_srv, struct packet_data* pk, struct packet_flow* f) { | ||
|  | 
 | ||
|  |   u32    ts_diff; | ||
|  |   u64    ms_diff; | ||
|  | 
 | ||
|  |   u32    freq; | ||
|  |   u32    up_min, up_mod_days; | ||
|  | 
 | ||
|  |   double ffreq; | ||
|  | 
 | ||
|  |   if (!pk->ts1 || f->sendsyn) return; | ||
|  | 
 | ||
|  |   /* If we're getting SYNs very rapidly, last_syn may be changing too quickly
 | ||
|  |      to be of any use. Perhaps lock into an older value? */ | ||
|  | 
 | ||
|  |   if (to_srv) { | ||
|  | 
 | ||
|  |      if (f->cli_tps || !f->client->last_syn || !f->client->last_syn->ts1) | ||
|  |        return; | ||
|  | 
 | ||
|  |      ms_diff = get_unix_time_ms() - f->client->last_syn->recv_ms; | ||
|  |      ts_diff = pk->ts1 - f->client->last_syn->ts1; | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |      if (f->srv_tps || !f->server->last_synack || !f->server->last_synack->ts1) | ||
|  |         return; | ||
|  | 
 | ||
|  |      ms_diff = get_unix_time_ms() - f->server->last_synack->recv_ms; | ||
|  |      ts_diff = pk->ts1 - f->server->last_synack->ts1; | ||
|  |    | ||
|  |   } | ||
|  | 
 | ||
|  |   /* Wait at least 25 ms, and not more than 10 minutes, for at least 5
 | ||
|  |      timestamp ticks. Allow the timestamp to go back slightly within | ||
|  |      a short window, too - we may be receiving packets a bit out of | ||
|  |      order. */ | ||
|  | 
 | ||
|  |   if (ms_diff < MIN_TWAIT || ms_diff > MAX_TWAIT) return; | ||
|  | 
 | ||
|  |   if (ts_diff < 5 || (ms_diff < TSTAMP_GRACE && (~ts_diff) / 1000 <  | ||
|  |       MAX_TSCALE / TSTAMP_GRACE)) return; | ||
|  | 
 | ||
|  |   if (ts_diff > ~ts_diff) ffreq = ~ts_diff * -1000.0 / ms_diff; | ||
|  |   else ffreq = ts_diff * 1000.0 / ms_diff; | ||
|  | 
 | ||
|  |   if (ffreq < MIN_TSCALE || ffreq > MAX_TSCALE) { | ||
|  | 
 | ||
|  |     /* Allow bad reading on SYN, as this may be just an artifact of IP
 | ||
|  |        sharing or OS change. */ | ||
|  | 
 | ||
|  |     if (pk->tcp_type != TCP_SYN) { | ||
|  | 
 | ||
|  |       if (to_srv) f->cli_tps = -1; else f->srv_tps = -1; | ||
|  | 
 | ||
|  |     } | ||
|  | 
 | ||
|  |     DEBUG("[#] Bad %s TS frequency: %.02f Hz (%d ticks in %llu ms).\n", | ||
|  |           to_srv ? "client" : "server", ffreq, ts_diff, ms_diff); | ||
|  | 
 | ||
|  |     return; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   freq = ffreq; | ||
|  | 
 | ||
|  |   /* Round the frequency neatly. */ | ||
|  | 
 | ||
|  |   switch (freq) { | ||
|  | 
 | ||
|  |     case 0:           freq = 1; break; | ||
|  |     case 1 ... 10:    break; | ||
|  |     case 11 ... 50:   freq = (freq + 3) / 5 * 5; break; | ||
|  |     case 51 ... 100:  freq = (freq + 7) / 10 * 10; break; | ||
|  |     case 101 ... 500: freq = (freq + 33) / 50 * 50; break; | ||
|  |     default:          freq = (freq + 67) / 100 * 100; break; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   if (to_srv) f->cli_tps = freq; else f->srv_tps = freq; | ||
|  | 
 | ||
|  |   up_min = pk->ts1 / freq / 60; | ||
|  |   up_mod_days = 0xFFFFFFFF / (freq * 60 * 60 * 24); | ||
|  | 
 | ||
|  |   start_observation("uptime", 2, to_srv, f); | ||
|  | 
 | ||
|  |   if (to_srv) { | ||
|  | 
 | ||
|  |     f->client->last_up_min = up_min; | ||
|  |     f->client->up_mod_days = up_mod_days; | ||
|  | 
 | ||
|  |   } else { | ||
|  | 
 | ||
|  |     f->server->last_up_min = up_min; | ||
|  |     f->server->up_mod_days = up_mod_days; | ||
|  | 
 | ||
|  |   } | ||
|  | 
 | ||
|  |   OBSERVF("uptime", "%u days %u hrs %u min (modulo %u days)", | ||
|  |           (up_min / 60 / 24), (up_min / 60) % 24, up_min % 60, | ||
|  |           up_mod_days); | ||
|  | 
 | ||
|  |   OBSERVF("raw_freq", "%.02f Hz", ffreq); | ||
|  | 
 | ||
|  | } |