竹磬网-邵珠庆の日记 生命只有一次,你可以用它来做些更多伟大的事情–Make the world a little better and easier


28月/170

记一次对 G-F-W 防火墙的探究

发布在 邵珠庆

突然朋友传了一个数据包给我,说他的openVPN连不上了。

抓包发现刚一握手结束便收到了一个RST包,导致一直连不上。我打开数据包,发现果然如此:

可以看到,三次握手刚完毕,客户端发送第一个控制消息到服务端,便收到了服务端发送的RST数据包,一直如此。

应该是有中间设备搞的鬼,于是我又到服务器端抓取了些数据:

果然的,服务器也收到了RST数据包,于是两者的连接便断开了。

再仔细分析下客户端的RST数据包:

IP包的序号是12345,TTL是120。再看正常的数据包:

IP包的序号是0,TTL是46。很明显RST数据包的TTL比正常的要大,而且每次RST的IP序号都是12345,应该是GFW没错了。

正常情况下初始的TTL是64,正常收到的TTL是46,跳数是15,说明我的电脑到服务器之间经过了15个路由设备。

为了证明这点,查看服务端收到的正常数据包:

服务器收到的TTL是50,因为我的电脑还要经过内部的一个路由器,所以TTL差了1。

同时查看服务端RST数据包的TTL值:

TTL值为117,因此得到的信息如下:

客户端->服务器:15、GWF->服务器:117、GFW->客户端:120。

假设GFW每次发送的TTL值都固定不变且为x,则有:x-117+x-120=15;得x=126。

所以GFW和我的电脑的跳数应该是6:

图示的应该就是GFW的位置。

接下来问题来了,她是怎么识别出openVPN流量的呢?

我猜测是根据数据包的特征来识别的,那么我单独发送单个数据包,应该也会返回RST数据,根据这一理论,我用scapy发送了单个的数据包,内容和三次握手之后客户端发送的第一个数据包一样,但结果是失望的,并没有收到RST数据包。

于是进一步猜测,TCP连接之后再发送相应的数据包,应该能收到RST,于是又根据这一理论,写下了如下代码:

from scapy.all import *
vpn_payload = "\x00\x0e\x38\x24\x5d\x21\xaa\x3a\x11\x2f\xb3\x00\x00\x00\x00\x00"
conf.verb = 0
vpn_s = IP(dst="yovey.me",id=12345)/TCP(sport=58620,dport=1194,flags="S",seq=0)
print "sending syn"
vpn_s.show()
ans0,unans0 = sr(vpn_s)
print "recv packet,seq = ",ans0[0][1].seq
ans0[0][1].show()
vpn_sa = IP(dst="yovey.me",id=12346)/TCP(sport=58620,dport=1194,flags="A",seq=1,ack=ans0[0][1].seq+1)
print "sending ack"
vpn_sa.show()
ans1,unasn1 = sr(vpn_sa,timeout=1)
vpn = IP(dst="yovey.me",id=12347)/TCP(sport=58620,dport=1194,flags="PA",seq=1,ack=ans0[0][1].seq+1)/vpn_payload
print "sending vpn payload"
ans2,unasn2 = sr(vpn)
ans2[0][1].show()

运行程序,还是没有收到RST数据包。

于是我打开tcpdump,抓取了发包过程的数据包,发现了问题:

在服务器返回syn+ack之后,客户端居然发送了RST到服务器,导致连接断开。经过短暂的思考,才明白客户端网卡在收到来自服务器的syn+ack之后,发现并没有进程在监听该数据包的端口,于是发送了RST数据包给服务器。

必须让客户端不发送RST数据包才行,想到可以通过iptable来过滤数据包,于是在iptable中添加如下规则:

iptables -t filter -A OUTPUT -p tcp --tcp-flags RST RST -j DROP

再运行程序,一切都在计划之中:

还是熟悉的IP序号,还是熟悉的TTL,看来GFW已经可以根据连接来识别流量了,真是下了血本啊。

想到建立连接,我立马联想到不用建立连接的UDP,是不是UDP数据只需要根据单个数据包就能识别了?于是将服务器配置成UDP模式,再次打开openVPN,特么的居然连上了!于是问题解决了,将配置改成UDP就能正常连接了。