Denuvo分析
近年来,随着 Denuvo(D加密)等强反篡改技术的不断迭代,传统的静态 Patch 破解(修改可执行文件逻辑、NOP掉跳转指令)正变得愈发艰难。Denuvo 依赖于极高强度的“基于虚拟化的代码混淆(Virtualization-based Obfuscation)”,配合完整性校验,让逆向工程师陷入了分析海量无意义 Bytecode 的泥潭。
既然在 Ring 3(用户态)修改代码会被检测,那么直接在 Ring -1(硬件虚拟化层)接管整个操作系统,欺骗环境指纹,便成为了当前绕过 Denuvo 的通法。
一、 原理篇:降维打击与双重虚拟化
Denuvo 的核心验证机制可以抽象为:硬件指纹绑定(The Lock)。游戏在首次运行时或特定触发点,会采集当前宿主机的硬件信息(CPU 型号、序列号、SMBIOS 等),并向服务器请求一个唯一的离线激活凭证(Token)。在后续的游玩过程中,Denuvo 会频繁在后台执行校验,比对当前硬件指纹与 Token 是否一致。
1. 为什么选择 Hypervisor?
破解者的思路发生了根本转变:既然无法修改游戏代码(因为会被检测),那就欺骗游戏所运行的环境。
这套机制利用了 AMD SVM 或 Intel VT-x 硬件辅助虚拟化技术,构建了一个极其轻量级的 Type-1 Hypervisor。
当游戏进程中的 Denuvo 试图执行 CPUID 或特定 MSR 读写操作来获取硬件信息时,Hypervisor 会触发 VMEXIT。此时,CPU 的控制权会瞬间从游戏(Ring 3)跃迁至 Hypervisor 驱动(Ring -1)。驱动并不会去查询真实的物理硬件,而是直接向寄存器中塞入一套预设的、与合法 Token 完美匹配的伪造硬件数据,随后执行 VMRUN 恢复游戏运行。
对于 Denuvo 而言,它拿到了“正确”的硬件信息,便会乖乖放行;而游戏的可执行文件保持 0 字节改动,完美规避了完整性扫描。
2. 核心技术难点:时序与状态
这并非简单的“拦截-替换”游戏。要在 Ring -1 完美隐身,必须处理极其凶险的检测雷区:
- 时序检测(Timing Checks):Denuvo 会利用
RDTSC指令测量CPUID的执行时间。一次物理CPUID耗时极短(数百时钟周期),而触发VMEXIT进行上下文切换再返回,通常需要数千周期。如果不进行时间戳补偿(修改 TSC Offset),Hypervisor 瞬间就会暴露。 - 状态一致性:处理伪造数据时,如果污染了未涉及的通用寄存器或标志寄存器(RFLAGS),交还控制权后游戏大概率会引发逻辑错误或蓝屏崩溃(BSOD)。
二、 实践篇:环境剥离与部署
基于驱动的 Hypervisor 部署,本质上是一次“系统裸奔”。因为微软的数字签名强制机制(DSE),包含虚拟化接管逻辑的 .sys 驱动是不可能拿到合法内核签名的。
1. 前置环境配置
为了让名为 SimpleSvm.sys 的未签名核心驱动能够加载,必须剥离 Windows 的底层防御:
1 | # 开启测试签名模式,允许加载无签名驱动 |
除此之外,必须在 BIOS 中禁用 Secure Boot(安全启动),并在 Windows 安全中心彻底关闭 VBS(基于虚拟化的安全性) 和 HVCI(内存完整性内核隔离)。
三、 代码剖析:逆向 SimpleSvm.sys
通过 IDA Pro 对 SimpleSvm.sys 进行逆向分析。
1. 初始化与“防睡死”机制
驱动加载早期的核心函数之一(在伪代码中标记为 sub_140003304)展示了出色的工程素养:
1 | // ...片段截取... |
这里的重点在于 ExRegisterCallback。当宿主机进入休眠并唤醒时,CPU 的 SVM 状态会被重置。如果不注册电源回调在唤醒瞬间重新接管 CPU,系统将会立刻蓝屏。这证明了该驱动并非粗制滥造的玩具,而是经过严密设计的内核级工具。
2. VMEXIT 分发器(The Dispatcher)
当 Denuvo 执行敏感指令时,CPU 陷入驱动的 sub_1400034D0 函数。此函数是整个 Hypervisor 的总控台:
1 | // v5 获取 VMCB 中的 Exit Code |
这段看似奇葩的连环减法,实则是编译器对 switch-case 优化的二叉跳表。通过数学反推:
- 当
v5 == 114(即十六进制0x72VMEXIT_CPUID) 时,进入核心伪造函数sub_140001D10。 - 当
v5 == 124(VMEXIT_RDTSCP) 时,进入时序补偿函数,处理 Denuvo 的反虚拟机时间检测。
函数末尾的指令更是直接揭示了金蝉脱壳的底层机制:
1 | __svm_stgi(); // 设置全局中断标志 |
3. CPUID 伪造引擎与隐蔽后门
深入 sub_140001D10(CPUID 拦截逻辑),我们看到了逆向工程中最具“黑客文化”色彩的实现。由于单体驱动没有创建通信端口,用户层 Loader 如何将目标游戏的进程 ID 和 Denuvo 内存区段告诉 Ring -1 呢?答案是利用 CPUID 的魔法参数建立秘密后门。
1 | // 用户层将魔法数字通过 EAX 寄存器传入,触发特定的行为 |
当校验通过后,代码进入了极其严密的“精准伪造”阶段。为了防止全局拦截导致系统崩溃,驱动仅对 Denuvo 的内存地址进行欺骗:
1 | // a1+25976 存储了当前触发 VMEXIT 的 RIP (指令指针) |
可以看到,开发者将获取合法 Token 时所用的真实硬件名称(例如 AMD Ryzen 9 5950X)硬编码为整型常量。当游戏查询时,Hypervisor 便无视物理硬件,强制返回这套假身份,配合离线 Token 实现秒进游戏。
若当前调用 CPUID 的是系统中的正常程序(如浏览器或内核本身),驱动则会执行内联汇编 __asm { cpuid },将真实的硬件信息如实返回,完美保障了系统的稳定性。
