二进制安全优化实践
“这样存在毫无意义,安全地存在才有价值”(《麦克白》3.1)
编译器不仅要生成高效代码,还必须生成安全代码。尽管编译器开发过程中经过严格测试和正确性认证,但其优化过程可能意外引入信息泄漏或删除程序员编写的安全关键操作。图1展示了CWE-733漏洞实例:编译器通过"死存储消除"优化移除了加密密钥清零操作,导致密钥可能被攻击者恢复。
代码重用攻击的构建原理
攻击者使用返回导向编程(ROP)和跳转导向编程(JOP)等技术绕过恶意代码注入防御。这些技术通过复用目标程序中的代码片段(gadgets)构建攻击载荷。每个gadget包含有效计算指令和终止分支指令(如ret/jmp),攻击者通过链式调用gadgets编写漏洞利用程序。图2展示了一个简单的ROP攻击链:
研究发现总结
通过分析GCC/clang编译的20个程序的1000多个变体,我们发现:
- 85%的优化案例增加了gadget集合规模、效用或可组合性
- 主要根源来自两种行为:间接分支指令复制和代码布局变更
间接分支指令复制
GCC的帧指针省略优化会复制返回指令(retn),如图3所示。虽然减少了代码大小和执行时间,但会引入更多可能被利用的gadgets。
二进制布局变更
优化导致的控制流指令位移变化可能意外编码间接分支指令(如0xC3对应retn)。如图4所示,x86_64的非对齐变长指令特性使得这些位移可能被解码为有效gadgets。
解决方案:二进制安全优化
我们在Egalito二进制重编译器中实现了五种优化方案:
- 返回指令合并:合并函数内所有返回指令到单个实例
- 间接跳转合并:合并同目标寄存器的间接跳转
- 指令屏障扩展:消除跨预期指令的特殊用途gadgets
- 位移填充:消除跳转位移中的gadgets
- 函数重排序:消除调用偏移中的gadgets
实际效果:
- 平均消除31.8%有用gadgets
- 78%变体中降低gadget集合整体效用
- 75%变体中消除至少一类特殊用途gadgets
- 零性能影响,平均仅增加6.1kB代码大小
结论
编译器行为显著影响二进制gadget集合,但当前设计缺乏对潜在安全属性的关注。通过二进制重编译技术可以在不牺牲性能的前提下消除这些安全隐患。未来将研究寄存器分配等更多问题,并探索其对控制流完整性(CFI)等防御机制的辅助优化效果。