昨天晚上,为了解决运营商的网络挟持问题,我苦看了很久 iptables 的文档:iptables(8) - Linux man page
在文档当中,其中的 u32
值得我们去学习。下面就来简单讲解一下这个匹配方式。
u32 简介
U32 allows you to extract quantities of up to 4 bytes from a packet, AND them with specified masks, shift them by specified amounts and test whether the results are in any of a set of specified ranges.
u32 允许你从一个包中提取 4 个字节,然后通过指定的掩码,偏移一定数量的单位到某个位置然后比对这个数值是否为某个值或者是否在某个区间中。
u32 默认是从 ip 头开始截取数据,当然,我们可以通过适当的掩码以及右移,来移动到 TCP 头又或者是 SSL 头中去,这都取决与你的选择。
u32 入门
首先,我们先来认识以下一个普通的 packet 包,下面我将用 wireshark 随便抓一个包进行演示。
(MAC 帧头)
(IP 头)
(TCP 头)
正如我上面引入所说, u32 是从 IP 头开始取,因此我们完全可以跳过 MAC 帧头,因此里面的东西对我们实际的应用并没有什么用。我们主要关注下面的 IP 头以及 TCP 头即可。
为了方便大家看,我把 IP 头以及 TCP 头的东西手写出来
45 00 00 3c 00 00 40 00 31 06 ef 34 79 29 59 34 c0 a8 c7 81
(IP 头)
00 50 95 3c 8d 7f 52 ac 69 15 33 be a0 12 71 20 cd dc 00 00 02 04 05 14 04 02 08 0a 08 c8 62 fa 00 1c 30 a1 01 03 03 07
(TCP 头)
u32 基础
iptables -m u32 --u32 "Start&Mask=Range"
上面就是一个 u32 简单的语法,其中:
Start
表示开始取值的位置 / 字节
Mask
表示掩码
Range
表示匹配的范围,可以是一个使用 :
分割开的区间,也可以只是一个数
如果我们需要同时检查多个规则,规则之间可以通过 &&
隔开
小技巧:通常我们可以通过(我们想要的字节的末位位置)减去 3 的方法来得到我们开始取值的位置
u32 操作符
在上面的基础中,我们已经使用到了一个操作符 &
,此外,总共有四个操作符可供使用。
&
按位与操作符:过滤出我们需要的四个字节(最多),或者只要其中一个字节
<<
左移操作符: 把操作数的各二进位全部左移若干位,高位丢弃,低位补 0
>>
右移操作符:把操作数的各二进位全部右移若干位,低位丢弃,高位补 0(或 1)
@
向前推进操作符:向前推进若干的字节数
取 TCP 头数据
由于 IP 头的长度未知,一般来所是 20 个字节,但是如果有 IP options
的话, IP 头的长度会更长,因此我们无法直接取第 20 个字节之后的数据,来跳到 TCP 头。
因此这里我们需要使用到 @
运算符。
我们直接看一个例子(仅做示范):
0>>22&0x3C@ 4 &0xFF=0x29
0>>22
,它的含义是取 IP 报头的第 0 偏移处的 4 字节值一共 32 位,右移 22 位得到 10 位的数值,接下来和 0x3C
即二进制的 111100
按位与,得到上述的 IP 头长度,然后使用 @
运算符直接推进到 TCP 头
取 TCP 头之后的数据(仅做示范)
0>>22&0x3C@12>>26&0x3C@ 4 &0xFF=0x29
实例:判断 TTL 是否在 50 到 60 之间
根据上文,本个 packet 的十六进制值是 31 ,位置是第 8 个字节(从 0 开始算)。
那么我们 Start
的字节就是 8-3=5
,我们要取的是最后的一个字节,所以掩码是 0x000000FF
或者使用等价的 0xFF
那么我们的代码就是 5&0xFF=0x32:0x3C
实例:检查来源 IP 是否为 121.41.89.52
我们先将 IP 地址 121.41.89.52
转换为数字地址 2032752948
然后将数字地址 2032752948
通过十进制转十六进制转换成 79 29 59 34
观察可知, 34
是 IP 头中的第 16 个字节,加上偏移是从 0 开始计算,那么 Start
的位置就应该是 16-4=12
由于我们现在需要的是整个取出来的 4 个字节,因此我们的掩码为 0xFFFFFFFF
或者不填
因此,我们的代码是 12=0x79295934
实例:检查目标端口是否为 38204
同样,将 38204
转换成十六进制,那就是 953c
目标端口在 TCP 头中,观察,可以 3c
在 TCP 头中的第 3 个字节,因此, Start
的位置是 3-3=0
结合上面跳到 TCP 头的代码,现在我们的代码是
0>>22&0x3C@0&0xFFFF=0x953C
实例:检查 UDP 有效负荷的值
等于号后面的值其实不用十六进制也可以,直接使用十进制。
6&0xFF=17 && 4&0x1FFF=0 && 0>>22&0x3C@0&0xFFFF=53 && 0>>22&0x3C@8>>15&0x01=1
6&0xFF=17
判断这个是否是一个 UDP 包
4&0x1FFF=0
判断这个包是否是第一个分片
0>>22&0x3C@0&0xFFFF=53
判断目标端口是否为 53
0>>22&0x3C@8>>15&0x01=1
为了得到字节 2 的高位,用偏移量 8 来获取头 4 个字节的有效负荷,并且右移 15 位,把 Query 位放到最低位置,再弃去其它的位,然后使用掩码 0×01
参考资料
How to take down SSLv3 in your network using iptables firewall? (POODLE)
写在最后
由于本人学习 u32 的时间不长,上面的也是我凭感觉写出来的,如果有纰漏的地方,敬请指出!