/* * ipt_osf.c * * Copyright (c) 2003-2006 Evgeniy Polyakov * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ipt_osf.h" #define OSF_DEBUG #ifdef OSF_DEBUG #define log(x...) printk(KERN_INFO "ipt_osf: " x) #define loga(x...) printk(x) #else #define log(x...) do {} while(0) #define loga(x...) do {} while(0) #endif #define FMATCH_WRONG 0 #define FMATCH_OK 1 #define FMATCH_OPT_WRONG 2 #define OPTDEL ',' #define OSFPDEL ':' #define MAXOPTSTRLEN 128 #define OSFFLUSH "FLUSH" static DEFINE_SPINLOCK(ipt_osf_lock); static LIST_HEAD(ipt_finger_list); #ifdef CONFIG_CONNECTOR #include /* * They should live in connector.h. */ #define CN_IDX_OSF 0x0008 #define CN_VAL_OSF 0x0000 static char osf_finger_buf[sizeof(struct ipt_osf_nlmsg) + sizeof(struct cn_msg)]; static struct cb_id osf_id = { CN_IDX_OSF, CN_VAL_OSF }; static u32 osf_seq; static void ipt_osf_send_connector(struct ipt_osf_finger *f, const struct sk_buff *sk) { struct cn_msg *m; struct ipt_osf_nlmsg *data; m = (struct cn_msg *)osf_finger_buf; data = (struct ipt_osf_nlmsg *)(m + 1); memcpy(&m->id, &osf_id, sizeof(m->id)); m->seq = osf_seq++; m->ack = 0; m->len = sizeof(*data); memcpy(&data->f, f, sizeof(struct ipt_osf_finger)); memcpy(&data->ip, sk->nh.iph, sizeof(struct iphdr)); memcpy(&data->tcp, (struct tcphdr *)((u32 *)sk->nh.iph + sk->nh.iph->ihl), sizeof(struct tcphdr)); cn_netlink_send(m, m->id.idx, GFP_ATOMIC); } #else static void ipt_osf_send_connector(struct ipt_osf_finger *f, const struct sk_buff *sk) { } #endif static char *ipt_osf_strchr(char *ptr, char c) { char *tmp; tmp = strchr(ptr, c); while (tmp && tmp + 1 && isspace(*(tmp + 1))) tmp++; return tmp; } static struct ipt_osf_finger *ipt_osf_finger_alloc(void) { return kzalloc(sizeof(struct ipt_osf_finger), GFP_KERNEL); } static void ipt_osf_finger_free(struct ipt_osf_finger *f) { memset(f, 0, sizeof(struct ipt_osf_finger)); kfree(f); } static void ipt_osf_parse_opt(struct ipt_osf_opt *opt, int *optnum, char *obuf, int olen) { int i, op; char *ptr, wc; unsigned long val; ptr = &obuf[0]; i = 0; while (ptr != NULL && i < olen) { val = 0; op = 0; wc = 0; switch (obuf[i]) { case 'N': op = OSFOPT_NOP; ptr = ipt_osf_strchr(&obuf[i], OPTDEL); if (ptr) { *ptr = '\0'; ptr++; i += (int)(ptr - &obuf[i]); } else i++; break; case 'S': op = OSFOPT_SACKP; ptr = ipt_osf_strchr(&obuf[i], OPTDEL); if (ptr) { *ptr = '\0'; ptr++; i += (int)(ptr - &obuf[i]); } else i++; break; case 'T': op = OSFOPT_TS; ptr = ipt_osf_strchr(&obuf[i], OPTDEL); if (ptr) { *ptr = '\0'; ptr++; i += (int)(ptr - &obuf[i]); } else i++; break; case 'W': op = OSFOPT_WSO; ptr = ipt_osf_strchr(&obuf[i], OPTDEL); if (ptr) { switch (obuf[i + 1]) { case '%': wc = '%'; break; case 'S': wc = 'S'; break; case 'T': wc = 'T'; break; default: wc = 0; break; } *ptr = '\0'; ptr++; if (wc) val = simple_strtoul(&obuf[i + 2], NULL, 10); else val = simple_strtoul(&obuf[i + 1], NULL, 10); i += (int)(ptr - &obuf[i]); } else i++; break; case 'M': op = OSFOPT_MSS; ptr = ipt_osf_strchr(&obuf[i], OPTDEL); if (ptr) { if (obuf[i + 1] == '%') wc = '%'; *ptr = '\0'; ptr++; if (wc) val = simple_strtoul(&obuf[i + 2], NULL, 10); else val = simple_strtoul(&obuf[i + 1], NULL, 10); i += (int)(ptr - &obuf[i]); } else i++; break; case 'E': op = OSFOPT_EOL; ptr = ipt_osf_strchr(&obuf[i], OPTDEL); if (ptr) { *ptr = '\0'; ptr++; i += (int)(ptr - &obuf[i]); } else i++; break; default: op = OSFOPT_EMPTY; ptr = ipt_osf_strchr(&obuf[i], OPTDEL); if (ptr) { ptr++; i += (int)(ptr - &obuf[i]); } else i++; break; } if (op != OSFOPT_EMPTY) { opt[*optnum].kind = IANA_opts[op].kind; opt[*optnum].length = IANA_opts[op].length; opt[*optnum].wc.wc = wc; opt[*optnum].wc.val = val; (*optnum)++; } } } static int ipt_osf_proc_read(char *buf, char **start, off_t off, int count, int *eof, void *data) { struct ipt_osf_finger *f = NULL; int i, __count, err; *eof = 1; __count = count; count = 0; rcu_read_lock(); list_for_each_entry(f, &ipt_finger_list, flist) { log("%s [%s]", f->genre, f->details); err = snprintf(buf + count, __count - count, "%s - %s[%s] : %s", f->genre, f->version, f->subtype, f->details); if (err == 0 || __count <= count + err) break; else count += err; if (f->opt_num) { loga(" OPT: "); /* count += sprintf(buf+count, " OPT: "); */ for (i = 0; i < f->opt_num; ++i) { /* count += sprintf(buf+count, "%d.%c%u; ", f->opt[i].kind, (f->opt[i].wc.wc)?f->opt[i].wc.wc:' ', f->opt[i].wc.val); */ loga("%d.%c%u; ", f->opt[i].kind, (f->opt[i].wc.wc) ? f->opt[i].wc.wc : ' ', f->opt[i].wc.val); } } loga("\n"); err = snprintf(buf + count, __count - count, "\n"); if (err == 0 || __count <= count + err) break; else count += err; } rcu_read_unlock(); return count; } static int ipt_osf_proc_write(struct file *file, const char *buffer, unsigned long count, void *data) { int cnt, i; char obuf[MAXOPTSTRLEN]; struct ipt_osf_finger *finger, *n; char *pbeg, *pend; if (count > 0xffff) return -E2BIG; if (count == strlen(OSFFLUSH) && !strncmp(buffer, OSFFLUSH, strlen(OSFFLUSH))) { int i = 0; synchronize_rcu(); spin_lock_bh(&ipt_osf_lock); list_for_each_entry_safe(finger, n, &ipt_finger_list, flist) { i++; list_del_rcu(&finger->flist); ipt_osf_finger_free(finger); } spin_unlock_bh(&ipt_osf_lock); log("Flushed %d entries.\n", i); return count; } cnt = 0; for (i = 0; i < count && buffer[i] != '\0'; ++i) if (buffer[i] == ':') cnt++; if (cnt != 8 || i != count) { log("Wrong input line cnt=%d[8], len=%u[%lu]\n", cnt, i, count); return count; } memset(obuf, 0, sizeof(obuf)); finger = ipt_osf_finger_alloc(); if (!finger) { log("Failed to allocate new fingerprint entry.\n"); return -ENOMEM; } pbeg = (char *)buffer; pend = ipt_osf_strchr(pbeg, OSFPDEL); if (pend) { *pend = '\0'; if (pbeg[0] == 'S') { finger->wss.wc = 'S'; if (pbeg[1] == '%') finger->wss.val = simple_strtoul(&pbeg[2], NULL, 10); else if (pbeg[1] == '*') finger->wss.val = 0; else finger->wss.val = simple_strtoul(&pbeg[1], NULL, 10); } else if (pbeg[0] == 'T') { finger->wss.wc = 'T'; if (pbeg[1] == '%') finger->wss.val = simple_strtoul(&pbeg[2], NULL, 10); else if (pbeg[1] == '*') finger->wss.val = 0; else finger->wss.val = simple_strtoul(&pbeg[1], NULL, 10); } else if (pbeg[0] == '%') { finger->wss.wc = '%'; finger->wss.val = simple_strtoul(&pbeg[1], NULL, 10); } else if (isdigit(pbeg[0])) { finger->wss.wc = 0; finger->wss.val = simple_strtoul(&pbeg[0], NULL, 10); } pbeg = pend + 1; } pend = ipt_osf_strchr(pbeg, OSFPDEL); if (pend) { *pend = '\0'; finger->ttl = simple_strtoul(pbeg, NULL, 10); pbeg = pend + 1; } pend = ipt_osf_strchr(pbeg, OSFPDEL); if (pend) { *pend = '\0'; finger->df = simple_strtoul(pbeg, NULL, 10); pbeg = pend + 1; } pend = ipt_osf_strchr(pbeg, OSFPDEL); if (pend) { *pend = '\0'; finger->ss = simple_strtoul(pbeg, NULL, 10); pbeg = pend + 1; } pend = ipt_osf_strchr(pbeg, OSFPDEL); if (pend) { *pend = '\0'; cnt = snprintf(obuf, sizeof(obuf), "%s", pbeg); pbeg = pend + 1; } pend = ipt_osf_strchr(pbeg, OSFPDEL); if (pend) { *pend = '\0'; if (pbeg[0] == '@' || pbeg[0] == '*') cnt = snprintf(finger->genre, sizeof(finger->genre), "%s", pbeg + 1); else cnt = snprintf(finger->genre, sizeof(finger->genre), "%s", pbeg); pbeg = pend + 1; } pend = ipt_osf_strchr(pbeg, OSFPDEL); if (pend) { *pend = '\0'; cnt = snprintf(finger->version, sizeof(finger->version), "%s", pbeg); pbeg = pend + 1; } pend = ipt_osf_strchr(pbeg, OSFPDEL); if (pend) { *pend = '\0'; cnt = snprintf(finger->subtype, sizeof(finger->subtype), "%s", pbeg); pbeg = pend + 1; } cnt = snprintf(finger->details, ((count - (pbeg - buffer) + 1) > MAXDETLEN) ? MAXDETLEN : (count - (pbeg - buffer) + 1), "%s", pbeg); log("%s - %s[%s] : %s\n", finger->genre, finger->version, finger->subtype, finger->details); ipt_osf_parse_opt(finger->opt, &finger->opt_num, obuf, sizeof(obuf)); synchronize_rcu(); spin_lock_bh(&ipt_osf_lock); list_add_tail_rcu(&finger->flist, &ipt_finger_list); spin_unlock_bh(&ipt_osf_lock); return count; } static inline int ipt_osf_ttl(const struct sk_buff *skb, struct ipt_osf_info *info, unsigned char f_ttl) { struct iphdr *ip = ip_hdr(skb); #if 0 log("f_ttl: %u, ip_ttl: %u, info->ttl: %u, flags_ttl: %u.\n", f_ttl, ip->ttl, info->ttl, info->flags & IPT_OSF_TTL); #endif if (info->flags & IPT_OSF_TTL) { if (info->ttl == IPT_OSF_TTL_TRUE) return (ip->ttl == f_ttl); if (info->ttl == IPT_OSF_TTL_NOCHECK) return 1; else { struct in_device *in_dev = in_dev_get(skb->dev); for_ifa(in_dev) { if (inet_ifa_match(ip->saddr, ifa)) { in_dev_put(in_dev); return (ip->ttl == f_ttl); } } endfor_ifa(in_dev); in_dev_put(in_dev); return (ip->ttl <= f_ttl); } } return (ip->ttl == f_ttl); } static bool ipt_osf_match_packet(const struct sk_buff *skb, const struct net_device *in, const struct net_device *out, const struct xt_match *match, const void *matchinfo, int offset, unsigned int unused, bool *hotdrop) { struct ipt_osf_info *info = (struct ipt_osf_info *)matchinfo; struct iphdr _iph, *ip; struct tcphdr _tcph, *tcp; int fmatch = FMATCH_WRONG, fcount = 0; unsigned int optsize = 0, check_WSS = 0; u16 window, totlen, mss = 0; unsigned char df, *optp = NULL, *_optp = NULL; unsigned char opts[MAX_IPOPTLEN]; struct ipt_osf_finger *f; if (!info) return 0; ip = skb_header_pointer(skb, 0, sizeof(struct iphdr), &_iph); if (!ip) return 0; tcp = skb_header_pointer(skb, ip->ihl * 4, sizeof(struct tcphdr), &_tcph); if (!tcp) return 0; if (!tcp->syn) return 0; totlen = ntohs(ip->tot_len); df = ((ntohs(ip->frag_off) & IP_DF) ? 1 : 0); window = ntohs(tcp->window); if (tcp->doff * 4 > sizeof(struct tcphdr)) { optsize = tcp->doff * 4 - sizeof(struct tcphdr); if (optsize > sizeof(opts)) { log("%s: BUG: too big options size: optsize=%u, max=%zu.\n", __func__, optsize, sizeof(opts)); optsize = sizeof(opts); } _optp = optp = skb_header_pointer(skb, ip->ihl * 4 + sizeof(struct tcphdr), optsize, opts); } rcu_read_lock(); list_for_each_entry_rcu(f, &ipt_finger_list, flist) { if (!(info->flags & IPT_OSF_LOG) && strcmp(info->genre, f->genre)) continue; optp = _optp; fmatch = FMATCH_WRONG; if (totlen == f->ss && df == f->df && ipt_osf_ttl(skb, info, f->ttl)) { int foptsize, optnum; check_WSS = 0; switch (f->wss.wc) { case 0: check_WSS = 0; break; case 'S': check_WSS = 1; break; case 'T': check_WSS = 2; break; case '%': check_WSS = 3; break; default: log("Wrong fingerprint wss.wc=%d, %s - %s\n", f->wss.wc, f->genre, f->details); check_WSS = 4; break; } if (check_WSS == 4) continue; /* Check options */ foptsize = 0; for (optnum = 0; optnum < f->opt_num; ++optnum) foptsize += f->opt[optnum].length; #if 0 log("%s.%s.%s: optsize: %u, foptsize: %u, fopt_num: %u, optp: %p, win: %u, mss: %u, totlen: %u, df: %d, ttl: %u.\n", f->genre, f->version, f->subtype, optsize, foptsize, f->opt_num, optp, window, mss, totlen, df, ip->ttl); #endif if (foptsize > MAX_IPOPTLEN || optsize > MAX_IPOPTLEN || optsize != foptsize) continue; for (optnum = 0; optnum < f->opt_num; ++optnum) { if (f->opt[optnum].kind == (*optp)) { __u32 len = f->opt[optnum].length; __u8 *optend = optp + len; int loop_cont = 0; fmatch = FMATCH_OK; switch (*optp) { case OSFOPT_MSS: mss = ntohs(*(u16 *)(optp + 2)); break; case OSFOPT_TS: loop_cont = 1; break; } #if 0 if (loop_cont) { optp = optend; continue; } if (len != 1) { /* Skip kind and length fields */ optp += 2; if (f->opt[optnum].wc.val != 0) { u32 tmp = 0; u32 copy = len > 4 ? 4 : len; /* Hmmm... It looks a bit ugly. :) */ memcpy(&tmp, optp, copy); /* 2 + 2: optlen(2 bytes) + * kind(1 byte) + length(1 byte) */ if (len == 4) tmp = ntohs(tmp); else tmp = ntohl(tmp); if (f->opt[optnum].wc.wc == '%') { if ((tmp % f->opt[optnum].wc.val) != 0) fmatch = FMATCH_OPT_WRONG; } else if (tmp != f->opt[optnum].wc.val) fmatch = FMATCH_OPT_WRONG; } } #endif optp = optend; } else fmatch = FMATCH_OPT_WRONG; if (fmatch != FMATCH_OK) break; } if (fmatch != FMATCH_OPT_WRONG) { fmatch = FMATCH_WRONG; switch (check_WSS) { case 0: if (f->wss.val == 0 || window == f->wss.val) fmatch = FMATCH_OK; break; case 1: /* MSS */ #define SMART_MSS_1 1460 #define SMART_MSS_2 1448 if (window == f->wss.val * mss || window == f->wss.val * SMART_MSS_1 || window == f->wss.val * SMART_MSS_2) fmatch = FMATCH_OK; break; case 2: /* MTU */ if (window == f->wss.val * (mss + 40) || window == f->wss.val * (SMART_MSS_1 + 40) || window == f->wss.val * (SMART_MSS_2 + 40)) fmatch = FMATCH_OK; break; case 3: /* MOD */ if ((window % f->wss.val) == 0) fmatch = FMATCH_OK; break; } } if (fmatch == FMATCH_OK) { fcount++; log("%s [%s:%s:%s] : %u.%u.%u.%u:%u -> %u.%u.%u.%u:%u hops=%d\n", f->genre, f->version, f->subtype, f->details, NIPQUAD(ip->saddr), ntohs(tcp->source), NIPQUAD(ip->daddr), ntohs(tcp->dest), f->ttl - ip->ttl); if (info->flags & IPT_OSF_CONNECTOR) ipt_osf_send_connector(f, skb); if ((info->flags & IPT_OSF_LOG) && info->loglevel == IPT_OSF_LOGLEVEL_FIRST) break; } } } rcu_read_unlock(); if (!fcount && (info->flags & (IPT_OSF_LOG | IPT_OSF_NETLINK | IPT_OSF_CONNECTOR))) { unsigned char opt[4 * 15 - sizeof(struct tcphdr)]; unsigned int i, optsize; struct ipt_osf_finger fg; memset(&fg, 0, sizeof(fg)); #if 1 if ((info->flags & IPT_OSF_LOG) && (info->loglevel != IPT_OSF_LOGLEVEL_ALL_KNOWN)) log("Unknown: win: %u, mss: %u, totlen: %u, df: %d, ttl: %u : ", window, mss, totlen, df, ip->ttl); if (optp) { optsize = tcp->doff * 4 - sizeof(struct tcphdr); if (skb_copy_bits(skb, ip->ihl * 4 + sizeof(struct tcphdr), opt, optsize) < 0) { if (info->flags & IPT_OSF_LOG) loga("TRUNCATED"); if (info->flags & IPT_OSF_NETLINK) strcpy(fg.details, "TRUNCATED"); } else { for (i = 0; i < optsize; i++) { if (info->flags & IPT_OSF_LOG) loga("%02X ", opt[i]); } if (info->flags & IPT_OSF_NETLINK) memcpy(fg.details, opt, min_t(unsigned int, optsize, MAXDETLEN)); } } if ((info->flags & IPT_OSF_LOG) && (info->loglevel != IPT_OSF_LOGLEVEL_ALL_KNOWN)) loga(" %u.%u.%u.%u:%u -> %u.%u.%u.%u:%u\n", NIPQUAD(ip->saddr), ntohs(tcp->source), NIPQUAD(ip->daddr), ntohs(tcp->dest)); #endif if (info->flags & (IPT_OSF_NETLINK | IPT_OSF_CONNECTOR)) { fg.wss.val = window; fg.ttl = ip->ttl; fg.df = df; fg.ss = totlen; fg.mss = mss; strncpy(fg.genre, "Unknown", MAXGENRELEN); if (info->flags & IPT_OSF_CONNECTOR) ipt_osf_send_connector(&fg, skb); } } if (fcount) fmatch = FMATCH_OK; return (fmatch == FMATCH_OK) ? 1 : 0; } static bool ipt_osf_checkentry(const char *tablename, const void *data, const struct xt_match *match, void *matchinfo, unsigned int hook_mask) { struct ipt_ip *ip = (struct ipt_ip *)data; if (ip->proto != IPPROTO_TCP) return false; return true; } static struct xt_match ipt_osf_match = { .name = "osf", .revision = 0, .family = AF_INET, .hooks = (1 << NF_INET_LOCAL_IN) | (1 << NF_INET_PRE_ROUTING), .match = ipt_osf_match_packet, .checkentry = ipt_osf_checkentry, .matchsize = sizeof(struct ipt_osf_info), .me = THIS_MODULE, }; static int __devinit ipt_osf_init(void) { int err = -EINVAL; struct proc_dir_entry *p; log("Startng OS fingerprint matching module.\n"); p = create_proc_entry("osf", S_IFREG | 0644, proc_net_netfilter); if (!p) goto err_out_exit; p->write_proc = ipt_osf_proc_write; p->read_proc = ipt_osf_proc_read; err = xt_register_match(&ipt_osf_match); if (err) { log("Failed to register OS fingerprint matching module.\n"); goto err_out_remove; } return 0; err_out_remove: remove_proc_entry("osf", proc_net_netfilter); err_out_exit: return err; } static void __devexit ipt_osf_fini(void) { struct ipt_osf_finger *f, *n; remove_proc_entry("osf", proc_net_netfilter); xt_unregister_match(&ipt_osf_match); list_for_each_entry_safe(f, n, &ipt_finger_list, flist) { list_del(&f->flist); ipt_osf_finger_free(f); } log("OS fingerprint matching module finished.\n"); } module_init(ipt_osf_init); module_exit(ipt_osf_fini); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Evgeniy Polyakov "); MODULE_DESCRIPTION("Passive OS fingerprint matching.");