由奇安信集团、清华大学网络研究院、蚂蚁集团、腾讯安全大数据实验室、Coremail论客主办的DataCon2022大数据安全分析竞赛线上赛和决赛已圆满落幕,五大赛道第一名也已各归其主。本期为大家分享的是物联网安全赛道排名第一的S7一1200战队writeup。
来自中国科学院信息工程研究所的S7一1200战队,指导老师是李红老师。战队研究方向包括物联网安全、工控安全、软件供应链安全等。DataCon2021的比赛中,曾以“老板说得真队”为名组队参加软件供应链与物联网安全赛道,并夺得第二名。
[温馨提示:本文篇幅较长
欢迎收藏转发,随时浏览查看]
第一部分 固件基地址识别
背景
在对固件的分析过程中,准确识别固件的加载基址是后续工作的基础。与PC主机等的程序不同,嵌入式设备由于计算性能有限,通常使用固定的加载基址,将指令与数据加载到固定的内存地址上。这样固定的加载基址使得设备不需要额外的、大量的计算开销来实现地址随机化,节约了计算资源。也正是因为使用了固定的加载基址,固件程序中的可执行指令所引用到的地址也都是最终的逻辑地址。
如上图图1所示,在使用正确的基址完成固件加载之后,指令中引用到的逻辑地址得以指向正确的内存空间,从而使得数据引用得以正常完成。一般来说,处理器类型可以通过查阅产品手册或硬件物理检查来辨别。但是,固件加载基址却无法从中获取。如果没有固件加载基址,反汇编器无法创建基于绝对地址的交叉引用。在面对晦涩难懂的反汇编代码时,如果没有这些交叉引用信息,分析人员在寻找待分析的目标汇编代码时往往会迷失方向。因此,了解正确的固件加载基址对于理解整个固件至关重要。
一、赛题理解
本赛题中共给出50个固件文件,需要选手分别识别它们的加载基地址。根据我们对本次赛题数据的观察,我们总结出数据的以下特点:
a) 各固件的文件格式不属于任何我们已知的格式(如ELF,PE);
b) 所有固件文件都无法通过 file 命令获取有效信息(如指令集、字节序),而即使是使用 binwalk 也只能获取部分固件的所属指令集信息;
c) 固件文件涉及多种指令集,包括但不局限于ARM等常见指令集;
d) 多数固件中包含丰富的字符串信息(最少的有4条,最多有7w+)。
其中,我们将这些固件中的字符串信息情况展示在图2中。图中横轴为不同文件的编号,纵轴则为某个文件中所含有的有效字符串数量(统计时的筛选标准为字符串长度≥10)。可见,超过半数的文件中含有超过3000条的字符串信息,而那些字符串数量少于3000的文件中,也大多含有数百条字符串。
由此可见,固件中的字符串数量非常丰富,可以利用字符串信息进行加载基址识别。
根据现有研究可知,固件的程序中需要进行信息输出(如版本信息),而程序加载字符串通常使用间接寻址方式,因此文件中会存在字符串的逻辑地址常量。字符串等数据被使用加载基址a正确加载之后,其在文件中偏移位置x与在内存中的逻辑地址y会构成y=x+a的线性关系。因此,只要能够找到有效的字符串信息以及它们的逻辑地址常量,就可以计算出加载基址。
但是实际操作时,我们发现该方案中还存在如下两个需要解决的问题:
a) 如何对字符串进行筛选?
b) 如何提取字符串的逻辑地址常量?
二、解题思路
我们使用图3对加载基址识别流程进行阐述。
图3加载基址识别过程示意图
整个过程主要分为如下4个步骤:
1、提取固件中有效字符串在文件内的偏移量,构成集合 F;
2、提取固件中所有可能的逻辑地址,构成集合 L;
3、遍历所有可能的基址值,并将F中各值加上,构成集合;
4、寻找使得最大的,作为加载基址结果。
针对字符串的筛选问题,我们设置的筛选条件为“字符串长度≥10,且该字符串能够被分词”,而针对字符串的逻辑地址搜索问题,我们将任意连续的4字节都作为可能的立即数。其中,由于查找字符串逻辑地址的方法较为“粗暴”,会导致大量无效常数混入,因此我们在获取所有可能的立即数之后,通过这些立即数的高2字节进行分组并仅保留最常见的高2字节。以图3为例,在获取所有可能的逻辑地址常量之后,可知这些常量中最常见的高2字节为0x1000,即图中右下角绿色部分地址的高2字节。因此,我们剔除常量中高2字节不为0x1000的那些常量值。
三、总结
我们的方案成功在50个固件文件中识别出其中48个固件的加载基址,与其他满分队伍还是存在差距,也说明我们还有很大提升空间。而在针对本题的工作中,我们也总结出如下2点小小体会,在这里也与各位前辈及技术大佬们分享:
a) 真实固件往往存在丰富的字符串信息,但是如何筛选出有效字符串很重要
b) 固件所用指令集多种多样,不依赖具体指令集的通用方法会更实用
当然,我们的方案并不完美。例如,我们在解题过程中简单地认为所有地址都为4字节大小,但实际分析工作中,还是会遇到地址长度为8字节的固件,这就使得我们的逻辑地址搜索方法可能会出现错误。另外,即使是在32位的固件中,我们使用高2字节对逻辑地址常量进行分组与过滤的方法也可能会导致真正的逻辑地址被误剔除。综上所述,虽然基于字符串的加载基址识别方案能够应用于多种指令集中,但是其中还是有很多细节问题需要解决,也还是有非常大的提升空间。
以上便是我们队对于“固件基地址识别”给出的方案,还请各位前辈与技术大佬不吝指教!
第二部分 函数符号恢复
1. 前言
本题提供了20个固件二进制文件,需要从中寻找给定的27个常见函数(后面统称目标函数),可转换为函数检索任务。在下面的章节中,我们首先分析了比赛中面临的挑战以及简单叙述了我们初期的思路,然后我们按照两个不同阶段分析我们最终采取的解题思路,在过程中我们还加入了简单的例子帮助读者更形象地理解我们的思想,最终我们总结了对于本次赛题的一些思考和感悟,希望对大家有所帮助。
1. 1研究挑战
针对数据集我们分析了检测过程中面临的挑战:
1. 多种不同架构:我们对数据集中的架构组成进行了分析,统计如下图,除了大部分是ARM以外,其他各式各样的架构都有涉及;
2. 编译优化选项,编译环境未知:不同的编译选项和环境会导致得到的固件二进制相差甚远;
3. 目标函数特异性较低:需要识别函数名的大部分是libc库函数,代码量少,没有显著差异特征,容易与其他简单函数混淆。
1.2 初始思路
我们一开始的思路是利用BCSD(二进制代码相似性检测)方法,将预先编译的目标函数与固件中函数进行相似性比对,找出候选函数进一步筛查,但实操发现同样的目标函数在不同架构和编译选项下变化十分巨大,已有的BCSD工具难以直接覆盖这些特征,并且很多目标函数属于小函数(代码量少),区分度不高,所以效果并没有预期好。
2. 解题思路
在分析过程中,发现许多二进制的字符串特征被保留了下来,从中可以获取到一些与固件本身相关的信息,这有助于我们对还原固件中函数提高辅助性指导。因此我们思路可总结为两点:
1. 基于保留字符串挖掘固件二进制公开信息,提取辅助信息(e.g. 固件来源,代码),生成特征库;
2. 基于特征库在二进制中匹配和关联需要的目标函数。
具体而言,包含预处理和目标函数检索两个不同阶段。
2.1 预处理阶段
在预处理阶段,我们主要通过固件中保留的敏感字符串信息去推测和关联固件可能来源,以获取更多有助于分析的辅助数据。因为同样的目标函数,其使用的版本,编译的架构和优化选项等都会对其识别造成影响,根据字符串大致锁定固件的来源有利于得到更多识别函数的辅助信息,帮助我们减少检索所需的范围。
在检索字符串的时候我们主要依据敏感字符串(e.g. OS, IoT, version) 以及版本正则pattern去匹配找出有效字符串。下图展示了部分敏感字符串发现固件信息的例子:
基于此,我们分析了全部20个固件及其可能来源,并统计如下:
通过上图可知,有大量固件使用的是Zephyr项目代码,可同类化处理。
因此,针对Zephyr OS 的固件,我们首先根据其架构缩小范围,然后根据deviceTree去锁定具体的硬件和board之间的关系。deviceTree的构建如下图所示,由sources(deviceTree本身)和bindings(其内容和数据类型)生成一个C header,方便开发者通过API获取相应deviceTree信息。
基于deviceTree可以生成一个由 名称@单元地址 组成的标签,用于确定具体的固件二进制和源码之间的联系。
● 一个deviceTree例子
首先在固件中利用正则匹配到标签
然后基于在zephyr project中检索该标签
由此可知是esp32c3的板子,在确定了源码后我们编译对应的例子并加入到我们的特征库中。针对非Zephyr 的固件,若能够编译成功则同样存储到特征库中,否则保存源代码用于下一步检索。
2.2目标函数检索
2.2.1基本思想
基于预处理阶段得到的特征库,我们进一步对测试数据集中的20个固件进行目标函数检索。主要的思想根据有无编译生成的固件二进制划分成两部分:
1. 基于函数相似度的特征库函数匹配
-
直接匹配:根据函数相似度得分判断是否为目标函数
-
基于过程间语义的低相似度函数关联:根据相似度得分高的结果
2. 基于字符串引用和源代码的函数感知
-
直接引用:目标函数的参数直接使用了字符串 (e.g. getenv(“str”))
-
间接引用关联: 目标函数调用者(caller)存在字符串,先锁定caller,再根据调用关系找到目标函数
2.2.2基于函数相似度的特征库函数匹配
我们最开始尝试的是直接利用手头编译的二进制函数与固件二进制中的函数进行匹配。为了应对多种架构,我们选择了较为成熟的函数相似度比对工具:BinDiff (BinDiff 处理可执行文件的抽象结构,忽略反汇编中具体的汇编级指令。根据函数的(规范化)流图的结构,每个函数都会获得一个签名(block数量,block之间边的数量,子函数调用数量),基于这些前面和函数调用关系,计算来源于不同二进制的两个函数之间的相似性)。
通过将生成好的固件idb,i64文件和相应特征库文件传入bindiff可得到匹配的函数及其相似度等信息,如下图所示:
从结果中过滤出目标函数即获取到相应的函数地址。
3 基于过程间语义的低相似度目标函数关联
在测试过程中,由于编译选项的差别,导致部分函数的相似度无法达到预期,即匹配得分并不高。如下图所示:
许多目标函数的相似度很低,无法确认其存在性。
然而,我们发现仍然有许多函数被正确匹配成功。考虑到libc函数常用于被其他函数调用,我们制定了一套基于匹配函数的函数调用定位目标函数的策略,主要步骤如下:
① 在特征库中定位目标函数Ft,获取其所有caller;
② 检索caller的匹配得分,将完全匹配的caller记为锚点函数Fm;
③ 定位Ft在Fm中的位置(基于CFG),根据Fm在固件中匹配函数相同位置附近定位目标函数。
当有多个匹配的caller时,取对应固件中匹配的caller’的所有callee交集快速缩小检索范围 当caller匹配得分不高时,将其视为新的Ft继续向上进行递归检索
● 一个识别的例子
1. 获取目标函数caller:从特征库中筛选出我们的目标函数Ft,获取其所有函数调用关系:
2. 检索caller相似度:对调用它的所有caller在bindiff结果中检索其匹配得分,若完全匹配,则标记为锚点函数Fm。
3. 锚点函数定位关联目标函数:定位目标函数Ft在Fm中的位置,并在固件中检索相同位置附近的函数调用以确定固件中的目标函数。
4 基于字符串引用和源代码的函数感知
字符串作为编译过程中的不变量,对于我们定位具体的函数有着十分重要的作用,考虑到:
1. 字符串在编译过程基本不会变化。
2. 设备在交互过程中需要提供提示信息,作为对用户的反馈。
因此我们采取如下策略利用字符串引用关系找到目标函数:
1. 提取固件二进制中的所有可读字符串strings;
2. 取strings在源代码仓库中进行匹配,并标记对于字符串成功匹配的代码其所在函数为源码锚点函数;
3. 在源码中解析下所有函数调用,当遇到目标函数时,将匹配的字符串标记为敏感字符串;
4. 在固件二进制中获取的引用,定位到二进制锚点函数 Bin ;
5. 根据目标函数和在源码中的相对位置,定位并识别目标函数。
● 一个识别的例子:
1. 基于字符串,首先检索到源代码如下:
2. 在二进制中定位到如下汇编代码:
根据CFG可以确定sub_51A10 为strtol
5. 总结
本次函数符号恢复整体来说,比赛紧张有序,我们团队最终比赛得分也超过了80。比赛过程中我们发现,许多论文中的方法在面临实际应用中千差万别的代码时受限较大,并不能很好地满足需求。函数符号恢复最大的挑战还是在于函数语义的识别,因编译选项和环境的不同,同样的源代码往往差距很大。在过程中,我们发现完全依赖目标函数本身的语义是远远不足的,通过借助过程间函数语义(例如函数调用)不仅能提高函数识别的精度,同时也能加速函数识别的过程。除此之外,一些静态特征,如字符串能帮助我们缩小检索范围,静态常量能帮助我们验证函数。
总而言之,对于系统性的函数符号恢复可以总结为以下三点:
1. 丰富的函数特征库:包括多种架构,编译平台等,此外,不同的版本中提取的函数对于识别也存在影响
2. 有效的函数语义匹配手段:我们参考了学术界工业界知名的函数相似度比对工具,发现他们在实际比赛中面对数据集中多种不同的情况仍然存在欠缺,主要是不同情况编译下目标函数的差异化导致,这部分仍有待提高
3. 灵活结合函数语义与过程间语义:函数调用关系,作为函数内较为稳定的控制流特征,在目标函数本身无法确定的情况下,通过寻找其caller或callee间接完成目标函数的定位是一种有效地应对函数特征库不足的手段
6.参考资料
[2] Zephyr. A new generation, scalable, optimized, secure RTOS for multiple hardware architectures. 2022. https://github.com/zephyrproject-rtos/zephyr
[3] IDA Pro. The best-of-breed binary code analysis tool. 2022. https://hex-rays.com/ida-pro/
[4] mini_httpd. A small HTTP server. 2018. https://github.com/Iscle/mini_httpd
[5] lighttpd. A secure, fast, compliant, and very flexible web server. 2023. https://github.com/lighttpd/lighttpd1.4
[6] servent. A simple httpd, implemented in C, with nosql DB, script, template. 2013. https://github.com/wkliang/servent
[7] centiki. The open source OS for the Internet of Things. 2019. https://github.com/contiki-os/contiki
[8] esp-idf. About Espressif IoT Development Framework. Official development framework for Espressif SoCs. 2022. https://github.com/espressif/esp-idf
[9] boa. The Boa web server. 2017. https://github.com/gpg/boa
[10] AliOS-Things. IoT oriented, highly scalable IoT operating system。2022. https://github.com/alibaba/AliOS-Things
[11] HuaWei-LiteOS. Lightweight IoT operating system. 2023. https://gitee.com/LiteOS/LiteOS
[12] thttpd. A simple, small, portable, fast, and secure HTTP server. 2016. https://github.com/jacklicn/thttpd
[13] Nuttx. A mature, real-time embedded operating system (RTOS). 2022. https://github.com/apache/nuttx
第三部分 整数溢出检测
前言
本篇解题报告中,我们首先在第一节中介绍我们对赛题理解及方案选择,在第二节中简要介绍了我们方案的流程,第三节中介绍了方案的技术细节,并且在最后总结了我们选择的方案的优势与局限性。另外,我们在附录中添加了部分示例来帮助读者更好的理解我们的技术细节。
1. 赛题理解与方案选择
1.1背景介绍
本次赛题中共给出25个二进制程序,要求设计一种自动化的漏洞检测方案,寻找所有可能的整数溢出漏洞点。整数溢出漏洞是指在整数的算术运算的过程中,运算结果超出了数据类型所能表示的范围,最终发生导致运算结果发生变化,最终改变程序预期执行目的。
例如在下图例子中,SSL_read作为污点源读取外部数据并存入变量namelen,namelen最终被传入malloc函数中。如果攻击者精心构造namelen为一个较大的数,例如0xffffffff,那么无符号32位整数namelen+1的运算结果为0,导致malloc分配了一个长度为0的缓冲区,最终造成栈溢出。
在后文中为了便于说明,我们统一使用“污点源(source)”来表示(攻击者可控)数据输入的位置,并使用”污点函数(sink)”表示使用不安全变量作为参数并产生危害的函数。根据整数溢出漏洞的性质,我们可以知道要检测这类漏洞,首先要判断外部输入数据是否会导致运算的结果发生溢出,其次要判断溢出后的数据是否会被污点函数使用并导致错误。
另外,题目中明确指出了产生漏洞的情况是由于发生了算术运算后(ADD, MUL操作等)导致数据溢出,不考虑直接类型转换操作导致的溢出。最后,题目中给出了部分污点源函数和部分污点函数名称。
1.2漏洞检测难点
题目中仅给出二进制程序,没有对应源码,难以获取程序原语义信息。另外,赛题程序无法直接仿真,无法使用动态漏洞挖掘方法。因此,结合上述赛题描述,我们总结了在本次比赛中设计一个自动化的二进制静态漏洞检测方案的挑战:
1. 分析效率。题中给出的程序代码量较大,25个二进制程序中平均有1278个函数,最多的文件中有5297个函数。在此情况下,传统的符号执行技术面临路径爆炸、约束求解困难的问题,导致效率低下。
2. 漏洞检测准确率。题中要求准确找到导致整数溢出的算术运算操作,而不是识别可能发生危险操作的污点函数位置,使得传统数据流分析方法不再适用。
3. 异构指令集架构。题中给出的25个二进制程序有多种异构指令集架构,包括7个x64二进制程序、9个arm二进制程序、2个aarch64二进制程序,要求设计一种针对多架构的漏洞检测系统。
1.3解决方案
在本次比赛有限的时间中,我们构建的自动化漏洞挖掘系统核心思想是“间接调用识别构造完整控制流图+静态数据流分析+基于代码片段的选择符号执行”,主要基于团队所在实验室的已有工作EmTaint[1]以及开源工作Arbiter[2]实现,并结合整数溢出漏洞的特点进行了相应的修改。
方案大致流程如下:
1. 识别间接调用点,构建完整控制流图,并使用库函数类污点源(例如fread或recv)减少误报。
2. 借助精确的数据流分析,快速确定可能存在的整数溢出漏洞的代码路径。
3. 借助符号执行,对数据流可达的代码片段,验证是否符合漏洞条件。丢弃其余数据流无关代码路径,从而加快符号执行速度。
该方案的核心优势在于,兼顾了数据流分析的效率以及符号执行的精度。另外,我们选择使用库函数类污点源减少误报,并进行间接调用识别问题以构造完整控制流图。
2. 解题过程概述
在本节中,我们简要说明我们的方案的工作流程,方案具体技术细节见第三节。
2.1 预处理
本次比赛中我们首先提取三类信息,其中函数调用频次被用作后续污点源包装函数和污点包装函数识别的支撑信息之一。
* 函数调用频次
* 污点源函数
* 污点函数
(1)污点源函数识别
本次比赛中题面信息共给出三类污点源函数,包括:
* 导出函数的参数
* 可写的全局变量
* 用于读取外部数据的库函数
我们在比赛初期探索了使用导出函数的参数作为污点源,但在漏洞检测结果中误报较高。因此我们选择使用数据流更为完备的库函数作为污点源,与之相对的问题是我们需要识别间接调用目标点并构建完整控制流图,以及需要解决数据流过长带来的分析效率问题。
在本次比赛中我们使用的污点源包括题目中给定的污点源:
[“gets”, “scanf”, “fgets”, “recv”, “recvfrom”, “recvmsg”, “getc”, “fread”, “read”, “getenv”, “GetUrlValue”, “fprintf”, “accept”]
在此基础上我们结合人工分析新增了部分污点源:
[“SSL_read”]
为了提高分析效率,我们基于这些库函数类污点源,进一步识别库函数类污点源的包装函数。例如,在赛题17中,我们可以基于malloc,在控制流图上搜索其父节点,同时基于启发式的字符串相似性比较,结合函数的调用频次信息,找到其包装函数xmlMallocAtomicLoc。类似的,我们可以在赛题14中基于SSL_read,识别到包装函数rfbssl_read。
另外,我们需要决定在包装函数中如何初始化污点源,我们通过根据库函数的数据流来判断,若库函数返回的污点在后向跟踪中被传递到包装函数的参数中,则将该参数位置视为污点源,若库函数反汇的污点被用作到包装函数的返回值,则将返回值视为污点源。
(2)污点函数
赛题中已经给出了部分污点函数列表:
[“read”, “assert”, “alloc”, “malloc”, “realloc”, “calloc”, “fseek”, “memcpy”, “j__TIFFrealloc”, “j__TIFFReadEncodedStripAndAllocBuffer”, “j__TIFFmalloc”, “png_malloc_warn”, “xmlMallocAtomic”]
我们使用与污点源包装函数识别类似的方法来识别污点函数包装函数。另外,我们结合人工经验,进一步添加部分污点函数:
[“memset”, “memmove”]
2.2 间接调用识别
间接调用是指在代码中程序不直接调用另一个函数,而是通过一个指针来间接调用该函数。这种方式可以让程序在运行时动态决定要调用哪个函数,从而提高程序的灵活性。在静态二进制分析中,需要高精度的数据流分析(或指针分析)技术。若无法确定函数之间的调用关系,则会导致程序控制流图被划分为多个独立的部分,无法对程序进行整体分析,产生大量漏报;若识别的函数调用关系不够准确,会产生大量误报。
例如,在 C 语言中可以通过函数指针来实现间接调用。例如,假设有一个函数指针ptr,指向函数func,那么可以通过如下代码来实现间接调用,并通过arg1和arg2向函数func传递参数。通过间接调用,程序可以在运行时动态决定要调用哪个函数,从而提高程序的灵活性。
在汇编语言中,可以通过 call 指令来实现间接调用。例如,假设寄存器eax指向一个函数 指针ptr,那么可以通过如下代码来实现间接调用:
在本次比赛中,我们在实践中发现使用导出函数类的污点源误报率较高,所以我们选择了使用读取外部输入的库函数作为污点源(例如fread)。在此前提下,我们面临的难点是在IDA Pro等工具生成的控制流图中,难以识别程序间接调用目标地址(IDA Pro对于x64类架构间接调用识别支持较好,对于arm架构支持有待完善)。
为了建立完整的控制流图,我们结合赛题程序中间接调用的特点对Emtaint进行了相应的修改。简而言之,我们首先识别简介调用起始点(例如 call eax ),然后从该点初始化指针分析,最终得到一个间接调用点的指针计算表达式。之后,在程序数据段上识别所有的函数指针,判断起始点的指针表达式计算后是否与目标函数指针匹配,从而恢复间接调用,详细方法描述见后文。最终我们在19个程序中识别到2642个间接调用点,以及共13437个间接调用目标。
2.3 数据流分析
基于间接调用识别信息,我们首先构建完整的过程间控制流图。在整数溢出漏洞的初步检测与定位中,我们首先将该类问题转换为一类污点类漏洞检测问题,通过判断数据流的可达性来初步判断漏洞路径。例如,如果从fread读取到的数据最终被malloc函数使用,我们认为这条代码路径上可能存在整数溢出漏洞。基于上述识别到的污点源和污点函数信息,我们借助EmTaint分析实现精确的过程间数据流分析,详细方法描述见后文。
2.4 符号执行
在成功获取从污点源到污点函数的代码路径后,我们借助符号执行来对漏洞触发条件进行进一步判断。例如,如果从污点源初始化的符号变量在运算过程中超过了有符号数或无符号数的最大表示范围,我们最终认为这条代码路径上存在漏洞。由于在上一步中我们已经知道了漏洞触发的具体路径,我们可以在符号执行的过程中跳过对无关路径的分析,避免路径爆炸问题,加快符号执行速度。
3. 技术细节
在本章中,为了便于理解,我们首先介绍EmTaint中的指针分析算法(即数据流分析),在此基础上进一步介绍如何借助指针分析算法实现间接调用识别。最后,我们介绍符号执行的基本思路。
3.1 数据流分析算法
本次比赛中仅给出了赛题程序的二进制文件,无法动态调试且没有源码,因此我们需要实现二进制层面的指针分析算法。二进制层面的汇编指令本身属于低级语言,源代码中的语义信息缺失(缺少符号名和数据结构布局),且具有一定的抽象性。二进制数据流传播过程中包括成百上千次的寄存器赋值、指针引用和解引用、函数拷贝传播。代码不同位置处的不同寄存器、不同内存位置可能指向同一数据,使得精确的二进制指针分析困难重重。
我们在本次比赛中基于EmTaint[1]的指针分析算法实现初步的漏洞检测。具体技术细节可以参阅论文,我们在此简要介绍其核心思想。EmTaint属于按需别名分析技术,可以从程序任意位置初始化指针分析,避免全程序复杂带来的时间开销。例如,有以下代码:
我们可以从malloc的第一个参数初始化指针分析,求解得到过程内的所有指针别名v和q,后续继续进行过程间指针分析,而无需从函数入口点(例如main函数)开始进行全局分析。
为了在二进制代码中实现精确的指针分析,EmTaint提出首次提出了基于汇编代码操作特征的结构化符号表达式,实现对二进制代码在静态分析过程中进行别名的嵌套计算,最终通过前向和后向的迭代求解得到不动点,找到找出所有可能的指针关系。
例如,有以下等价代码,在源码中易知a和e是别名关系(即指向关系相同),但我们无法简单的知道R1与R2的指向关系,特别是在真实代码中嵌套计算次数过多时:
为了在汇编代码中判断多个寄存器(例如R1、R2)和多个内存地址(例如[R7+0x8]、[R3+0x8])之间的指向关系,我们首先进行多轮前向与后向迭代分析。另外,二进制代码中有嵌套运算的特点,我们使用load与store操作符增强指针表示,load表示内存读,store表示内存写,多个load与store操作之间可以嵌套计算。
在第一轮前向分析中可知,R7与R3、R2与[R7+0x8]、R3与[R3+0x8]互为别名。
在第二轮后向分析中,由于我们已知R7与R3互为别名,我们将load(R3+0x8)转换为load(R7+0x8)。
在第三轮前向分析中,观察此时store(R7+0x8)与load(R7+0x8)指向内存相同位置,可知此代码中R1与R2互为别名。
在更复杂的情况中,R1寄存器可能被表示为例如store(load(load(R2)+0x8)+0x4)的形式,此时需要通过多轮迭代计算求解指针关系,在附录中我们展示了更复杂的例子。
在精确数据流传播的基础上,我们进一步实现间接调用识别,以及过程间的数据流传播。另外,上述为了便于理解我们使用arm代码进行了说明,实际EmTaint在VEX IR上实现了指针分析算法,因此可以解决本次赛题中异构指令集架构程序的分析。
3.2 间接调用识别
基于3.1节的介绍,我们可以将间接调用点处的指针,转换为一个可计算的结构化符号化表达式,再通过计算其值是否等于程序内的函数指针地址,实现间接调用识别。
本次赛题中我们主要考虑了四类间接调用类型:
(1)间接调用目标指针与初始化的函数指针互为别名
(2)保存在全局变量中的函数指针
(3)函数表
(4)复杂多层指针
此处我们以第三种函数表的情况进行说明。与函数表相关的间接调用的特征是:通过一个循环来索引函数表从而读取对应的回调函数。因此,总结该间接调用的2个特征:
1. 间接调用的目标指针????依赖于一个函数表的开始地址????;
2. 指针????的数据依赖图包含一个循环,即指针????为论一个循环可变变量。
例如有以下代码,其中第11行的*fp动态调用了函数表中的不同函数。
我们从反汇编代码的第17行BLX R3初始化指针分析,最终可以得到R3的值等价于指针关系load(dptr+i*stride+o1),其中dptr是一个常量,代表函数表的开始地址,变量i是索引,stride代表每次循环中的步长,o1为常量偏移。
通过上一节结构化符号表达式指针分析算法,我们可以在下述代码中计算得到????的指针关系为load(0x92c44+i*0xc+0x8),i分别取值0、1、2时可以计算得到间接调用目标地址为0x442b4、0x442e4、0x37680,即调用了对应的地址的函数代码。
基于以上方法,我们我们在19个程序中识别到2642个间接调用点,以及共13437个间接调用目标,作为构建完整控制流图的基础。
3.3 基于代码片段的符号执行
基于上述两步骤中得到的潜在漏洞路径,我们沿着该路径使用符号执行进行进一步验证,在遇到路径分支时,若遇到路径分支,我们只选择在数据流分析中报告的路径,丢弃其余分支。如果从污点源初始化的符号值在到达污点函数之前超过了整数能表示的最大范围,则我们认为存在漏洞。
我们承认这样的方法可能由于忽略程序中的隐式控制流从而产生漏报,在本团队其他人的研究成果中证明真实世界中隐式控制流存在的情况较少(论文已接收未公开),因此我们认为在这种方法是可行的。
在符号执行阶段,我们基于Arbiter同样的思想实现漏洞约束条件判断。特别地,我们基于三类CWE的整数溢出漏洞的条件进行进行了拓展:
* CWE-131. Incorrect Calculation of Buffer Size.
* CWE-190. Integer Overflow or Wraparound.
* CWE-680. Integer Overflow to Buffer Overflow.
在这些漏洞原本的sink点中,我们结合赛题说明添加了fread、fseek、assert等一系列sink函数。并且,我们我们针对每种情况都会进行有符号数和无符号数的溢出条件判断。另外,值得说明的是,在Arbiter中作者将代码设计为发现整数溢出漏洞导致的缓冲区溢出漏洞,因此他们假设假设内存分配区域不会被减少(即只会被增加)。在本次赛题中我们发现这样的假设并不总是正确,因此我们进一步添加了判断整数下溢的一些约束条件,这样的方法帮助我们找到了部分真实CVE漏洞,也导致了部分误报。
4. 方案优势与局限性
本次比赛中我们提出的静态数据流分析+基于代码片段的选择符号执行的漏洞检测思想。本方案的优势在于,兼顾了数据流分析的效率以及符号执行的准确度,最终实现可对真实软件进行高效准确分析的二进制静态漏洞检测系统。并且,我们借助高精度的数据流分析方法Emtaint,可以在前期定位更准确的数据流作为后续符号执行的输入。另外,Arbiter中构建控制流图以及识别间接调用效率较低。本方案中借助IDA Pro获得初步的控制流图,并借助间接调用分析算法快速获得完整控制流图。值得说明的是,本方案中在静态数据流分析和间接调用识别两个步骤中,单个步骤耗时通常不超过10分钟,而符号执行阶段分析时间取决于具体程序,由几分钟到几小时不定。
本方案的局限性在于,受限于赛题程序的复杂性,我们在6个程序中没有计算得到间接调用目标(由于程序陷入死锁循环)。另外,在进行符号执行时,仍然由于赛题程序的复杂性,我们没有能够完成在有限时间内对所有数据流分析发现的路径的符号求解。最后,由于真实世界程序的复杂性,我们基于符号执行的约束条件判断依赖于人工经验,而人工经验未能囊括所有可能的漏洞情况,这也导致了一些误报和漏报。
[1] Cheng K, Liu T, Guan L, et al. Finding Taint-Style Vulnerabilities in Linux-based Embedded Firmware with SSE-based Alias Analysis[J]. arXiv preprint arXiv:2109.12209, 2021.
[2] Vadayath J, Eckert M, Zeng K, et al. Arbiter: Bridging the Static and Dynamic Divide in Vulnerability Discovery on Binary Programs[C]//31st USENIX Security Symposium (USENIX Security 22). 2022: 413-430.
附录——二进制指针分析示例
考虑以下汇编代码:
我们最终可以按照3.1节中的思想,求解得到R0与R1互为别名。
前后向迭代的图示如下:
[end]
【往期回顾】
微信号:DataConofficial
获取更多大数据安全知识
进群还有超多活动、福利
DataCon定制服饰、背包等精彩好礼
等你来拿!
原文始发于微信公众号(DataCon大数据安全分析竞赛):冠军Writeup大放送 | DataCon2022物联网安全赛道之“S7一1200”战队