哥使用Linux
分类: LINUX
2008-01-22 11:16:30
英文原文的地址http://www.stearns.org/doc/iptables-u32.current.html
介绍
IPTables是一个比较灵活的模块化的防火墙。如果它当前不能够精确的检测具有某些特征的包,你可以选择自己写一个或者是修改一个现存的检测模块。但问题是我们大多数人并不是程序员,虽然有那么多开源的dd。
我们有一个选择是不需要写代码的。好心的Don Cohen写了个IPTables的模块,该模块从数据包中抽出你所感兴趣的一些字节,进行一些操作,看看得到的结果是否在指定的范围之内。比如,我可以获取IP头的Fragmentation信息,丢弃More Fragments标记以外所有的东西,看看它是否设置了这个标记。
根本不需要写任何的C代码
我在这里所要做的就是介绍核心概念,还加入了希望是足够多的带注释的例子,使你能够编写自己的检测模块。
我不会专注于介绍这些部分是什么,还有为什么你要检测它们;有很多的(警告:不好意思,再往前插入了我雇主的信息)资料在做这样的事情。如果你仅仅是需要一个数据包头的快速参考,请看。
所有在本文提到的字节位置是以0作为包的第一个字节来计算的。比如,在IP头中,字节“0”保存着4bit的“版本”和4bit的“IP头长度”,字节“1”保存的是“TOS”,等等。
检查2字节范围的值
u32最简单的形式是从Start开始获取4字节的块,用Mask作为掩码,所得的值再和Range作比较。下面是我们第一个例子所用的语法:
iptables -m u32 --u32 "Start&Mask=Range"
通常我们从感兴趣的最后的一个字节减去3而得到“start”值。所以,如果你想得到IP头的字节4和5(IPID),Start就是5-3=2。“Mask”用来 除去你不需要的部分(进行“与”操作),它是一个可以最大可以是0xFFFFFFFF的bitmask。为了得到我们的目标–字节4和5,我们要忽略字节2和3。我们就可以使用掩码0×0000FFFF。事实上我们选用一个短一点的,等值的0xFFFF来代替。
因此,检测2到256的IPID,iptable命令为:
iptables -m u32 --u32 "2&0xFFFF=0x2:0x0100"
从左到右读这个命令:“加载u32模块,对数据包执行以下u32检测:从字节2处获得4个字节(字节2和3是总长,字节4和5是IPID),应用掩码0×0000FFFF(它把前两个字节全部设为0,不改变后两个字节),看看所得的值(即IPID)是否在2和256之间;如果是这样,返回true,否则返回false。”
IPTables没有独立的模块来检查IPID,但这个跟“ip[2:2] >= 2 and ip[2:2] < = 256” 的tcpdump/bpf filter等价。
这个例子我舍去了动作,但你自己可以加上,如:
-j LOG --log-prefix "ID-in-2-256 "
-j DROP
或者其他的动作。你还可以加上其它的检测,正如我过一会所要做的。
Don提供了一个检验包总长大于或等于256的测试方法。包总长是IP头的字节2和3,所以我们的开始位置为3-3=0。因为我们要排除两个字节,掩码就是0xFFFF。最后得到的检测语句为:
iptables -m u32 --u32 "0&0xFFFF=0x100:0xFFFF"
它和以下的是一样的:
iptables -m length --length 256:65535
or the bpf filter
"len >= 256"
检查1字节范围的值
基本相同,只是用掩码0×000000FF(或者短一点的 0xFF)从u32的4字节中找出一个字节以适合我们的需求。比如说我想检测TTL区域,根据TTL小于3来找出对我们的机器使用tracerout命令的人。没错,有一个ttl的模块,但这只是让我们了解它也可以用u32来实现。
我想以IP头的第8个字节结束,所以开始位置为8-3=5。这就是检测语句:
iptables -m u32 --u32 "5&0xFF=0:3"
它和下面的是等价的:
iptables -m ttl --ttl-lt 4
or the bpf filter
"ip[8] < = 3"
一次考虑4个字节
为了检查完整的目的IP地址,我们将检查16-19字节。因为我们需要所有的4个字节,我们不需要掩码。假设目的地址是224.0.0.1:
iptables -m u32 --u32 "16=0xE0000001"
它和这个是一样的:
iptables -d 224.0.0.1/32
如果我们只考虑前3个字节(检查一个地址是否为一个特定C网段的一员),我们又要使用掩码了。我们选用的掩码是0xFFFFFF00,它扔掉了最后的八个bit。假如检查源地址(从12到15字节,尽管我们会用掩码来忽略第15字节)是否在192.168.15.0(0xC0A80F00)这个C类网段:
iptables -m u32 --u32 "12&0xFFFFFF00=0xC0A80F00"
它和下面的做相同的工作:
iptables -s 192.168.15.0/24
检查包头中靠前的字节
显然,如果我想看看TOS区域(IP头的字节1),我不能以1-3=-2开始。我们要做什么才能改为以0开始,找出我们需要的字节并且将它移到最后的位置以便于检测。这并不是我们实现的唯一方法,但它演示了一种马上就可以实现我们需求的技术。
为了得出TOS区域,我先让u32用0偏移量得到0-3字节。现在,我用掩码0×00FF0000找出字节1(在这数据块中的第二个字节)。我要把TOS值移到最右边的位置以方便比较。为了达到这个目的,我用来一个称为“按位右移”的技术。右移的符号是“>>”,它会把数据往右移动数字所指的位数。如果你对右移不熟悉,先看看来自的指南。
我用“>>16”把TOS往右移动2字节(或者16位)。既然我们把TOS放到了正确的位置,我们把它和0×08进行比较(最大吞吐量):
iptables -m u32 --u32 "0&0x00FF0000>>16=0x08"
它和这个等价:
iptables -m ttl --tos 8
检查单个bit
我想看一下“More Fragments”标记,iptables没有用于检查该标记的现成模块(-f 匹配第二个以后的分片,但我想匹配除最后一个以外的所有分片)。字节6保存了这个,所以我从偏移量3开始并且忽略3到5字节。通常可以使用掩码0×000000FF,但我还要忽略最后一个字节中的一些bits。我想保留的只是第3个bit(0010 0000),因此我选用的掩码是(0×00000020)。现在我有两个选择:把这个bit移到最后位置进行比较,或者是把它留在当前位置做对比。
我要把它右移5位,如下:
iptables -m u32 --u32 "3&0x20>>5=1"
如果采用把这个bit留在原来的位置方法,就得小心选择右边的比较值。如果这个bit是正值,那么比较值也应该为0×20。
iptables -m u32 --u32 "3&0x20=0x20"
如果“More Fragments”是作了标记的,用这两种方法都会返回正值。
Combining tests
组合几种检测
如果你需要对每个包检查多于一个的特征,在不同的检测之间加上:&&
前进到TCP头
这有点棘手。假设我要考虑一下TCP头的4-7字节(the TCP sequence number)。我们先用最简单的方法,再考虑一些其他改进的方法。
我们的第一个版本,假定IP头长度为20字节–通常这是个好的假设。我们的开始位置是TCP头的第4个字节,TCP头是紧接着IP头的。我们要检测的就是序列号是否为41(0×29),看起来就是这样:
iptables -m u32 --u32 "24=0x29"
对于IP头长度为20的包,这样做是没有问题的,但这还是有一些问题的。OK,我们一个个把他们搞掂。
首先,我们没有检查这个包是否为一正常的TCP包。这个是保存于IP头的字节9,所以我们要从字节6处开始抓取4个字节,丢弃字节6-8,检查它是否为6。下面这个新的规则首先检查它到底是不是TCP包,再检查序列号是否为41:
iptables -m u32 --u32 "6&0xFF=0x6 && 24=0x29"
第二个问题就是我们暂时忽略了IP头的长度。确实是这样,它的长度通常是20字节,但是用了IP options的话,它可以更长一些。
下一步,我们要得到IP头长度(在IP头有多少个4字节?通常是5)。我们把它乘以4,得到IP头的字节数。我们用这个数字表示需要跳过多少才能到达TCP头开始的地方,再跳过4字节即得到序列号。
为了得到头长度,我们需要把第一个字节:”0>>24″,但我们想要的只是低位的半位元组并且把它乘以4以得到头的真实字节数。做这个乘法,我们将要把它右移22位而不是24。做完这个,我们要用掩码0×3c代替以前用的0×0F。这个表达式就是:”0>>22&0×3C”。在没有options的IP头,这个表达式会返回20,正如我们所预料的。现在我们要告诉u32跳过数字对应的字节,用“@”操作符来做这件事。
iptables -m u32 --u32 "6&0xFF=0x6 && 0>>22&0x3C@4=0x29"
“@”得到它左边生成的数字并向前跳过相应的字节数(我们可以不止一次的做这样的事情–看下面TCP payload的部分)。它右边的4告诉u32取得4-7字节,但u32知道这是相当于它已经跳过了的20字节来说的。我们还是可以得到序列号,即使IP头因为options而增长了。hoho
最后的事情就是处理分片啦。当我们只是分析IP头的时候,这不会造成什么问题。IP设计为IP头只身不可分片,TCP头及application payload则是可能分片的。如果我们要处理第二个或更多的分片,我们在4-7字节可能是得不到序列号的。或许在TCP头的其他部分,甚至是在应用层的数据。
我们所要做的就是检查它是否为第一个分片(或者是一个没有分片的包,这里并不关心这个),这样我们查看的TCP头信息才是可靠的。我们检查大部分(丢弃前3个标记位)位于IP头6和7字节的fragment偏移量,确认它是0。表达式为:”4&0×1FFF=0″
最终得到的表达式(确定是否为TCP包,看看它是未分片包或第一个分片包,跳过IP头,检查TCP头的4-7字节是否等于41)是:
iptables -m u32 --u32 "6&0xFF=0x6 && 4&0x1FFF=0 && 0>>22&0x3C@4=0x29"
事实上,如果一个包是分片了的,我们还得考虑考虑一种情况。分片可能很小,我们需要检查的区域放到了后面的分片!在这种情况并不会出什么问题,因为每个IP包最小有68字节,即使IP头达到了它的最大值–60字节,TCP头的前8个字节也会包含在第一个分片。
当我们要对数据包作进一步的检查时,我们得依赖u32的一个特性。当我们试图请求某个超出了数据包大小的值时,u32会返回false。
检查UDP有效负荷的值
现在我们一起来考虑数据包的有效负荷,匹配UDP DNS查询的数据。在这里我们并不仅仅检查目的端口为53,我们还要检查有效负荷的字节2中的最高位,如果该位是设置了的话,这就是一个DNS查询。
我们先检查这是一个UDP包:"6&0xFF=17"
。现在,我们用已经熟悉了的方法检查它是否为第一个分片:"4&0x1FFF=0"
。
为了检查目的端口,我们从UDP头取得字节2和3(向前面的例子一样跳过IP头):"0>>22&0x3C@0&0xFFFF=53"
。
如果这个数据包通过以上检查,我们回去检查有效负荷(记住我们要跳过可变长度的IP头和8字节的UDP头"0>>22&0x3C@8 ..."
)来确保这个一个DNS查询而不是一个响应。为了得到字节2的高位,我要用偏移量8来获取头4个字节的有效负荷,并且右移15位,把Query位放到最低位置,再弃去其它的位,然后使用掩码0×01:"0>>22&0x3C@8>>15&0x01=1"
。
最后得到的语句就是:
iptables -m u32 --u32 "6&0xFF=17 && 4&0x1FFF=0 && 0>>22&0x3C@0&0xFFFF=53 && 0>>22&0x3C@8>>15&0x01=1"
噢,我们看到的有用信息太少了,垃圾成分太多;-)注意我们只使用了u32来实现整个事情;我们可以把”udp包”,”第一个/未分片包”和”53端口”这些检查用其它的模块来完成,最后得到这个可读性比较强的版本:
iptables -p udp --dport 53 \! -f -m u32 --u32 "0>>22&0x3C@8>>15&0x01=1"
原文出处: