利用污点分析批量挖掘路由器固件安全漏洞
2021-08-11 12:40:00 Author: www.4hou.com(查看原文) 阅读量:74 收藏

在过去的几个月里,我们开发了一个污点分析工具,专门用于寻找路由器中的安全漏洞。我们还参考编号为CVE-2019-8312到CVE-2019-8319之间安全漏洞开发了一个检测工具;这些漏洞都是固件版本为1.12A1的D-Link DIR-878路由器中的命令注入漏洞。我们的目标是自动检测此类漏洞。在理想情况下,使用该工具应该比手动查找漏洞更快。

在本文中,我们将分享我们所使用的方法,以及通过该工具在PROLiNK PRC2402M、D-Link DIR-1960和D-Link DIR-X1560路由器上找到的安全漏洞。

关于工具

现有工具

现在,现成的污点分析工具已经有很多了。其中,我最感兴趣的是Tritonbincat,因为两者已经相当成熟。然而,我们却无法使用这两种工具,因为它们不支持目标设备所使用的MIPS架构。

使用angr进行符号执行测试

因此,我们把工作重点放在利用angr打造自己的工具上;angr是一个基于Python的二进制分析框架。我们之所以选择angr,是因为它支持大多数架构,包括我们所针对的MIPS和ARM架构。早些时候,@puzzor曾经对angr进行了一些自定义修改,用于静态污点分析:在angr的帮助下,通过符号执行模拟程序,然后根据生成的VEX IR程序跟踪信息进行静态分析。通过这种方法,我们成功地在测试固件中发现了命令注入漏洞。

然而,我们很快就遇到了一个问题:为了生成程序跟踪信息,我们需要angr通过模拟每条指令来模拟每个函数,并使用符号执行来决定是否跟随一条分支指令。

具体来说,angr会维护一个状态堆栈。一个状态包含诸如寄存器值和内存内容方面的信息。因此,当模拟一个函数时,它只会从一个状态开始。当遇到一个分支指令时,如果angr不确定是否跟随该分支,angr将重复该状态,其中一个将跟随该分支,而另一个则不跟随。

大多时候,函数中都会存在循环。如果循环条件是基于一些用户的输入,那么,状态的堆栈就会“爆炸”。由于angr将始终不确定是继续还是从循环中跳出,所以,它会不断复制状态。此外,还需要注意的一件事是,这些状态并不是同时模拟的。相反,每次只有一个状态被模拟。在这种情况下,需要很长的时间才会有一个状态达到易受攻击的代码;或者,如果该函数根本没有易受攻击的代码,那么,模拟过程可能永远不会终止。

作为一个符号执行框架,angr提供了多种可定制设置(称为模拟技术)来决定先模拟哪个状态。并且,在尝试了许多不同的技术后,我们仍然无法改善执行时间。

例如,在为待分析二进制代码中的每个函数设置了2分钟的超时的情况下,有时候即使经过了2小时,仍然无法完成对二进制文件的分析(因为如果一个函数没有漏洞,它将一直模拟执行下去,直到超时位置)。更糟糕的是,angr中存在一个未知的内存泄漏问题,所以,2小时后,电脑的内存早就耗尽了……

别忘了,我们之前的目标是希望这个工具比手工方式更快一些。所以,通过这种方式是不可能的,所以,我们继续寻找改进方法或替代方案。

应用angr的Reaching Definition分析技术

最终,我们偶然发现了这个漏洞后,燃起了我们对于angr的Reaching Definitions分析技术的热情,于是仔细阅读了下面的资料:

A reaching definition engine for binary analysis built-in in angr

◼Handle function calls during static analysis in angr

◼CSE545 Guest Lecture: Binary Analysis

Use-def关系

总之,这种分析方法将生成函数中原子之间的use-def关系。在这里,所谓原子类似于变量,并且,原子也具有各种类型,如寄存器、栈变量、堆变量。实际上,只要把原子看作变量,事情就很简单了,下面,我们举例说明:

1.png

在上面的函数中,存在一个明显的命令注入漏洞:含有漏洞的代码为system(command),其中注入的命令来自querystring的参数name。在这个函数中,querystring和其他原子之间的use-def关系如下所示:

1.png

首先,我们看到querystring被定义为函数vuln的参数,并被get_querystring_value函数作为参数querystring使用。除此之外,函数get_querystring_value还定义了一个参数name。最后,get_querystring_value函数定义了一个返回值,并用到了上面定义的两个参数。

然后,通过下图我们可以看到,在调用sprintf函数时,还用到了name变量(函数get_querystring_value的返回值)和一个字符串echo %s >> /tmp/log。这一次,情况略有不同。因为我们知道sprintf函数的第一个参数是目标,所以,我们必须对command进行适当的定义,从而让它全面使用提供给spritnf函数的2个参数,而不是仅使用返回值。生成的use-def关系如下所示:

1.png

使用相同的概念,通过这种分析方法可以为函数中的所有原子生成相应的use-def关系。正如我们在上面看到的,这种关系可以被建模为一个图:“使用”用边表示,“定义”用节点表示。 因此,我们可以将其转换为图分析问题。

在污点分析术语中,source点是程序中产生污点数据的地方,而sink点是污点数据可能到达也可能不到达的地方。污点分析是确定来自source点的数据是否到达sink点。在上面的例子中,get_querystring_value函数是source点,因为它从用户输入中提取一些值,而system函数是sink点。就本例来说,来自source点的数据确实到达了sink点。

在我们的use-def图中,我们可以确定source点和sink点的定义(即节点),然后用一些启发式方法遍历该图,以确定source点的数据是否被sink点所使用。如果是,那么我们就把source点标记为易受攻击的,并继续对其进行筛查。

工具总结

总而言之,我们的工具首先利用angr的Reaching Definitions分析方法,生成一个由路由器固件中的函数的组成的use-def关系图。然后,对该图进行相应的分析,以检测可能的安全漏洞,比如,如果(来自source点)用户输入到达一个危险的函数(即sink点),如system函数,我们就认为发现了一个潜在的安全漏洞。

实际上,我们的工具与CodeQL或Joern等引擎的功能非常类似,只是我们的工具缺乏强大的查询界面而已。

测试结果

前面我们曾提到,使用符号执行的方法时,有时分析一个程序需要花费2个多小时。但是,使用上面介绍的方法,分析同样的程序只需2分钟左右就能搞定了。所以,这看起来的确是一个不错的工具。 在对该工具进行改进以消除假阳性并覆盖更多的假阴性后,我们在DLink和PROLiNK路由器上进行了测试。

PROLiNK PRC2402M

使用该工具,我们立即发现了近20个命令注入漏洞,其中10个不需要身份验证,可以直接通过WAN接口进行访问。我们马上向PROLiNK报告了这些漏洞,他们也很快做出了回应。在这些漏洞被修复后,我们申请了相应的CVE编号,它们分别为CVE-2021-35400到CVE-2021-35409。 下面是一些易受攻击的代码片段,其中source点和sink点分别是:

◼Source点:web_get

◼Sink点:system, do_system, popen

2.png

硬编码的密码,还是后门?

在这个过程中,我还发现了一些其他的安全漏洞。似乎有一个硬编码的密码或后门密码,可以用来登录到路由器的管理面板:管理页面将用户提供的密码的md5哈希值发送到login.cgi进行验证,相应的伪代码如下所示:

3.png

然而,在它后面还有一段可疑的代码:

4.png

通过将user作为密码,我们竟然成功登录到管理页面。通过这种方式登录后,显示的仪表板会略有不同,它似乎比通过实际密码登陆的用户提供的功能要少。然而,我们仍然可以访问http://prc2402m.setup/setting.shtml,这就足以控制路由器的设置了。

我们向供应商报告了这一情况,他们很快就更新了固件。为了确定该后门已经消失,我再次打开了同一个函数。这次,我并没有再看到strcat(salted_password, "user"),却看到了以下内容:

5.png

实际上,通过nvram查找Password_backup的值也不是什么难事。

6.png

我们很快又向供应商报告了这个问题。幸运的是,经过再次修复后,已经找不到Debugdoor或后门密码了。

基于堆栈的缓冲区溢出

由于缺乏边界检查,我们还发现了许多基于堆栈的缓冲区溢出漏洞。通过利用这些漏洞,攻击者可以覆盖堆栈上的返回地址,从而获得对程序执行的控制。在上面的一些命令注入的例子中可以看到,用户的输入是通过sprintf函数而不是snprintf函数复制到一个字符串中的。

拒绝服务漏洞

在测试缓冲区溢出漏洞的PoC时,我还发现了一个漏洞,它导致路由器停止响应请求,直到用电源按钮手动重启为止。在下面的伪代码中,cli_num被作为参数传给/sbin/sta_qos.sh脚本。

7.png

通过检查脚本内容,我发现以下for循环,其中$sta_num用于保存cli_num的值。

8.png

如果cli_num是一个很大的值,例如999999999,那么这个脚本几乎会永远困在循环中,实际上,这就是陷入了一个无限的循环。通过向路由器发送这样的请求,会有很多这种脚本被执行并卡在循环中。 一段时间后,路由器会停止响应任何请求,这时,只能通过手动重启,它们才能再次正常工作。

时间线

●6月9日:向供应商报告了10个命令注入漏洞。

●6月11日:供应商修复了相应的漏洞。

●6月11日:建议供应商使用一些额外的过滤器来防止此类漏洞。

●6月28日:供应商根据我们的建议进行了修复。

●7月9日:向供应商报告了另外3个漏洞(后门、缓冲区溢出、DoS)。

●7月23日:供应商进行了修复。

DLink DIR-1960

除了PROLiNK路由器外,我们还在DIR-1960固件上运行了该工具。这一次,该工具返回了近200个检测结果。然而,在对结果进行筛查后,发现只有4个是通过HNAP API的命令注入漏洞(我们已经在前面报告过了),并且所有这些都需要认证。(由此看来,在消除假阳性方面,还有很大的改进空间!)

对于HNAP,这里简单介绍一下:它是Home Network Administration Protocol的首字母缩写,它实际上就是一个基于SOAP的协议,用于与路由器管理面板进行通信。

DLink DIR-X1560

接着,我决定也在DIR-X1560固件上试一下这个工具。上面的两个路由器都是基于MIPS架构的,但DIR-X1560是在ARM处理器上运行的。通过稍加调整,该工具就可以正确地分析基于ARM的固件。由此证明,这个工具具有很好的架构兼容性,这让我很开心。

然而,由于抽象层很多,识别固件上的漏洞并不那么简单。有鉴于此,该工具在固件逆向工程中提供了巨大的帮助。我不确定固件所基于的框架的确切名称,但我设法在GitHub上找到了一些源代码,这非常有帮助,因为其中含有许多注释。我在源代码中找到的最接近的术语是CMS(CPE管理系统)、CPE(客户驻地设备)和TR-069。但是,请注意,此repo不包含任何特定于dlink的代码,因此需要执行一些逆向分析。

在我看来,它类似于MVC(模型-视图-控制器)架构,尽管它也可能不是这样的。

9.png

关于术语和缩略语的进一步解释,请参考这里。

DAL(数据聚合层)API,顾名思义,是用来与数据进行交互的,主要是传递路由器的配置。但是数据的实际存储是由MDM(内存数据模式)和ODL(对象调度层)API完成的。DAL使用cmsObj_get和cmsObj_set函数(或其变体)作为与MDM/ODL的接口,以获取或设置某些对象的值。例如,获取IP_PING_DIAG MDM对象并将其存储在ipPingObj中,然后在修改后将其保存回来的代码如下所示:

10.png

下面解释用到的参数: 

◼MDMOID_DEV2_IP_PING_DIAG:一个枚举变量,指定访问IP_PING_DIAG对象。

◼iidStack:一些我们不需要关心的内部数据。

◼ipPingObj:IP_PING_DIAG对象的内容。

除此之外,还有RCL(运行时配置层)和RUT(运行时使用工具)API。每个MDM对象(例如MDMOID_DEV2_IP_PING_DIAG)都有一个相应的RCL处理程序(rcl_dev2IpPingDiagObject)。每次调用cmsObj_set时,ODL都会调用该对象的RCL处理程序,它又会进一步调用RUT的实用工具函数。

通过逆向分析,我们发现其工作流程如下所示:    

1、用户提出一个POST请求,与HNAP API进行交互(例如SetTimeSettings)。

2、HNAP API处理程序调用DAL API(例如:cmsDal_setNtpCfgDLink_dev2)。

3、DAL API调用MDM/ODL API(cmsObj_set)来设置MDM对象(例如Dev2TimeDlinkObject)。         例如 cmsObj_set(MDMOID_DEV2_TIME_DLINK, &iidstack, 0, &timeDlinkObj)    

4、ODL API调用RCL处理程序(例如rcl_dev2TimeDlinkObject)。

5、RCL处理程序调用RUT API(例如rut_TZ_Nvram_update)。

如果我们查看上面提到的HNAP和RUT函数,我们会看到:

11.png

经过漫长的旅程,NTPServer参数最终出现在一个传递给system的命令中。

正如我们在上面看到的,用户输入字符串(来自 HNAP)途径许多函数后,最终到达system调用(在RUT中),从而导致命令注入漏洞。如果我手动查看固件,除非我很幸运,否则我几乎要花很长时间才能找到它。然而,在这个工具的帮助下,虽然无法直接实现HNAP到RUT的连接,但至少我能把相关的DAL函数列出来看看,从而节省了我很多时间。

DAL和RCL/RUT之间的关系

在这里,我们进一步考场一下DAL API与RCL/RUT API的关系。其中,cmsDal_setNtpCfgDLink_dev2(如前所述由HNAP API调用的DAL API)的伪代码如下所示:

12.png

上面的代码片段展示了DAL函数设置/更新MDM对象的典型过程。请注意,cmsObj_get是以值为0x416的MDMOID(MDM对象ID)进行调用的。由于没有源代码,我只看到值(0x416),而没有看到枚举名称(MDMOID_DEV2_TIME_DLINK),这些都是从固件中的函数名称和一些字符串推断出来的。

如前所述,当cmsObj_set被调用时,ODL API将调用相应的RCL处理程序,在这种情况下是rcl_dev2TimeDlinkObject。我没有研究cmsObj_set的实现细节,因为它相当复杂——需要执行许多检查和函数调用。如果你有兴趣,可以考察这一行,它将调用RCL处理函数。

获得MDMOID和RCL处理程序之间的映射关系并不困难,因为它存储在固件的OID表中,具体如下所示:

13.png

在这个表中,我们很容易看出0x416是TimeDlink MDM对象的MDMOID,而rcl_dev2TimeDlinkObject是RCL处理程序。在这里,我们也看到了一个叫做STL处理程序的东西,但是它并没有做太多事情。

现在,RCL处理程序rcl_dev2TimeDlinkObject看起来像下面这样:

14.png

我们看到,newMdmObj被传递给易受攻击的函数rut_TZ_Nvram_update(前面讲过)。而这个newMdmObj正是刚才DAL函数传递给cmsObj_set的那个timeDlinkObj。所以,DAL和RCL的关系如下图所示:

15.png

DAL会为cmsObj_set提供一个MDMOID和一个对象,然后    

◼MDMOID决定调用哪个RCL处理程序

◼该对象被交给RCL处理程序进行处理

我们看到,从一个DAL函数中,找出哪个RCL函数被调用并非难事,因为我们不仅有MDMOID,而且还可以参考上面的OID表。但在寻找命令注入漏洞时,步骤就反过来了。

首先,通过这个工具,我找到了可能有漏洞的RCL/RUT函数,source点是函数的参数,sink点是system函数(或其变体)。这里没有什么新东西。但是现在,我需要找到访问相关MDM对象的DAL函数。换句话说,就像上面一样,虽然我知道MDMOID,但这次,我不是找RCL处理程序,而是回答下面的问题:哪些DAL函数用这个MDMOID调用cmsObj_set?

16.png

起初,我使用的是一种笨方法:逐一查看cmsObj_set的交叉引用,直到找到一个被调用的MDMOID正确为止。因为这里的交叉引用太多,足足有200多个,几分钟后,我就放弃了。所以,我决定用这个工具来帮助我过滤出使用某个MDMOID的函数。特别是,我只关心作为字符串/缓冲区的MDM对象字段。如果一个字段保存一个整数值,那么它对于命令注入或缓冲区溢出其实是没有用的。

回顾一下,一个MDM对象的字符串字段是这样设置的:

17.png

所以,我不需要对这个工具做太多的修改,只需把source点设置为cmsObj_get,把sink点设置为cmsMem_free即可。结果,我成功了。对于每个MDMOID,我过滤出了修改相关MDM对象的几个DAL函数。然后,我检查这些DAL函数的交叉引用,看它们是如何被HNAP API调用的,以找出用户输入是如何被传入MDM对象的。 借助于这个工具,工作效率获得了巨大提升,最后,我成功地在这个固件中找到了4个命令注入漏洞。

演示视频

现在是演示时间,演示视频地址见这里

小结

目前,该工具仍处于开发的初级阶段:只能挖掘命令注入漏洞。此外,在分析像DIR-X1560这样复杂的固件时,仍然需要做一些手工工作,因为它不能自动判断出哪些HNAP函数是易受攻击的。我会继续对这个工具进行改进,希望有一天它能协助发现其他类型的漏洞,如固件中的缓冲区溢出、UAF等漏洞。

本文翻译自:https://starlabs.sg/blog/2021/08/identifying-bugs-in-router-firmware-at-scale-with-taint-analysis/如若转载,请注明原文地址


文章来源: https://www.4hou.com/posts/GzBr
如有侵权请联系:admin#unsafe.sh