简介
因为最近工作中有个需求是需要记录终端的dns请求,并进行对比和记录,这里研究了一下,使用python写了一个简单的本机dns请求的监控脚本。脚本设置后可以监控本机对特定域名的请求记录,以及记录这些dns请求的响应内容。用到了python一个看起来应用可以很广的(据我搜索的结果,可以使用这个模块进行本机的, 好像并没有什么明显的意义, DNS欺骗)模块——scapy。本文只使用了这个模块的一小部分功能。
Scapy
Scapy是python一个非常强大的应用框架,可以非常方便的实现数据包的发送,嗅探,拆解和伪造(原文是forge,想了好几个词,不太确定要用生成和捏造...orz好像更奇怪了)数据包,可以用于编写对网络探测,扫描和攻击的工具。官方的介绍文档在这里:
https:// scapy.readthedocs.io/en /latest/ --scapy official documentation
我在自己的python 3.7环境下使用pip安装了scapy[basic]
pip install --pre scapy[basic]
这里因为时间比较赶,直接使用了官方的推荐安装方式(这种做法以后还是少一点好),之后就可以愉快的使用命令行中的wireshark了。另外需要注意的是,我使用的是win10系统,并且安装了npcap,所以其他版本的捕包软件不一定能和脚本兼容,所以运行前一定先检查scpay能在当前环境下抓取到什么格式的报文,如果报文格式不对,脚本运行不会出结果或直接报错,请知悉。
开始干活!
开始之前有几点需要注意的,scapy会使用wincap捕捉数据包,所以开始前确保电脑是安装了winpcap或npcap的,不然嗅探是无法正常工作的。另外先了解一下TCP/IP四层模型中的DNS协议报文。 我在下面直接贴了一个:
![v2-b1a7c330721615b7359ad2b516a24c9a_b.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=fa1c25ba-1f2e-eb11-8da9-e4434bdf6706&url=https://pic3.zhimg.com/v2-b1a7c330721615b7359ad2b516a24c9a_b.jpg)
这个报文是直接使用sniff(filter='udp and port 53', prn=lambda x:x.summary)模块抓取下来的,我对长报文进行了重新排版(狠狠夸一波VScode,相见恨晚),你可以看出这个报文遵循了TCP/IP的四层模型,对应如下:
Ether部分 -- 对应网络接口层
IP部分 -- 对应网络层
UDP部分 -- 对应传输层
DNS部分 -- 对应应用层
从报文的实际内容也可以看出,上面是query,下面是收到的response,即客户机向1.1.1.1这个dns服务器请求http://bing.com的地址,1.1.1.1则回复了两个可用的IP,204开头的和13开头的。下面是实际nslookup的回复内容:
![v2-0f301bde1aa6a52d2e5b207c91c23586_b.jpg](http://img-02.proxy.5ce.com/view/image?&type=2&guid=fa1c25ba-1f2e-eb11-8da9-e4434bdf6706&url=https://pic3.zhimg.com/v2-0f301bde1aa6a52d2e5b207c91c23586_b.jpg)
上面的dns报文只是使用过滤器简单过滤了可能的dns报文后进行的简单展示,我们后面会展示代码对DNS请求和DNS应答进行进一步过滤。
代码部分
参考了一些其他脚本后写的,使用VScode 预运行的时候提示有很多import了但未使用的组件,估计还有优化空间。(优化和前面的几篇文章说的一样,是以后的事orz..)
# -*- coding:utf8 -*-
from scapy.all import *
from scapy.layers.dns import DNSRR, DNSQR
import time
def dns_sniff(pkt):
if DNSQR in pkt and pkt.dport == 53:
if 'bing.com' in str(pkt[DNSQR].qname):
print(time.strftime("%H:%M:%S",time.localtime()))
print("Host is requesting for url: %s from %s" %(
pkt[DNSQR].qname[:-1], pkt[IP].dst))
elif DNSRR in pkt and pkt.sport == 53:
if 'bing.com' in str(pkt[DNSRR].rrname):
for i in range(pkt[DNS].ancount):
dnsrr = pkt[DNS].an[i]
print("Server offers: %s is in %s" %(
dnsrr.rrname[:-1], dnsrr.rdata))
def main():
sniff(filter="udp and port 53", prn=dns_sniff)
if __name__ == "__main__":
main()
这里实现的主要是监控并过滤出对http://bing.com的DNS请求。代码部分请结合sniff得到的较原始的报文内容进行查阅,如果有对报文其他部分内容的摘出需求,可以结合报文去修改脚本以实现更自主化的应用。
运行后,我们再在客户机上访问目标地址或直接使用nslookup发送DNS请求,你就可用看到输出的内容了:
![v2-7e5416505c27b65f27de12c919f51331_b.png](http://img-01.proxy.5ce.com/view/image?&type=2&guid=fa1c25ba-1f2e-eb11-8da9-e4434bdf6706&url=https://pic2.zhimg.com/v2-7e5416505c27b65f27de12c919f51331_b.png)
实际上脚本可以使用python对字符串的操作把域名附近的b'**'内容去掉,不过这是美观的需求了,这个脚本实际上已经可以满足我的需求了,为了方便记录我还加上了时间戳,为了记录DNS请求发生的时间。
后面可以继续使用探测到的结果进行其他的操作,这里就不一一说明了。另外发现nslookup进行DNS请求的时候实际上会进行两次,一次是ipv4的请求,另一次是ipv6的请求,暂时没有对这个现象进行深入的研究,应该是nslookup会默认针对ipv4和ipv6进行两次请求,因为sniff给出的报文在Ether部分有type字段,估计不能在一个请求中同时包含两种地址的type。
参考链接
https:// stackoverflow.com/quest ions/25092538/scapy-dns-sniff-with-additional-records --参考了帖子中对报文解析的思路 https:// blog.csdn.net/nixawk/ar ticle/details/45933299 --参考了sniff的用法