预估稿费:200RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
一、前言
在另一篇文章中,我们初步介绍了Devil’ Ivy这个漏洞的整体情况及影响范围,本文从技术角度深入分析了这一漏洞的细节。
从去年起,我们开始分析远程配置类服务的安全性,彼时我们并不知道我们会发现那么多漏洞,也不知道这些漏洞会影响那么多用户。我们一直在研究远程配置类服务中漏洞的普遍性及共同特性,因此当我们接触到M3004这个设备时,我们自然会去寻找这类服务。首先我们枚举了M3004设备的开发端口,检查负责处理输入数据的那些代码。我们发现了一个名为wsd的服务,该服务会从gSOAP中导入一个第三方库。我们利用IDA Pro这个逆向分析工具来检查负责将输入数据写到栈缓冲区中的那些代码,最终在这些代码中发现了一个漏洞。
图1. IDA Pro中正在分析的某个函数
作为一款安全摄像头,M3004在80端口上提供了一个ONVIF服务,如果向该服务发送一个POST命令就会触发存在漏洞的代码。该设备内部预置了gdbserver工具,我们通过Web界面启用SSH接口,然后利用SSH接口对设备服务进行远程调试,通过这种方式,我们得以观察服务的内部工作流程以及我们代码的输出结果。我们在栈上设置了一个断点,当溢出数据覆盖掉栈上保存的某个返回指针时,我们观察到程序会发生崩溃,进而验证了漏洞的存在。接下来我们需要做的就是实现目标设备上的代码执行。
虽然将数据写入栈上时我们没有受到字节数的限制,但仅凭这一点我们还是无法实现代码执行,并且设备存在过滤机制,要求我们发送的数据值必须大于31。我们使用了一种名为ROP(Return Oriented Programming,返回导向编程)的技术,以便将地址信息写入栈中,强迫程序执行libc中的代码片段,从而实现对栈上代码执行限制条件的规避。通过这种技术,我们分配了可以执行的空间,将我们的shellcode复制到该空间中,然后将执行过程引向此地址。虽然我们所使用的地址中包含的值必须大于31,这在某种程度上的确给我们造成一些限制,但我们仍然可以完成代码执行目标。
一旦我们研究到这一步,我们就可以编写shellcode(当然所有值都应该大于31),利用shellcode打开端口,使远程用户可以连接到设备的shell。此时我们利用Devil’s Ivy(CVE-2017-9765)已经拿到了代码执行权。由于Axis做了些安全设置,利用这个漏洞我们只能在M3004上以非特权用户身份访问shell。然而,我们可以执行ONVIF中包含的某些命令,而通常情况下只有特权用户能够执行这些命令。我们可以将摄像头恢复到出厂设置状态,可以控制摄像头,可以重启摄像头防止操作员监控视频,也可以更改网络设置。
读者可以继续阅读下文的技术细节,也可以直接拉到文章末尾,观看演示视频。
二、访问设备
首先我们从Axis的官网上下载了M3004设备的最新版固件。官网的确要求注册账户才能下载固件,但并没有去验证用户是否是合法的客户。我们构造了一个Nate Johnson身份,使用了一个可达的邮箱地址,然后非常顺利地下载到了设备固件。我们使用binwalk以及专用于JFFS2文件系统的Jefferson提取器提取了固件中的文件系统以及Linux内核。
图2. binwalk的输出结果
我们运行nmap来扫描摄像头开放的端口,发现1900(upnp)、3702(ws-discover)以及5353(mdns)开放。对文件系统做了一些分析后,我们发现ws-discover与处理SOAP协议的wsd有关。wsd需要导入libsoap.so库(来自于Genivia提供的gSOAP产品)来解析输入的SOAP消息,我们仔细检查了负责将输入数据写入栈中的那些代码,然后使用IDA Pro查找栈缓冲区,手动跟踪复制到缓冲区中的那些数据的来源。通过这种方法,我们只花了1天时间,就从汇编代码中找到了这个漏洞。
图3. 从libsoap.so中导入的数据
三、代码分析
我们发现soap_get()函数中有一段代码,将输入数据写入大小为0x40字节的栈缓冲区中。这段代码会在一个循环中检查“?”结束符是否存在,或者会检查某个结尾符是否存在,条件成立才会跳出循环,而没有去检查已写入0x40字节堆缓冲区中的实际字节数。
图4. soap_get()中存在漏洞的代码
在上图中,R11为数据计数器,其值被设置栈缓冲区的大小,R7为栈缓冲区指针,R12为从网络中读取的输入字节。如果R11计数器的值小于0,函数会跳过栈缓冲区的写入过程,但会继续使用j_soap_getchar()读入数据。如果我们向wsd写入足够多的数据,就能将计数器的值重新恢复到正整数值,这样一来我们就能绕过0x40字节数的限制,将数据写入栈中。这个过程需要好几分钟,但对输入数据的数量没有作限制,并且在netcat的帮助下我们很容易就能完成这一过程。经过计算,我们发现我们需要发送0x8000000个字节才能将计数器重新恢复到一个正整数值,然后发送0x40个字节来填充大小固定的栈缓冲区,在覆盖返回地址前我们还需要再发送0x30个字节。
我们向80端口上的“onvif/device_service”服务发送了一个POST命令,进而接触到漏洞利用点。为了发送0x80000070个字节,我们构造了一个文本文件,文件开头为“POST /onvif/device_service”,在随后新的一行中使用“<?”来表示SOAP消息的开头部分,然后使用垃圾数据填充文件的剩余部分。我们使用如下命令,通过netcat发送这个文件:
nc [camera_ip] 80 < postpwn.txt
我们需要更多的信息才能确定我们是否能够利用这个漏洞,此时此刻,当我们将全部数据发送完毕后,目标服务已经没有任何响应了。在Asix官方支持中心的指引下,我们通过ssh接口成功连接上摄像头。摄像头内置了一个Web服务器,我们转到高级菜单页面,编辑/etc/ocnf.d/ssh,启用了ssh功能。重启摄像头后,我们使用已有的用户名及密码成功连入设备的ssh接口。随后我们发现摄像头已经预先安装了gdbserver,因此我们在本地计算机上使用ARM编译的gdb来观察漏洞触发时服务的工作过程。正如我们预期的那样,当服务处理到我们提供的溢出数据时就会发生崩溃。
图5. wsd发生崩溃
如上图所示,此时wsd已经崩溃,R4-R11以及当前的PC值都存放在栈上,从输出结果中可知,我们已经成功使用输入的数值覆盖掉原始值。
四、代码执行
我们面临的下一个挑战是获得代码执行,因为栈上是没有执行权限的。与我们最近看过的其他设备不同,传入的数据没有被存储在可执行堆的固定值上,这增加了一些难度,减缓了我们的研究进度。
但是,我们可以根据需要将多个字节写入栈中,并且libc处于静态位置。我们使用libc中的代码片段构造一个ROP链达到执行代码的目的。其中的棘手之处在于,我们不能使用任何包含低于0x20、0x3F、0xFF字节的地址。低于0x20的值会被替换为0x20,0x3F或0xFF的意义是标记缓冲区的结尾。幸运的是,libc处在一个固定的地址,允许我们在ROP链中使用大量的代码。
我们手动去寻找ROP地址,使用IDA和正则表达式功能进行查找。我们将ROP链附加到我们的大文本文件中,并写了一个脚本来检查是否有坏字符。总而言之,我们花了几天的时间稳定地整理了包含19个地址的长链。我们首先调用pvalloc()来分配页对齐的内存缓冲区,然后使用strcpy()将我们的shellcode从栈中的较低层复制到缓冲区中。我们通过调用mprotect()来标记缓冲区可执行文件,然后跳转到可执行缓冲区以开始执行我们的shellcode。
令人惊讶的是,编写shellcode是最麻烦的。我们开始bind到socket,并允许远程用户连接到一个shell。因为我们被限制在固定的范围内,我们要做的第一件事是xor编码大部分的shellcode代码,然后在有限制shellcode的位置进行解码。ARM处理器缓存的指令和数据,你可以使用ISB或MCR指令清除。虽然网上目前有一个通过改变MCR指令来清除指令路劲的例子,但是对我们来说没什么效果。我们也了解到,在某些芯片上,由于处理器仅缓存顺序指令,所以可以简单的分支到你的代码中。但是,也没有什么效果。研究其他的可能性,还要花费一个小时到几个星期的时间。
为了编写value-restricte shellcode,我们很大程度上依赖于在libc中执行代码的能力。我们在我们的代码中设置参数,然后调用libc中的函数执行我们需要的系统调用。例如,要使socket系统调用,我们编写了如下代码片段。
基于这点,我们通过利用Devil's Ivy漏洞获得了一个代码执行权限和摄像头的交互式shell。虽然其他设备可以使用gSOAP以一个root用户运行服务,但此特定设备仅授予wsd用户(非root用户)的访问权限。尽管如此,我们能够执行通常只允许root权限用户执行的ONVIF规范中的命令。权限设置位于摄像头的文本文件中,wsd用户具有该文件的写权限。
sed -i /SystemReboot=8/SystemReboot=f/ access_policy
关闭连接后,导致wsd重新启动并重新加载access_policy文件,我们可以发送SystemReboot命令并重新启动摄像头。攻击者可能会持续重启摄像头或更改其网络设置,以防止访问Feed。
修改权限后发送,部分执行SystemReboot命令后的响应
相机在重置为出厂设置后会提示输入新密码
下面为Axis M3004安全摄像机上Devil's Ivy漏洞的演示视频,你可以访问我们的博客,了解更多详细信息。