通过智能TTL锁定防止ISP广告、DNS劫持的一些想法

最近思考两个事情,一个是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

3 thoughts on “通过智能TTL锁定防止ISP广告、DNS劫持的一些想法

  1. Silver

    同为铁通用户,这两天因为TCP劫持和铁通吵。觉得可能没必要每次都计算TTL值,我从抓包数据中看到的现象是TTL有一个突变。

    Reply
    1. gmsj0001 Post author

      用ttl模块过滤一个固定的ttl基本能解决问题,不过还是有少量的漏网。目前我觉得可能更好的方法是用string模块匹配劫持跳转的部分字符串。不过这两天铁通网到期了,暂时没法测试了。

Leave a Reply

Your email address will not be published. Required fields are marked *

Using REAL email address will help you receive reply notifications.