使用Binary Ninja API检测潜在释放后使用漏洞
释放后使用(Use-after-free)是一种内存破坏条件,程序在内存被释放回分配器后仍然引用该内存。静态检测这类漏洞具有挑战性。过去已有多种方法解决这个问题,如Josselin Feist的GUEB和Sean Heelan在静态分析中发现释放后使用漏洞的工作。
本文探讨了使用Binary Ninja的中级中间语言(MLIL)通过追踪特定内存分配与其他内存区域之间的交互来建立数据流图。基于数据流图,进一步用于跨函数的上下文不敏感可达性分析,以识别二进制文件中的潜在释放后使用(UAF)漏洞。
构建内存分配的数据流图
在此上下文中,“数据"指的是与特定内存分配相关联的指针,是跟踪和分析的主题。数据流信息可视化为图,其中:
- 节点代表不同的内存区域
- 边代表指针存储操作,建立这些区域之间的关系或交互
实现中使用了四种不同类型的节点构建数据流图:
- 跟踪分配节点(红色):代表感兴趣的内存分配,作为跟踪图中交互的焦点
- 函数栈帧节点(绿色):代表过程间分析期间访问的各个函数的栈帧
- 动态内存节点(蓝色):代表无法绑定到特定源的SSA变量
- 全局内存节点(黑色):跨函数的全局变量未被全面跟踪
图中的边代表指针存储操作,建立内存分配之间的连接。源节点对应被写入的内存,目标节点代表被存储的指针值。边属性捕获从分配基地址的偏移量。
SSA变量、图节点和边之间的映射关系
在自动化分析中,第一个要跟踪的SSA变量是被分配器调用(如malloc()或calloc())返回值的变量。一旦确定了感兴趣的SSA变量,就可以利用定义-使用链遍历其在函数内的所有使用。
变量指向节点,当变量赋值涉及指针算术时,除了节点信息外还会存储偏移信息。本质上构建了两个数据结构:将SSA变量映射到节点的字典,以及连接各种内存区域(表示为节点)的图。
将内存加载转换为图边
虽然内存存储操作被转换为图边,但来自函数范围外的内存加载操作也被表示为图边。基本假设是:如果内存被加载,它必须事先已被初始化。内存存储、赋值和加载操作是数据流图的基本构建块。
遍历数据流图以传播信息
信息传播通过SSA变量字典和先前初始化的图来实现:
- 直接变量赋值很简单
- 涉及指针算术的变量赋值会存储偏移信息
- 从内存加载数据的变量赋值会访问图的边来获取目标节点
通过调用栈进行过程间分析以检测释放后使用
检测潜在释放后使用漏洞涉及分析所有分类为"Free"的基本块,并验证是否存在导致分类为"Use"的基本块的路径。由于双重释放漏洞与释放后使用相关,分析还会检查是否存在从一个"Free"块到另一个"Free"块的路径。
自动化检测分配器和释放器调用
虽然理想情况是使用程序特定的分配器和释放器包装器作为分析的输入,但手动识别它们可能具有挑战性。更简单的起点是输入标准函数如malloc()、realloc()和free(),检查结果,并根据结果逐步完善分析。
分析过去的真实漏洞
为了理解工具的工作原理,我们在一些已知易受攻击的程序上进行了测试。由于GUEB已经提供了已识别漏洞的列表,我们选择使用它们作为示例。
文章详细分析了多个CVE漏洞案例,包括CVE-2015-5221(JasPer JPEG-2000)、CVE-2016-3177(Giflib)、GNOME-Nettool漏洞和CVE-2015-5177(OpenSLP),展示了工具在不同场景下的检测能力。
结论
本文介绍了使用Binary Ninja通过数据流分析和图可达性来发现释放后使用漏洞的方法。虽然当前的日志记录还很原始,每个被分类为潜在UAF条件的指令都被单独记录,但通过按基本块或函数对指令进行分组可以显著提高可读性。
致谢和参考文献
- Trail of Bits关于Binary Ninja的各种博客文章
- Josh Watson使用Binary Ninja的各种项目
- Jordan的代码片段和Binary Ninja slack社区
- Josselin Feist的GUEB静态分析器
- Sean Heelan关于使用静态分析发现释放后使用漏洞的工作