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