* Passive OS fingerprinting.
@ 2008-07-01 11:39 Evgeniy Polyakov
2008-07-01 11:53 ` Patrick McHardy
2008-07-01 19:56 ` Paul E. McKenney
0 siblings, 2 replies; 21+ messages in thread
From: Evgeniy Polyakov @ 2008-07-01 11:39 UTC (permalink / raw)
To: netdev; +Cc: netfilter-devel
[-- Attachment #1: Type: text/plain, Size: 1373 bytes --]
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@2ka.mipt.ru>
--
Evgeniy Polyakov
[-- Attachment #2: ipt_osf.c --]
[-- Type: text/plain, Size: 18961 bytes --]
/*
* ipt_osf.c
*
* Copyright (c) 2003-2006 Evgeniy Polyakov <johnpol@2ka.mipt.ru>
*
*
* 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@2ka.mipt.ru>");
MODULE_DESCRIPTION("Passive OS fingerprint matching.");
[-- Attachment #3: ipt_osf.h --]
[-- Type: text/plain, Size: 3603 bytes --]
/*
* ipt_osf.h
*
* Copyright (c) 2003 Evgeniy Polyakov <johnpol@2ka.mipt.ru>
*
*
* 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 */
[-- Attachment #4: libipt_osf.c --]
[-- Type: text/plain, Size: 5176 bytes --]
/*
* libipt_osf.c
*
* Copyright (c) 2003-2006 Evgeniy Polyakov <johnpol@2ka.mipt.ru>
*
*
* 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);
}
[-- Attachment #5: Makefile --]
[-- Type: text/plain, Size: 865 bytes --]
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
^ permalink raw reply [flat|nested] 21+ messages in thread* Re: Passive OS fingerprinting. 2008-07-01 11:39 Passive OS fingerprinting Evgeniy Polyakov @ 2008-07-01 11:53 ` Patrick McHardy 2008-07-01 12:03 ` Evgeniy Polyakov 2008-07-01 19:56 ` Paul E. McKenney 1 sibling, 1 reply; 21+ messages in thread From: Patrick McHardy @ 2008-07-01 11:53 UTC (permalink / raw) To: Evgeniy Polyakov; +Cc: netdev, netfilter-devel Evgeniy Polyakov wrote: > 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. [Only some general comments without having looked at the code in detail] My two main objections are that this only works for TCP and can be trivially evaded. What use cases does it have? I'm also wondering whether this couldn't be implemented using the u32 match. > 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 I don't think it should be using connector. AFAIK we only have a single user in the tree currently and new stuff usually uses genetlink (which is pretty similar), so we might be able to remove connection in the future unless we add new users. But netfilter modules should use nfnetlink anyway. ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Passive OS fingerprinting. 2008-07-01 11:53 ` Patrick McHardy @ 2008-07-01 12:03 ` Evgeniy Polyakov 2008-07-01 12:35 ` Patrick McHardy 0 siblings, 1 reply; 21+ messages in thread From: Evgeniy Polyakov @ 2008-07-01 12:03 UTC (permalink / raw) To: Patrick McHardy; +Cc: netdev, netfilter-devel Hi Patrick. On Tue, Jul 01, 2008 at 01:53:43PM +0200, Patrick McHardy (kaber@trash.net) wrote: > My two main objections are that this only works for TCP and > can be trivially evaded. What use cases does it have? Yes, it is TCP specific module. > I'm also wondering whether this couldn't be implemented > using the u32 match. I'm not sure it is that simple. OSF uses common rules database shared with OpenBSD (and other *BSDs as well), so converting it into u32 match would require noticeble efforts. But in theory it is probably doable. > >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 I don't think it should be using connector. AFAIK we > only have a single user in the tree currently and new > stuff usually uses genetlink (which is pretty similar), > so we might be able to remove connection in the future > unless we add new users. But netfilter modules should > use nfnetlink anyway. This module was created way before genetlink was ever designed (on behalf of connector btw :) Also I do not know why we want to remove connector in favour of genetlink, since the former is much simpler to work with. Connector logging is optional in OSF. -- Evgeniy Polyakov ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Passive OS fingerprinting. 2008-07-01 12:03 ` Evgeniy Polyakov @ 2008-07-01 12:35 ` Patrick McHardy 2008-07-01 13:08 ` Evgeniy Polyakov 2008-07-01 13:32 ` Jeff Garzik 0 siblings, 2 replies; 21+ messages in thread From: Patrick McHardy @ 2008-07-01 12:35 UTC (permalink / raw) To: Evgeniy Polyakov; +Cc: netdev, netfilter-devel Evgeniy Polyakov wrote: > On Tue, Jul 01, 2008 at 01:53:43PM +0200, Patrick McHardy (kaber@trash.net) wrote: >> My two main objections are that this only works for TCP and >> can be trivially evaded. What use cases does it have? > > Yes, it is TCP specific module. What about the use cases? I certainly like the idea you suggest in your blog ("Ever dreamt to block all Linux users in your network from accessing internet and allow full bandwidth to Windows worm?") :) But something this easy to evade doesn't seem to provide a real benefit for a firewall. I can see that something like "Block IE6 running on Windows version X" might be useful (NUFW can do this I think), but that needs support from the host. >> I'm also wondering whether this couldn't be implemented >> using the u32 match. > > I'm not sure it is that simple. OSF uses common rules database > shared with OpenBSD (and other *BSDs as well), so converting it into u32 > match would require noticeble efforts. But in theory it is probably > doable. This would be preferrable in my opinion since they both allow programmable filters, but u32 appears to be more flexible. I'm very reluctant to add new iptables modules that don't increase expressiveness or provide other clear benefits since we already have an insane amount of modules. From the fingerprint file: # Fingerprint entry format: # # wwww:ttt:D:ss:OOO...:OS:Version:Subtype:Details # # wwww - window size (can be *, %nnn, Snn or Tnn). The special values # "S" and "T" which are a multiple of MSS or a multiple of MTU # respectively. # ttt - initial TTL # D - don't fragment bit (0 - not set, 1 - set) # ss - overall SYN packet size # OOO - option value and order specification (see below) # OS - OS genre (Linux, Solaris, Windows) # Version - OS Version (2.0.27 on x86, etc) # Subtype - OS subtype or patchlevel (SP3, lo0) # details - Generic OS details MSS can be matched using the tcpmss match, options (but not order) using the tcp match, TTL using the ttl match, DF using the IP flags match, packet size using the length match. That leaves WS for u32 (or adding support to the tcp match). The conversion looks pretty easy actually. >>> # iptables -I INPUT -j DROP -p tcp -m osf --genre Linux --log 2 \ >>> --ttl 2 --connector >> And I don't think it should be using connector. AFAIK we >> only have a single user in the tree currently and new >> stuff usually uses genetlink (which is pretty similar), >> so we might be able to remove connection in the future >> unless we add new users. But netfilter modules should >> use nfnetlink anyway. > > This module was created way before genetlink was ever designed (on > behalf of connector btw :) Yes, I know. > Also I do not know why we want to remove connector in favour of > genetlink, since the former is much simpler to work with. Connector > logging is optional in OSF. I'm not sure if we want to, but they appear to provide similar functionality and connector only has a single user in the tree so far. But thats a different discussion, for netfilter related things nfnetlink should be used. ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Passive OS fingerprinting. 2008-07-01 12:35 ` Patrick McHardy @ 2008-07-01 13:08 ` Evgeniy Polyakov 2008-07-01 13:41 ` Patrick McHardy 2008-07-01 14:26 ` Jan Engelhardt 2008-07-01 13:32 ` Jeff Garzik 1 sibling, 2 replies; 21+ messages in thread From: Evgeniy Polyakov @ 2008-07-01 13:08 UTC (permalink / raw) To: Patrick McHardy; +Cc: netdev, netfilter-devel On Tue, Jul 01, 2008 at 02:35:19PM +0200, Patrick McHardy (kaber@trash.net) wrote: > >>My two main objections are that this only works for TCP and > >>can be trivially evaded. What use cases does it have? > > > >Yes, it is TCP specific module. > > What about the use cases? I certainly like the idea you suggest in > your blog ("Ever dreamt to block all Linux users in your network > from accessing internet and allow full bandwidth to Windows worm?") > :) But something this easy to evade doesn't seem to provide a real > benefit for a firewall. Actually worm detection is one of the use cases - I was told about successful installations several years ago. > I can see that something like "Block IE6 running on Windows version X" > might be useful (NUFW can do this I think), but that needs support > from the host. It allows to determine that it is Windows version X, there is no way to distinguish userspace application from each other if they use default parameters. OS version detection is somewhat subtle - in earlier days it was simpler, since each major version broke/changed something especially in XP stack, in Linux I do not know any similar changes in recent 2.6 kernels. > >>I'm also wondering whether this couldn't be implemented > >>using the u32 match. > > > >I'm not sure it is that simple. OSF uses common rules database > >shared with OpenBSD (and other *BSDs as well), so converting it into u32 > >match would require noticeble efforts. But in theory it is probably > >doable. > > This would be preferrable in my opinion since they both allow > programmable filters, but u32 appears to be more flexible. I'm > very reluctant to add new iptables modules that don't increase > expressiveness or provide other clear benefits since we already > have an insane amount of modules. OSF does increase expressiveness! :) '--genre Linux' is much more clear than... Do you want me to at least roughly draw mathing rules enciphered in u32 format? I agree that matching can be done with u32 (even private OSF TTL games), but it will much much much more ugly than simple module. I am also not sure OSF should live in kernel, but what it does it does good and there is no simple way to do the same with existing functionality. It is possible, but not simple, and definitely not trivial for administrator :) > From the fingerprint file: > > # Fingerprint entry format: > # > # wwww:ttt:D:ss:OOO...:OS:Version:Subtype:Details > # > # wwww - window size (can be *, %nnn, Snn or Tnn). The special values > # "S" and "T" which are a multiple of MSS or a multiple of MTU > # respectively. > # ttt - initial TTL > # D - don't fragment bit (0 - not set, 1 - set) > # ss - overall SYN packet size > # OOO - option value and order specification (see below) > # OS - OS genre (Linux, Solaris, Windows) > # Version - OS Version (2.0.27 on x86, etc) > # Subtype - OS subtype or patchlevel (SP3, lo0) > # details - Generic OS details > > MSS can be matched using the tcpmss match, options (but not order) > using the tcp match, TTL using the ttl match, DF using the IP flags > match, packet size using the length match. That leaves WS for u32 > (or adding support to the tcp match). > > The conversion looks pretty easy actually. Hmm, I can assure you that simple rule for OSF matching module is much more clear than zillions above rules, but it can be done indeed. I'm not sure that 5 years ago all that matches existed, but right now it is possible. Not trivial and not simple, but probably possible. To use multple matches one has to chain them into single rule/table, listing and understaning of which is not simple enough compared to OSF rules. So it is helper module which allows really simple installation. Various tricks with TTL used in OSF will complicate that chaining even more. > >Also I do not know why we want to remove connector in favour of > >genetlink, since the former is much simpler to work with. Connector > >logging is optional in OSF. > > I'm not sure if we want to, but they appear to provide similar > functionality and connector only has a single user in the tree > so far. But thats a different discussion, for netfilter related > things nfnetlink should be used. IIRC there are at least threee: w1, various accountings and uvesafb. And I authored only the first :) There was no nfnetlink either 5 years ago, when OSF was created, this release is just subsequent update to the project. At some moment OSF shared netlink group with ulog, but it was considered harmful, so I dropped support. Netlink usage is rather trivial: it just sends information about matched packt to userspace, so it can block it on its own, rise a message in the window or perform some other steps. Nothing exceptionally complex :) -- Evgeniy Polyakov ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Passive OS fingerprinting. 2008-07-01 13:08 ` Evgeniy Polyakov @ 2008-07-01 13:41 ` Patrick McHardy 2008-07-01 14:14 ` Evgeniy Polyakov 2008-07-01 14:26 ` Jan Engelhardt 1 sibling, 1 reply; 21+ messages in thread From: Patrick McHardy @ 2008-07-01 13:41 UTC (permalink / raw) To: Evgeniy Polyakov; +Cc: netdev, netfilter-devel Evgeniy Polyakov wrote: > On Tue, Jul 01, 2008 at 02:35:19PM +0200, Patrick McHardy (kaber@trash.net) wrote: > >>>> My two main objections are that this only works for TCP and >>>> can be trivially evaded. What use cases does it have? >>>> >>> Yes, it is TCP specific module. >>> >> What about the use cases? I certainly like the idea you suggest in >> your blog ("Ever dreamt to block all Linux users in your network >> from accessing internet and allow full bandwidth to Windows worm?") >> :) But something this easy to evade doesn't seem to provide a real >> benefit for a firewall. >> > > Actually worm detection is one of the use cases - I was told about > successful installations several years ago. > How does that work? I assume in combination with some kind of rate-limit? >>>> I'm also wondering whether this couldn't be implemented >>>> using the u32 match. >>>> >>> I'm not sure it is that simple. OSF uses common rules database >>> shared with OpenBSD (and other *BSDs as well), so converting it into u32 >>> match would require noticeble efforts. But in theory it is probably >>> doable. >>> >> This would be preferrable in my opinion since they both allow >> programmable filters, but u32 appears to be more flexible. I'm >> very reluctant to add new iptables modules that don't increase >> expressiveness or provide other clear benefits since we already >> have an insane amount of modules. >> > > OSF does increase expressiveness! :) > '--genre Linux' is much more clear than... Do you want me to at least > roughly draw mathing rules enciphered in u32 format? > > I agree that matching can be done with u32 (even private OSF TTL games), > but it will much much much more ugly than simple module. > > I am also not sure OSF should live in kernel, but what it does it does > good and there is no simple way to do the same with existing > functionality. It is possible, but not simple, and definitely not > trivial for administrator :) > I don't like the current way such things are implemented in iptables (have all logic in the kernel instead of just providing a mechanism for implementing it in userspace and presenting a nice view to the administrator). Thats not your fault of course and your module is also not the first one to do this. Unfortunately its most likely not possible to convince me to like this, so lets just say that I'm fine with merging it if someone speaks up in favour of it :) >>> Also I do not know why we want to remove connector in favour of >>> genetlink, since the former is much simpler to work with. Connector >>> logging is optional in OSF. >>> >> I'm not sure if we want to, but they appear to provide similar >> functionality and connector only has a single user in the tree >> so far. But thats a different discussion, for netfilter related >> things nfnetlink should be used. >> > > IIRC there are at least threee: w1, various accountings and uvesafb. > And I authored only the first :) > > There was no nfnetlink either 5 years ago, when OSF was created, > this release is just subsequent update to the project. > At some moment OSF shared netlink group with ulog, but it was > considered harmful, so I dropped support. Netlink usage is > rather trivial: it just sends information about matched packt to > userspace, so it can block it on its own, rise a message in the window > or perform some other steps. Nothing exceptionally complex :) > Yes, but I don't want to add another interface netfilter userspace has to know about. It should either use nfnetlink and remove the proc interface, or remove the connector interface and use proc. Preferrably the former. ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Passive OS fingerprinting. 2008-07-01 13:41 ` Patrick McHardy @ 2008-07-01 14:14 ` Evgeniy Polyakov 2008-07-01 14:16 ` Patrick McHardy 0 siblings, 1 reply; 21+ messages in thread From: Evgeniy Polyakov @ 2008-07-01 14:14 UTC (permalink / raw) To: Patrick McHardy; +Cc: netdev, netfilter-devel On Tue, Jul 01, 2008 at 03:41:58PM +0200, Patrick McHardy (kaber@trash.net) wrote: > >Actually worm detection is one of the use cases - I was told about > >successful installations several years ago. > > How does that work? I assume in combination with some kind of > rate-limit? IIRC during worm attacks all windows (according to OSF) traffic was just routed via slow/shaped device (that was forwarding on a gateway with appropriate NAT). Right now I would recomment to use recent/rateest module to make fine-grained tuning of the connection. > >I am also not sure OSF should live in kernel, but what it does it does > >good and there is no simple way to do the same with existing > >functionality. It is possible, but not simple, and definitely not > >trivial for administrator :) > > I don't like the current way such things are implemented in iptables > (have all logic in the kernel instead of just providing a mechanism > for implementing it in userspace and presenting a nice view to the > administrator). Thats not your fault of course and your module is > also not the first one to do this. I bet it is not the last one :) > Unfortunately its most likely not possible to convince me to like > this, so lets just say that I'm fine with merging it if someone > speaks up in favour of it :) Cool. If no none will reply, nothing actually changes :) OSF lived on its own all the time except several months in patch-o-matic and then its next generation. > >There was no nfnetlink either 5 years ago, when OSF was created, > >this release is just subsequent update to the project. > >At some moment OSF shared netlink group with ulog, but it was > >considered harmful, so I dropped support. Netlink usage is > >rather trivial: it just sends information about matched packt to > >userspace, so it can block it on its own, rise a message in the window > >or perform some other steps. Nothing exceptionally complex :) > > > > Yes, but I don't want to add another interface netfilter userspace > has to know about. It should either use nfnetlink and remove the proc > interface, or remove the connector interface and use proc. > Preferrably the former. It uses proc to load rules - I do not like it either, but it was the simplest way to do so :) -- Evgeniy Polyakov ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Passive OS fingerprinting. 2008-07-01 14:14 ` Evgeniy Polyakov @ 2008-07-01 14:16 ` Patrick McHardy 2008-07-01 14:48 ` Evgeniy Polyakov 0 siblings, 1 reply; 21+ messages in thread From: Patrick McHardy @ 2008-07-01 14:16 UTC (permalink / raw) To: Evgeniy Polyakov; +Cc: netdev, netfilter-devel Evgeniy Polyakov wrote: > On Tue, Jul 01, 2008 at 03:41:58PM +0200, Patrick McHardy (kaber@trash.net) wrote: > >>> I am also not sure OSF should live in kernel, but what it does it does >>> good and there is no simple way to do the same with existing >>> functionality. It is possible, but not simple, and definitely not >>> trivial for administrator :) >>> >> I don't like the current way such things are implemented in iptables >> (have all logic in the kernel instead of just providing a mechanism >> for implementing it in userspace and presenting a nice view to the >> administrator). Thats not your fault of course and your module is >> also not the first one to do this. >> > > I bet it is not the last one :) > I truely hope it will be since I'm working (slowly, as time permits) on the *tables successor that will implement things like this in userspace. Every module we add that adds more complicated logic in the kernel will make adding an iptables compat layer harder. >> Unfortunately its most likely not possible to convince me to like >> this, so lets just say that I'm fine with merging it if someone >> speaks up in favour of it :) >> > > Cool. If no none will reply, nothing actually changes :) > OSF lived on its own all the time except several months in patch-o-matic > and then its next generation. > > I'd CC the netfilter user list, its likely you'll find some voices in favour there :) >>> There was no nfnetlink either 5 years ago, when OSF was created, >>> this release is just subsequent update to the project. >>> At some moment OSF shared netlink group with ulog, but it was >>> considered harmful, so I dropped support. Netlink usage is >>> rather trivial: it just sends information about matched packt to >>> userspace, so it can block it on its own, rise a message in the window >>> or perform some other steps. Nothing exceptionally complex :) >>> >>> >> Yes, but I don't want to add another interface netfilter userspace >> has to know about. It should either use nfnetlink and remove the proc >> interface, or remove the connector interface and use proc. >> Preferrably the former. >> > > It uses proc to load rules - I do not like it either, but it was the > simplest way to do so :) We can rethink that part if it will actually get merged. ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Passive OS fingerprinting. 2008-07-01 14:16 ` Patrick McHardy @ 2008-07-01 14:48 ` Evgeniy Polyakov 2008-07-01 14:54 ` Patrick McHardy 0 siblings, 1 reply; 21+ messages in thread From: Evgeniy Polyakov @ 2008-07-01 14:48 UTC (permalink / raw) To: Patrick McHardy; +Cc: netdev, netfilter-devel On Tue, Jul 01, 2008 at 04:16:28PM +0200, Patrick McHardy (kaber@trash.net) wrote: > I truely hope it will be since I'm working (slowly, as time permits) > on the *tables successor that will implement things like this in > userspace. Every module we add that adds more complicated logic in > the kernel will make adding an iptables compat layer harder. It still is very tempting to implement such things as iptables modules. For example I consider to create tunnel-like device and iptables target to implement ip-over-dns tunnel, and I need iptables extension since I only control single machine outside of my ISP which is not firewalled. Having new way of writing iptables extensions requires to update existing machines, which is not possible frequently (like existing enterprise (r) (c) (tm) solutions...) > I'd CC the netfilter user list, its likely you'll find some voices > in favour there :) :) > >>Yes, but I don't want to add another interface netfilter userspace > >>has to know about. It should either use nfnetlink and remove the proc > >>interface, or remove the connector interface and use proc. > >>Preferrably the former. > >> > > > >It uses proc to load rules - I do not like it either, but it was the > >simplest way to do so :) > > We can rethink that part if it will actually get merged. Sure. -- Evgeniy Polyakov ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Passive OS fingerprinting. 2008-07-01 14:48 ` Evgeniy Polyakov @ 2008-07-01 14:54 ` Patrick McHardy 0 siblings, 0 replies; 21+ messages in thread From: Patrick McHardy @ 2008-07-01 14:54 UTC (permalink / raw) To: Evgeniy Polyakov; +Cc: netdev, netfilter-devel Evgeniy Polyakov wrote: > On Tue, Jul 01, 2008 at 04:16:28PM +0200, Patrick McHardy (kaber@trash.net) wrote: >> I truely hope it will be since I'm working (slowly, as time permits) >> on the *tables successor that will implement things like this in >> userspace. Every module we add that adds more complicated logic in >> the kernel will make adding an iptables compat layer harder. > > It still is very tempting to implement such things as iptables modules. > For example I consider to create tunnel-like device and iptables target > to implement ip-over-dns tunnel, and I need iptables extension since I > only control single machine outside of my ISP which is not firewalled. > Having new way of writing iptables extensions requires to update > existing machines, which is not possible frequently > (like existing enterprise (r) (c) (tm) solutions...) Yes, I only mean for matching purposes. Target functionality can usually not be reached through combination. ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Passive OS fingerprinting. 2008-07-01 13:08 ` Evgeniy Polyakov 2008-07-01 13:41 ` Patrick McHardy @ 2008-07-01 14:26 ` Jan Engelhardt 2008-07-01 14:25 ` Patrick McHardy 1 sibling, 1 reply; 21+ messages in thread From: Jan Engelhardt @ 2008-07-01 14:26 UTC (permalink / raw) To: Evgeniy Polyakov; +Cc: Patrick McHardy, netdev, netfilter-devel On Tuesday 2008-07-01 15:08, Evgeniy Polyakov wrote: >> > >> >I'm not sure it is that simple. OSF uses common rules database >> >shared with OpenBSD (and other *BSDs as well), so converting it into u32 >> >match would require noticeble efforts. But in theory it is probably >> >doable. >> >> This would be preferrable in my opinion since they both allow >> programmable filters, but u32 appears to be more flexible. I'm >> very reluctant to add new iptables modules that don't increase >> expressiveness or provide other clear benefits since we already >> have an insane amount of modules. An iptables extension which you can use with -m osf --genre Linux but which internally uses xt_u32.ko would be the perfect solution ATM IMO. It would require a number of changes to the iptables API though... ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Passive OS fingerprinting. 2008-07-01 14:26 ` Jan Engelhardt @ 2008-07-01 14:25 ` Patrick McHardy 0 siblings, 0 replies; 21+ messages in thread From: Patrick McHardy @ 2008-07-01 14:25 UTC (permalink / raw) To: Jan Engelhardt; +Cc: Evgeniy Polyakov, netdev, netfilter-devel Jan Engelhardt wrote: > On Tuesday 2008-07-01 15:08, Evgeniy Polyakov wrote: > >>>> I'm not sure it is that simple. OSF uses common rules database >>>> shared with OpenBSD (and other *BSDs as well), so converting it into u32 >>>> match would require noticeble efforts. But in theory it is probably >>>> doable. >>>> >>> This would be preferrable in my opinion since they both allow >>> programmable filters, but u32 appears to be more flexible. I'm >>> very reluctant to add new iptables modules that don't increase >>> expressiveness or provide other clear benefits since we already >>> have an insane amount of modules. >>> > > An iptables extension which you can use with -m osf --genre Linux > but which internally uses xt_u32.ko would be the perfect solution > ATM IMO. It would require a number of changes to the iptables API > though... > I agree that this would be much nicer. I assume you would either need a way to associate multiple matches with a single userspace extension or a much more intelligent parser in userspace? ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Passive OS fingerprinting. 2008-07-01 12:35 ` Patrick McHardy 2008-07-01 13:08 ` Evgeniy Polyakov @ 2008-07-01 13:32 ` Jeff Garzik 2008-07-01 13:35 ` Patrick McHardy 2008-07-01 13:39 ` Evgeniy Polyakov 1 sibling, 2 replies; 21+ messages in thread From: Jeff Garzik @ 2008-07-01 13:32 UTC (permalink / raw) To: Patrick McHardy; +Cc: Evgeniy Polyakov, netdev, netfilter-devel Patrick McHardy wrote: > Evgeniy Polyakov wrote: >> On Tue, Jul 01, 2008 at 01:53:43PM +0200, Patrick McHardy >> (kaber@trash.net) wrote: >>> My two main objections are that this only works for TCP and >>> can be trivially evaded. What use cases does it have? >> >> Yes, it is TCP specific module. > > What about the use cases? I certainly like the idea you suggest in > your blog ("Ever dreamt to block all Linux users in your network > from accessing internet and allow full bandwidth to Windows worm?") > :) But something this easy to evade doesn't seem to provide a real > benefit for a firewall. > > I can see that something like "Block IE6 running on Windows version X" > might be useful (NUFW can do this I think), but that needs support > from the host. Not addressing Evgeniy's module but speaking generally... It sure would be nice for regular socket applications to have an easy, unprivileged way to query the OS fingerprint information of a given socket. Speaking purely from a userspace application API perspective, it would be most useful for an app to be able to stop OSF collection, start OSF collection, and query OSF stats. start/stop would be a refcount that disables in-kernel OSF when not in use. To present a specific use case: I would like to know if incoming SMTP connections are Windows or not. That permits me to better determine if the incoming connection is a hijacked PC or not -- it becomes a useful factor in spamassassin scoring. In this case, incoming SMTP is -always- TCP, thus being a TCP-specific module is not a problem. You cover a huge swath of apps even if the module is TCP-specific. Another use case is validating whether a browser is "lying" about its OS, when parsing HTTP user-agent info, or in general when any remote agent is "lying" about its OS. Security software can use that as an additional red-flag factor. Jeff ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Passive OS fingerprinting. 2008-07-01 13:32 ` Jeff Garzik @ 2008-07-01 13:35 ` Patrick McHardy 2008-07-01 13:47 ` Evgeniy Polyakov 2008-07-01 15:34 ` Jeff Garzik 2008-07-01 13:39 ` Evgeniy Polyakov 1 sibling, 2 replies; 21+ messages in thread From: Patrick McHardy @ 2008-07-01 13:35 UTC (permalink / raw) To: Jeff Garzik; +Cc: Evgeniy Polyakov, netdev, netfilter-devel Jeff Garzik wrote: > Patrick McHardy wrote: >> What about the use cases? I certainly like the idea you suggest in >> your blog ("Ever dreamt to block all Linux users in your network >> from accessing internet and allow full bandwidth to Windows worm?") >> :) But something this easy to evade doesn't seem to provide a real >> benefit for a firewall. >> >> I can see that something like "Block IE6 running on Windows version X" >> might be useful (NUFW can do this I think), but that needs support >> from the host. > > Not addressing Evgeniy's module but speaking generally... > > It sure would be nice for regular socket applications to have an easy, > unprivileged way to query the OS fingerprint information of a given > socket. I'm not sure how much OSF depends on the TTL, but doing this more than one hop away from the host (or without knowledge of the number of hops) makes using the TTL basically impossible. > Speaking purely from a userspace application API perspective, it would > be most useful for an app to be able to stop OSF collection, start OSF > collection, and query OSF stats. start/stop would be a refcount that > disables in-kernel OSF when not in use. > > To present a specific use case: I would like to know if incoming SMTP > connections are Windows or not. That permits me to better determine > if the incoming connection is a hijacked PC or not -- it becomes a > useful factor in spamassassin scoring. > > In this case, incoming SMTP is -always- TCP, thus being a TCP-specific > module is not a problem. You cover a huge swath of apps even if the > module is TCP-specific. > > Another use case is validating whether a browser is "lying" about its > OS, when parsing HTTP user-agent info, or in general when any remote > agent is "lying" about its OS. Security software can use that as an > additional red-flag factor. I for one would be much happier to only have netfilter as a user of this :) ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Passive OS fingerprinting. 2008-07-01 13:35 ` Patrick McHardy @ 2008-07-01 13:47 ` Evgeniy Polyakov 2008-07-01 15:34 ` Jeff Garzik 1 sibling, 0 replies; 21+ messages in thread From: Evgeniy Polyakov @ 2008-07-01 13:47 UTC (permalink / raw) To: Patrick McHardy; +Cc: Jeff Garzik, netdev, netfilter-devel On Tue, Jul 01, 2008 at 03:35:02PM +0200, Patrick McHardy (kaber@trash.net) wrote: > >It sure would be nice for regular socket applications to have an easy, > >unprivileged way to query the OS fingerprint information of a given > >socket. > > I'm not sure how much OSF depends on the TTL, but doing this > more than one hop away from the host (or without knowledge of > the number of hops) makes using the TTL basically impossible. There are three modes in OSF: LAN where things are simple, no-ttl, where things are even more simpler and false positive, and heueristic mode, which checks ttl, but with some addons. Like if ttl is 31, it is possible that it is OS with initial TTL being equal to 32, and other OS, with initial TTL 48, and whatever other checks succeeded for that cases, determine what OS is. It works quite good in internet not only LAN, since it is frequently only enough to roughly determine initial TTL. > >Another use case is validating whether a browser is "lying" about its > >OS, when parsing HTTP user-agent info, or in general when any remote > >agent is "lying" about its OS. Security software can use that as an > >additional red-flag factor. > > I for one would be much happier to only have netfilter as a user > of this :) Security checkers do like to put its hands into sooo deep places in the stack :) -- Evgeniy Polyakov ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Passive OS fingerprinting. 2008-07-01 13:35 ` Patrick McHardy 2008-07-01 13:47 ` Evgeniy Polyakov @ 2008-07-01 15:34 ` Jeff Garzik 2008-07-01 15:44 ` Patrick McHardy 1 sibling, 1 reply; 21+ messages in thread From: Jeff Garzik @ 2008-07-01 15:34 UTC (permalink / raw) To: Patrick McHardy; +Cc: Evgeniy Polyakov, netdev, netfilter-devel Patrick McHardy wrote: > Jeff Garzik wrote: >> Patrick McHardy wrote: >>> What about the use cases? I certainly like the idea you suggest in >>> your blog ("Ever dreamt to block all Linux users in your network >>> from accessing internet and allow full bandwidth to Windows worm?") >>> :) But something this easy to evade doesn't seem to provide a real >>> benefit for a firewall. >>> >>> I can see that something like "Block IE6 running on Windows version X" >>> might be useful (NUFW can do this I think), but that needs support >>> from the host. >> >> Not addressing Evgeniy's module but speaking generally... >> >> It sure would be nice for regular socket applications to have an easy, >> unprivileged way to query the OS fingerprint information of a given >> socket. > > I'm not sure how much OSF depends on the TTL, but doing this > more than one hop away from the host (or without knowledge of > the number of hops) makes using the TTL basically impossible. The userspace version works quite well over the Internet: http://lcamtuf.coredump.cx/p0f.shtml >> Speaking purely from a userspace application API perspective, it would >> be most useful for an app to be able to stop OSF collection, start OSF >> collection, and query OSF stats. start/stop would be a refcount that >> disables in-kernel OSF when not in use. >> >> To present a specific use case: I would like to know if incoming SMTP >> connections are Windows or not. That permits me to better determine >> if the incoming connection is a hijacked PC or not -- it becomes a >> useful factor in spamassassin scoring. >> >> In this case, incoming SMTP is -always- TCP, thus being a TCP-specific >> module is not a problem. You cover a huge swath of apps even if the >> module is TCP-specific. >> >> Another use case is validating whether a browser is "lying" about its >> OS, when parsing HTTP user-agent info, or in general when any remote >> agent is "lying" about its OS. Security software can use that as an >> additional red-flag factor. > > I for one would be much happier to only have netfilter as a user > of this :) I'm not sure I understand? When site A connects to site B via TCP, is there a problem letting site A know the OS of site B? Jeff ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Passive OS fingerprinting. 2008-07-01 15:34 ` Jeff Garzik @ 2008-07-01 15:44 ` Patrick McHardy 0 siblings, 0 replies; 21+ messages in thread From: Patrick McHardy @ 2008-07-01 15:44 UTC (permalink / raw) To: Jeff Garzik; +Cc: Evgeniy Polyakov, netdev, netfilter-devel Jeff Garzik wrote: > Patrick McHardy wrote: >> I for one would be much happier to only have netfilter as a user >> of this :) > > I'm not sure I understand? When site A connects to site B via TCP, is > there a problem letting site A know the OS of site B? No, I expressed myself very unclear, sorry :) I meant to say that I would be happier to have netfilter as a user of some generic infrastructure instead of having all of this implemented as netfilter module. ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Passive OS fingerprinting. 2008-07-01 13:32 ` Jeff Garzik 2008-07-01 13:35 ` Patrick McHardy @ 2008-07-01 13:39 ` Evgeniy Polyakov 1 sibling, 0 replies; 21+ messages in thread From: Evgeniy Polyakov @ 2008-07-01 13:39 UTC (permalink / raw) To: Jeff Garzik; +Cc: Patrick McHardy, netdev, netfilter-devel Hi Jeff. On Tue, Jul 01, 2008 at 09:32:47AM -0400, Jeff Garzik (jeff@garzik.org) wrote: > It sure would be nice for regular socket applications to have an easy, > unprivileged way to query the OS fingerprint information of a given socket. > > Speaking purely from a userspace application API perspective, it would > be most useful for an app to be able to stop OSF collection, start OSF > collection, and query OSF stats. start/stop would be a refcount that > disables in-kernel OSF when not in use. > > To present a specific use case: I would like to know if incoming SMTP > connections are Windows or not. That permits me to better determine if > the incoming connection is a hijacked PC or not -- it becomes a useful > factor in spamassassin scoring. > > In this case, incoming SMTP is -always- TCP, thus being a TCP-specific > module is not a problem. You cover a huge swath of apps even if the > module is TCP-specific. > > Another use case is validating whether a browser is "lying" about its > OS, when parsing HTTP user-agent info, or in general when any remote > agent is "lying" about its OS. Security software can use that as an > additional red-flag factor. It is possible right now in OSF: it sends a netlink notification to userspace about received and matched packet. We can even think some more about reverse channel - to inform kernel about some steps for this match, it requires root priveledges though. It can also be done via different channel (like running script to install iptables rule). -- Evgeniy Polyakov ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Passive OS fingerprinting. 2008-07-01 11:39 Passive OS fingerprinting Evgeniy Polyakov 2008-07-01 11:53 ` Patrick McHardy @ 2008-07-01 19:56 ` Paul E. McKenney 2008-07-01 21:21 ` Evgeniy Polyakov 1 sibling, 1 reply; 21+ messages in thread From: Paul E. McKenney @ 2008-07-01 19:56 UTC (permalink / raw) To: Evgeniy Polyakov; +Cc: netdev, netfilter-devel On Tue, Jul 01, 2008 at 03:39:28PM +0400, Evgeniy Polyakov wrote: > 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. A few quick questions below. Thanx, Paul > 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@2ka.mipt.ru> > > -- > Evgeniy Polyakov > /* > * ipt_osf.c > * > * Copyright (c) 2003-2006 Evgeniy Polyakov <johnpol@2ka.mipt.ru> > * > * > * 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) { Does the above need to be list_for_each_entry_rcu()? > 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) { This is OK -- we hold the update-side lock, so don't need _rcu. > i++; > list_del_rcu(&finger->flist); > ipt_osf_finger_free(finger); Why is it safe to immediately free the element that we just removed from an RCU-protected list? The above synchronize_rcu() won't help us given that it is still in the list at that point. This could be fixed by using call_rcu() in ipt_osf_finger_free(). > } > 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(); I don't understand what this synchronize_rcu() is doing for us. Is the idea to make sure that all RCU readers see the prior deletes as having happened before we do the below addition? If so, please add a comment to this effect. > 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) { Why is it safe to do the above without being in an RCU read-side critical section? Have all possible RCU readers somehow been banished? If so, how have they been banished? > list_del(&f->flist); Ditto for why the list_del() is safe without the update-side lock and why it doesn't have to be list_del_rcu(). > 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@2ka.mipt.ru>"); > MODULE_DESCRIPTION("Passive OS fingerprint matching."); > /* > * ipt_osf.h > * > * Copyright (c) 2003 Evgeniy Polyakov <johnpol@2ka.mipt.ru> > * > * > * 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@2ka.mipt.ru> > * > * > * 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 ^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: Passive OS fingerprinting. 2008-07-01 19:56 ` Paul E. McKenney @ 2008-07-01 21:21 ` Evgeniy Polyakov [not found] ` <20080701224149.GA8449@linux.vnet.ibm.com> 0 siblings, 1 reply; 21+ messages in thread From: Evgeniy Polyakov @ 2008-07-01 21:21 UTC (permalink / raw) To: Paul E. McKenney; +Cc: netdev, netfilter-devel Hi Paul. On Tue, Jul 01, 2008 at 12:56:48PM -0700, Paul E. McKenney (paulmck@linux.vnet.ibm.com) wrote: > > 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) { > > Does the above need to be list_for_each_entry_rcu()? Yeah, I messed with procfs handlers. ... > > 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) { > > This is OK -- we hold the update-side lock, so don't need _rcu. > > > i++; > > list_del_rcu(&finger->flist); > > ipt_osf_finger_free(finger); > > Why is it safe to immediately free the element that we just removed from > an RCU-protected list? The above synchronize_rcu() won't help us given > that it is still in the list at that point. > > This could be fixed by using call_rcu() in ipt_osf_finger_free(). Yup, it is safe on UP machine, since without lock it is only accessed from the bottom half, which is disabled, but running it on different CPU will lead to crash. procfs was not a very good choice here :) > > 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(); > > I don't understand what this synchronize_rcu() is doing for us. > > Is the idea to make sure that all RCU readers see the prior deletes as > having happened before we do the below addition? If so, please add a > comment to this effect. Just to be sure, that return from the call does update the list. It is not needed in practice. > > 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) { > > Why is it safe to do the above without being in an RCU read-side critical > section? Have all possible RCU readers somehow been banished? If so, > how have they been banished? > > > list_del(&f->flist); > > Ditto for why the list_del() is safe without the update-side lock and > why it doesn't have to be list_del_rcu(). Procfs was messed, it should use proper rcu freeing path, and although procefs entry was removed, there may be some reference. Thanks a lot for your review Paul. -- Evgeniy Polyakov ^ permalink raw reply [flat|nested] 21+ messages in thread
[parent not found: <20080701224149.GA8449@linux.vnet.ibm.com>]
* Re: Passive OS fingerprinting. [not found] ` <20080701224149.GA8449@linux.vnet.ibm.com> @ 2008-07-02 4:46 ` Evgeniy Polyakov 0 siblings, 0 replies; 21+ messages in thread From: Evgeniy Polyakov @ 2008-07-02 4:46 UTC (permalink / raw) To: Paul E. McKenney; +Cc: netdev, netfilter-devel On Tue, Jul 01, 2008 at 03:41:49PM -0700, Paul E. McKenney (paulmck@linux.vnet.ibm.com) wrote: > Glad it wasn't just me going senile!!! ;-) > > Will you be fixing the procfs stuff as well? If not, could you please > send a pointer? I will, probably just by moving to netlink. I think I will also implement kind of hash table for fingerprint set. -- Evgeniy Polyakov ^ permalink raw reply [flat|nested] 21+ messages in thread
end of thread, other threads:[~2008-07-02 4:46 UTC | newest]
Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-07-01 11:39 Passive OS fingerprinting Evgeniy Polyakov
2008-07-01 11:53 ` Patrick McHardy
2008-07-01 12:03 ` Evgeniy Polyakov
2008-07-01 12:35 ` Patrick McHardy
2008-07-01 13:08 ` Evgeniy Polyakov
2008-07-01 13:41 ` Patrick McHardy
2008-07-01 14:14 ` Evgeniy Polyakov
2008-07-01 14:16 ` Patrick McHardy
2008-07-01 14:48 ` Evgeniy Polyakov
2008-07-01 14:54 ` Patrick McHardy
2008-07-01 14:26 ` Jan Engelhardt
2008-07-01 14:25 ` Patrick McHardy
2008-07-01 13:32 ` Jeff Garzik
2008-07-01 13:35 ` Patrick McHardy
2008-07-01 13:47 ` Evgeniy Polyakov
2008-07-01 15:34 ` Jeff Garzik
2008-07-01 15:44 ` Patrick McHardy
2008-07-01 13:39 ` Evgeniy Polyakov
2008-07-01 19:56 ` Paul E. McKenney
2008-07-01 21:21 ` Evgeniy Polyakov
[not found] ` <20080701224149.GA8449@linux.vnet.ibm.com>
2008-07-02 4:46 ` Evgeniy Polyakov
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for NNTP newsgroup(s).