bypass

逆向剪映的旧补丁

剪映 v6.1 之后再也没有破解版,是作者突然从良了,还是觉得用爱发电太累。出于好奇,把 v6.1 的破解补丁逆向拆开,搞清楚它到底是怎么绕过付费验证的。结果发现这个曾让无数人白嫖的补丁,其实是个凑合用的“半成品”...

背景与动机

2025 年左右,我接触过剪映的破解版本。当时我发现一个现象:破解版在 v6.1 之后就停止更新了,后续的版本都没有破解版本。
这让我产生了疑问:

为什么在 v6.1 之后,破解者不再更新补丁了?中间到底发生了什么?

直觉告诉我,这不只是"作者不想做了"这么简单。

了解补丁的原理

v6.1 的破解补丁是一个 VMP 加壳的文件:ying6.1.0嵌入版.vmp.exe。要回答上面的问题,首先需要弄清楚:
1. 这个补丁到底做了什么;它是如何绕过剪映的验证
2. v6.1 和最新版(v10.9)之间有什么根本性的变化;为什么同样的方法在新版本上不适用

第一阶段:解开 VMP 壳

工具链

经过 IDE 的查壳,发现这是 VMP3.5,VMP 是 VMProtect 的商业壳,它将原始代码虚拟化为自定义字节码,使静态分析极其困难。
在2023年,VMP3.5.1 的源代码泄露,人人都可以用泄露的源代码打包一个软件来给自己的软件加上 VMP 保护。
破解 VMP 的标准流程分为三步:

| 步骤 | 工具 | 目的 |
| ① Dump | x64dbg(附加到运行中的进程) | 将解密后的内存镜像导出为文件 |
| ② VMPDump | VMPDump.exe | 修复 IAT(导入地址表),重建 PE 结构 |
| ③ NoVmp | NoVmp.exe(Can Boluk 开源项目) | 将 VMP 虚拟机字节码还原为 x64 指令 |

执行过程

步骤 ① — x64dbg 内存 Dump
启动补丁程序后,VMP 会在内存中解密真实代码。通过 x64dbg attach 到进程,导出 .text 段(0xBA000 字节,即 762KB 的明文 x64 代码)和虚拟机段 .vmp0.vmp1。同时发现补丁的 PE 头中有完整性校验——如果用 x64dbg 的"启动并暂停"方式,VMP 会检测到调试器并弹出:
File corrupted! This program has been manipulated and maybe it's infected...
所以先正常启动补丁,等 VMP 解密完成后再用 x64dbg 附加。

步骤 ② — VMPDump 修复 IAT
VMPDump.exe <PID> "ying6.1.0嵌入版.vmp.exe"
输出 ying6.1.0嵌入版.vmp.VMPDump.exe(10.1MB)完成了:

  • 解析原始 IAT(477 个 API 调用)
  • 修复导入表(合并去重为 210 个唯一导入)
  • 剥离 VMP 壳代码- 保留解密后的明文 `.text` 段

步骤 ③ — NoVmp 去虚拟化(部分成功)

NoVmp 是 Can Boluk 开发的开源工具,基于 VTIL-Core 的符号执行引擎,能将 VMP 的虚拟机字节码转换回 x64 汇编。

遇到的问题:

  • 原始 NoVmp.exe 崩溃:exit code 0xC0000409(STATUS_STACK_BUFFER_OVERRUN),因为编译环境不匹配
  • 源码编译:安装 MinGW-w64 + CMake,从源码构建 NoVmp
    • 修复了 VTIL-Core API 兼容性(unique_ptr → 裸指针)
    • 修复了 Keystone LLVM 的 MinGW 编译错误(::write_write
    • 增大栈空间(-Wl,--stack,33554432)解决 stack overflow
  • 结果:NoVmp 成功运行但未找到 VM 入口点 — VMP 3.5 的 VMEntry 模式不在 NoVmp 的识别范围内

虽然第三步未完全成功,但 前两步已经产出了 762KB 的完全可读 x64 明文代码,IAT 已修复,足够进行静态分析。

第二阶段:IDA Pro 静态分析补丁逻辑

将 VMPDump 的输出文件加载到 IDA Pro,通过交叉引用(xref)追踪关键 API 的调用链:

补丁核心 API

| 类别 | API | 用途 |
| 进程枚举 | CreateToolhelp32Snapshot + Process32First/Next | 枚举系统进程 |
| 线程枚举 | Thread32First + Thread32Next | 枚举目标进程的线程 |
| 线程劫持 | SuspendThread + GetThreadContext + SetThreadContext + ResumeThread | 挂起目标线程,改 RIP 为注入代码 |
| 进程验证 | OpenProcess + CloseHandle | 检测目标进程是否存在 |
| 文件操作 | CreateFileA + ReadFile + WriteFile + FlushFileBuffers | 读写 VECreator.dll |
| 同步 | CreateMutexA + WaitForSingleObject + SetEvent | 防重复运行 + 线程同步 |
| TLS | TlsAlloc + TlsSetValue + TlsGetValue | 线程本地存储管理 |

完整架构:VMP 虚拟机调度 + 线程注入 + 文件 Patch

Text
┌──────────────────────────────────────────────────┐
│  DLL 加载 (TLS Callback, DLL_PROCESS_ATTACH)      │
│    └→ 注册静态初始化器 → 注册调度处理器             │
└──────────────────────┬───────────────────────────┘

┌──────────────────────────────────────────────────┐
│  VMP 虚拟机 (.vmp0 段, 虚拟化字节码)               │
│    ├→ 读取 .rdata 中的函数指针表 (CAB 段)           │
│    ├→ 枚举进程 (CreateToolhelp32Snapshot)          │
│    ├→ 枚举线程 (Thread32First/Next)                │
│    ├→ 通过 OpenProcess 验证目标进程存在             │
│    └→ 调用 sub_7FF60C0CE5D0(ID, action) 发起注入   │
└──────────────────────┬───────────────────────────┘

┌──────────────────────────────────────────────────┐
│  .text 段 — 线程注入执行器                         │
│    sub_7FF60C0CE5D0: 临界区保护 → 状态检查          │
│    sub_7FF60C0CE3D0: SuspendThread → Get Context   │
│       → 改 RIP 为 sub_7FF60C0CE130                  │
│       → SetThreadContext → ResumeThread            │
└──────────────────────┬───────────────────────────┘

┌──────────────────────────────────────────────────┐
│  .text 段 — 注入代码 (在目标线程中执行)           │
│    sub_7FF60C0CE130:                              │
│       → 获取 TLS 上下文                            │
│       → 遍历 Block 链表,调用每个节点的函数指针     │
│         (每个函数是一个 Patch 操作:                   │
│          搜索字节签名 → 匹配 → 替换 → 写文件)        │
│       → 通过 longjmp 恢复目标线程的正常执行路径       │
└──────────────────────────────────────────────────┘


文件 Patch 不是直接在补丁主线程中执行,而是注入到目标进程的线程中执行。这样做的好处是——注入代码拥有目标进程的完整地址空间访问权限,可以绕过 VMP 的内存保护检查。

补丁运行时只提供两个选项:

Text
1. 修改 —— 应用 SD 模式的字节码补丁 (模式 1)
2. 还原 —— 恢复原始字节码 (模式 2)

用户输入 "1" 或 "2" 后,补丁调用核心编排函数 sub_7FF60C0B1A84(filepath, mode)

核心编排逻辑(反编译自 IDA)

C
// sub_7FF60C0B1A84 — 核心编排函数
void orchestrator(wchar_t* jianying_path, int mode) {
    path = jianying_path + "\\VECreator.dll";
    
    switch(mode) {
    case 1: // "修改"
        // SD 补丁 A: 搜索 75 22 48 8B 44 24 20 49
        //           替换为 90 90 48 8B 44 24 20 49 (NOP 掉 JNE)
        // SD 补丁 B: 搜索 0F 84 BD 03 00 00
        //           + 搜索 0F 84 9C 03 00 00 (两处 JZ NOP 为 NOP)
        break;
    case 2: // "还原"
        // 逆向替换:恢复原始字节码
        break;
    case 3: // 死代码 — HD 模式,从未被调用
    case 4: // 死代码 — HD 还原,从未被调用
        // HD 补丁 C: 搜索 89 41 70 48 8B 4B 10 → 替换为 90 90 90 48 8B 4B 10
        // HD 补丁 D: 搜索 0F 84 2F 07 00 00 E8 → 替换为 0F 85 2F 07 00 00 E8
    }
}

补丁机制总结

不是运行时代码注入,而是磁盘文件字节码替换。

补丁的完整工作流程:

  1. 枚举系统进程,找到剪映安装路径
  2. 定位 VECreator.dll
  3. 硬编码的字节签名在 DLL 中搜索特定代码位置
  4. 将匹配的字节替换为绕过代码(如条件跳转 → NOP)
  5. 保存修改后的 DLL

在 v6.1 的 VECreator.dll(253MB)中,我们验证了所有 5 个字节签名确实存在:
| 签名 | 文件偏移 | 模式 |
| 75 22 48 8B 44 24 20 49 | 0x11D7939 | SD 补丁 A |
| 0F 84 BD 03 00 00 | 0xFECF1E | SD 补丁 B1 |
| 0F 84 9C 03 00 00 | 0xEE76B1 | SD 补丁 B2 |
| 89 41 70 48 8B 4B 10 | 0x245DC09 | HD 补丁 C |
| 0F 84 2F 07 00 00 E8 | 0x245BD0A | HD 补丁 D |

补丁的局限性:它是一个半成品

仅有 SD(标清)模式的 2 种操作可用。HD(高清)模式的 2 种操作虽然代码完整存在,但控制台菜单只提供 "1" 和 "2" 两个选项——HD 模式是死代码。

这个补丁可能是一个更大破解项目的一部分(SD 版),HD 版可能计划另发或已丢失。

第三阶段:对比 v6.1 与 v10.9 — 寻找转折点

版本

| 版本 | 来源 | gp.dll | gpShell.dll | gpHackerProc.dll | device_register_shared.dll |
|------|------|--------|-------------|------------------|---------------------------|
| v6.1.0.11946 | 完整离线安装包 (175 DLLs) | ❌ | ❌ | ❌ | ✅ (128KB) |
| v10.9.0.14199 | 完整安装目录 | ✅ (6.7MB) | ✅ (387KB) | ✅ (3.6MB) | (合入 gp.dll) |

gp.dll 引入的确切版本无法精确定位(本地无 v6.2-v10.8 的中间版本)。
### v6.1 的安全架构

v6.1 的安全架构

Text
剪映主程序

VECreator.dll(253MB,本地许可检查)
    ├── 检查注册表/文件中的许可信息
    ├── 如果无效 → 弹出"请登录"门禁弹窗
    └── 如果有效 → 正常启动

无云 Token、无设备绑定、无远程验证

攻击面VECreator.dll 中的许可检查函数。修改几字节即可绕过。
v10.9 的安全架构

Text
剪映主程序

gp.dll(6.7MB,939 个导出,原生 DLL 无 VMP 壳)    ← v6.1 不存在!
    ├── gpShell.dll (387KB)        → VECreator.dll 通过 gShell 访问 gp 层
    ├── gpHackerProc.dll (3.6MB)   → 反黑客进程监控
    ├── device_register_shared.dll  → 设备注册(v6.1 已存在,v10.9 合入 gp 体系)
    ├── TokenSessionManager         → Token 会话管理
    ├── SignatureSettingsService    → 签名验证服务
    ├── /risk_ctl/v2/get_token      → 云端风控 API 端点
    │        ↓
    │   RSA 公钥验证 ← 服务器签名
    │   HMAC-SHA256 请求认证
    │   硬件绑定 Token(有过期时间)
    │        ↓
    ├── 反调试保护                  → IsDebuggerPresent, NtQueryInformationProcess
    │                               → GetThreadContext, SetThreadContext
    │                               → TerminateProcess, OpenProcess
    └── DLL 注入黑名单 (safe_guard.dat) → nahimicosd, rtsshooks64 等

gp.dll 中的关键字符串证据

从 v10.9 的 gp.dll 提取的字符串直接揭示了新的安全模型:

Text
RSA Public Key (%zu bits)           ← RSA 非对称加密
/risk_ctl/v2/get_token              ← 云端风控 API 端点
HMAC-SHA256                          ← 消息认证码
TokenSessionManager                  ← Token 生命周期管理
SignatureSettingsService             ← 签名策略服务
-----BEGIN CERTIFICATE-----          ← X.509 证书验证
DigiCert Trusted G4 RSA4096 SHA256   ← 受信任的根证书
AUTHENTICATE %s %s                   ← 双向认证

注意:RSA PEM 块(offset 0x261450)是空的——信封存在但密钥数据全为零。这暗示公钥可能从服务器动态下载或由服务端在运行时写入。

意外收获:safe_guard.dat 解密

safe_guard.dat 是 Windows DPAPI 加密(CryptProtectData, CALG_AES_256),用当前用户身份成功解密后,内容不是设备指纹或 Token 缓存,而是一个 DLL 注入黑名单配置

Json
{
  "global": {
    "inject_check_enable": 1,
    "inject_sign_check": 0,
    "inject_app_sign_check": 1,
    "privacy_check_enable": 0
  },
  "inject_module_blacklist": {
    "module1": "nahimicosd.dll",
    "module2": "audiodevprops2.dll",
    "module3": "shellexc64.dll",
    "module4": "rtsshooks64.dll",
    "module5": "grooveex.dll",
    "module_size": 5
  },
  "app_sign_check_list": {
    "app_sign_dir1": ".",
    "app_sign_dir1_mod1": "openvino.dll",
    "app_sign_dir1_size": 1,
    "app_sign_size": 1
  }
}
  • inject_check_enable: 1 — 启动时检查是否有未授权 DLL 被注入到进程
  • inject_module_blacklist — 已知 hook/injection DLL 黑名单(Nahimic 音效、RivaTuner 覆盖层等)
  • inject_app_sign_check: 1 — 检查关键 DLL 的数字签名完整性(如 openvino.dll

这意味着 v10.9 不仅在云端验证 Token,还在本地主动检测是否被注入/篡改。 这是一个多层防御体系。

结论:为什么破解在 v6.1 后停止

三道防线

对于 v10.9,破解者需要同时:
1. 绕过云 Token 验证 — 需要 RSA 私钥(服务器持有)或 TLS MITM。
2. 绕过反调试 — 需要 hook 10+ 个 Windows API。
3. 绕过注入检测 — 需要修改 safe_guard.dat 黑名单或 hook 相关检查。
主要是安全架构升级;从单层本地门禁变成了多层云端+本地防御。

在 v10.9 时代,破解者面对的是一个多层防御体系:每层都需要不同的技术栈和工具链来突破。任何一层的失败都会导致整个破解失效。