本以为Android手机就和Linux一样改Host会比较方便,然而发现权限是个问题,很大的问题。有了这个契机,正好捣鼓一下Android提供的VPNService这个api,直接本地拦截DNS报文,然后根据host污染掉再直接回复。
当然,这篇文章作为前置,跟Android没半毛钱关系,只单纯的涉及DNS协议。
写在前面
- DNS协议详解及报文格式分析
- 目的是加深对DNS协议的理解;最终目标是实现Android手机无Root下Host解析;现有目标是实现本地DNS A查询的答复(一般更多的使用的是域名解析功能,同时现在基本上使用的是IPv4)。
思考
- 现在的思路如下:
- 简单起见,先不做任何处理 - 客户端来了DNS查询请求,转给服务器; 服务器来了回复,直接转给客户端。
- 考虑到只为本地服务,可直接采用DNS报文的ID作为Key,通过HashMap来保存DNS查询记录。服务器回复后查询Map匹配客户端端口。
- 再拓展一下,可以在客户端来了DNS查询请求后直接构造回复污染DNS。
实现
接收到UDP报文的处理:
udpSocket.receive(packet);
DnsHeader header = new DnsHeader(receMsgs, 0);
Short dnsId = header.getID();
// 如果是来自客户端的, 直接回复或转发给DNS服务器
if (!dnsHost.equals(packet.getAddress())) {
ByteBuffer buffer = ByteBuffer.wrap(receMsgs);
DnsPacket dnsPacket = DnsPacket.FromBytes(buffer);
Question q = dnsPacket.Questions[0];
System.out.printf("查询的地址是: %s \r\n", q.Domain);
String ipAddr = null;
// 如果已有设定, 直接构造回复
if ((ipAddr = domainIpMap.get(q.Domain)) != null) {
createDNSResponseToAQuery(dnsPacket, packet, ipAddr);
DatagramPacket sendPacket = new DatagramPacket(receMsgs, dnsPacket.Size,
packet.getSocketAddress());
udpSocket.send(sendPacket);
} else {
// 如果没有设定, 转发服务器
QueryInfo info = new QueryInfo();
info.socketAddr = packet.getSocketAddress();
info.lastnanos = System.nanoTime();
queryMap.put(dnsId, info);
DatagramPacket sendPacket = new DatagramPacket(receMsgs, packet.getLength(), dnsHost, 53);
udpSocket.send(sendPacket);
}
} else {
// 收到来自服务器的包,直接转发给客户端
// System.out.println("收到来自服务器的消息, 会话标识为: " + dnsId);
if (queryMap.containsKey(dnsId)) {
// System.out.println("存在该会话标识, 返回结果");
DatagramPacket sendPacket = new DatagramPacket(receMsgs, packet.getLength(),
queryMap.get(dnsId).socketAddr);
udpSocket.send(sendPacket);
queryMap.remove(dnsId);
}
}
构造DNS答复的方法:
public void createDNSResponseToAQuery(DnsPacket dnsPacket, DatagramPacket packet, String ipAddr) {
Question question = dnsPacket.Questions[0];
dnsPacket.Header.setResourceCount((short) 1);
dnsPacket.Header.setAResourceCount((short) 0);
dnsPacket.Header.setEResourceCount((short) 0);
ResourcePointer rPointer = new ResourcePointer(packet.getData(), question.Offset() + question.Length());
rPointer.setDomain((short) 0xC00C);
rPointer.setType(question.Type);
rPointer.setClass(question.Class);
rPointer.setTTL(300);
rPointer.setDataLength((short) 4);
rPointer.setIP(CommonMethods.ipStringToInt(ipAddr));
dnsPacket.Size = 12 + question.Length() + 16;
}