[md]# 领域特定虚拟机DSVM指令完整还原
副标题:一款针对 UE4 移动游戏外挂的深度混淆分析与反混淆实践
日期:2026-06-21
标签:ARM64 · Unicorn · OLLVM · DSVM · UE4 · PhysX · 反混淆 · 字节码解释器
本文记录了对 PhysX.sh(一个 ARM64 ELF 可执行文件)进行逆向分析的完整过程。该文件最初被判定为 OLLVM 控制流平坦化(CFF)重度混淆,但深入分析后揭示其真实面目是一个领域特定虚拟机(DSVM)——一套针对 Unreal Engine 4 游戏对象遍历的自定义字节码解释器。本文详述从误判到真相的推理链、Capstone + Unicorn 工具链的工程实践、790 个操作码的语义提取,以及恶意代码检测结论。
PhysX.sh 基本属性:
初始 OLLVM 检测器报告该文件含有 49 个 CFF 混淆函数(占 238 个函数的 20.6%),其中最严重的 0x14350 包含 1843 条指令和 113 个比较分支。这似乎是一个重度 OLLVM 混淆的典型案例——直到两个关键异常出现。
使用基于 Capstone 的静态分析器扫描 .text 段,对每个函数统计四个 CFF 特征:
检测到的 Top 10 可疑函数:
使用项目自带的 ollvm_deobfuscator.py 进一步确认了 7 个 CFF 函数及其状态变量和调度器地址。
对 func_0x12f28 的 Unicorn 模拟触发了 4,998 次 syscall——这在 OLLVM 混淆函数中不可能出现(OLLVM 只修改控制流,不引入大量系统调用)。
更关键的异常在 0x18234:该函数被检测为单个 4124 字节的巨型函数,但内部包含 12 个独立的"调度器"——标准 OLLVM 每个函数只有一个分发器。
这两处异常迫使我们重新审视整个判断。
ARM64 PAC(Pointer Authentication Code,ARMv8.3-A)保护的函数使用非传统序言:
扩展函数检测以包含 PAC 序言后,函数数量从 238 跃升到 291。0x18234 区域实际包含 3 个独立函数:
对 0x1828c 入口代码的分析发现了关键模式:
这是经典的字节码解释器分发循环。"gs"(0x67, 0x73)是魔数前缀,后续每个字节是操作码,通过跳转表映射到处理器。
解释器包含 12 个调度器,各自负责不同的操作码范围和语义域:
分析过程中自然提出了"这是否为 VMProtect"的疑问。从四个维度对比:
处理器语义。 VMP 处理器是通用的(Add/Sub/Push/Pop/Jmp/Call),而本系统所有处理器都是领域特定的——ParseInt(ASCII→整数)、HandleBone(骨骼矩阵)、HandlePhysics(PhysX 坐标)、HandleAI(行为树数据)。83 次 BL 调用中,领域特定调用的占比为 98.4%。
循环结构。 VMP 使用单层平面循环(一个中央分发器),本系统实现递归下降解析——33% 的调用是递归 bl 0x18288,支持嵌套子对象遍历。
字节码格式。 VMP 是紧密二进制编码。本系统使用 "gs" 魔数 + 单字节操作码的文本式格式,rodata 段 71% 为 printable 字符。
虚拟栈。 VMP 标志性地使用虚拟栈。本系统中未检测到虚拟栈模式——不存在单个寄存器大量用作 ldr/str 基址的情况。
结论: 这是一个借鉴 VMP 架构模式(跳转表分发、字节码解释)但指令集完全领域化的自定义虚拟机,类似于 SQL 引擎与通用 CPU 的关系。
对 12 个调度器的 53 个跳转表进行静态解析。每个跳转表条目是 16 位偏移,指向解释器函数内的 case 块。通过解析每个 case 块的指令序列(特别是 bl 调用目标),建立操作码到处理器的映射。
InitScan(0x1a400)是最频繁调用的处理器,每次切换字段类型时设置目标缓冲区地址、字段映射表和起始偏移。
StoreField(0x16018)写入输出结构体,LinkFields(0x1a560)建立父子关系,Finalize(0x1924c)标记解析完成。
完整的内存偏移链:
构建了专用 Unicorn 全模拟器以验证静态分析结论。核心参数:
使用合成字节码 "gs1a" 输入,成功追踪解释器完整执行路径(50,000 条指令,295 个唯一基本块):
其他输入验证一致:"gsi0" 触发递归处理器,"gsL0" 触发结构体解析,"gs12ab" 展示连续多操作码分发。
传统 stp x29, x30 检测遗漏了 PAC 保护函数。扩展为三种序言模式后函数数从 238 增至 291:
rodata 段中同时包含跳转表(16 位偏移数组)和 C++ Itanium ABI demangler 名称表。解决方案:验证每个跳转表条目的目标地址是否在 .text 段内,排除指向 0x7xxx(字符串区)的条目。
12 个调度器使用不同状态变量(x9、x10、x12),同一寄存器在不同阶段持有不同语义。通过静态数据流分析追踪每个变量的定义-使用链,确定调度器间的传递关系。
76 个 PLT 导入函数需要合理模拟才能避免模拟器陷入循环或崩溃:
一个重要细节:二进制中的偏移不是 libUE4.so 偏移,而是外挂自身运行时上下文字段索引:
实际的 libUE4.so 偏移(通常在 0x04000000+ 范围)不存在于 ARM 指令中的任何 MOVZ/MOVK 常量中。这与 DSVM 架构一致——外挂是通用引擎,所有游戏特定数据(GWorld 偏移、字节码程序)作为运行时配置加载。
对 76 个导入函数和全部内嵌字符串进行恶意行为扫描:
可疑函数的真实用途:
结论:纯游戏外挂程序,不具备任何恶意软件特征。 没有网络功能意味着无法外传数据或接收远程指令。
不要急于下结论。 初始"49 个 CFF 函数"的判断如果被接受,整个分析会走错方向。关键异常(4998 次 syscall、12 个调度器在一个函数中)必须追查到底。
动静结合的必要性。 纯静态分析只能看到跳转表和分发器,纯动态模拟会陷入无限循环。静态提取跳转表 + 动态验证调度流是揭示真相的必要组合。
编译器特征即信息。 PAC 指令和 BTI 着陆点不仅是保护特性,它们提供了函数边界和间接跳转目标的精确标记,反而辅助了逆向分析。
DSVM 是语义级混淆。 传统 OLLVM 混淆代码的执行逻辑,DSVM 混淆的是语义意图。面对 DSVM,理解"它在做什么"(UE4 遍历)比"它怎么做的"(操作码分发)更重要。
该 DSVM 体现了外挂开发的工业化水平:
这种架构级别的保护手段值得游戏安全从业者重视——单纯的特征码检测或完整性校验无法防御这种灵活的解释器架构。
相关代码在仓库955K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6T1M7Y4y4*7P5Y4A6Q4x3V1k6S2L8X3c8J5L8$3W2V1i4K6u0V1M7$3q4F1k6r3u0G2P5l9`.`.
| 属性 |
值 |
| 文件大小 |
198,496 字节(193.8 KB) |
| 文件类型 |
AArch64 ELF 可执行文件(PIE),扩展名伪装为 .sh |
| 符号状态 |
完全剥离(stripped) |
| 编译器 |
Clang 14.0.7, Android NDK r450784d1 |
| 外部导入 |
76 个函数(libc + libdl + libm) |
| 目标游戏 |
7 款 UE4 手游 |
| 函数地址 |
指令数 |
CMP数 |
间接跳转 |
CFF评分 |
| 0x14350 |
1843 |
113 |
3 |
10 |
| 0x13374 |
414 |
49 |
2 |
10 |
| 0x16ae8 |
608 |
39 |
5 |
10 |
| 0x23608 |
323 |
31 |
7 |
10 |
| 0x18234 |
1031 |
65 |
13 |
8 |
| 0x20664 |
707 |
42 |
14 |
8 |
| 0x1eb28 |
500 |
40 |
4 |
8 |
| 0x1f4ec |
220 |
24 |
4 |
8 |
| 0x21a80 |
103 |
8 |
4 |
8 |
| 0x21c1c |
146 |
11 |
6 |
8 |
| 维度 |
初始误判(CFF混淆) |
真实架构(DSVM) |
| 12个调度器 |
多级混淆状态机 |
操作码分配网络 |
| 2580个"状态" |
混淆状态值 |
合法操作码映射 |
| 80个case块 |
被混淆的真实块 |
操作码处理器 |
ldrh [table, idx*2] |
混淆查找表 |
操作码→处理器跳转表 |
递归 bl 0x18288 |
混淆间接调用 |
递归下降解析器 |
| 调度器 |
地址 |
跳转表 |
语义域 |
| D1 |
0x18350 |
0x9818 |
主操作码表 |
| D2 |
0x18390 |
0x9abc |
字符串字段解析 |
| D3 |
0x183d4 |
0x98a0 |
数组/向量字段 |
| D4 |
0x1843c |
0x9b26 |
对象引用字段 |
| D5 |
0x18480 |
0x9afe |
变换/矩阵字段 |
| D6 |
0x185d8 |
0x9a78 |
标志枚举字段 |
| D7 |
0x1861c |
0x99fe |
物理属性字段 |
| D8 |
0x1865c |
0x99bc |
网络状态字段 |
| D9 |
0x186a0 |
0x98c6 |
渲染字段 |
| D10 |
0x18730 |
0x9a2e |
碰撞检测字段 |
| D11 |
0x18774 |
0x996a |
音频/动画字段 |
| D12 |
0x18818 |
0x991c |
输入/UI 字段 |
| 处理器 |
地址 |
功能 |
| HandleBone |
0x1b518 |
骨骼变换矩阵计算 |
| HandleVector |
0x1b3dc |
FVector (X,Y,Z) 解析 |
| HandleFloat |
0x1a6a4 |
IEEE 754 单精度浮点 |
| HandleEnum |
0x19f34 |
枚举值映射 |
| HandleTransform |
0x1b8e0 |
FTransform 组合 |
| UE4 子系统 |
处理器 |
遍历对象 |
| PhysX 物理 |
HandlePhysics |
PxScene → PxRigidActor → PxShape |
| 骨骼动画 |
HandleBone + HandleSkeletal |
USkeletalMeshComponent → FBoneTransform[] |
| 世界管理 |
HandleWorld + HandleLevel |
UWorld → ULevel → AActor[] |
| AI 系统 |
HandleAI |
UAIController → UBehaviorTree → UBlackboard |
| 网络复制 |
HandleNet |
AActor → FObjectReplicator |
| 渲染 |
HandleRender |
UPrimitiveComponent → FSceneProxy |
| 碰撞 |
HandleCollision |
FCollisionShape → FHitResult |
| 武器系统 |
HandleWeapon + HandleDamage |
AWeapon → ProjectileComponent |
[内核课程]《Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。