PSV-2020-0437:Buffer-Overflow-on-Some-Netgear-Routers
距离上一篇 Blog 更新已经快两个月了,想了想应该给长草的 Blog 除除草了。于是从我的笔记文档里翻了一下, 把这个漏洞翻出来给大家分享分享。具体官方通告可以参考:
当时我的利用是在 Netgear R6400v2 固件版本为 1.0.4.102 的环境下编写的 ,因此本篇文章也以此为基础进行讲述。
固件获取
固件下载链接: 【2】R6400v2-1.0.4.102 固件
当我们获取固件后,我们需要从中解压出文件系统 , 这里我通过 binwalk 和 unsquashfs 成功提取出对应的固件
1 | # swing @ swingdeiMac in ~/Downloads/PSV-2020-0437 [16:43:07] |
1 | # root @ docker-desktop in /workhub/Downloads/PSV-2020-0437/_R6400v2-V1.0.4.102_10.0.75.chk.extracted [8:44:40] |
漏洞介绍
众所周知 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 进行模拟的方案。
具体几个踩坑以及解决方案如下:
-
/dev/nvram: No such file or directory
由于 netgear 使用到了 NVRAM , 因此我们需要 hook 下 NVRAM 相关的函数, 这里我用的一个网上编译好的实现 :【3】 Shared Library to intercept nvram
- 缺失符号
找不到 dlsym 的符号。之所以会用到 dlsym,是因为该库的实现者还同时 hook 了 system、fopen、open 等函数
/lib/libdl.so.0
导出了该符号,所以 LD_PRELOAD 的时候把这个也加上
- 缺少一些目录以及配置信息
1 | # cat tmp/nvram.ini |
- 挂载 /proc /dev 目录
1
2mount -t proc /proc ./squashfs-root/proc
mount -o bind /dev ./squashfs-root/dev
到这我们就基本利用正常运行 upnpd 程序了。
1 | # root @ server in /home/squashfs-root [19:32:11] |
PS: 操作的过程没有看到 qemu, 那是因为我系统有 qemu binfmt 的支持。
利用编写
由于漏洞的根本原因是 strncpy
的缓冲区溢出, 我们知道 strncpy
函数在溢出的时候会存在 \x00
截断。然而程序每次不同链接使用的是同一块内存, 我们可以在第一次 recvfrom
的时候在栈上布局好 rop, 然后通过栈迁移跳转到布局好的 rop 上。
- 第一次连接布局好 rop
- 第二次连接, 构造缓冲区溢出,栈迁移到 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 | loc_22DB8 |
其中 PC
寄存器为需要的栈迁移 gadget
1 | ropper -f usr/sbin/upnpd --search "add sp, sp" |
这里我选择了这条 gadget, 将栈迁移到 SP+0x800
的位置, 另外通过这条 gadget 我可以接着控制 R4、R5、R6、PC
四个寄存器
1 | .text:00011B90 ADD SP, SP, #0x800 |
另外最终我期望通过调用 system
函数执行任意命令, 因此我需要在 bss 这样全局的地址上写入命令, 因此我需要找对应可以往任意地址写入值的 gadget 。 在 arm 的汇编中,写入值的汇编指令为 str
1 | $ ropper -f usr/sbin/upnpd --search "str r?" |
对于这样的需求,我从这些 gadget 中选取了 0x0002dd4c: str r6, [r5]; pop {r3, r4, r5, r6, r7, pc};
这条指令, 通过控制 r5、 r6 寄存器,我们可以将任意值从r6 写到 r5 所指向到地址中。然后我通过 for 循环就可以构造出将任意字符串,写到任意地址中的 rop 链
1 | def build_rop(cmd): |
最后呢,在找一条 mov r0, r?, bl system
这样的gadget, 将为可控的 R3
到 R7
寄存器中的一个覆写成刚刚写入了命令的地址,然后将值 mov 到 r0
寄存器上。 因为 arm 到参数传递是由寄存器传递的,通过控制 r0
寄存器, 我们就可以控制 system
执行任意命令。
这里了使用的是:
1 | .text:0002704C MOV R0, R4 ; command |
找齐所有的gadget ,并且将 rop 链布置到 SP+0x800
的位置, 因此第一次链接到代码如下:
1 | # write cmd to bss |
然后第二次链接,为只需劫持返回地址到 stack pivot 的地址上即可
1 | #.text:00011B90 ADD SP, SP, #0x800 |
所以最后的 exploit为如下:
1 | from pwn import * |
相关链接
【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