|
本文是我正在写的《翻墙路由器的原理与实现》中的一部分,全文链接:https://docs.google.com/document ... SD1CZV4XY1OMG8/edit
目前写到了穿墙部分,基于OpenWRT路由器做了一些穿墙实验。第一部分关于DNS劫持的已经完成。摘录如下:
实验环境准备
穿墙比翻墙要复杂得多,但也有意思得多。本章节以实验为主。实验的设备是家庭用的路由器,我用的是水星4530R。需要有公网IP。刷的操作系统是OpenWRT Attitude Adjustment 12.09 rc-1版本。使用的包有:
NetfilterQueue(https://github.com/fqrouter/fqrouter 中有)
bind-dig
shadow
dpkt (不是OpenWRT的包,是python的 http://dpkt.googlecode.com/files/dpkt-1.7.tar.gz )
本文并不打算详细讲解实验环境的设置。对于有OpenWRT编译和刷机经验的朋友可能可以按照我的叙述重建出实验环境来。整个实验的关键在于
公网上的ip地址
Linux
python
python访问netfilter queue的库
如果你有一台公网上的Linux机器,安装了Python和Python的NetfilterQueue,也可以进行同样的实验。
如果你使用的是路由器,需要验证你有公网ip。这个可以访问ifconfig.me来证实。其次要保证路由器是OpenWRT的并且有足够的空间安装python-mini。到这里基本上都和普通的OpenWRT刷机没有什么两样。重点在于:
安装Python的NetfilterQueue
OpenWRT提供了NetfilterQueue的C的库。但是使用C来做实验太笨重了。所以我选择了Python。但是Python的NetfilterQueue的库没有在OpenWRT中。下载https://github.com/fqrouter/fqrouter 解压后可以得到一个名字叫fqrouter的目录。然后给feeds.conf添加一行src-link fqrouter /opt/fqrouter/package。把/opt/fqrouter替换为你解压的目录。然后scripts/feeds update -a,再执行scripts/feeds install python-netfilterqueue就添加好了。然后在make menuconfig中选择Languages=>Python=>python-netfilterqueue。
有了这个库就赋予了我们使用Python任意抓包,修改包和发包的能力。在OpenWRT上,除了python没有第二种脚本语言可以如此简单地获得这些能力。
安装Python的dpkt
能够抓取和发送IP包之后,第二个头疼的问题是如何解析和构造任意的IP包。Python有一个库叫dpkt可以帮我们很好地完成这项任务。这是我们选择Python做实验的第二个重要理由。
在路由器上直接下载http://dpkt.googlecode.com/files/dpkt-1.7.tar.gz,然后解压缩,拷贝其中的dpkt目录到/usr/lib/python2.7/site-packages下。
DNS劫持观测
我们要做的第一个实验是用python代码观测到DNS劫持的全过程。
应用层观测
dig是DNS的客户端,可以很方便地构造出我们想要的DNS请求。dig @8.8.8.8 twitter.com。可以得到相应如下
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 5494
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;twitter.com. IN A
;; ANSWER SECTION:
twitter.com. 4666 IN A 59.24.3.173
;; Query time: 110 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Sun Jan 13 13:22:10 2013
;; MSG SIZE rcvd: 45
可以很清楚地看到我们得到的错误答案59.24.3.173。
抓包观测
使用iptables我们可以让特定的IP包经过应用层的代码,从而使得我们用python观测DNS查询过程提供了可能。代码如下,保存文件名dns_hijacking_obversation.py(https://gist.github.com/4524294):
from netfilterqueue import NetfilterQueue
import subprocess
import signal
def observe_dns_hijacking(nfqueue_element):
print('packet past through me')
nfqueue_element.accept()
nfqueue = NetfilterQueue()
nfqueue.bind(0, observe_dns_hijacking)
def clean_up(*args):
subprocess.call('iptables -D OUTPUT -p udp --dst 8.8.8.8 -j QUEUE', shell=True)
subprocess.call('iptables -D INPUT -p udp --src 8.8.8.8 -j QUEUE', shell=True)
signal.signal(signal.SIGINT, clean_up)
try:
subprocess.call('iptables -I INPUT -p udp --src 8.8.8.8 -j QUEUE', shell=True)
subprocess.call('iptables -I OUTPUT -p udp --dst 8.8.8.8 -j QUEUE', shell=True)
print('running..')
nfqueue.run()
except KeyboardInterrupt:
print('bye')
执行python dns_hijacking_observation.py,再使用dig @8.8.8.8 twitter.com应该可以看到package past through me。这就说明DNS的请求和答案都经过了python代码了。
上一步主要是验证NetfilterQueue是不是工作正常。这一步则要靠dpkt的了。代码如下,文件名相同(https://gist.github.com/4524299):
from netfilterqueue import NetfilterQueue
import subprocess
import signal
import dpkt
import traceback
import socket
def observe_dns_hijacking(nfqueue_element):
try:
ip_packet = dpkt.ip.IP(nfqueue_element.get_payload())
dns_packet = dpkt.dns.DNS(ip_packet.udp.data)
print(repr(dns_packet))
for answer in dns_packet.an:
print(socket.inet_ntoa(answer['rdata']))
nfqueue_element.accept()
except:
traceback.print_exc()
nfqueue_element.accept()
nfqueue = NetfilterQueue()
nfqueue.bind(0, observe_dns_hijacking)
def clean_up(*args):
subprocess.call('iptables -D OUTPUT -p udp --dst 8.8.8.8 -j QUEUE', shell=True)
subprocess.call('iptables -D INPUT -p udp --src 8.8.8.8 -j QUEUE', shell=True)
signal.signal(signal.SIGINT, clean_up)
try:
subprocess.call('iptables -I INPUT -p udp --src 8.8.8.8 -j QUEUE', shell=True)
subprocess.call('iptables -I OUTPUT -p udp --dst 8.8.8.8 -j QUEUE', shell=True)
print('running..')
nfqueue.run()
except KeyboardInterrupt:
print('bye')
执行python dns_hijacking_observation.py,再使用dig @8.8.8.8 twitter.com应该可以看到类似如下的输出:
DNS(ar=[RR(type=41, cls=4096)], qd=[Q(name='twitter.com')], id=8613, op=288)
DNS(an=[RR(name='twitter.com', rdata=';\x18\x03\xad', ttl=19150)], qd=[Q(name='twitter.com')], id=8613, op=33152)
59.24.3.173
DNS(an=[RR(name='twitter.com', rdata='\xc7;\x95\xe6', ttl=27), RR(name='twitter.com', rdata='\xc7;\x96\x07', ttl=27), RR(name='twitter.com', rdata="\xc7;\x96'", ttl=27)], ar=[RR(type=41, cls=512)], qd=[Q(name='twitter.com')], id=8613, op=33152)
199.59.149.230
199.59.150.7
199.59.150.39
可以看到我们发出去了一个包,收到了两个包。其中第一个收到的包是GFW发回来的错误答案,第二个包才是正确的答案。但是由于dig只取第一个返回的答案,所以我们实际看到的解析结果是错误的。
|
|