“不造轮子,何以知轮之精髓?” — To truly understand the wheel, you must build one.
SmallVMP 是一个从零开始构建的、用于学习和研究的虚拟机保护(Virtual Machine Protection)项目。它并非一个生产级的安全解决方案,而是我个人在探索 VMP 技术原理过程中的产物。
项目的核心目标是:
- 亲手实现一个微型虚拟机(VM),包括其指令集、解释器和字节码格式。
- 利用 LLVM Pass 技术,在编译期间自动将被保护函数的 LLVM IR 转换为我们自定义的字节码。
- 解决在 VMP 中处理外部函数调用和全局变量访问等关键问题。
本项目深度集成了强大的 Hikari Obfuscator 框架,不仅实现了基础的 VMP 功能,还能利用 Hikari 对 VMP 的核心——解释器本身——进行混淆,从而极大提升了分析难度。
- 基于 LLVM Pass: 在编译时自动完成虚拟化,对源代码无侵入。
- 自定义指令集 (ISA): 包含一套精简的 指令集,用于算术、逻辑、访存和控制流操作。
- Thunk 机制: 巧妙地通过生成原生“桥接函数 (Thunk)”来处理对外部函数和全局变量的引用,避免了手动模拟链接器的复杂性。
- 与 Hikari 深度集成: 可以轻松地将 Hikari 的多种混淆策略(如 BCF, CFF, SPL, SUB)应用于 VMP 解释器和项目中的其他原生代码。
- 跨平台: 作为 LLVM 的一部分,理论上支持所有 LLVM/Clang 支持的目标平台。
SmallVMP 的保护流程可以概括为以下几个步骤:
- 标记: 开发者使用
IRVM_SECTION宏标记需要保护的 C/C++ 函数。 - IR 转换 (LLVM Pass):
- 在编译过程中,
SmallVMP的 Pass 会识别出被标记的函数。 - 它遍历函数的 LLVM IR,并将其逐条翻译成我们自定义的字节码。
- 所有对外部函数或全局变量的引用都会被记录下来,并为之生成一个原生代码的桥接函数 (Thunk)。字节码中将只保留对这个 Thunk 的调用 ID。
- 在编译过程中,
- 函数替换:
- 原始函数体被清空。
- 替换为一个跳板 (Stub) 函数。这个 Stub 的唯一作用就是调用 VM 解释器 (
vm_exec),并将字节码、寄存器上下文等信息传递给它。
- 运行时执行:
- 当程序调用被保护的函数时,实际上是在调用这个 Stub。
- Stub 启动 VM 解释器,解释器开始逐条执行字节码,模拟原始函数的逻辑。
- 当遇到需要调用外部函数(如
printf)的指令时,解释器会通过 ID 查找到对应的 Thunk 函数并执行它。
SmallVMP 是作为 LLVM 的一个 Pass 实现的,并依赖于特定的 Hikari 混淆框架。因此,您需要先编译和配置好集成了 SmallVMP 的 LLVM/Clang 工具链。
- 获取工具链:
- 克隆或下载集成了
SmallVMP的 LLVM/Hikari 源码。 [(https://github.com/NiTianErXing666/SmallVmp)]
- 克隆或下载集成了
- 编译 LLVM/Clang:
- 根据该仓库的说明文档编译工具链。
- 配置环境变量:
- 将编译好的
bin目录(包含clang,clang++等)添加到您的系统PATH中,以确保后续命令能调用这个定制版的编译器。
- 将编译好的
使用 SmallVMP 非常简单:
-
引入头文件: 在您的 C/C++ 代码中,包含
VMP.h。#include "VMP.h"
-
标记目标函数: 使用
IRVM_SECTION宏来标记您希望进行 VMP 保护的函数。// 示例代码,包含全局变量和外部函数调用 int gArr[10] = {0}; const char* gMsg = "Hello, VMP!"; IRVM_SECTION int test_calls(int a, int b) { puts("call puts(const)"); int *p = &gArr[4]; *p = a + b; printf("gArr[4]=%d, gMsg=%s ", gArr[4], gMsg); return a + b; }
-
编译代码: 使用定制的
clang进行编译。SmallVMP默认启用,无需额外参数。/path/to/your/custom/clang test.c -o app
编译时,您会看到类似如下的日志,表明
SmallVMP已成功处理目标函数:[irvm] emit code global: test_calls_code (266 bytes) [irvm] + bytes initialized [irvm] + rewritten to vm_exec stub: test_calls -
(可选) 叠加 Hikari 混淆: 您可以附加 Hikari 的
-mllvm参数来混淆 VM 解释器本身,或项目中的其他原生代码,以达到双重保护。# 开启伪控制流 (Bogus Control Flow) clang test.c -o app -mllvm -enable-bcfobf # 开启控制流平坦化 (Control Flow Flattening) clang test.c -o app -mllvm -enable-cffobf # 开启所有混淆 clang test.c -o app -mllvm -enable-allobf
可以去看雪论坛看效果:https://bbs.kanxue.com/thread-288435-1.html
SmallVMP 目前仍是一个实验性项目,存在一些局限:
指令支持不完整: 对某些复杂的 LLVM IR 指令(如 select, phi 的复杂用法等)尚未支持。为保证编译稳定性,遇到不支持的指令时,Pass 会自动放弃对该函数的虚拟化。 性能开销: 和所有 VMP方案一样,解释执行会带来显著的性能下降。不适合用于性能敏感的热点函数。 安全性: 字节码目前是明文存储的,容易被 Dump 和分析。
字节码加密: 对生成的字节码进行加密,在解释器执行前动态解密,对抗静态分析。 动态分发器 (Dynamic Dispatcher): 使操作码 (Opcode) 与处理函数 (Handler) 的映射关系在运行时动态生成,对抗基于特征码的分析。 嵌套 VM (Nested VM): 实现二级 VM,用一个 VM 来保护另一个 VM 的解释器,形成层层嵌套,将分析难度提升到新的高度。
本项目深受 Hikari Obfuscator 项目的启发,并直接基于其框架进行构建。感谢 Hikari 团队的杰出工作。https://github.com/HikariObfuscator/Hikari 感谢 LLVM 社区提供的强大而灵活的编译器基础设施。 感谢 Xvmp 的思路!感谢 Xvmp 团队的杰出工作 https://github.com/GANGE666/xVMP
本项目旨在学习和交流,代码尚不完美,欢迎任何形式的贡献!如果您有任何建议、发现 Bug 或希望添加新功能,请随时提交 Pull Request 或创建 Issue。