劫持DNS是个很简单的工作,家用路由器基本都自带dnsmasq,直接加解析就行。
之前某次尝试劫持某视频App的广告接口解析到一个空的本地服务器上,发现该App使用了DnsPod的HttpDNS服务,所以传统的DNS劫持方案不好用。而EdgeRouter的DPI功能也没有对外开放墙一般的高级接口,所以这次用NAT来实现。
首先,原有的App广告流程如下:
图中的流程很传统,12为HttpDNS请求过程,34为广告资源拉取。没有画路由器,因为这里没有用到路由器的DNS转发功能,所以路由器不重要。
针对这个场景,如果想劫持HttpDNS Server的解析,就需要在终端和HttpDNS Server之间加上一个代理,图就变成了:
这里Hijack Server存在的前提是使用了非ssl/tls的传输协议。在HttpDNS的常规逻辑上,也不太好加常用的SSL,因为不会有CA给IP颁发证书。DnsPod的HttpDNS的一个参考地址是 http://119.29.29.29/d?dn= 。当然企业级自己预置证书这种玩法我就管不了了。应该不会有人问为什么HttpDNS不用域名来设置目标服务器地址吧。
从实际角度来说,图应该变成下边这个样子,我没有刻意地画路由、防火墙设备,只是拿个方框区分出所谓的LAN和WAN。
又回到常识性的逻辑上,1/4这个链路其实是不可能成立的,因为App这边的IP是写死的。在不修改App的前提下,图其实应该是这样的:
从路由/防火墙上配置规则,将App的HttpDNS Server的请求,转发到Hijack Server上。
所以在EdgeRouter上,我们要做的第一件事情是,DNAT。我的Hijack Server IP是192.168.1.13,路由器IP是192.168.1.1,待劫持的设备IP是192.168.1.151。我定义了两个组,一个是DnsPod HttpDNS的所有IP,一个是需要劫持的LAN IP。所以参考的配置如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
[edit]
root@ubnt# show firewall group address-group DNSPOD_HTTPDNS_SERVER
address 182.254.118.118
address 182.254.116.116
address 119.28.28.28
address 119.29.29.29
[edit]
root@ubnt# show firewall group address-group SRC_HIJACK_DNSPOD_HTTPDNS
address 192.168.1.151
[edit]
root@ubnt# show service nat rule 4108
description "hijack dnspod http dns"
destination {
group {
address-group DNSPOD_HTTPDNS_SERVER
}
port 80
}
inbound-interface eth0
inside-address {
address 192.168.1.1
port 80
}
log disable
protocol tcp
source {
group {
address-group SRC_HIJACK_DNSPOD_HTTPDNS
}
}
type destination
不出意料的,挂了。请求发出去一致没有响应,开日志发现,只有SYN包到了192.168.1.13上。原因是:在LAN里直接做DNAT,只是修改了目的地址,源地址没改,导致在同一个局域网下,1.13这台机器看到源IP是同网段,所以把ACK直接发给了源IP的机器,于是出现了下图:
解决方案就是再开一个MASQUERADE。配置如下:
root@ubnt# show service nat rule 5108
description "hijack dnspod http dns"
destination {
address 192.168.1.13
port 80
}
log disable
outbound-interface eth0
protocol tcp
source {
group {
address-group SRC_HIJACK_DNSPOD_HTTPDNS
}
}
type masquerade
1.13上,我用php写了一个简单的HttpDNS查询接口,预留了一个读配置文件的地方:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<?php
$upstream = [
'182.254.118.118',
'182.254.116.116',
'119.28.28.28',
'119.29.29.29',
];
$hijack = include(__DIR__ . '/hijack_config.php');
if (isset($hijack[$_GET['dn']])) {
$result = $hijack[$_GET['dn']];
if (!empty($_GET['ttl'])) {
$result .= ',3600';
}
echo $result;
exit;
}
$url = 'http://' . $upstream[array_rand($upstream)] . '/d?dn=' . $_GET['dn'];
if (!empty($_GET['ttl'])) {
$url .= '&ttl=' . $_GET['ttl'];
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_URL, $url);
echo curl_exec($ch);
curl_close($ch);
exit;
配置文件就是同目录下的 hijack_config.php,内容如下
<?php
return [
'aaa.bbb.com' => '192.168.1.13',
];
nginx配置也很简单
server {
listen 80 default_server;
root /opt/deploy/dnspod-httpdns;
rewrite ^/d$ /proxy.php break;
location ~ \.php$ {
include fastcgi-php.conf;
fastcgi_pass unix:/tmp/php-fpm.sock;
}
}