游戏逆向(Unity / Unreal / Lua 脚本引擎 / 图形 Shader)

SkillDev tools

游戏逆向:Unity/Unreal、Cheat Engine、Lua 脚本引擎、图形 Shader。触发词:游戏逆向、Unity、Unreal、Cheat Engine、内存修改、反作弊、Lua、LuaJIT、.luac、.jsc、脚本引擎、Shader、着色器、SPIR-V、DXIL、DXBC、GLSL

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the 游戏逆向(Unity / Unreal / Lua 脚本引擎 / 图形 Shader) skill

What this skill tells your AI

The instructions your AI receives, as published by dslsdzc/rev-skills in .claude/skills/re-game/SKILL.md and read by ahel’s review.

何时使用 / 何时不用

  • 用:游戏逻辑逆向(Unity mono/IL2CPP、Unreal);内存搜索与修改(CE 数值/指针扫描);函数定位与修改([[re-patching]]);反作弊(EAC/BE)检测规避(分析环境内)
  • 不用:纯静态读 Unity mono 逻辑(直接 [[re-dotnet]] 反编译 Assembly-CSharp.dll,无需本技能)
  • 不用:移动端游戏([[re-mobile]] / [[re-apk]] / [[re-ios]] / [[re-frida]])
  • 不用:只要成品修改器/外挂——本技能是分析路径,不做发布工具,在线对战反作弊风险自担
  • 不用:Electron 应用的 V8 字节码 .jsc(走 [[re-electron]])

工具准备

反作弊游戏分析默认沙箱 + 隔离([[re-analyze/platform-tips]] 最高原则;[[re-sandbox]] 强制前置);动态调试按 OS 分支。

Cheat Engine(CE,Windows)

  • 安装: 官网 cheatengine.org 下载安装(choco install cheatengine 可选)
  • 验证: 打开 CE,能 attach 任意进程并做数值扫描(First Scan 有结果)

Unity 工具

  • Il2CppDumper(IL2CPP 还原): GitHub Perfare/Il2CppDumper releases(Windows exe / dotnet tool)
  • mono 场景: 无额外工具,Game_Data/Managed/*.dll 直接交 [[re-dotnet]](dnSpy/ILSpy)
  • 验证: Il2CppDumper 对目标 global-metadata.dat 能产出 dump.cs(类/方法签名)与 script.json(函数地址)

Unreal 工具(UnrealFinder 思路)

  • UnrealFinder(GitHub povlhp/UnrealFinder 或同类 Dumper-7): 定位 GObjects/GNames 等全局符号,导出 UObject 类树与函数地址
  • 验证: 对目标 UE 游戏 exe 能列出 GWorld/GNames 地址并导出对象列表

调试器(按 OS)

  • Windows: [[re-x64dbg]](轻量断点调试)或 [[re-windbg]](异常分析)
  • Linux: [[re-gdb]](Wine 下跑 Windows 游戏时读 Wine 进程内存,见 [[re-analyze/platform-tips]] Linux 分支 Wine 直读方案)

操作步骤

按顺序执行,每步存档(引擎识别证据、地址与偏移记录、dump 产物 sha256,[[re-triage]] 存证)。

  1. 运行时/引擎识别

    • 目录特征: Game_Data/Managed/Assembly-CSharp.dll → Unity mono;Game_Data/il2cpp_data/ + global-metadata.dat → Unity IL2CPP
    • Unreal 特征: Engine/Binaries/*.pak<游戏名>-Win64-Shipping.exe;字符串里搜 GWorld/GNames
    • 其他/自研引擎 → 常规 [[re-binary-core]] 流程,不走本技能
    • 存档: 引擎类型 + 版本(决定后面工具选型)
  2. Unity mono vs IL2CPP 导出

    • mono: Game_Data/Managed/*.dll 直接反编译([[re-dotnet]],dnSpy/ILSpy 打开 Assembly-CSharp.dll 即得全部 C# 逻辑,无需本技能动态部分)
    • IL2CPP(无托管 DLL): Il2CppDumper 导出:
    Il2CppDumper.exe <Game.exe> <Game_Data>/il2cpp_data/Metadata/global-metadata.dat
    → dump.cs(类/方法/字段签名) + script.json(方法地址) + il2cpp.h(结构体)
    

    dump.cs 定位目标方法名,script.json 拿对应函数地址 → 到 exe 对应地址调试/patch([[re-x64dbg]] / [[re-windbg]])

    • 存档: dump.cs/script.json + 目标方法地址表
  3. 内存搜索(CE)

    • 数值扫描: attach 进程 → 输入当前值 First Scan → 游戏中改变数值 → Next Scan 收敛地址列表
    • 指针扫描(ASLR 下地址会变): 对已定位地址 What accesses this address → 记录访问指令偏移 → Pointer scan 生成指针链(保存 .pt 文件),重启后仍可用
    • 多级指针: 结构体基址形如 Game.exe+0x2A3F4C → +0x10 → +0x28——偏移由引擎对象结构决定(Unity 看 MonoBehaviour 字段顺序,Unreal 看 UPROPERTY 偏移)
    • 修改验证: 内存编辑或 CE Lua 脚本冻结值,观察游戏内行为变化
    • 存档: 地址表达式(模块+偏移)、指针链文件、扫描参数
  4. 函数定位与修改

    • 代码↔内存: 对关键地址 Find out what writes to this address → 断点命中写代码位置 → 记下模块内偏移 → 在反编译器([[re-ghidra]] / [[re-binaryninja]])对应地址分析该函数
    • 或从 dump.cs/script.json 直接拿 IL2CPP 函数地址 → 反编译 → 修改([[re-patching]]: 改跳转/参数/返回值)
    • 修改后测试: 沙箱内运行验证功能生效([[re-sandbox]]),记录修改前后行为差异
  5. 反作弊对抗(检测规避,沙箱内)

    • 认知: EAC/BattleEye 检测调试器存在、内存改写痕迹、CE/外挂痕迹、多开/虚拟化特征——分析环境与真实在线环境完全隔离(专用 VM + 快照,[[re-sandbox]] + [[re-analyze/platform-tips]] 最高原则)
    • 手段: 先静态分析检测点([[re-anti-analysis]] 反调试思路),hook/绕过检测 API(NtQuerySystemInformation 等),修改最小化(用后恢复),避免长期驻留痕迹
    • 收尾: 分析完恢复快照;不做在线对战用途

游戏脚本引擎(Lua / LuaJIT)

游戏逻辑常由脚本层承载(角色行为、技能数值、关卡配置),当分析对象从引擎 C++ 侧转到脚本字节码时:先识别脚本 VM 与精确版本,再按版本走字节码解析路径。

字节码识别(header 签名 / 版本)

  • 标准 Lua 字节码头部: 0x1B 4C 75 61\x1bLua)+ 版本字节——5.0=0x50、5.1=0x51、5.2=0x52、5.3=0x53、5.4=0x54;5.3 起头部含格式/校验数据与 int、size_t、指令、lua_Integer、lua_Number 各宽度字段,尾部带校验值
  • LuaJIT 字节码头部: 0x1B 4C 4A\x1bLJ)+ 版本字节——2.0=0x01、2.1=0x02,其后是端序与各类型宽度标志
  • 内存侧识别: 扫描魔数([[re-memdump]]),或按解释器版本字符串(Lua 5.x / LuaJIT 2.x)与错误消息(attempt to call a nil value 类)的引用定位解释器
  • 存档: VM 类型 + 精确版本 + 端序/宽度参数(决定解析路径)

指令结构

  • Lua 5.1~5.3: 32 位指令 = opcode(6) + A(8) + B(9) + C(9);Bx 为 B|C 拼接(18 位),sBx 带符号
  • Lua 5.4: opcode 扩到 7 位,A 8 位、B/C 收窄到 8 位并新增 k 标志位,Bx 17 位——5.3 及之前的解析表不可复用
  • LuaJIT: 32 位指令两种格式——ABC 格式 opcode(8) + A(8) + C(8) + B(8)(内存小端字节序为 OP/A/C/B 顺序)、AD 格式 A(8) + D(16);无 Bx 字段,D 字段承担常量索引与跳转偏移(跳转偏移带偏置,D − 0x8000)
  • 解析思路: 按版本取指令布局表,把 32 位字拆字段;布局表版本取错的表现是跳转目标/寄存器索引越界
  • 跳转偏移以指令索引为单位(不是字节),基准随版本略有差异,解析时按版本确认

常量表 / upvalue 表定位

  • 函数原型(Proto)序列化顺序固定: source 字符串 → 行号/参数/vararg/maxstacksize → 代码段 → 常量表 → upvalue 表 → 嵌套 Proto → debug 信息;字符串为长度前缀编码,按序推进
  • 常量表价值: 脚本内数值/字符串(技能数值、关卡配置、校验用密钥)直接可读,比还原完整语义更快
  • 内存侧: 在加载/执行 API 处拿 Proto 布局,或按已知常量值特征(格式字符串、特定数值)在堆中搜索

反汇编思路(luac 类工具)

  • 按「头部 → 指令 → 常量/upvalue → 嵌套 Proto」逐层解析,输出伪汇编(GETGLOBAL/CALL 类)
  • 现成解析器不支持目标版本时,按上述字段顺序自写解析器(量级小),核心是版本正确的指令布局表
  • 产出用于: 按常量表字符串/数值交叉引用找目标函数;改指令操作数或常量值实现修改([[re-patching]] 思路)

LuaJIT 字节码差异

  • LuaJIT 字节码与标准 Lua 5.1 字节码不兼容(语言层面兼容,指令集不同);LuaJIT 2.0 与 2.1 之间也不兼容(opcode 编号/语义有变)
  • 字节码与运行时绑定: 端序、指针宽度、类型宽度都写进文件头,异平台 dump 的文件直接换环境会解析失败
  • 对策: 严格按 \x1bLJ + 版本字节走 LuaJIT 解析路径;修改优先在源码/字节码层面完成,不跨版本搬运

Cocos 系脚本资源提取(.jsc / .luac 混淆变体)

  • Cocos2d-x Lua 场景: .luac 为标准 Lua/LuaJIT 字节码,常见混淆变体为头部截断(删/改 \x1bLua 魔数)与单字节 XOR 全文件;加密类变体(XXTEA 方案,密文带 xxtea 起始标记时先解密再走字节码路径)
  • Cocos Creator 场景: .jsc 为 JS 引擎字节码,同样常见头部截断/XOR 变体
  • 对策(先识别再解): 以已知魔数为 crib 恢复——掩码 = 魔数对应字节 XOR 文件首字节,恢复魔数后再按版本解析;截断则先补齐头部字节;方法与 [[re-fw-extract]] 的 XOR 章节同构(块首掩码、分块周期变换思路直接复用)

脚本函数 hook([[re-frida]] / 内存侧)

  • 加载面: luaL_loadbuffer/luaL_loadbufferx/luaL_loadfile/lua_load——hook 截获原始字节码/源码,适合改脚本逻辑(替换字节码、改常量)
  • 执行面: lua_pcall/lua_pcallk/lua_call——hook 截参数/返回值,适合改运行行为;LuaJIT 另有不经 C API 包装的汇编调用路径
  • 内存侧: 不 hook 时直接 patch 已加载字节码(改常量值/跳转),或改资源文件后替换加载路径
  • 无符号场景: 按版本字符串/错误消息引用定位解释器函数地址

常见坑

  • Lua 版本间字节码不兼容:现象——dump 出的字节码换工具/换运行时解析乱码或直接失败;原因——Lua 5.1~5.4 与 LuaJIT 的指令布局、头部字段、校验数据都不同,同版本还分端序/宽度,5.4 连 opcode 位数都改了;对策——先按魔数 + 版本字节精确判定版本与端序/宽度,再选对应解析路径;跨版本场景先找同版本源码/解释器重编译对照,不硬解
  • .jsc/.luac 变体魔数被截断:现象——文件头不是 \x1bLua/\x1bLJ,直接按字节码解析失败;原因——混淆变体截掉或改掉魔数(截断头部、单字节 XOR 全文件);对策——先识别再解:以已知魔数为 crib 恢复掩码(掩码 = 魔数字节 XOR 文件对应位置字节)或补齐截断头部,恢复魔数后再按版本解析;XXTEA 类加密先按加密特征解密再走字节码路径
  • hook 点选择:lua_pcall 不是唯一入口:现象——只 hook lua_pcall 时部分脚本行为没被触发或重复触发,hook 加载类 API 又拿不到目标脚本;原因——解释器存在多个调用面(lua_call/lua_pcallk/加载类 API 各自成面),LuaJIT 还有不经 C API 的汇编调用路径;对策——先定目标:改脚本逻辑 hook 加载面拿字节码,改运行行为 hook 执行面或内存 patch;hook 不到时按版本字符串/错误消息引用定位解释器内部调用点
  • 跳转目标换算错误:现象——反汇编出的跳转目标地址荒谬/越界;原因——Lua 字节码跳转偏移以指令索引为单位(不是字节),且 Bx 位宽与基准随版本变化(5.1~5.3 为当前指令索引 + 1 + sBx);对策——按版本确认位宽与基准,换算后核对目标是否落在代码段内

图形与 Shader

图形侧逆向的目标通常是: 改可视效果(颜色/参数/剔除)、定位资源绑定与常量更新点、还原渲染管线逻辑。入口分文件侧(shader 资源)与内存侧(运行时加载点)。

SPIR-V 反汇编(spirv-dis 类思路)

  • 魔数 0x07230203(文件字节序 03 02 23 07),word 流结构: 头部(魔数/版本/生成器/bound/schema)+ 指令序列(每条: word 数 + opcode + 操作数)
  • 反汇编思路: 按 word 边界切分指令流,还原 OpFunction/OpEntryPoint/OpConstant/OpType*/OpDecorate 等语义
  • 定位要点: OpEntryPoint 定入口(stage 类型 + 入口函数)、OpDecorate 的 DescriptorSet/Binding/Location 定资源绑定与顶点属性、OpConstant 定常量初值(可直接改)
  • 泛化: 二进制 IR 解析套路(魔数 → 结构头 → 指令流 → 语义表)在多种 GPU IR 间通用,换格式只换魔数与指令表

DXIL / DXBC 反汇编(dxc 类思路)

  • DXBC: DX10/11 容器格式("DXBC" 魔数 + chunk 表),着色器代码在 SHDR/SHEX chunk,输入/输出签名与资源反射在 ISGN/OSGN/RDEF 等 chunk
  • DXIL: DX12 SM6+ 字节码,外层仍是 DXBC 容器,内层 "DXIL" 子对象为 LLVM bitcode 风格——解析路径与 DXBC 完全不同
  • 反汇编思路: 先判容器/子对象类型(DXBC chunk vs DXIL),再按对应格式走;关注 SM 版本(决定指令集)与 cbuffer 反射(名称/成员偏移)
  • 内存侧: 扫描 "DXBC"/"DXIL" 魔数定位已编译着色器([[re-memdump]]),或 hook 创建类 API 截获字节

GLSL 还原(spirv-cross 类思路,泛化)

  • 还原路径: IR → 目标语言(GLSL/HLSL/MSL),思路为类型重建(OpType* → 语言类型)→ 语义还原(OpDecorate → layout/限定词)→ 表达式重建
  • 还原是有损的: 循环/switch/优化后的结构重建不完整,还原结果用于阅读分析,不一定能原样重编译
  • 泛化: IR→源码还原器按「类型 → 声明 → 函数体」三遍组织,换目标语言只换语法发射层

找渲染入口与常量表定位

  • 文件侧: shader 资源(.spv/.dxbc/.hlsl/.cso 或包内资源)先解包([[re-fw-extract]])再按格式反汇编;在二进制里按加载 API 字符串引用定位加载点(glShaderSource/glCompileShader、CreateVertexShader/CreatePixelShader/CreateComputeShader、vkCreateShaderModule 类)
  • 内存侧: hook 加载类 API 截获原始字节(源码或已编译字节码),或扫描魔数 dump([[re-memdump]])
  • 常量表定位: CPU 侧更新点——glUniform* 系列(单值 uniform 按 location 索引)、VSSetConstantBuffers/UpdateSubresource 类、Vulkan 的 vkCmdPushConstants/descriptor 更新类;GPU 侧布局——cbuffer 按结构体成员偏移 + 16 字节对齐打包(float4 数组间有 padding),单值 uniform 无此规则
  • 改参入口: 常量更新点运行时改值、字节码内常量初值静态 patch、或改常量表数据

常见坑

  • DXIL vs DXBC 判断错(SM6 vs SM5 路径不同):现象——按 DXBC 的 SHDR/SHEX 解析失败,或按 DXIL 路径反汇编乱码;原因——SM5.x 及以下是 DXBC 字节码,SM6+ 是 DXIL(LLVM bitcode 风格),解析路径完全不同,且 DXIL 外层容器同样是 "DXBC" 魔数,只看容器名会误判;对策——先确认 SM 版本(反射/RDEF 或编译配置),SM6+ 在容器内找 "DXIL" 子对象走 DXIL 路径,SM5- 走 DXBC chunk 路径,两条路径互不通用
  • SPIR-V 无函数名(按入口点/资源绑定定位):现象——反汇编结果全是 OpFunction/OpLabel 编号,找不到目标逻辑;原因——发布版通常剥离 OpName/OpMemberName 调试信息;对策——按 OpEntryPoint 定入口(stage 类型 + 函数引用),按 OpDecorate 的 Binding/Set/Location 定资源,按 OpConstant 已知值特征(矩阵初值/阈值常量)定位目标片段
  • 着色器常量表 vs 统一缓冲区混淆:现象——CPU 侧找到的 uniform 更新点和 GPU 侧反汇编的常量布局对不上;原因——同段逻辑可能走两套路径: 单值 uniform(按 location 序号直传)与统一/常量缓冲区(结构体成员偏移 + 16 字节对齐打包,含 padding);对策——先由反汇编/反射确认路径(SPIR-V 看 OpDecorate Uniform/Block,DXBC 看 RDEF 的 cbuffer 成员表),单值按 location 号、cbuffer 按成员偏移换算,两套打包规则不混用

跨域联合

  • [[re-binary-core]]:游戏 exe 静态分析底座([[re-format-pe]] 等)
  • [[re-dotnet]]:Unity mono 托管逻辑反编译(本技能静态侧的主要工具)
  • [[re-patching]]:函数修改落地(跳转/字节级 patch)
  • [[re-anti-analysis]]:反作弊检测对抗(反调试思路复用)
  • [[re-sandbox]]:反作弊游戏分析强制隔离([[re-analyze/platform-tips]] 最高原则)
  • [[re-mobile]]:移动端游戏(Unity/Unreal 移动版)转 [[re-apk]] / [[re-ios]] / [[re-frida]]
  • [[re-cracking]]:单机游戏授权/激活校验绕过
  • [[re-console]]:现代主机(Switch/PS4/PS5)与复古平台(ROM 头/卡带格式/Cheat 码)游戏逆向

常见坑与陷阱

  • IL2CPP 无托管 DLL 还去找 DLL:现象——Game_Data/Managed/ 下只有少量/无 DLL,反编译找不到目标逻辑;原因——IL2CPP 把 C# 编成原生二进制,托管 DLL 不存在;对策——直接用 Il2CppDumper(版本须支持目标 Unity 版本,太旧解析 global-metadata.dat 会失败),从 dump.cs 走函数地址
  • 指针链扫描繁琐且易失效:现象——数值扫描出的地址每次启动都变,直接改无效;原因——游戏使用动态内存分配/ASLR,基址每次不同;对策——CE Pointer scan 保存 .pt 指针链,或以模块基址+偏移(Game.exe+0x...)定位,启动后重算基址再偏移;多层偏移逐层确认
  • 反作弊(EAC/BE)强检测:现象——attach 被拒、调试器被检测、账号被封;原因——反作弊驱动级监控调试器与内存痕迹;对策——分析环境完全隔离(专用 VM/离线,[[re-sandbox]] + [[re-analyze/platform-tips]] 最高原则),检测点静态分析后绕过([[re-anti-analysis]]),绝不在在线环境挂调试器
  • 游戏更新频繁补丁失效:现象——更新后原地址全失效、patch 不生效;原因——每次更新重编译,地址/偏移变化;对策——patch 记录用「模块+偏移+字节模式」而非绝对地址,更新后用新旧 exe diff 定位变化点,或按字符串/函数名(IL2CPP dump)重新定位
  • 反作弊检测到虚拟化/多开:现象——VM 里游戏直接退出或行为异常;原因——部分反作弊检测 VBS/VMP 特征与虚拟化环境;对策——按反作弊已知规避清单调整 VM 配置(隐藏 hypervisor 特征选项),仍不行就静态分析为主,动态部分转到无反作弊的旧版本/离线模式
  • UE5 Dx12 绘制 hook(虚表替换):现象——Dx11 的 Present hook(抄 ImGui)在 UE5 Dx12 上失效,Present 断下后堆栈里找不到主模块代码;原因——UE5 自带单独编译的 D3D12Core.dllRelease 版叫 D3D12Core.dll、Debug 版叫 d3d12SDKLayers.dll,不用系统目录 dll,特征码在系统 dll 里搜不到),且开启 GPU 加速/超分辨率时 Present 上层堆栈被其他 dll 占据;对策——用开源 UE5 源码自己编译一份 D3D12Core.dll 拖 IDA 做参照(ExecuteCommandLists 在 CommandQueue 虚表 index=10);Present 断下后直接定位主模块下的返回地址(超分辨率按 dll 名识别,换模式换 dll 名思路不变);虚表替换点:mov rax,[rcx] 调用处取 rcx(对象指针)替换虚表
  • 游戏内 5 字节 hook 检测(反 hook):现象——定位到的目标函数头部已被改(经典 5 字节 hook 桩),且游戏自身调用能过、外部 hook 调用失败;原因——游戏/引擎自带调用来源检测(查堆栈返回),非自身调用会还原头部 5 字节;对策——不 hook 被保护函数本体,改在调用它的位置(堆栈返回处)hook 取对象指针(rcx/r12 等)替换虚表达成同等效果;识别特征:函数头 5 字节被改 + 函数内部有还原头部逻辑(FName::ToString/StaticFindObject 等关键函数多处同样处理)
  • IL2CPP 元数据还原:现象——dump 工具报版本不支持/输出不全;原因——Unity 版本改 metadata 格式;对策——升级 dump 工具(Il2CppDumper 或 Il2CppInspectorRedux),必须用同一次 dump 的产物对(script.json 与 so 匹配,换 IDA 清缓存)
  • 加密的 global-metadata.dat:现象——元数据文件是密文;原因——反作弊/自定义加密;对策——找初始化解密函数(il2cpp_init 周围),Frida 在 mmap/read 后 dump 内存中已解密的元数据再喂给 dump 工具
  • IL2CPP 方法 hook:现象——Frida 裸 hook 报错;原因——IL2CPP 方法非标准 Java/ObjC,需按 metadata 算偏移;对策——用 frida-il2cpp-bridge 库,不硬写 Interceptor.attach
  • patch 后闪退:现象——静态修改后启动崩溃;原因——文件 hash 校验/anti-tamper;对策——hook 优先(不改文件);必须静态 patch 时同步处理校验逻辑;重打包后删 META-INF 重新签名(apksigner) (来源:reverse-skill field-journal,MIT)
  • IL2CPP 双文件结构配对(metadata 骨架 + 二进制血肉):现象——只有 global-metadata.dat 或只拿二进制,dump 结果缺方法实现/字段偏移/类型图,两份文件版本错位时解析错乱;原因——IL2CPP 产物分两半:metadata(magic 0xFAB11BAF)只存声明型骨架(类型/方法/字段定义、参数默认值、字符串字面量、泛型容器、接口偏移、vtable 方法索引),二进制(GameAssembly.dll / libil2cpp.so 等)存血肉(Il2CppType 类型图、字段偏移表、方法指针表、泛型实例表),类型引用跨两侧互链,任一缺失或版本错位都无法完整还原;且信息归属随版本漂移(v27 移除 metadata usage、新版本又把 method specs 等迁回 metadata——具体迁移版本以实测为准,别按记忆中的版本号写死);对策——两份文件同版本配对使用;信息归属按版本分叉处理;二进制侧解析只依赖「虚拟地址→文件偏移映射 + 指针大小」两个原语,PE/ELF/Mach-O/WASM/NSO 统一抽象 (来源:Cpp2IL(SamboyCoding),MIT)
  • metadata 版本判定要靠 version 号 + Unity 版本联合裁决:现象——dump 工具对同一 version 号在不同游戏上时好时坏,结构解析错位(字段读歪、索引越界);原因——头部 version 字段是粗粒度信号,同一版本号对应多代结构(24 细分为 24.1~24.5、27 分 27.1/27.2 等),必须用 Unity 版本号二次裁决才能正确读结构;不同产品线还可能同号不同义(换皮陷阱),需按版本走分支;Unity 6 起(v38+)类型/方法/字段索引不再固定 4 字节,而是 8/16/32 位动态宽度,宽度从 section 的 Size/Count 反推,类型索引宽度甚至要从二进制侧 interfaceOffsets 结构推导(类型表不在 metadata 里);对策——version 字段 + Unity 版本联合裁决,按「引入变更的 Unity 版本」建版本映射表并记录每代变更点(字段增删、位宽变化、结构移动),hotfix 版本号有时是关键分界;索引变宽后先反推元素宽度再读表,不写死 4 字节 (来源:Cpp2IL(SamboyCoding),MIT)
  • 注册结构定位:计数模式搜索 + 候选仲裁:现象——在二进制里搜 MetadataRegistration/CodeRegistration 结构,假命中一堆,按第一个解析全是垃圾;原因——这两个注册结构(MetadataRegistration:类型/字段偏移/metadata usage 的指针与计数表;CodeRegistration:方法指针表、泛型方法指针表、codegen module 指针表)是 dump 的第一步,没有名字可依,只能以 metadata 已知计数(方法数/类型定义数)为字节模式全文件搜索,假命中极多;对策——命中处按结构体解析后用验证规则集排除假命中:count 字段不超上限(方法数/类型数十万量级)、指针字段必须能映射回文件内、关键计数与 metadata 侧交叉一致(fieldOffsetsCount == 类型定义数、numTypes 不小于类型定义数);上限阈值随样本库持续更新(真实样本把某计数推高到数十万量级);v24.2+ 可走 xref 引用链(mscorlib 字符串 → 指向它的 codegen module 指针 → 模块列表 → 回退到 CodeRegistration 基址);全部失败时降级人工输入地址 (来源:Cpp2IL(SamboyCoding),MIT)
  • metadata 被篡改/加密的静态对抗:现象——metadata 解析失败(magic 不符、section 数对不上、布局乱),只想到运行时 dump 一条路;原因——反作弊/自定义保护可能加密或打乱 metadata 头部;对策——先定 Unity 版本再解析,版本来源有优先级与互验(globalgamemanagers 优先 → data.unity3d 包文件 → 主程序版本,出现可疑值自动换备用来源重试);加密密钥用「已知明文特征」反推而非爆破——头部 offset/length 字段高字节必为 0(文件小于 32MB)、字符串区以两个 0 字节开头、头部字段是顺序字节流(XOR 后相邻字节差固定),约束收敛后逐段校验确认;布局被打乱时把头部所有候选 offset/length 当池子 DFS 拼接重建 section 链(偏移与当前位置差在合理区间、长度使位置推进且不越界、对齐约束);解析失败可挂 fixup 回调(原始字节 + Unity 版本 → 返回修复后字节重读),这是对抗加密/混淆 metadata 的挂载点 (来源:Cpp2IL(SamboyCoding),MIT)
  • AOT 泛型实例不完整 + 共享实现:现象——反编译里某些泛型方法没有实现或地址是占位值,不同泛型调用点还原出同一实现、类型对不上;原因——AOT 只生成游戏实际用到的泛型实例,未用实例指针为占位值;值类型参数与引用类型参数按尺寸分组共享实现(List<某引用类型> 与 List 共享同一实现指针,List 单独实现);编译器去重/共享方法体使「地址→方法」多对一;对策——从调用地址反查 ConcreteGenericImplementationsByAddress 列出该地址对应的全部泛型实例,这是还原泛型调用的关键通道;共享体还原用保守仲裁(仅当恰好一个非静态候选的声明类型匹配接收者类型才定案,宁可不解不猜);共享泛型体调用时额外隐藏传 MethodInfo* 尾参,还原时注意别把隐藏参数当普通参数 (来源:Cpp2IL(SamboyCoding),MIT)

Signals

GitHub stars
57
Forks
8
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
re-game
Source
github.com/dslsdzc/rev-skills
游戏逆向(Unity / Unreal / Lua 脚本引擎 / 图形 Shader) (re-game) · ahel