软件中的“怪异机器”:漏洞利用的新前沿与自动化识别
自1988年莫里斯蠕虫以来,对抗软件漏洞利用一直是一场猫鼠游戏。攻击者使用特定的利用原语来实现非预期的代码执行。主要软件供应商引入漏洞利用缓解措施来打破这些原语。一来一回,反复拉锯。缓解措施确实提高了成功利用的门槛,但仍有机会更接近可证明的安全收益。
今年早些时候,在D60会议上,我向项目经理和其他国防官员介绍了使用“怪异机器”绕过这些缓解障碍或证明程序不可利用的方法,作为DARPA Risers会议的一部分。简洁地描述这个问题很困难,尤其是对非实践者而言。
为什么“怪异机器”重要
攻击者寻找“怪异机器”来击败现代漏洞利用缓解措施。“怪异机器”是部分图灵完备的代码片段,它们固有地存在于函数和函数组周围的“松散契约”中。松散契约是一段代码,它不仅实现了预期的程序,而且以程序经历比应有更多状态变化的方式实现(即程序状态的前提条件集比必要的大)。我们想要“紧密契约”以获得更好的安全性,其中程序仅在确切预期的前提条件下改变状态,并且不会出现“怪异”或非预期的状态变化。
“怪异机器”是一个代码片段,它会以程序员未预期的方式处理有效或无效输入。
不幸的是,松散契约存在于大多数软件中,并且是功能的副产品,例如链表遍历、文件解析、小型单一用途函数以及任何其他从复杂系统中出现的功能。现代攻击者利用这些非预期的计算,并构建绕过漏洞利用缓解措施和安全检查的“怪异机器”。让我们看一个“怪异机器”的例子。
|
|
函数ListItem::TrySetItem
看起来有这些前提条件:
- 你必须传入
this
和item
,两者都是指针 this
和item
必须是已分配和构造的ListItem
实例
然而,一旦生成机器码,前提条件实际上是:
this
参数必须是指向至少8字节已分配内存的指针- 你必须传入第二个参数(
item
),但它可以是任何类型
这是一个松散契约的例子,它固有于我们编写代码的方式。覆盖了m_next
指针的攻击者可以利用此函数检查任意地址的内存是否已设置:如果是,则攻击者可能泄露内存;如果不是,则攻击者可能设置内存。
漏洞用于改变程序执行或数据状态。此后的执行要么是怪异状态,要么是在非预期数据上操作的状态。
收紧契约
一种松散契约是“执行”契约,它是程序中有效间接分支的可能地址集。
1995年的Windows NT是松散执行契约的一个例子,其中所有标记为“读取”的内存也隐含“执行”。这包括程序中的所有数据——不仅仅是代码。2003年,微软在Windows XP SP2中引入不可执行数据(NX,DEP)时收紧了执行契约。微软在2006年Windows Vista中引入地址空间布局随机化(ASLR)时进一步改进了契约,该技术随机化可执行代码的位置。2016年,随着Windows 8.1/10引入了控制流防护(CFG),它验证间接分支的前向边缘指向一组批准的函数。
在下图中,很明显,剩余的有效间接目标很少。这种紧密的“执行”契约使得利用更加困难,对“怪异机器”的需求更大,显著增加了“怪异机器”的价值。如果我们能进一步收紧程序契约,将使“怪异机器”更难识别。
执行契约定义了程序中可执行的区域(黄色)。随着契约的收紧,这些区域多年来已经减少。
“怪异”的样子
识别“怪异机器”是一个难题。当我们甚至不知道契约是什么时,如何识别松散契约?自动识别这些“怪异机器”将使我们能够正确分类漏洞是否实际上可利用,以及在没有“怪异机器”的情况下是否不可利用。
一种以编程方式描述“怪异机器”的方法是通过霍尔三元组。霍尔三元组描述了一段代码的执行如何改变计算状态:进入新状态所需的前提条件以及描述如何离开状态的后条件。当我们识别“怪异机器”时,可以通过移除它们或约束前提条件恰好是状态所期望的来自动收紧这些契约。这将使我们更接近创建可证明安全的程序。
重新审视我们的例子,我们可以添加dynamic_cast
来强制执行契约前提条件。如果我们作为霍尔三元组分析代码片段,我们会注意到函数执行的前提条件是松散的,因此任何地址都可以传递给函数。此外,后条件不存在,因此一旦执行,函数将设置或返回内存,无论程序状态如何。
|
|
dynamic_cast
是运行时防护,检查以验证函数是否在预期的指针上操作。这个新函数在利用中显然不如以前有用。
具有不精确定义前提条件的霍尔三元组允许发生“怪异”状态变化。通过改进输入检查来收紧这些前提条件将使这些状态无法实现。
那么我们如何找到它们?
在通往解决方案的道路上存在许多难题。首先是规模。我们不关心简单的测试用例,我们关心的是今天部署给数十亿人的真实代码:浏览器、操作系统、邮件客户端、消息应用程序。自动识别这些平台上的“怪异机器”是一个重大挑战。
给定一组可能的执行路径及其对象创建和访问模式,我们必须识别具有特定和可控副作用的程序切片。这些切片本身必须是图灵完备的。这些“图灵thunk”的行为可能在其正常放置的执行路径之外或具有不同的数据状态时不同。为了扩展我们的分析,我们可以将问题分解为子组件。
从识别图灵thunk开始,分析它们的副作用,并确定它们的可达性。我们可以使用数据流分析和形状分析来识别这些“图灵thunk”并测量它们的副作用。将测量这些已识别“怪异机器”的副作用,以确定这些候选“怪异机器”如何组合。对全局状态的改变可能改变后续“怪异机器”的执行。数据流提供基于受控输入可转换的路径。形状分析有助于重建堆对象、布局以及对象之间的交互。这有助于确定生成执行路径到“怪异机器”所需的输入约束,以及执行“怪异机器”之前和之后的堆状态。
一旦识别出候选,就可以基于特定功能和副作用进行优先级排序。我们可以使用符号和具体执行来验证这些候选,并使用机器学习按行为、执行约束和副作用对候选进行分组,以便后续查询更容易。
漏洞利用的未来
最终,“怪异机器”是漏洞利用的基本工具。随着程序变得更复杂和缓解措施的堆积,“怪异机器”的重要性只会增加。在现实世界程序中找到这些图灵片段并枚举它们的属性将有助于下一代漏洞利用和安全。
一旦我们能自动识别“怪异机器”,我们将有能力移除这些怪异状态,并确定程序的可利用程度。我们或许还能证明特定漏洞不可利用。
解决方案的一部分是改进术语,需要成熟。解决方案的另一部分是进一步研究问题空间。虽然对该主题有兴趣,但我希望DARPA未来投资这一领域。
识别和分类“怪异机器”的工具和系统尚不存在。我们还有很多工作要做,但构建块已经存在。有了它们,我们将更接近解决明天的问题。
如果你想了解更多关于这一研究领域的信息,我建议你从这些出版物开始:
- 怪异机器、可利用性和可证明的不可利用性
- 漏洞利用编程:从缓冲区溢出到“怪异机器”和计算理论
- 作为代码重用的漏洞利用:论形式化的必要性
- 证明携带代码中的怪异机器
如果你喜欢这篇文章,请分享: Twitter LinkedIn GitHub Mastodon Hacker News
页面内容 为什么“怪异机器”重要 收紧契约 “怪异”的样子 那么我们如何找到它们? 漏洞利用的未来 最近帖子 我们构建了MCP一直需要的安全层 利用废弃硬件中的零日漏洞 Inside EthCC[8]:成为智能合约审计员 使用Vendetect大规模检测代码复制 构建安全消息传递很难:对Bitchat安全辩论的细致看法 © 2025 Trail of Bits。 使用Hugo和Mainroad主题生成。