Hi. Passive OS fingerprinting iptables (xtables) allows to match incoming packets by different sets of SYN-packet and determine, which remote system is on the remote end, so you can make decisions based on OS type and even version at some degreee and perform various netfilter actions based on that knowledge. This module compares some data (WS, MSS, options and it's order, ttl, df and others) from packets with SYN bit set with dynamically loaded OS fingerprints. This version existed quite for a while in patch-o-matic(-ng), but suddenly was dropped and then only was updated on its own repo: http://tservice.net.ru/~s0mbre/old/?section=projects&item=osf I've updated OSF to match new iptables standards (namely xtables support) and present new kernelspace and userspace library files in attach. To setup single rule, which will drop and log all Linux incoming access one needs to do following steps: # insmod ./ipt_osf.ko # ./load ./pf.os /proc/sys/net/ipv4/osf # iptables -I INPUT -j DROP -p tcp -m osf --genre Linux --log 2 \ --ttl 2 --connector And you will find following lines in dmesg: ipt_osf: Linux [2.5-2.6::Linux 2.5/2.6] : aa:aa:aa:aa:32885 -> bb:bb:bb:bb:23 hops=3 More info can be found on homepage: http://tservice.net.ru/~s0mbre/old/?section=projects&item=osf Enjoy! Signed-off-by: Evgeniy Polyakov <johnpol@xxxxxxxxxxx> -- Evgeniy Polyakov
/* * ipt_osf.c * * Copyright (c) 2003-2006 Evgeniy Polyakov <johnpol@xxxxxxxxxxx> * * * 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 <linux/module.h> #include <linux/kernel.h> #include <linux/types.h> #include <linux/string.h> #include <linux/smp.h> #include <linux/skbuff.h> #include <linux/file.h> #include <linux/ip.h> #include <linux/proc_fs.h> #include <linux/fs.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/ctype.h> #include <linux/list.h> #include <linux/if.h> #include <linux/inetdevice.h> #include <net/ip.h> #include <linux/tcp.h> #include <linux/netfilter/x_tables.h> #include <linux/netfilter_ipv4/ip_tables.h> #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 <linux/connector.h> /* * 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 <johnpol@xxxxxxxxxxx>"); MODULE_DESCRIPTION("Passive OS fingerprint matching.");
/* * ipt_osf.h * * Copyright (c) 2003 Evgeniy Polyakov <johnpol@xxxxxxxxxxx> * * * 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 */ #ifndef _IPT_OSF_H #define _IPT_OSF_H #define MAXGENRELEN 32 #define MAXDETLEN 64 #define IPT_OSF_GENRE 1 #define IPT_OSF_TTL 2 #define IPT_OSF_LOG 4 #define IPT_OSF_NETLINK 8 #define IPT_OSF_CONNECTOR 16 #define IPT_OSF_INVERT 32 #define IPT_OSF_LOGLEVEL_ALL 0 #define IPT_OSF_LOGLEVEL_FIRST 1 #define IPT_OSF_LOGLEVEL_ALL_KNOWN 2 #define IPT_OSF_TTL_TRUE 0 /* True ip and fingerprint TTL comparison */ #define IPT_OSF_TTL_LESS 1 /* Check if ip TTL is less than fingerprint one */ #define IPT_OSF_TTL_NOCHECK 2 /* Do not compare ip and fingerprint TTL at all */ #ifndef __KERNEL__ #include <netinet/ip.h> #include <netinet/tcp.h> struct list_head { struct list_head *prev, *next; }; #endif struct ipt_osf_info { char genre[MAXGENRELEN]; __u32 len, flags, loglevel, ttl; } __attribute__ ((packed)); struct ipt_osf_wc { __u8 wc; __u32 val; } __attribute__ ((packed)); /* This struct represents IANA options * http://www.iana.org/assignments/tcp-parameters */ struct ipt_osf_opt { __u8 kind, length; struct ipt_osf_wc wc; } __attribute__ ((packed)); struct ipt_osf_finger { struct list_head flist; struct ipt_osf_wc wss; __u8 ttl, df; __u16 ss, mss; unsigned char genre[MAXGENRELEN]; unsigned char version[MAXGENRELEN], subtype[MAXGENRELEN]; /* Not needed, but for consistency with original table from Michal Zalewski */ unsigned char details[MAXDETLEN]; int opt_num; struct ipt_osf_opt opt[MAX_IPOPTLEN]; /* In case it is all NOP or EOL */ } __attribute__ ((packed)); struct ipt_osf_nlmsg { struct ipt_osf_finger f; struct iphdr ip; struct tcphdr tcp; } __attribute__ ((packed)); #ifdef __KERNEL__ #include <linux/list.h> #include <net/tcp.h> /* Defines for IANA option kinds */ #define OSFOPT_EOL 0 /* End of options */ #define OSFOPT_NOP 1 /* NOP */ #define OSFOPT_MSS 2 /* Maximum segment size */ #define OSFOPT_WSO 3 /* Window scale option */ #define OSFOPT_SACKP 4 /* SACK permitted */ #define OSFOPT_SACK 5 /* SACK */ #define OSFOPT_ECHO 6 #define OSFOPT_ECHOREPLY 7 #define OSFOPT_TS 8 /* Timestamp option */ #define OSFOPT_POCP 9 /* Partial Order Connection Permitted */ #define OSFOPT_POSP 10 /* Partial Order Service Profile */ #define OSFOPT_EMPTY 255 /* Others are not used in current OSF */ static struct ipt_osf_opt IANA_opts[] = { {0, 1,}, {1, 1,}, {2, 4,}, {3, 3,}, {4, 2,}, {5, 1,}, /* SACK length is not defined */ {6, 6,}, {7, 6,}, {8, 10,}, {9, 2,}, {10, 3,}, {11, 1,}, /* CC: Suppose 1 */ {12, 1,}, /* the same */ {13, 1,}, /* and here too */ {14, 3,}, {15, 1,}, /* TCP Alternate Checksum Data. Length is not defined */ {16, 1,}, {17, 1,}, {18, 3,}, {19, 18,}, {20, 1,}, {21, 1,}, {22, 1,}, {23, 1,}, {24, 1,}, {25, 1,}, {26, 1,}, }; #endif /* __KERNEL__ */ #endif /* _IPT_OSF_H */
/* * libipt_osf.c * * Copyright (c) 2003-2006 Evgeniy Polyakov <johnpol@xxxxxxxxxxx> * * * 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 */ /* * iptables interface for OS fingerprint matching module. */ #include <stdio.h> #include <netdb.h> #include <string.h> #include <stdlib.h> #include <getopt.h> #include <ctype.h> #include <iptables.h> typedef unsigned int __u32; typedef unsigned short __u16; typedef unsigned char __u8; #include "ipt_osf.h" static void osf_help(void) { printf("OS fingerprint match options:\n" "--genre [!] string Match a OS genre by passive fingerprinting.\n" "--ttl Use some TTL check extensions to determine OS:\n" " 0 true ip and fingerprint TTL comparison. Works for LAN.\n" " 1 check if ip TTL is less than fingerprint one. Works for global addresses.\n" " 2 do not compare TTL at all. Allows to detect NMAP, but can produce false results.\n" "--log level Log determined genres into dmesg even if they do not match desired one:\n" " 0 log all matched or unknown signatures.\n" " 1 log only first one.\n" " 2 log all known matched signatures.\n" "--connector Log through kernel connector [2.6.14+].\n\n" ); } static const struct option osf_opts[] = { { .name = "genre", .has_arg = 1, .flag = 0, .val = '1' }, { .name = "ttl", .has_arg = 1, .flag = 0, .val = '2' }, { .name = "log", .has_arg = 1, .flag = 0, .val = '3' }, { .name = "netlink", .has_arg = 0, .flag = 0, .val = '4' }, { .name = "connector", .has_arg = 0, .flag = 0, .val = '5' }, { .name = NULL } }; static void osf_init(struct xt_entry_match *m) { } static void osf_parse_string(const unsigned char *s, struct ipt_osf_info *info) { if (strlen(s) < MAXGENRELEN) strcpy(info->genre, s); else exit_error(PARAMETER_PROBLEM, "Genre string too long `%s' [%d], max=%d", s, strlen(s), MAXGENRELEN); } static int osf_parse(int c, char **argv, int invert, unsigned int *flags, const void *entry, struct xt_entry_match **match) { struct ipt_osf_info *info = (struct ipt_osf_info *)(*match)->data; switch(c) { case '1': /* --genre */ if (*flags & IPT_OSF_GENRE) exit_error(PARAMETER_PROBLEM, "Can't specify multiple genre parameter"); check_inverse(optarg, &invert, &optind, 0); osf_parse_string(argv[optind-1], info); if (invert) info->flags |= IPT_OSF_INVERT; info->len=strlen((char *)info->genre); *flags |= IPT_OSF_GENRE; break; case '2': /* --ttl */ if (*flags & IPT_OSF_TTL) exit_error(PARAMETER_PROBLEM, "Can't specify multiple ttl parameter"); *flags |= IPT_OSF_TTL; info->flags |= IPT_OSF_TTL; info->ttl = atoi(argv[optind-1]); break; case '3': /* --log */ if (*flags & IPT_OSF_LOG) exit_error(PARAMETER_PROBLEM, "Can't specify multiple log parameter"); *flags |= IPT_OSF_LOG; info->loglevel = atoi(argv[optind-1]); info->flags |= IPT_OSF_LOG; break; case '4': /* --netlink */ if (*flags & IPT_OSF_NETLINK) exit_error(PARAMETER_PROBLEM, "Can't specify multiple netlink parameter"); *flags |= IPT_OSF_NETLINK; info->flags |= IPT_OSF_NETLINK; break; case '5': /* --connector */ if (*flags & IPT_OSF_CONNECTOR) exit_error(PARAMETER_PROBLEM, "Can't specify multiple connector parameter"); *flags |= IPT_OSF_CONNECTOR; info->flags |= IPT_OSF_CONNECTOR; break; default: return 0; } return 1; } static void osf_final_check(unsigned int flags) { if (!flags) exit_error(PARAMETER_PROBLEM, "OS fingerprint match: You must specify `--genre'"); } static void osf_print(const void *ip, const struct xt_entry_match *match, int numeric) { const struct ipt_osf_info *info = (const struct ipt_osf_info*) match->data; printf("OS fingerprint match %s%s ", (info->flags & IPT_OSF_INVERT) ? "!" : "", info->genre); } static void osf_save(const void *ip, const struct xt_entry_match *match) { const struct ipt_osf_info *info = (const struct ipt_osf_info*) match->data; printf("--genre %s%s ", (info->flags & IPT_OSF_INVERT) ? "! ": "", info->genre); } static struct xtables_match osf_match = { .name = "osf", .version = XTABLES_VERSION, .size = XT_ALIGN(sizeof(struct ipt_osf_info)), .userspacesize = XT_ALIGN(sizeof(struct ipt_osf_info)), .help = &osf_help, .init = &osf_init, .parse = &osf_parse, .print = &osf_print, .final_check = &osf_final_check, .save = &osf_save, .extra_opts = osf_opts }; void _init(void) { xtables_register_match(&osf_match); }
obj-m := ipt_osf.o KDIR := /lib/modules/$(shell uname -r)/build IPTABLES:= /usr/include ifeq ($(IPTABLES),path_to_iptables_sources_or_header_files) $(error "Edit IPTABLES variable in Makefile and read README") endif PWD := $(shell pwd) iptables_version=$(shell (/sbin/iptables -V | awk {'print $$2'} | cut -c 2-)) LCFLAGS = -DIPTABLES_VERSION=\"$(iptables_version)\" default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: rm -f *.o *.ko *.mod.* .*.cmd *~ rm -rf .tmp_versions rm -f *.o *~ *.so *.ko load osfd ucon_osf lib: libipt_osf.c ipt_osf.h gcc $(LCFLAGS) libipt_osf.c -D_INIT=_init -fPIC -I$(IPTABLES)/include -c -o libipt_osf.o gcc -shared -nostdlib -rdynamic -Wl,-soname,libipt_osf.so -o libipt_osf.so libipt_osf.o bin: osfd.c load.c ucon_osf.c gcc -W -Wall osfd.c -o osfd gcc -W -Wall load.c -o load gcc -W -Wall ucon_osf.c -o ucon_osf