分享自:

基于硬件增强的事后崩溃构件进行事后程序分析

期刊:26th USENIX Security Symposium

这篇文档属于类型a:报告单一原创研究的科学论文

POMP:利用硬件增强的事后崩溃构件进行事后程序分析的研究报告

第一, 研究的基本信息

本研究的主要作者为宾夕法尼亚州立大学的徐军(Jun Xu)、邢新宇(Xinyu Xing)、刘鹏(Peng Liu)、陈平(Ping Chen),以及南京大学的牟东亮(Dongliang Mu)和毛斌(Bing Mao)。他们的论文《POMP:利用硬件增强的事后崩溃构件进行事后程序分析》(“POMP: Postmortem Program Analysis with Hardware-Enhanced Post-Crash Artifacts”)发表于第26届USENIX安全研讨会(the 26th USENIX Security Symposium)的会议论文集,会议于2017年8月16日至18日在加拿大温哥华举行,论文已被收录于会议录中。

第二, 研究的学术背景与目标

本研究属于软件安全与调试(Software Security and Debugging)领域,具体聚焦于程序崩溃(Crash)后的根源诊断(Root Cause Diagnosis)自动化技术。

研究背景:尽管操作系统会在程序崩溃时自动生成核心转储(Core Dump),但核心转储仅是崩溃时刻内存状态的快照,缺乏导致崩溃的完整执行历史信息,难以直接用于高效定位软件缺陷。近年来,随着英特尔处理器追踪(Intel Processor Tracing, Intel PT)等硬件辅助追踪技术的出现,可以在程序运行时低开销地记录其控制流(Control Flow)转移信息,并集成到崩溃时生成的事后构件(Post-Crash Artifact)中。这种增强型的事后构件包含了崩溃时的内存快照和导致崩溃的指令执行历史(即控制流),为调试提供了更多线索。

然而,即使有了这种增强构件,诊断过程仍需大量人工努力。执行历史可能包含数十亿条指令,即使只关注缺陷触发到崩溃发生之间的“关键”片段,也可能包含成千上万条指令。人工回溯(Backward Analysis)分析时,经常会遇到指令不可逆(如覆盖写)或内存别名(Memory Alias,即不同符号指向同一内存地址)等问题而受阻。先前的研究,如基于静态分析(Static Analysis)与动态分析(Dynamic Analysis)结合的方法,在处理因内存破坏(Memory Corruption,如缓冲区溢出)漏洞导致的崩溃时效果不佳,因为攻击者可能篡改控制流或数据流,违背了静态分析依赖的完整性假设。同时,一些方法需要以众包(Crowd-Sourcing)方式收集多个崩溃报告,适用场景有限。

研究目标:因此,本研究的目标是设计并实现一个名为POMP的自动化工具,其核心功能是:给定一个由硬件增强的事后崩溃构件,自动分析并精确定位真正导致程序崩溃的指令集。研究的最终目的是通过自动化分析,显著减少软件开发者或安全分析师在诊断程序崩溃时所需的人工工作量。本研究的一个关键假设是,通过POMP识别并高亮出与崩溃最相关的少量指令,可以极大地方便故障诊断。

第三, 研究的详细工作流程

本研究的工作流程主要分为两个核心部分:逆向执行机制(Reverse Execution)和数据流重建与后向污点分析(Backward Taint Analysis)。

1. 逆向执行机制 逆向执行的目标是从崩溃时刻的内存状态和指令执行历史(轨迹)出发,逆向恢复出崩溃前每个指令执行前的机器状态(内存足迹,Memory Footprints)。POMP的逆向执行算法是创新的,它不依赖于崩溃内存的完整性(允许内存被破坏),也不需要在程序正常运行时记录数据对象(无运行时开销)。该算法包含两个主要步骤:

  • 步骤一:使用-定义链(Use-Define Chain)构建

    • 研究对象与处理:算法以逆向顺序解析执行轨迹中的每一条指令。对于每条指令,根据其语义提取其对变量的“使用”(Use,读取值)和“定义”(Def,写入值),并将这些关系链接到一个全局的“使用-定义链”中。链中的每个节点包含指令ID、变量规格(如eax[ebp-0xc])和变量的值(已知或未知)。
    • 核心算法:算法在构建链的同时,不断检查变量的可解析性(Resolvability)。一个变量的值在以下情况可被解析:(a) 其定义能无干扰地到达链尾;(b) 能到达一个已知其值的后续使用;© 链前端一个已解析的定义能到达该变量;(d) 可直接从指令语义推导(如mov eax, 0x00)。
    • 特殊处理与保守策略:当遇到一条向内存地址(如[eax])写入的指令,而该地址(eax的值)无法从链上解析时,由于该写入可能与链上其他内存访问存在别名关系,算法会保守地将此定义视为一个“干预标签”(Intervening Tag)。该标签会阻止其上游的定义/使用向下游传播,以防止在别名情况不明时引入错误的数据流。同时,算法会维护从指令语义和已解析关系推导出的约束(Constraints),如相等、不等关系。
  • 步骤二:内存别名验证

    • 目标:第一步的保守策略会阻碍数据流的完整构建。第二步旨在通过假设检验(Hypothesis Test)来验证内存别名,从而移除不必要的干预标签,恢复更多数据流。
    • 算法细节:对于一对可能存在别名关系的内存访问符号(如[eax][ebp+0x8]),算法建立两个竞争假设:假设H1(两者是别名)和假设H2(两者不是别名)。基于每个假设,算法调整使用-定义链,释放或保持相应的数据流阻塞,并检查在传播新数据流时是否产生冲突(Conflict)。冲突有两种:数据依赖不一致(Constraints mismatch)或产生了会导致程序更早崩溃的无效值。
    • 递归与深度限制:算法可以递归地进行假设检验,即为了解决一个未知地址,可能需要先验证另一个潜在别名。但为了控制计算复杂度(避免指数爆炸),POMP实证性地将递归深度限制为最多两层。
    • 系统调用处理:对于执行轨迹中陷入内核的系统调用,POMP根据其是否影响用户空间内存进行处理。对于可能修改寄存器(如read的返回值)或写入已知大小和地址内存的系统调用,在链上添加相应的定义。对于写入地址和大小未知内存的系统调用,则将其视为干预标签。

2. 后向污点分析 * 目标:在逆向执行恢复出数据流关系后,此步骤的目标是自动化地筛选出真正导致崩溃的指令。 * 流程: * 确定污点源(Sink):根据崩溃类型确定污点源。如果是执行非法指令,则将程序计数器(eip)作为污点源;如果是解引用非法地址,则将持有该地址的寄存器(如eax)作为污点源。 * 后向传播:从污点源开始,逆向查找使用-定义链。对于当前污点变量,找到其最近的上游定义(该定义能无干扰地到达此变量)。然后,将该定义中的所有操作数、基址寄存器和变址寄存器都标记为新的污点变量(一个过度污点策略,确保不漏报)。此过程递归进行。 * 处理未知与干预:当污点需要传播到一个地址未知的内存变量时,传播可能因内存别名问题而暂停。此时,POMP会使用与逆向执行中类似的假设检验来尝试解决。如果检验失败,则采用过度污点策略,将该定义中的变量也标记为污点,以确保根原因被包含在结果集中。

3. 系统实现 * 平台与规模:POMP的原型在32位Linux系统(内核4.4)上实现,运行于支持Intel PT的Intel i7-6700HQ处理器。代码约22,000行C语言。 * 核心组件: * 分析子系统:实现了65个不同的指令处理器(Handler)来执行逆向执行和污点分析;使用libelf和libdisasm库解析核心转储和指令;集成Z3定理证明器(Theorem Prover)来管理和验证约束。 * 追踪子系统:配置Intel PT在TOPA(Table of Physical Addresses)模式下运行,分配物理内存缓冲区存储追踪数据包(Packets);修改Linux内核以支持按进程/线程管理PT缓冲区,并在上下文切换时迁移数据包到用户空间循环缓冲区,确保追踪的连续性;通过暂停内核空间追踪和过滤无关数据包来优化日志。

第四, 研究的主要结果

本研究通过在31个由真实世界安全漏洞(CVE)导致的程序崩溃上进行测试,全面评估了POMP的有效性和效率。测试用例涵盖了栈溢出、堆溢出、整数溢出、空指针解引用、释放后使用等多种漏洞类型。

  • 有效性结果

    • 根因包含:在31个测试用例中,POMP成功在29个用例的输出指令集中包含了导致崩溃的根因指令。唯一的两个失败案例中,一个(aireplay-ng)是因为涉及系统调用写入地址和大小未知的内存,导致大量数据流被阻断;另一个(0verkill)是由于整数溢出导致PT日志中算术指令过多,而根因指令未被充分记录。
    • 覆盖真实指令集:在所有成功案例中,POMP自动识别出的指令集均完全覆盖了研究者通过手动分析确定的“真实相关指令集”(Ground Truth)。
    • 显著缩减分析范围:与POMP需要逆向执行的指令总数(从几十到超过十万条不等)相比,POMP最终高亮出的指令数量极少(通常在2到49条之间)。这极大地缩减了安全分析师需要手动审查的代码范围。例如,对于unalz程序,POMP逆向执行了10,905条指令,但仅高亮了14条,其中7条是真实相关指令。过度污点(Over-Tainting)带来的额外审查负担相对于手动遍历成千上万条指令而言是微不足道的。
    • 内存恢复能力:尽管将假设检验递归深度限制为2,导致一部分内存地址无法被解析(数量在0到1033个之间),但POMP仍然能够有效地定位崩溃相关指令。这表明其设计在效用和计算复杂度之间取得了合理平衡。
  • 效率结果

    • POMP的分析时间从不足1秒到数小时不等,总体上与需要逆向执行的指令数量及遇到的内存别名验证次数成正比。大多数案例的分析在几分钟内完成。例如,处理一个包含约2.5万条指令的轨迹(corehttp)耗时52分钟,而处理一个包含约10万条指令的轨迹(unrar)耗时6小时。对于较短的轨迹,分析可在秒级完成。
  • 结果间的逻辑关系

    • 逆向执行机制成功恢复内存足迹和数据流关系,是后向污点分析能够准确进行的基础。没有逆向执行构建出的精确使用-定义链,污点分析将无法在存在覆盖写和内存别名的情况下正确回溯。
    • 实验结果表明,即使逆向执行未能解析所有内存地址(结果表中“mem addr unknown”列),但已恢复的数据流信息通常已足够支持污点分析定位到关键指令。这验证了POMP设计在非完美信息下的鲁棒性。
    • 效率结果证实了POMP的实用性,虽然处理极长轨迹可能较慢,但对于大多数崩溃诊断场景,其分析时间是可接受的,并且自动化带来的时间节省远超分析本身耗时。

第五, 研究的结论与价值

本研究的结论是:POMP是一种有效且实用的自动化工具,能够通过分析硬件增强的事后崩溃构件,自动重建程序崩溃前的数据流,并精确定位真正导致崩溃的指令,从而显著降低软件故障诊断的人工成本。

  • 科学价值

    1. 方法创新:提出了首个仅基于事后崩溃构件(无需源代码、无需运行时记录、无需众包)即可恢复崩溃前程序数据流的方法。其逆向执行算法创新性地结合了使用-定义链构建和基于假设检验的内存别名验证,放宽了对内存完整性的假设。
    2. 问题解决:有效地解决了传统逆向执行依赖内存完整性和记录的问题,以及现有自动化调试方法在处理内存破坏漏洞和单次崩溃场景时的局限性。
    3. 通用性证明:通过实验证明,该技术不仅适用于内存破坏漏洞,也适用于空指针解引用等其他类型软件缺陷导致的崩溃。
  • 应用价值

    1. 提升调试效率:为软件开发者、安全响应团队和漏洞分析师提供了一个强大的自动化工具,能够快速从崩溃报告(尤其是来自生产环境的崩溃)中定位问题根源,加速补丁开发和漏洞修复。
    2. 降低技术门槛:使不具备深厚逆向工程技能的分析师也能进行有效的崩溃分析。
    3. 非侵入性与广泛适用:POMP完全依赖操作系统和硬件已提供的崩溃信息与追踪功能,无需修改或插桩目标程序,适用于各种编程语言编写的程序,且不依赖协同调试环境。

第六, 研究的亮点

  1. 核心技术创新:研究提出的“递归假设检验”逆向执行算法是最大亮点。它能够在内存可能被破坏、且无先验记录的情况下,有效地恢复数据流和验证内存别名,这是实现自动化精确定位的关键。
  2. 问题定位精准:研究准确地抓住了当前利用硬件增强崩溃构件进行诊断的核心痛点——信息过载与人工回溯困难,并提出以“定位最小相关指令集”为目标,直击问题本质。
  3. 系统实现完整:研究不仅提出了理论方法,还构建了一个完整的、与操作系统及硬件深度集成的原型系统(POMP),包括自定义的PT配置与内核修改,并在真实漏洞上进行大规模评估,证明了其可行性和有效性。
  4. 评估全面可信:使用31个来自不同程序、不同类型的真实漏洞进行测试,评估指标涵盖有效性(根因包含、指令集覆盖、范围缩减)和效率,结果具有说服力。

第七, 其他有价值的内容

  • 对未来工作的展望:论文在讨论部分指出了POMP当前的局限性,并提出了明确的未来改进方向,包括处理多线程干扰问题(通过记录执行时序合成完整轨迹)和支持即时编译(JIT)代码(通过监控和转储运行时生成的原生代码)。这表明研究团队对技术的边界和发展有清晰的认识。
  • 开源承诺:作者声明将公开POMP的源代码(提供GitHub链接),这有利于学术界的验证和工业界的应用与改进,体现了研究的开放性和可重复性。
上述解读依据用户上传的学术文献,如有不准确或可能侵权之处请联系本站站长:admin@fmread.com