最近思考两个事情,一个是DNS劫持的升级,以往的方法失效了,另一个是铁通劫持了百度统计(hm.baidu.com)造成几乎全网的页面都被插广告。
劫持的原理都是抢先发送IP包,这样TTL一般会与正确的网站发来的数据不同。据抓包观察,几乎所有的劫持都没智能到使用伪造的正确TTL。大部分都是没有改TTL,抓到的是固定的,高级一点的如目前的DNS劫持会使用随机TTL。
很容易想到ping一下目标服务器,拿到正确的TTL,然后在路由器里用iptables的ttl模块做限制。但这样需要针对每个ip手动去ping出一个TTL。并且有时候TCP与UDP走的路由数并不一样,不是一个固定的值,而是一个范围,那么想要得到相对准确的范围就要手动去抓包。
所以我的想法是通过编程自动地进行这个工作,比如写一个iptables内核模块。
由于TTL是一个范围而不是定值,这个模块应该能足够地聪明,能够通过收集各个TTL出现的频率来计算出一个最佳的范围。这个算法的设计还是有点意(jiu1)思(jie2)的。
目前实验中遇到与解决或未解决的典型问题记录如下:
1、初始范围如何设定,不限制还是给一个+-5的范围。+-5的话在优酷的案例会失败(优酷的TCP第四个包会有两台主机重复发送,TTL初始值不一样,下面会详解)
2、线路路由调整。如果TTL在之前的计算中已经锁定,则路由调整后则没法再访问,除非重新计算。
3、如果为了解决问题2而增加计算频率,如何平衡性能损失。
4、大型网站具有网关结构,表现在IMCP、UDP、TCP的SYN等无连接数据由防火墙处理,建立连接后的数据包转发至真正的服务器处理。例如百度,SYN包的TTL会比数据包大2-4,这个还可以通过范围设定解决,更蛋疼的是例如优酷的案例,防火墙设定的TTL初值是256,而数据包的TTL初值是64,相差这么大但是哪个都不能被过滤,目前我的做法是区分无连接与有连接。
5、正常的随机TTL情况,例如114.114.114.114。如何与劫持包的区分。
开发内核模块比较麻烦,openwrt自带一个iptables-mod-lua模块,可以在内核里写lua脚本处理封包,可以先用这个做实验。不过似乎有一些限制,例如我写的时候没法使用table.insert、table.sort这些库函数(阅读了一下源代码,虽然PacketScript有编译string lib与table lib,但是在初始化state时没有(忘了?)调用lua open把这些库注册到全局变量中)。
贴出实验代码,以后还会更新:
iplist = {} function process_packet(p) local saddr = p:data(packet_ip):saddr():get() local ttl = p:data(packet_ip):ttl():get() local tcp = p:data(packet_ip):data(packet_tcp) if tcp and not tcp:flags():get(15) then saddr = saddr .. ':tcp' end -- i dont know why syn become bit 15 if not iplist[saddr] then print(saddr .. ',' .. ttl) iplist[saddr] = {count = 0, lb = 0, ub = 255, map = {}} end local data = iplist[saddr] if not data.map[ttl] then data.map[ttl] = 0 end data.map[ttl] = data.map[ttl] + 1 data.count = data.count + 1 if data.count == 100 or data.count % 1000 == 0 then if (data.count > 1000000) then data.count = 0 for i, v in pairs(data.map) do data.map[i] = data.map[i] / 10 data.count = data.count + data.map[i] end end local zero = 0 local flag = true local block function ttlval(i) if not data.map[i] then return 0 end if data.map[i] < zero then return 0 end return data.map[i] - zero end while flag == true do local blocks = {} local i = 0 flag = false while i <= 255 do while i <= 255 and ttlval(i) == 0 do i = i + 1 end if i > 255 then break end block = {lb = i, ub = i, count = ttlval(i)} i = i + 1 while ttlval(i) > 0 or ttlval(i + 1) > 0 or ttlval(i + 2) > 0 do --连续3个ttl为空认为可分块 block.count = block.count + ttlval(i) i = i + 1 end block.ub = i - 1 blocks[#blocks + 1] = block end block = nil for i = 1, #blocks do if not block then block = blocks[i] end if blocks[i].count > block.count then block = blocks[i] end end if block.ub - block.lb > 9 then local av = block.count / (block.ub - block.lb + 1) for i = block.lb, block.ub do if ttlval(i) > av * 2 then zero = av flag = true break end end end end data.lb = block.lb data.ub = block.ub print(saddr .. ',' .. data.lb .. '-' .. data.ub) end if (ttl < data.lb or ttl > data.ub) then return NF_DROP end return XT_CONTINUE end
使用很简单:iptables -t mangle -A PREROUTING -i pppoe-wan -j LUA –script ttl.lua
同为铁通用户,这两天因为TCP劫持和铁通吵。觉得可能没必要每次都计算TTL值,我从抓包数据中看到的现象是TTL有一个突变。
用ttl模块过滤一个固定的ttl基本能解决问题,不过还是有少量的漏网。目前我觉得可能更好的方法是用string模块匹配劫持跳转的部分字符串。不过这两天铁通网到期了,暂时没法测试了。
哈哈 有兴趣欢迎来测试