Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

订阅源联系隐私© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
VMProtect-devirtualization — 探索 VMProtect 软件保护机制。利用符号执行和 LLVM 对纯函数进行自动去混淆。 | Kitploit
工具/GitHubGitHub/jonathansalwan/vmprotect-devirtualization
静态分析动态分析 (沙盒)逆向工程模糊测试二进制分析
GitHubjonathansalwan/vmprotect-devirtualization

VMProtect-devirtualization

探索 VMProtect 软件保护机制。利用符号执行和 LLVM 对纯函数进行自动去混淆。

查看仓库

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享
1.5k208164年前Kitploit 审核通过

VMProtect 去虚拟化

一种 实验性 的动态方法,用于去虚拟化受 VMProtect 3.x 保护的纯函数

 

 

  • TL;DR
  • 简介
  • 方法
    • 示例 1:一个受保护的简单位运算
    • 示例 2:一个受保护的 MBA 运算
    • 示例 3:多个基本块
  • 结论与局限性
  • 参考

 

 

TL;DR

我分享一些关于以动态方法去虚拟化受 VMProtect 保护的纯函数的笔记。 如果被虚拟化的函数只包含一个基本块 (无论其大小如何),这种方法已显示出非常好的效果。当二进制文件保护算术运算时,这是一种常见场景。然而, 当目标函数包含多个基本块时,这种方法会更具实验性。 尽管如此,我们仍成功对包含 2 个基本块的样本进行了去虚拟化并重建了二进制代码,这可能表明 完全动态去虚拟化小型函数是可能的。

简介

VMProtect 是一种软件保护方案,它通过让代码在一种非标准架构的 虚拟机中运行来保护代码。这种保护对汇编语言爱好者来说是一个绝佳的游乐场 [0, 1, 2, 3, 4, 5, 6, 11]。此外,已经有许多工具针对这种保护进行攻击 [7, 8, 9, 12, 13]。 2016 年,我们研究了 Tigress 软件 保护方案,并成功利用符号执行和 LLVM 破解了它的虚拟化。这一方法 已在 DIMVA 2018 [10] 上展示,而我想在 VMProtect 上测试它。请注意,不存在 能适用于所有二进制文件的万能解决方案,根据目标和你的需求,总是存在各种权衡。 这份微薄的贡献旨在提供一个针对 VMProtect 虚拟化的 纯函数 进行动态攻击的示例。 动态攻击的主要优势在于,它从设计上就能绕过 VMProtect 的一些静态保护, 例如自修改代码、密钥和操作数加密等。

我们所说的纯函数,是指路径数量有限且没有副作用的函数。 它可以有多个输入,但只有一个输出。下面是一个纯函数的示例:```cpp int secret(int x, int y) { int r = x ^ y; return r; }

# 方法

我们依赖一个关键直觉:混淆后的轨迹 T'(来自混淆代码 P')组合了原始代码 P 中的原始指令(原始代码中与 T' 对应的轨迹 T)以及虚拟机 VM 的指令,因此 T' = T + VM(T)。如果我们能够区分这两个指令子序列 T 和 VM(T),那么我们就能从轨迹 T' 重构原始程序 P 的一条路径。通过重复此操作以覆盖被虚拟化程序的所有路径,我们将能够重构原始程序 P。在我们的实际示例中,原始代码具有有限数量的可执行路径,这在许多涉及知识产权保护的情况下都是如此。为此,我们按以下步骤进行:

1. 识别被虚拟化的函数及其参数
2. 生成目标的 VMProtect 轨迹
3. 重放 VMP 轨迹并构建符号表达式,以获得输入与输出之间的关系
4. 对符号表达式应用优化,尽可能避免来自 VM 的指令
5. 将我们的符号表示提升到 LLVM-IR,以构建目标的一个新的未受保护版本

## 示例 1:一个简单的按位运算

让我们以以下函数作为第一个示例:它接受两个输入,并返回受 VMProtect 保护的 `x ^ y`。```cpp
int secret(int x, int y) {
  VMProtectBegin("secret");
  int r = x ^ y;
  VMProtectEnd();
  return r;
}

我们首先识别哪些函数使用了 VMProtect,以及它们有多少个参数。在我们的示例中,我们可能会得到如下内容:

仅通过阅读代码,我们就能知道该函数起始地址为 0x4011c0,有两个 32 位参数(edi 和 esi) 并在 0x4011ef 返回。这就是我们需要的全部逆向工程工作。接下来的部分将自动完成。现在我们需要 生成这个虚拟化函数的执行跟踪。为此,我们使用一个 Pintool。 它只需要一个 start 地址和一个 end 地址(在我们的示例中为 0x4011c0 和 0x4011ef),它们表示插桩的范围。 请注意,任何类型的 DBI 或模拟器都可以完成这项工作。``` $ ./pin/pin -t ./pin/source/tools/VMP_Trace/obj-intel64/VMP_Trace.so -start 4198848 -end 4198895 -- ./vmp_binaries/binaries/sample2.vmp.bin 1 2 &> ./vmp_traces/sample2.vmp.trace

你可以在这里查看结果:[here](https://github.com/jonathansalwan/vmprotect-devirtualization/blob/main/vmp_traces/sample2.vmp.trace)。该 trace 格式使用三种操作:`mr`、`r` 和 `i`。
`mr` 是指令 `i` 执行的内存读访问,`r` 是 CPU 寄存器。例如:```
mr:0x7ffda459d718:8:0x227db4f8
r:0x40200a:0x0:0x7ffda459f571:0x2:0x40200a:0x0:0x0:0x7ffda459d688:0x0:0x0:0x7feee9b80ac0:0x7feee9b8000f:0xad1c3e:0x0:0x0:0x0
i:0x89173e:8:488BB42490000000

我们有一个内存读取,从地址 0x7ffda459d718 加载了一个 8 字节常量 0x227db4f8。 该指令在地址 0x89173e 处执行,其 8 字节长操作码为 488BB42490000000,即 mov rsi, qword ptr [rsp + 0x90]。 执行前的寄存器状态如下:```python (1) RAX = 0x40200a (9) R8 = 0 (2) RBX = 0 (10) R9 = 0 (3) RCX = 0x7ffda459f571 (11) R10 = 0x7feee9b80ac0 (4) RDX = 0x2 (12) R11 = 0x7feee9b8000f (5) RDI = 0x40200a (13) R12 = 0xad1c3e (6) RSI = 0 (14) R13 = 0 (7) RBP = 0 (15) R14 = 0 (8) RSP = 0x7ffda459d688 (16) R15 = 0

一旦生成了 VMP 跟踪,我们使用 [attack_vmp.py](https://github.com/jonathansalwan/vmprotect-devirtualization/blob/main/attack_vmp.py) 脚本重放它。该脚本使用 [Triton](https://github.com/jonathansalwan/Triton) 构建跟踪的路径谓词。请注意,所有涉及符号变量(函数的输入)的表达式都会保持符号化,而所有与输入无关的表达式都会被具体化。换句话说,我们的符号表达式不包含任何与虚拟机相关的操作(虚拟机机制本身不依赖于用户),而只包含与原始程序相关的操作。

例如,下面是一个具体化的示例。左侧是一个包含不涉及符号变量的子表达式(`1 + 2` 和 `6 ^ 3`)的 AST。因此,这些分支被具体化并替换为常量 `3` 和 `5`,从而得到右侧的 AST。**这就是我们对代码进行反虚拟化的方式。**

<p align="center">
  <img src="https://assets.kitploit.com/production/public/readmes/8096/857ed50cfe9cb2347f816ece1d8dc4c13174c971dcb65ebc5621be14269a5ca8.png">
</p>

**关于公式级反向切片的一点说明**:与符号执行中常见的做法一样,首先沿路径以正向方式计算符号表示,然后从符号表达式中移除所有既不影响最终结果也不影响所跟随路径的逻辑操作和定义(公式切片,又称公式剪枝)。这相当于对公式执行从程序输出出发的反向切片代码分析。因此,在 `secret` 函数返回时,我们得到了一个不包含 VMProtect 指令的输入与输出之间关系的表达式。

`./attack_vmp.py` 脚本将跟踪文件和符号变量的大小作为参数。回想一下,符号变量是 `edi` 和 `esi`,因此它们都是 4 字节长。该脚本的运行结果如下:```
$ ./attack_vmp.py --trace1 ./vmp_traces/sample2.vmp.trace --symsize 4
[+] Replaying the VMP trace
[+] Symbolize inputs
[+] Instruction executed: 12462
[+] Emulation done
[+] Return value: 0x3
[+] Devirt expr: (bvor (bvnot (bvor (bvnot (bvnot x)) (bvnot y))) (bvnot (bvor (bvnot x) (bvnot (bvand (bvnot y) (bvnot y))))))
[+] Synth expr: (bvxor x y)

[+] LLVM IR ==============================

; ModuleID = 'tritonModule'
source_filename = "tritonModule"

define i32 @__triton(i32 %SymVar_0, i32 %SymVar_1) {
entry:
  %0 = xor i32 %SymVar_0, %SymVar_1
  ret i32 %0
}

[+] EOF LLVM IR ==============================

正如我们所见,secret 函数返回的去虚拟化表达式相当简洁,不包含来自 虚拟机的指令。```smt (bvor (bvnot (bvor (bvnot (bvnot x)) (bvnot y) ) ) (bvnot (bvor (bvnot x) (bvnot (bvand (bvnot y) (bvnot y) ) ) ) ) )

但是,我们未能恢复原始表达式,它原本是一个简单的`XOR`操作。看起来`XOR`已被转换为位运算。幸运的是,我们最近在Triton项目中发布了新功能,包括一个[合成器](https://github.com/JonathanSalwan/Triton/issues/1074)和一个到[LLVM-IR](https://github.com/JonathanSalwan/Triton/issues/1078)的提升器。因此,我们可以综合该表达式,得到`(bvxor x y)`。这是一次不错的胜利,现在我们可以更进一步,将该表达式提升到LLVM-IR,然后编译出新的去虚拟化二进制代码。


## 示例 2:受MBA操作保护的情况

好的,现在我们来看另一个示例,它试图隐藏一个MBA操作。原始源代码如下:```cpp
// This function is an MBA that computes: (x ^ 92) + y
// We will protect this MBA with VMProtect and see if we can recover "(x ^ 92) + y"
char secret(char x, char y) {
  VMProtectBegin("secret");
  int a = 229 * x + 247;
  int b = 237 * a + 214 + ((38 * a + 85) & 254);
  int c = (b + ((-(2 * b) + 255) & 254)) * 3 + 77;
  int d = ((86 * c + 36) & 70) * 75 + 231 * c + 118;
  int e = ((58 * d + 175) & 244) + 99 * d + 46;
  int f = (e & 148);
  int g = (f - (e & 255) + f) * 103 + 13;
  int r = (237 * (45 * g + (174 * g | 34) * 229 + 194 - 247) & 255) + y;
  VMProtectEnd();
  return r;
}

与第一个示例一样,我们需要确定该函数的起始位置和结束位置,并生成 VMP 跟踪。``` $ ./pin/pin -t ./pin/source/tools/VMP_Trace/obj-intel64/VMP_Trace.so -start 4198857 -end 4199140 -- ./vmp_binaries/binaries/sample3.vmp.bin 1 2 &> ./vmp_traces/sample3.vmp.trace

一旦 [VMP 跟踪](https://github.com/jonathansalwan/vmprotect-devirtualization/blob/main/vmp_traces/sample3.vmp.trace) 生成,让我们运行 `./attack_vmp.py` 脚本。```
$ ./attack_vmp.py --trace1 ./vmp_traces/sample3.vmp.trace --symsize 1
[+] Replaying the VMP trace
[+] Symbolize inputs
[+] A potential symbolic jump found on CF flag: 0x821dac: popfq - Model: {0: x:32 = 0xa3, 1: y:32 = 0xff}
[+] A potential symbolic jump found on CF flag: 0x87f437: popfq - Model: {0: x:32 = 0xa3, 1: y:32 = 0xff}
[+] Instruction executed: 25085
[+] Emulation done
[+] Return value: 0x5f
[+] Devirt expr: In: (bvadd (bvadd (bvshl (bvadd (_ bv1 32) (bvnot (bvlshr (concat (_ bv0 8) (_ bv0 8) ((_ extract 15 8)  ...
[+] Synth expr: In: (bvadd (bvadd (bvshl (bvadd (_ bv1 32) (bvnot (bvlshr (concat (_ bv0 8) (_ bv0 8) ((_ extract 15 8)  ...

[+] LLVM IR ==============================

; ModuleID = 'tritonModule'
source_filename = "tritonModule"
下载工具