陌小杨の小站
首页项目归档照片墙音乐灵境说说杂谈友链关于
封面

UE4 Engine 逆向分析文档

写作时间:2026-07-10
# 逆向工程
# UE4
# 游戏安全
# SDK

UE4 SDK 逆向分析

基本结构

Matrix(矩阵)

矩阵头部位置一直有一个值会变动

手游:

00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 80 3F 00 00 80 3F 00 00 80 3F 00 00 80 3F 00 00 00 00 00 00 00 00 00 00 80 3F 00 00 00 00 00 00 00 00 00 00 80 3F 00 00 80 3F

端游:

矩阵基址特征码:48 8B 05 ?? ?? ?? ?? 48 8D 0C D0 EB 03 48 8B ?? 48 ?? ??

矩阵数据:

00 00 00 00 00 00 00 00 00 00 80 3F 00 00 80 3F 00 00 80 3F 00 00 80 3F 00 00 00 00 00 00 00 00 00 00 80 3F 00 00 00 00 00 00 00 00 00 00 80 3F 00 00 80 3F 00 00 00 00

调试符号法(端游)

Ctrl+G 搜索 GetCanvasByName,然后往下拉。

箭头地址 +20] +270/280


UWorld

特征码:48 8B 1D ?? ?? ?? ?? 48 85 DB 74 3B 41 B0 01

1. 字符串法

UE4 通用关键字:SeamlessTravel FlushLevelStreaming

2. 调试符号法

关键字:Gworld

Ctrl+G 搜索 Gworld,复制跳到的地址 - 基地址 = 偏移

3. 反推法(手游)

找到 count,反推。


GName

UE4 2.22 以下:

48 8B 05 ? ? ? ? 48 85 C0 75 5F B9 ? ? ? ?

UE4 2.23 及以上:

74 09 48 8D 15 ? ? ? ? EB 16

1. 字符串法

UE4 2.22 版本以下: 关键字:ByteProperty

该 Gname 需要减 0x10

UE4 2.22 版本: 关键字:*UNINITIALIZED*

往上滑到函数头部,进入第一个 CALL

UE4 2.23 版本及以上: 关键字:ByteProperty

方法:Ida 打开 Dump,搜索关键字,选择 ASCII 字符串,交叉引用后,返回本层 CALL,取调用该 call 的参数或 rcx 的值。

该 Gname 不需要减 0x10

2. 反算法

UE4 2.23 版本以下:

每个 ID 都有唯一的字符串,以 ByteProperty 为例,该字符串的 Id 为 1。根据算法可以推出:

  • 页 = 0
  • 序 = 1
  • 段地址 = ReadMem<ULONG64>(Gname+0)
  • 名字地址 = ReadMem<ULONG64>(段地址+4 or 8)
  • 类型名 = ReadMem<ASCII>(名字地址+8 or 16)

CE 搜索 ByteProperty,Ctrl+B 查看内存,观察内存区域。

反推步骤:

  1. ByteProperty = ReadMem<ASCII>(名字地址+16) → 名字地址 = 地址 - 0x10
  2. 名字地址 = ReadMem<ULONG64>(段地址+8) → 段地址 = 地址 - 0x8
  3. 段地址 = ReadMem<ULONG64>(Gname) → Gname = ReadMem<ULONG64>(GName)

UE4 2.23 版本以上:

搜索 ByteProperty 后按照旧版本的方法找到正确地址,然后 -8 搜索一次,得到绿色基地,基地址 -0x10 即为 FnamePool

3. 调试符号法

关键字:FName::GetNames

红框 - 基地址 = 偏移(不用 -10)

GetGname_4.23:

方法一:搜索 Commandlet %s finished execution (result %d)

方法二:找到 ByteProperty,在函数头交叉引用,找到同时存在两个返回函数的,并且都是同一个段内的。这个函数就是 GetName。

方法三:48 8D ?? ?? 4C 3B E0 74 ?? 49 8B 0C 24 48 85 C9 74 ?? E8 ?? ?? ?? ?? 48 8B ?? ??


GameInstance

仅限端游。Ida 搜索字符串 GameInstance,找到与 InWorld->GetGameInstance() is null 相似的字符串,双击进入后交叉引用,上边的 cmp 中就有偏移。


穿墙通杀

特征码:E8 ?? ?? ?? ?? 85 ?? 75 ?? 0F ?? ?? ?? ?? ?? ?? ?? EB(改 je)

E8 ? ? ? ? 80 67 32 FD


弹道追踪通杀

方法一:特征码

Hook: 0F 28 F0 F2 0F 11 ?? ?? ?? F3 0F 10 ?? ?? ?? 0F ?? ?? ?? 0F 28 ?? 0F 28 ?? F3

追踪 call 内部: 72 06 F3 0F 58 ?? EB 04 F3 0F 5C ??

使用方法:CE 搜索特征码后,Ctrl+D 找到靠近头部的地址。记录头部地址,打开 X64dbg 或 Ida,在游戏模块搜索命令 Call 头部地址。

右键 - 在所有命令设置断点,未开枪就断下的断点全部禁用,留下的那个就是弹道追踪 call。


坐标 Hook

特征码: 48 8B 49 10 4C 8B C2 48 8B 01 48 FF A0 ?? ?? ?? ??

内存分配:至少 12 个字节

偏移: 0x2D0


骨骼遍历

1. 反推法

CE 搜索自己的血量值,指针扫描。血量地址 +0x10 处有 mesh 地址。Mesh 地址附近的地址进行调试,找到组件到组件的变换地址(特征:尾部 00 00 80 3F)。

2. 特征码法

骨骼数组起始地址特征码:48 8D 0C D0 EB 03 48 8B ?? 48 ?? ??

Mesh 的特征码:E8 ? ? ? ? 48 8B 8E ? ? ? ? 0F 28 74 24

3. 特征码

74 13 F3 0F 10 8E ?? ?? ?? ?? F3 0F 10 86 ?? ?? ?? ?? 48 8B CF F3 0F 59 C8 0F 28 C1

世界坐标转屏幕坐标

特征码: F2 0F 10 05 ? ? ? ? F2 0F 11 44 24 ? ?

内部调用世界转屏幕 call,屏幕坐标:xmm0、xmm1


视场角 FOV

FOV = 90 / (屏幕坐标[1] - 0.5) * 屏幕宽高比 / 57.29577951308232


GObject

UE4 通用关键字:Cannot create an instance of %s


自动写入 SDK

方法一:使用 Dumper-7

生成 C++ 项目 → 编译 → 注入 → SDK 自动写入

方法二:特征码法

GObject: E8 ? ? ? ? 48 8B 1D ? ? ? ? 48 85 DB 75 ? ? ? ?

GName: 48 8D 0D ?? ?? ?? ?? E8 ?? ?? ?? ?? C6 05 ?? ?? ?? ?? 01 0F 10 05

方法三:特征码搜索 GetName

GetName 函数特征码:48 8D ?? ?? 4C 3B E0 74 ?? 49 8B 0C 24 48 85 C9 74 ?? E8 ?? ?? ?? ?? 48 8B ?? ??


遍历 Actor

方式一:对象管理器法

  1. 找 GObject 地址
  2. 找到 FUObjectArray 结构
  3. 遍历 ObjObjects
  4. 对象分类,获取 UWorld
  5. 遍历 Levels
  6. 遍历 Actors

方式二:GWorld 法

  1. 获取 GWorld
  2. 获取 PersistentLevel
  3. 获取 Actors 数组
  4. 遍历 Actors

UE4 4.25 SDK 结构手找

Stride

  • Stride = 4 或 Stride = 2(视具体情况)

FUObjectItem

单个对象所占的大小:Size = 0x18

FName

  • 只有一个 ID:Number = 0x4
  • 多个 ID:Number = 0x8

FNameEntry

struct {
    uint16 Info;        // 填充了字符串类型和大小信息的内存偏移量
    uint16 WideBit;     // 显示字符串是否使用宽字符的位的偏移量
    uint16 LenBit;      // 偏移到具有字符串长度的位
    uint16 HeaderSize;  // FNameEntry 头的大小(字符串开始处的偏移量)
};

示例:00 00 00 00 08 00 4E 6F 6E 65

  • WideBit = 0
  • LenBit = 1(0x0008 右移 1 次 = 0x4)
  • Info = 4
  • HeaderSize = 6

UObject

struct {
    uint16 index;
    uint16 Class;
    uint16 Name;
    uint16 Outer;
};

非加密游戏:Index、Class、Name、Outer 顺序不变,直接看指针猜测。

UStruct

struct {
    uint16 SuperStruct = 0x40;
    uint16 Children = 0x48;
    uint16 ChildProperties = 0x50;
    uint16 PropertiesSize = 0x58;
};

特征:三个连续指针 + 下面紧接一个整数,第三个指针进入后头部结构符合。

FProperty

struct {
    uint16 ArrayDim;
    uint16 ElementSize;
    uint16 PropertyFlags;
    uint16 Offset;
    uint16 Size;
};

Offset 位置始终与 PropertyFlags 相差 0xC。

FField

struct {
    uint16 Class;
    uint16 Next;
    uint16 Name;
};

Class 那一层进入头部直接是 ID,Next 和 Name 相连。

UField

struct {
    uint16 Next = 0x28;
};

UFunction

struct {
    uint16 FunctionFlags;
    uint16 Func;  // UE3-UE4,始终从 Flags 位置 +0x28
};

UEnum

struct {
    uint16 Names;
};

Canvas 绘制

FCanvas::DrawItem 特征码:48 8B D3 FF 50 18 83 BB ?? ?? ?? ?? ?? 75 0F 33 D2

SquadGame.exe+277B3C0 - 40 53              - push rbx
SquadGame.exe+277B3C2 - 48 83 EC 20        - sub rsp,20
SquadGame.exe+277B3C6 - 48 8B 99 60020000  - mov rbx,[rcx+00000260]
SquadGame.exe+277B3CD - 4C 8B C2           - mov r8,rdx
SquadGame.exe+277B3D0 - 48 8B 02           - mov rax,[rdx]
SquadGame.exe+277B3D3 - 49 8B C8           - mov rcx,r8
SquadGame.exe+277B3D6 - 48 8B D3           - mov rdx,rbx

总结

本文档涵盖了 UE4 引擎逆向分析的核心内容:矩阵定位、UWorld/GName/GObject 查找、骨骼遍历、世界转屏幕坐标、SDK 结构手找以及 Canvas 绘制。这些技术是 UE4 游戏安全分析的基础。

avatar

陌小杨

热爱折腾代码的开发者,记录学习与生活的点点滴滴。

RECOMMENDED

(Anti-)Anti-Rootkit Techniques - Part I: UnKovering Mapped Rootkits

2024-03-23

(Anti-)Anti-Rootkit Techniques - Part II: Stomped Drivers and Hidden Threads

2024-09-19

(Anti-)Anti-Rootkit Techniques - Part III: Hijacking Pointers

2025-02-16

Table of Contents