我们在前面提到了 GFW 对 DNS 劫持和污染的根源是在向境外 DNS 发起解析请求时,抢先返回虚假的 IP 信息给解析器。根据观察分析,GFW 伪造的虚假信息格式是非常固定的,甚至可以说是非常便于识别和拦截的。我们只要利用 iptables 的过滤规则,就可以很轻松的丢弃这些污染信息。
我们在前面也说了,境外 DNS 的正确信息不是不会返回,只是被 GFW 的虚假信息抢先占了位置导致不被解析器接受而已,而防火墙是一个在解析器之前的关卡,只要在这里丢弃了污染信息,那么境外 DNS 返回的正确信息就可以作为第一个到达的信息顺利被解析器接受了。
工欲善其事,必先利其器,我们要想过滤 GFW 的劫持信息,就要先了解这个数据的格式。在网络数据抓取上,业界公认的神器莫过于WireShark了,启动 wireshare 之后选择网卡,在过滤项中写 port 53,表示我们只查看数据信息中包含 53 端口的信息。为什么是 53 端口? 因为 DNS 服务器的查询端口是 53,然后我们通过 Dig 命令,指定使用 8.8.8.8 去查询 twitter.com 的 A 记录。可以看到如下的查询和返回信息:
虚假信息和正确信息混杂
可以看到,GFW 凭借自己的位置优势 (离我们近) 抢先返回了两个虚假的 IP 地址给我们,但后面 Google DNS 的正确信息也返回了,只是没有被解析器接受,我们要干掉的就是前面两条错误信息。观察多次之后我们发现这种信息的格式是完全一样的,区别仅仅是 IP 地址会在一个数组范围内变化: 可以看到 GFW 伪造的这个虚假信息非常粗糙,一般 DNS 都会返回一些 Additional 信息,例如告诉你这个域名的
污染信息实例
权威名称服务器地址之类的。GFW 的目的就是污染,只要把错误 IP 传达给你就完了,所以他整个消息包在虚假 IP 之后就没了,还真是精打细算节约流量呢。
我目前搜集和统计到的 GFW 用于 DNS 劫持的污染 IP 有 48 个,如下表所示:
118.5.49.6
128.121.126.139
159.106.121.75
169.132.13.103
188.5.4.96
189.163.17.5
192.67.198.6
197.4.4.12
202.106.1.2
202.181.7.85
203.161.230.171
203.98.7.65
207.12.88.98
208.56.31.43
209.145.54.50
209.220.30.174
209.36.73.33
209.85.229.138
211.94.66.147
213.169.251.35
216.221.188.182
216.234.179.13
23.89.5.60
243.185.187.39
249.129.46.48
253.157.14.165
37.61.54.158
4.36.66.178
46.82.174.68
49.2.123.56
54.76.135.1
59.24.3.173
64.33.88.161
64.33.99.47
64.66.163.251
65.104.202.252
65.160.219.113
66.45.252.237
72.14.205.104
72.14.205.99
74.125.127.102
74.125.155.102
74.125.39.102
74.125.39.113
77.4.7.92
78.16.49.15
8.7.198.45
93.46.8.89
转换成 16 进制就是:
042442 B2
0807 C62D
1759053 C
253 D369E
2 E52AE44
31027 B38
364 C8701
3 B1803AD
402158 A1
4021632 F
4042 A3FB
4168 CAFC
41 A0DB71
422 DFCED
480 ECD63
480 ECD68
4 A7D2766
4 A7D2771
4 A7D7F66
4 A7D9B66
4 D04075C
4 E10310F
5 D2E0859
76053106
80797 E8B
9 F6A794B
A9840D67
BC050460
BDA31105
C043C606
C504040C
CA6A0102
CAB50755
CB620741
CBA1E6AB
CF0C5862
D0381F2B
D1244921
D155E58A
D1913632
D1DC1EAE
D35E4293
D5A9FB23
D8DDBCB6
D8EAB30D
F3B9BB27
F9812E30
FD9D0EA5
为什么这里要把 IP 地址转换成 16 进制呢? 因为你知道整个计算机是基于 2 进制的,从上面的抓包你也可以看到实际的 IP 地址都是 16 进制的,我们过滤这些地址要用的 iptables 的 u32 模块也只认识 16 进制的数字。
u32 模块是 iptables 的一个扩展包,他允许你从一个 IP 数据包中抓取 4 个字节 (32 比特) 的数据,然后对这个 4 个字节的数据进行数值分析,在符合条件之后交给 iptables 进行各种处理。而 IP 地址刚好就是 4 个字节,那么我们只需要在 DNS 返回信息的 IP 包中提取出 IP 地址信息,然后和上面的这个列表对比,如果发现匹配就丢弃这个数据就可以了。
我们用到的完整命令如下:
命令有 5 条是因为 u32 模块一次最多允许我们匹配 10 个数据,具体格式的分析我们只需要看一条就够了,以第一条为例详细说明:
1. iptables -t mangle -I PREROUTING,这些和上一节是一样的,不重复了
2. -p udp,表示只分析 UDP 协议的数据,因为 DNS 查询默认都是 UDP 协议的
3. –sport 53,表示源端口是 53 的数据,因为 DNS 服务器返回的数据都是从它的 53 端口返回的
4. -m u32 –u32,这里表示使用 u32 模块,这是个标准命令格式
在开始后面的 u32 模块分析前,我们先补充介绍一下 DNS 响应包的数据组成。首先这是一个 IP 包,就有 IP 报头,另外这是 UDP 协议的,就有 UDP 报头,后面的 UDP 数据就是 DNS 服务器的响应数据了,所以格式是这样的:
[ IP_Header ]::[ UDP_Header ]::[ UDP_Data == DNS_Response ]
u32 模块部分的匹配代码是这样的:
0&0 x0F000000 =0 x05000000 && 22&0 xFFFF@16 =0 x4A7D7F66
• 0
第一个 0 表示从第 0 个字节开始抓 4 个字节,计算机世界一直都是从 0 开始数的。
• &0x0F000000
“&” 表示把前面抓到的 4 个字节和 “&” 后面的掩码进行 “与” 操作,这个就算是文科生也应该在计算机基础课上学过,不解释了。0x0F000000 就是这个掩码了,这表示其他位全部置 0,只保留第 0 字节的低 4 位。
• =0x05000000
“=0x05000000” 表示判断前面进行完与操作的数是不是等于 0x05000000. 如果你知道 IP 包的数据格式,就知道第 0 个字节的低 4 位表示的是 IP 报头的长度,一般来说都是 20 字节但也有特例。这里我们要确定这个报头没有什么特殊格式,长度确实是 20,为什么要判断这个长度后面会解释。
• 22&0xFFFF
这个表示从第 22 个字节开始取 4 个字节 (22∼25),和掩码 0xFFFF(0x0000FFFF) 与操作,也就是取 24,25
字节的内容,得到一个数 X。你抓包分析看一下就会发现,24,25 字节对应的数字是 UDP_Header 里表示 UDP 包的长度,也就是 UDP_Header UDP_Data 的总长度。
• @
这个 @ 的作用非常大,一定要理解。@ 表示从 IP 包的最开始往后跳跃 X 个字节,具体跳跃多少呢? 就是 @ 前面的数值了。我们说了 @ 前面的一个与操作得到了 UDP 包的长度,也就是说这里我们要从 0 开始往后跳过 UDP 包的长度。整个 IP 包的长度是 IP 头长度 (20) UDP 包的长度 (X),现在我们从 0 开始跳过了 UDP 包的长度 (X),就位于了整个 IP 包的最末再往前倒退 20 个字节的位置。
• 16
我们前面的抓包已经看到了,虚假的 IP 信息刚好位于整个 IP 包的最后 4 个字节,我们现在已经处在最末 -20 个字节的位置了,那么从这个位置开始数,抓第 16 个字节开始的 4 个字节就行了,也就是 16∼19 字节,为什么不是 17∼20? 我们是从第 0 个字节开始算的,别忘了这点。
后面的 -j DROP 就很好理解了,直接丢弃。在我们的防火墙中应用以上规则,GFW 的对境外 DNS 查询的劫持信息就被过滤掉了,剩下的就是正确的解析信息了。
但是要记住! 只有对境外 DNS 查询的污染包是 GFW 发出的,也只有这个情况下这个数据格式才是和我们的命令匹配的。虽然这可以解决大部分问题了,但有的时候我们还是需要境内 DNS 境外 DNS 组合的方式,因为境内 DNS 的速度比较快,而且他们对一些大网站的节点解析都比较适合我们。
但是我们在使用这些境内 DNS 面对的问题不是 GFW 的劫持,而是他们给出的原始数据就是被污染(被劫持后污染) 的。这样的结果是,他们给出的 DNS 响应包会因为服务器配置的格式不同而千差万别,而且基本确定的是 IP 都不是在最末的 4 字节上,因为正常的响应包末尾一般都是 Additional RRs 数据。我们想要使用境内 DNS 获得高速的解析又不想被污染,就要对境内 DNS 的响应数据也做过滤,那么这种 IP 位置不固定的响应包要如何过滤呢? 这里我们就要用到更强大的 string 模块了。