跳转至

Static Devirtualization of Themida

Ch12.086 Static Devirtualization of Themida

📊 Level ⭐⭐ | 5.1KB | entities/back-engineering-static-devirtualization-themida.md

-> 原文存档

关键要点

  • Themida 是一种软件保护壳,使用虚拟化技术保护软件
  • 文章提供了静态去虚拟化的技术细节
  • 包含代码级别的技术分析 → 原文存档

深度分析

Themida 的虚拟机架构与 VMProtect 的主要区别在于支持嵌套虚拟化。VM context 和虚拟栈位于 binary 自身内部,而非原生栈上。devirtualization 的核心方法是将原生指令提升为可塑的中间表示(IR),通过将控制流具体化来驱动提升过程——当优化解析出未知的分支目标时, concrete control flow 使提升能够继续进行。 关键设计决策:起始时将栈指针(RSP)保持为具体值而非符号值。这意味着加载/存储传播机制可以自动处理栈访问,栈指针算术运算也能无需特殊处理即可被常量折叠。唯一的限制是不支持动态栈分配(如 alloca),但在实际虚拟化的函数中这种情况很少见。 devirtualization 的主要工作由一组通用优化完成,VM-specific 知识只在处理控制流时才变得必要——具体来说是虚拟分支和虚拟调用。几乎所有介绍内容都适用于 VMP 和其他基于 VM 的保护器,只有 VJCC handler 是 Themida 特有的构造。 常量折叠需要运行至收敛。每次优化 pass 都为下一个 pass 提供输入:bytecode load 被提升为常量 → 解密运算被折叠 → handler 索引变为具体 → handler 表查找被解析 → 下一 handler 地址成为常量。VM 脚手架由此逐步崩溃。 死存储消除(Dead Store Elimination)在这里是安全的,因为目标是 VM-private 内存。Themida 使用自己的 section 存放 VM context、虚拟栈和相关脚手架,这些内存对原始程序不可见。一旦 lifting 到达 VMEXIT,任何仅访问 Themida section 的存储都可以被证明是死的。 Themida 的 VJCC handler 与 VMP 不同:条件先被求值并将结果写入 branch_taken_flag,VIP 直到 handler 末尾才推进。这意味着符号执行在 VIP 被解析之前就遇到了分支分叉,两条路径都需要被探索,每条路径的正确 VIP 需要通过条件 VPC 更新逻辑追踪,而非从单一加载中读取。

实践启示

  1. 不要依赖模式匹配 VM handler:任何对 VM-specific 行为的依赖都会使工具变得脆弱。小的改变(如 handler 布局、opcode 表或调度逻辑的变化)可能无声地破坏整个版本范围的工具。最小化 VM-specific 知识才是跨版本工作的关键。
  2. 符号执行的引导策略:当指令指针无法确定时,说明优化还没运行足够距离,或者分支有多个真实目标(如虚拟化 JCC)。不要急于对未知分支做决策,继续运行优化直到可以具体化。
  3. load/store 传播的配置范围策略:指定哪些内存范围可以安全提升可以避免用户数据被意外处理。store 追踪防止从被写入的地址提升(会产生错误结果),而可配置的 range 策略可以区分 VM 脚手架和反映原始程序语义的加载。
  4. 降低寄存器压力:在 lowering 阶段,寄存器分配器溢出会导致 devirtualized 代码获得自己的栈帧,这会使栈传递的参数在被虚拟化区域内部的调用中被误读。目标是产生可执行输出,使其在任何反汇编器中都干净地加载。LLVM 在这方面有挑战性,因为让 LLVM 发出紧密的、行为良好的、可重新插入 binary 的原生代码是一个独立的项目。
  5. MBA 表达式已可被还原:通过 MBA 表达式隐藏分支目标的技术现在可以被轻易还原(参见相关演示)。更强的技术(如 CodeDefender 的重保护层)使符号执行在通用情况下变得不可行。

相关实体