分享自:

SAFER:高效且容错的二进制插桩技术

期刊:32nd USENIX Security Symposium

针对USENIX Security Symposium 2023论文《SAFER: Efficient and Error-Tolerant Binary Instrumentation》的学术研究报告

本研究由美国石溪大学(Stony Brook University)的Soumyakant Priyadarshan, Huan Nguyen, Rohit Chouhan和R. Sekar共同完成,其论文《SAFER: Efficient and Error-Tolerant Binary Instrumentation》于2023年8月9日至11日在美国加州阿纳海姆举行的第32届USENIX安全研讨会上发表并收录于会议论文集。

一、 学术背景 本研究的科学领域属于计算机安全与系统软件,具体聚焦于二进制插桩(Binary Instrumentation)技术。程序插桩是软件安全的核心基础技术,广泛用于漏洞利用缓解、安全策略强制实施、模糊测试(Fuzzing)和性能监控等。二进制插桩因其直接对部署在终端系统上的二进制可执行文件进行操作而具有最广泛的适用性,不仅适用于只有二进制形式的专有软件,也适用于大多数以二进制形式分发的Linux开源软件。

然而,对x86/x64架构的二进制文件进行准确插桩面临巨大挑战,主要源于三个方面:1) 反汇编错误:由于指令长度可变、代码与数据难以区分,即使最好的反汇编工具也存在不可忽略的错误率。2) 代码指针识别错误:插桩需要移动原始指令以插入新代码,因此必须识别并更新所有指向原始代码位置的指针常量(如函数指针、返回地址),但静态区分数据中的整数和指针常量极其困难。3) 对复杂代码的兼容性:例如位置相关代码(非PIE)、代码中嵌入的数据、C++异常处理等特性,增加了分析的复杂度。

现有的二进制插桩技术可分为两类:第一类(Group I)如DynamoRIO、Pin、BinCFI等,采用运行时地址翻译等方法,健壮性好、兼容性高,但性能开销大(尤其是对于大量使用间接调用的C++程序,开销可达30%以上)。第二类(Group II)如Egalito、RetroWrite等,通过静态转换代码指针来避免运行时开销,实现了近乎零开销,但其安全性建立在多个乐观假设之上,包括无错误的完整反汇编、仅使用位置无关代码(PIC/PIE)、以及对代码指针的识别无误报无漏报。一旦假设被违反,可能导致程序崩溃或更严重的数据损坏和安全漏洞。

因此,一个悬而未决的研究问题是:能否将第一类技术的安全兼容性优势与第二类技术的性能优势结合起来?本论文对此给出了肯定的答案,旨在设计一种既高效又能容忍常见分析错误的二进制插桩技术。

二、 详细工作流程 SAFER(Safe and Efficient Binary Rewriter)系统的工作流程是一个多阶段的分析与转换过程,主要包含以下核心步骤:

  1. 容忍错误的反汇编

    • 目标:最小化因反汇编错误导致的运行时故障。
    • 方法:采用以递归反汇编为主、线性扫描为辅的混合策略。首先从二进制入口点、动态符号表、以及静态分析发现的可能代码指针处开始递归反汇编。为了最小化漏报(即未识别的代码),系统会在初始递归未发现代码的字节偏移处重新启动反汇编尝试,确保代码段中每个字节都被某个指令解释所覆盖,类似于“超集反汇编”思路,但通过冲突解决算法(基于代码的统计和行为属性)来消除不可能的指令解释,从而控制代码膨胀。
    • 错误处理
      • 对于误报(数据被误判为代码):采用保留原始代码副本的策略。被误判的数据区域在插桩后的代码中保持不变,任何对该数据的访问都将获得与原始程序完全相同的值,从而不影响功能。
      • 对于漏报(代码未被识别):由于任何转移到未识别代码的控制流都必须通过间接分支,而间接分支需要通过地址翻译表。如果地址翻译表中没有该未识别位置的条目,则该转移会被拦截,触发“故障-崩溃”(Fail-Crash)机制,实现安全失败而非任意的未定义行为。
  2. 代码指针分类

    • 目标:准确识别并分类二进制文件中的所有代码指针。
    • 流程:如图2所示,系统首先识别所有“可能的代码指针常量”,包括重定位条目标记的指针、落在代码段内的常数值、PC相对地址加载指令以及返回地址。
    • 分类:将可能的指针进一步分为“确定代码指针”和“可能代码指针”。
      • 确定代码指针:指那些确认为指针的值(如重定位常量、PC相对取址指令生成的地址、调用指令压栈的返回地址),并且其目标地址满足函数接口属性(ABI),可选地还进行严格的函数序言模式匹配。这些指针将在重写时被安全地转换。
      • 可能代码指针:所有其他未被归类为确定指针的候选指针。这些指针将保持原样,依赖运行时地址翻译。
    • 计算型代码指针识别:主要针对跳转表(由switch语句等生成)。系统通过静态分析间接跳转指令的源操作数计算模式,识别三种常见模式(基址+表[索引*跨度]、表[索引*跨度]、基址+索引*跨度)。通过计算索引的可能取值范围(保守地从0到二进制文件末尾或下一个跳转表起始),生成所有可能的目标地址集合,并将其加入“可能代码指针”集合。对于分析失败的间接跳转,则保守地将其所在函数内的每个指令边界都视为可能目标。
  3. 安全转换代码指针常量

    • 核心创新——指针编码技术:为实现高效与安全的平衡,SAFER提出了一种新颖的乘法编码方案。对于每个被归类为“确定代码指针”的指针x,在静态重写时对其进行编码:enc(x) = (a*x mod 2^n-1) | 2^(n-1)。其中a是一个随机奇数,n是可用的地址位宽(如48)。编码后的指针最高有效位被设为1(在用户态地址中通常为0),使其指向未映射的内存区域。
    • 运行时决策:在每次间接控制转移时,插桩代码会检查目标地址。如果其最高有效位为1,则认为是已编码的确定指针,使用相应的解码函数dec(y) = b*(y & (2^(n-1)-1)) mod 2^(n-1)(其中ba的模乘法逆元)进行解码,恢复正确的目标地址。如果最高有效位为0,则认为是未转换的或“可能代码指针”,则通过查询地址翻译表来获取正确的目标地址。
    • 安全属性:该编码方案具有两个关键安全属性:1) 正确编码解码的指针总能恢复原值。2) 如果一个编码后的指针被意外修改(例如,它本是一个整数却被误判为指针并编码),那么对其进行解码后,有极大概率(1 - s/2^(n-1),其中s为有效代码大小)会得到一个无效地址,从而立即引发内存故障,实现“故障-崩溃”,阻止了错误传播导致的数据损坏或安全漏洞。
  4. 安全跳转表插桩

    • 问题:直接修改跳转表内容存在风险:如果跳转表边界识别错误,可能破坏相邻数据;如果程序其他地方将跳转表条目作为数据使用,修改会导致程序行为异常。
    • 安全转换方法:SAFER不直接修改原始跳转表,而是为其创建副本,并修改访问该跳转表的代码,使其指向新的副本。新副本中的条目被更新,以指向插桩后代码中的正确位置。
    • 安全性静态分析:为确保代码修改的安全性,对涉及跳转表访问的代码进行静态污点分析。将新的基址和表地址常量标记为污点源,并检查污点数据是否:1) 被用于非预期的内存解引用;2) 被存储到内存;3) 作为参数传递给其他函数或作为返回值逃逸出当前函数。只有当所有检查都通过时,才认为该跳转表转换是安全的,否则回退到使用地址翻译。
  5. 地址翻译机制

    • 结构:采用两级结构。每个模块在插桩时构建一个本地翻译表,存储原始代码偏移到新代码偏移的映射。运行时,一个全局翻译表将代码页地址映射到对应模块的本地翻译表。
    • 优化——返回地址优化:由于返回操作非常频繁,为其使用地址翻译开销巨大。SAFER在支持C++异常处理等非标准使用返回地址的场景下,通过重写异常处理元数据,允许在栈上存储新的返回地址(而非原始地址),从而避免了对返回指令的插桩和地址翻译,显著降低了开销。

三、 主要结果 1. 功能性与兼容性: * SAER成功对超过1.1GB的二进制代码(包括SPEC 2006/2017基准测试套件、coreutils以及多个现实世界应用程序如GIMP、FFmpeg、Clang等)进行了插桩,所有测试均通过。 * 成功处理了包含代码内嵌数据的二进制文件(通过自定义链接脚本编译的coreutils)。实验表明,当使用指令边界或ABI属性单独判断时,部分程序会因指针误分类而按设计触发“故障-崩溃”;当结合函数序言匹配后,所有程序均正常运行。 * 成功处理了位置相关可执行文件(非PIE),如SPEC 2006的非PIE版本以及GCC、Clang等现实程序,这是许多Group II工具无法做到的。 * 在存在复杂特性(如C++异常、栈回溯、longjmp)的程序中(如SPEC中的omnetpp、povray、perlbench),插桩后的程序均能正确运行。

  1. 性能开销

    • 在SPEC 2006 PIE基准测试上,SAFER在启用所有优化(返回地址优化和跳转表优化)后,平均运行时开销仅为1.91%,接近Group II工具(近乎零开销)的水平。
    • 与Group I工具对比优势明显:基础“全地址翻译”模式开销为30.8%,通过引入指针编码降至10.6%,启用返回地址优化后降至4.5%,最终结合安全跳转表优化达到1.91%。这远低于BinCFI(最佳报告8.54%)和Multiverse(最佳报告8.29%)的优化后开销。
    • 在非PIE程序上,由于缺乏重定位信息,更多指针被归类为“可能指针”,平均开销为5.2%,仍优于支持非PIE的现有技术。
    • 对SPEC 2017基准测试在不同编译优化级别(O0-O3, Ofast, Os)下的评估显示,平均开销为2.3%,在O3级别下为1.85%,证明了其在不同代码特征下的稳定性能。
  2. 安全跳转表转换的有效性

    • 在SPEC 2006程序中,约55%的跳转表被静态分析判定为可安全转换。其余被判定为不安全的主要原因是污点值可能作为参数传递(callargs,33%)或逃逸到调用者(retval,9%)。对于这些情况,系统保守地使用地址翻译,保证了安全性。
  3. 内存开销

    • 插桩后的二进制文件大小平均约为原文件的3.5倍。这包括:1) 保留的原始代码(1x);2) 新增的插桩代码(平均1.7x);3) 地址翻译哈希表(平均0.7x);4) 新建的跳转表(0.02x)。运行时内存足迹约为原始的2.5倍。该开销远低于Multiverse报告的平均值(10x-29x)。

四、 结论与价值 本研究的结论是,确实可以设计出一种二进制插桩系统,在保持低性能开销(~2%)的同时,显著提高对反汇编错误、代码指针识别错误以及复杂二进制文件特性(如非PIE、代码内嵌数据)的容忍度。SAFER通过创新的指针编码技术、安全的跳转表转换方法以及结合静态分类与运行时翻译的混合架构,实现了这一目标。

其科学价值在于: 1. 提出了新的安全抽象:“故障-崩溃”原语和指针编码方案为构建健壮的二进制分析工具提供了新的思路,确保错误可预测地失败,而非导致不可控的后果。 2. 解决了关键挑战:首次系统化地阐述并解决了跳转表安全转换的问题,并给出了一个实用的解决方案。 3. 实现了性能与安全的平衡:证明了高效的静态指针转换和稳健的运行时回退机制可以协同工作,打破了此前两类技术在此方面的取舍困境。

其应用价值在于: 1. 提高了二进制插桩的实用性:使得安全关键的安全插桩(如控制流完整性、影子栈)能够以更低的开销应用于更广泛的现实世界软件,包括那些包含遗留代码、复杂特性的应用程序。 2. 增强了工具的可靠性:降低了因二进制分析不完美而导致生产环境程序崩溃或出现隐蔽错误的风险。

五、 研究亮点 1. 创新的指针编码方案:利用模乘运算和地址空间冗余,仅需一个未使用的地址位即可实现指针身份验证和篡改检测,方法简洁高效,且概率性检测的失败率极低。 2. 安全的跳转表转换:首次明确提出了跳转表转换的安全性问题,并通过“复制-修改-验证”的流程和污点分析,确保了转换不会引入副作用。 3. 混合架构设计:巧妙地将静态确定的指针转换(高效)与对不确定指针的运行时地址翻译(稳健)结合起来,并通过编码机制在运行时无缝切换,是工程上的核心创新。 4. 全面的兼容性:在追求性能的同时,没有牺牲对非PIE、代码内嵌数据、C++异常等现实场景的支持,大大提升了工具的适用范围。 5. 详实的评估:在大型、多样的数据集(包括SPEC基准和众多现实应用)上进行了功能、性能、内存等多维度评估,结果令人信服。

六、 其他有价值内容 论文还讨论了SAFER的局限性:当前原型在处理超大型二进制文件(如>160MB的libxul)时,反汇编阶段可能因资源耗尽而失败;指针编码需要至少一个未使用的地址位,对于重用所有指针位的程序需要额外处理。作者指出,对于可能导致故障的少数二进制文件,可以采用回退到全地址翻译或加强函数序言检查的更安全模式,并计划未来利用反汇编信息主动识别高风险二进制以应用这些模式。这些讨论为后续研究和改进指明了方向。

上述解读依据用户上传的学术文献,如有不准确或可能侵权之处请联系本站站长:admin@fmread.com