阶段九:游戏引擎逆向
📅 创建日期: 2025-01-XX
⏱️ 预计学时: 8-12 周
🎯 学习目标: 掌握 UE4/UE5 和 Unity 引擎逆向、SDK 生成、引擎特定技术
📋 前置要求: [[阶段五:核心逆向技术]], [[阶段三:进阶编程]]
🔗 返回: [[逆向与驱动开发学习路径(详细版)]]
📚 本阶段内容概览
- [[#9.1 Unreal Engine逆向|9.1 Unreal Engine逆向]]
- [[#9.1.1 UE架构概述|UE架构概述]]
- [[#9.1.2 UObject与反射系统|UObject与反射系统]]
- [[#9.1.3 GNames与GObjects|GNames与GObjects]]
- [[#9.1.4 SDK生成|SDK生成]]
- [[#9.1.5 常用逆向技术|常用逆向技术]]
- [[#9.2 Unity逆向|9.2 Unity逆向]]
- [[#9.2.1 Mono与IL2CPP|Mono与IL2CPP]]
- [[#9.2.2 元数据恢复|元数据恢复]]
- [[#9.2.3 Unity特定技术|Unity特定技术]]
9.1 Unreal Engine逆向
9.1.1 UE架构概述
📝 学习笔记
UE 核心概念
| 概念 | 说明 |
|---|---|
| UObject | 所有引擎对象的基类 |
| AActor | 可放置在关卡中的对象 |
| UWorld | 游戏世界,包含所有Actor |
| ULevel | 关卡,World的子集 |
| APlayerController | 玩家控制器 |
| APawn/ACharacter | 可被控制的角色 |
类层次结构
UObject
├── AActor
│ ├── APawn
│ │ └── ACharacter
│ │ └── AMyCharacter
│ ├── AController
│ │ └── APlayerController
│ ├── AGameMode
│ └── APlayerState
├── UActorComponent
│ ├── USceneComponent
│ │ └── UPrimitiveComponent
│ │ └── UMeshComponent
│ └── UMovementComponent
└── UGameInstance
内存布局特点
// UObject基本布局 (简化)
class UObject {
void** VTable; // 虚表指针
int32 ObjectFlags; // 对象标志
int32 InternalIndex; // GObjects数组索引
UClass* ClassPrivate; // 类信息
FName NamePrivate; // 对象名称
UObject* OuterPrivate; // 外部对象
};
9.1.2 UObject与反射系统
📝 学习笔记
UClass 结构
class UClass : public UStruct {
// 继承自UStruct
UStruct* SuperStruct; // 父类
UField* Children; // 子字段链表
int32 PropertiesSize; // 实例大小
// UClass特有
UObject* ClassDefaultObject; // CDO默认对象
UFunction* FuncLink; // 函数链表
// ...
};
// 属性结构
class UProperty : public UField {
int32 ArrayDim; // 数组维度
int32 ElementSize; // 元素大小
int32 Offset_Internal; // 在实例中的偏移
// ...
};
遍历类属性
void DumpClass(UClass* Class) {
printf("Class: %s\n", Class->GetName());
printf("Size: %d\n", Class->PropertiesSize);
// 遍历属性
for (UProperty* Prop = Class->PropertyLink; Prop; Prop = Prop->PropertyLinkNext) {
printf(" +0x%04X %s (%s)\n",
Prop->Offset_Internal,
Prop->GetName(),
Prop->GetClass()->GetName());
}
// 遍历函数
for (UFunction* Func = Class->FuncLink; Func; Func = Func->Next) {
printf(" Function: %s\n", Func->GetName());
}
}
9.1.3 GNames与GObjects
📝 学习笔记
GNames (FNamePool)
// FName是字符串的索引引用
struct FName {
int32 ComparisonIndex; // 在NamePool中的索引
int32 Number; // 实例编号
};
// 名称池结构 (UE4)
struct FNamePool {
FNameEntry* Entries[...]; // 分块数组
};
// 获取字符串
const char* FName::GetName() {
FNameEntry* Entry = GNames->Entries[ComparisonIndex];
return Entry->AnsiName; // 或 Entry->WideName
}
GObjects (FUObjectArray)
// 对象数组
struct FUObjectArray {
FUObjectItem* Objects; // 对象数组
int32 MaxElements; // 最大容量
int32 NumElements; // 当前数量
};
struct FUObjectItem {
UObject* Object; // 对象指针
int32 Flags; // 标志
int32 ClusterIndex; // 集群索引
int32 SerialNumber; // 序列号
};
// 遍历所有对象
void EnumerateObjects() {
for (int i = 0; i < GObjects->NumElements; i++) {
UObject* Obj = GObjects->Objects[i].Object;
if (Obj) {
printf("[%d] %s\n", i, Obj->GetFullName());
}
}
}
定位 GNames 和 GObjects
// 模式搜索 (示例)
// GNames通常在静态初始化时设置
// 搜索特征: 48 8B 05 ?? ?? ?? ?? 48 85 C0 75 ?? (lea rax, [GNames])
// GObjects
// 搜索特征: 48 8B 05 ?? ?? ?? ?? 48 8B 0C C8 (mov rax, [GObjects])
// 或通过已知函数定位
// UObject::GetFullName() 会访问GNames
// FindObject<>() 会访问GObjects
9.1.4 SDK生成
📝 学习笔记
SDK 生成原理
// 遍历所有UClass生成头文件
void GenerateSDK() {
for (int i = 0; i < GObjects->NumElements; i++) {
UObject* Obj = GObjects->Objects[i].Object;
if (!Obj) continue;
if (Obj->IsA(UClass::StaticClass())) {
UClass* Class = (UClass*)Obj;
GenerateClassHeader(Class);
}
else if (Obj->IsA(UEnum::StaticClass())) {
GenerateEnumHeader((UEnum*)Obj);
}
}
}
// 生成类头文件
void GenerateClassHeader(UClass* Class) {
FILE* f = fopen(GetClassFileName(Class), "w");
// 输出类声明
fprintf(f, "class %s", Class->GetName());
if (Class->SuperStruct) {
fprintf(f, " : public %s", Class->SuperStruct->GetName());
}
fprintf(f, " {\npublic:\n");
// 输出属性
for (UProperty* Prop = Class->PropertyLink; Prop; Prop = Prop->PropertyLinkNext) {
fprintf(f, " %s %s; // 0x%X\n",
GetPropertyType(Prop),
Prop->GetName(),
Prop->Offset_Internal);
}
// 输出函数
for (UFunction* Func = Class->FuncLink; Func; Func = Func->Next) {
GenerateFunctionDecl(f, Func);
}
fprintf(f, "};\n");
fclose(f);
}
常用 SDK 生成工具
| 工具 | 说明 |
|---|---|
| UnrealDumper | 基础SDK生成 |
| Dumper-7 | 现代UE4/UE5支持 |
| KN4CK3R/UnrealDumper | 开源实现 |
9.1.5 常用逆向技术
📝 学习笔记
获取本地玩家
// 方法1: 通过GEngine
UEngine* GEngine = *(UEngine**)GEnginePtr;
UGameViewportClient* ViewportClient = GEngine->GameViewport;
UWorld* World = ViewportClient->World;
APlayerController* PC = World->OwningGameInstance->LocalPlayers[0]->PlayerController;
// 方法2: 通过UWorld
UWorld* World = *(UWorld**)GWorldPtr;
APlayerController* PC = World->OwningGameInstance->GetFirstLocalPlayerController(World);
APawn* LocalPawn = PC->AcknowledgedPawn;
Actor 遍历
void EnumerateActors(UWorld* World) {
for (int i = 0; i < World->Levels.Num(); i++) {
ULevel* Level = World->Levels[i];
for (int j = 0; j < Level->Actors.Num(); j++) {
AActor* Actor = Level->Actors[j];
if (Actor && Actor->IsA(ACharacter::StaticClass())) {
ProcessCharacter((ACharacter*)Actor);
}
}
}
}
世界坐标转屏幕坐标
bool WorldToScreen(APlayerController* PC, FVector WorldLocation, FVector2D& ScreenLocation) {
// 使用引擎函数
return PC->ProjectWorldLocationToScreen(WorldLocation, ScreenLocation, false);
}
// 手动计算
bool WorldToScreenManual(FVector WorldPos, FVector2D& ScreenPos) {
FMatrix ViewMatrix = GetViewMatrix();
FMatrix ProjectionMatrix = GetProjectionMatrix();
FMatrix ViewProjection = ViewMatrix * ProjectionMatrix;
FVector4 Projected = ViewProjection.TransformFVector4(FVector4(WorldPos, 1.0f));
if (Projected.W > 0.0f) {
float X = (1.0f + Projected.X / Projected.W) * 0.5f * ScreenWidth;
float Y = (1.0f - Projected.Y / Projected.W) * 0.5f * ScreenHeight;
ScreenPos = FVector2D(X, Y);
return true;
}
return false;
}
获取骨骼位置
FVector GetBoneLocation(ACharacter* Character, int BoneIndex) {
USkeletalMeshComponent* Mesh = Character->Mesh;
if (!Mesh) return FVector();
// 方法1: 使用引擎函数
return Mesh->GetBoneLocation(BoneIndex);
// 方法2: 直接读取骨骼矩阵
FTransform BoneTransform = Mesh->GetBoneTransform(BoneIndex);
return BoneTransform.GetLocation();
}
// 常用骨骼索引
enum EBoneIndex {
Head = 98, // 因游戏而异
Neck = 97,
Chest = 66,
// ...
};
9.2 Unity逆向
9.2.1 Mono与IL2CPP
📝 学习笔记
两种运行时对比
| 特性 | Mono | IL2CPP |
|---|---|---|
| 代码形式 | .NET IL字节码 | 编译为C++ |
| 性能 | JIT编译 | AOT编译,更快 |
| 逆向难度 | 简单 | 较难 |
| 主要文件 | Assembly-CSharp.dll | GameAssembly.dll |
| 元数据 | 完整保留 | 需要global-metadata.dat |
Mono 逆向
直接用dnSpy/ILSpy打开:
GameFolder/
├── Game_Data/
│ └── Managed/
│ ├── Assembly-CSharp.dll <- 主要游戏逻辑
│ ├── Assembly-CSharp-firstpass.dll
│ └── UnityEngine.*.dll <- Unity库
IL2CPP 结构
GameFolder/
├── Game_Data/
│ ├── il2cpp_data/
│ │ └── Metadata/
│ │ └── global-metadata.dat <- 元数据
│ └── Native/
│ └── GameAssembly.dll <- 编译后的代码
9.2.2 元数据恢复
📝 学习笔记
Il2CppDumper 使用
# 运行Il2CppDumper
Il2CppDumper.exe GameAssembly.dll global-metadata.dat output/
# 输出文件:
# - dump.cs # C#类定义
# - script.json # IDA/Ghidra脚本数据
# - stringliteral.json # 字符串信息
Il2Cpp 内部结构
// Il2CppClass (简化)
struct Il2CppClass {
Il2CppImage* image;
void* gc_desc;
const char* name;
const char* namespaze;
Il2CppType byval_arg;
Il2CppType this_arg;
Il2CppClass* element_class;
Il2CppClass* parent;
MethodInfo** methods;
FieldInfo* fields;
// ...
uint16_t method_count;
uint16_t field_count;
// ...
};
// MethodInfo
struct MethodInfo {
void* methodPointer; // 函数地址
void* invoker_method;
const char* name;
Il2CppClass* klass;
Il2CppType* return_type;
ParameterInfo* parameters;
// ...
};
运行时查找
// 查找类
Il2CppClass* FindClass(const char* namespaze, const char* name) {
Il2CppDomain* domain = il2cpp_domain_get();
Il2CppAssembly** assemblies;
size_t count;
assemblies = il2cpp_domain_get_assemblies(domain, &count);
for (size_t i = 0; i < count; i++) {
Il2CppImage* image = il2cpp_assembly_get_image(assemblies[i]);
Il2CppClass* klass = il2cpp_class_from_name(image, namespaze, name);
if (klass) return klass;
}
return nullptr;
}
// 查找方法
MethodInfo* FindMethod(Il2CppClass* klass, const char* name, int argCount) {
return il2cpp_class_get_method_from_name(klass, name, argCount);
}
// 调用方法
void* CallMethod(MethodInfo* method, void* obj, void** params) {
Il2CppException* exception = nullptr;
void* result = il2cpp_runtime_invoke(method, obj, params, &exception);
if (exception) {
// 处理异常
}
return result;
}
9.2.3 Unity特定技术
📝 学习笔记
获取游戏对象
// 查找场景中的对象
Il2CppArray* FindObjectsOfType(Il2CppClass* type) {
// Object.FindObjectsOfType(typeof(T))
MethodInfo* method = FindMethod(
FindClass("UnityEngine", "Object"),
"FindObjectsOfType",
1
);
Il2CppReflectionType* reflectionType = il2cpp_type_get_object(&type->byval_arg);
void* params[] = { reflectionType };
return (Il2CppArray*)CallMethod(method, nullptr, params);
}
// 通过名称查找
void* FindGameObject(const char* name) {
// GameObject.Find(name)
MethodInfo* method = FindMethod(
FindClass("UnityEngine", "GameObject"),
"Find",
1
);
Il2CppString* nameStr = il2cpp_string_new(name);
void* params[] = { nameStr };
return CallMethod(method, nullptr, params);
}
访问 Transform
// 获取位置
Vector3 GetPosition(void* gameObject) {
// 获取Transform组件
MethodInfo* getTransform = FindMethod(
FindClass("UnityEngine", "GameObject"),
"get_transform",
0
);
void* transform = CallMethod(getTransform, gameObject, nullptr);
// 获取position
MethodInfo* getPosition = FindMethod(
FindClass("UnityEngine", "Transform"),
"get_position",
0
);
Vector3 pos;
CallMethod(getPosition, transform, nullptr);
// 返回值处理...
return pos;
}
Camera 相关
// 获取主相机
void* GetMainCamera() {
MethodInfo* method = FindMethod(
FindClass("UnityEngine", "Camera"),
"get_main",
0
);
return CallMethod(method, nullptr, nullptr);
}
// 世界坐标转屏幕
Vector3 WorldToScreenPoint(void* camera, Vector3 worldPos) {
MethodInfo* method = FindMethod(
FindClass("UnityEngine", "Camera"),
"WorldToScreenPoint",
1
);
void* params[] = { &worldPos };
Vector3 screenPos;
// 调用并处理返回值...
return screenPos;
}
字段读写
// 获取字段值
template<typename T>
T GetFieldValue(void* obj, Il2CppClass* klass, const char* fieldName) {
FieldInfo* field = il2cpp_class_get_field_from_name(klass, fieldName);
T value;
il2cpp_field_get_value(obj, field, &value);
return value;
}
// 设置字段值
template<typename T>
void SetFieldValue(void* obj, Il2CppClass* klass, const char* fieldName, T value) {
FieldInfo* field = il2cpp_class_get_field_from_name(klass, fieldName);
il2cpp_field_set_value(obj, field, &value);
}
// 静态字段
template<typename T>
T GetStaticFieldValue(Il2CppClass* klass, const char* fieldName) {
FieldInfo* field = il2cpp_class_get_field_from_name(klass, fieldName);
T value;
il2cpp_field_static_get_value(field, &value);
return value;
}
✅ 阶段检查点
Unreal Engine检查
- [ ] 理解UObject/AActor/UWorld关系
- [ ] 能定位GNames和GObjects
- [ ] 能遍历游戏对象
- [ ] 理解SDK生成原理
- [ ] 实现基本的W2S转换
Unity检查
- [ ] 区分Mono和IL2CPP游戏
- [ ] 能使用dnSpy分析Mono游戏
- [ ] 能使用Il2CppDumper恢复元数据
- [ ] 理解Il2Cpp运行时结构
- [ ] 能在运行时调用Unity方法
📖 学习资料
| 资源类型 | 名称 | 说明 |
|---|---|---|
| 📚 文档 | UE4 Source | 需Epic账号 |
| 📚 文档 | Unity Scripting API | 官方API参考 |
| 🔧 工具 | Dumper-7 | UE4/UE5 SDK生成 |
| 🔧 工具 | Il2CppDumper | IL2CPP逆向 |
| 🔧 工具 | dnSpy | .NET反编译 |
| 🔧 工具 | Cpp2IL | IL2CPP分析 |
| 📹 视频 | GuidedHacking UE4教程 | YouTube系列 |
🔗 导航
← [[阶段八:脚本框架]] | [[逆向与驱动开发学习路径(详细版)| 返回主目录]]