UE4逆向实战:手把手教你用CE定位GName和DumpName(4.23以下版本适用)

UE4逆向实战:从GName定位到DumpName的完整操作指南(4.23以下版本)

如果你对虚幻引擎4(UE4)的逆向工程感兴趣,尤其是想了解游戏对象名称系统的内部工作原理,那么这篇文章就是为你准备的。在UE4游戏的安全分析、外挂开发或模组制作中,理解并能够提取游戏中的对象名称是至关重要的一步。今天,我将带你深入UE4 4.23以下版本的核心,手把手教你如何定位关键的GName全局变量,并实现DumpName操作。

与那些只讲理论的教程不同,这篇文章完全从实战出发。我会假设你已经有了一些基础的逆向知识,比如知道如何使用调试器和内存扫描工具,但即使你是初学者,跟着步骤走也能掌握核心技巧。我们将使用Cheat Engine(CE)作为主要工具,因为它免费、强大且社区支持完善。整个过程就像侦探破案一样,通过线索一步步找到目标,既有挑战性又有成就感。

1. 理解UE4的FName系统与GName全局变量

在开始动手之前,有必要先理解我们要找的是什么。UE4使用一种称为FName的高效字符串管理系统,它不像普通字符串那样直接存储文本内容,而是将字符串存储在全局的**名称池(Name Pool)**中,每个字符串分配一个唯一的索引。FName对象本身只存储这个索引,比较和传递时只操作索引,大大提升了性能。

这个全局名称池就是通过GName(或TNameEntryArray)这个全局变量来管理的。在UE4 4.23之前的版本中,GName是一个直接可访问的全局数组;而在4.23及之后版本中,它被封装在FNamePool结构中,访问方式有所不同。我们今天聚焦在4.23以下版本。

为什么GName如此重要?

  • 它是所有游戏对象名称的中央存储库
  • 通过它可以将FName索引转换为可读的字符串
  • 是逆向分析中定位游戏对象、函数和属性的关键入口点

从源码层面看,FName的核心结构是这样的:

// 简化版的FName结构(基于UE4 4.22)
class FName
{
public:
    // 获取名称字符串
    FORCEINLINE FString ToString() const
    {
        return GetDisplayNameEntry()->GetPlainNameString();
    }
    
private:
    // 名称索引(核心数据)
    FNameEntryId ComparisonIndex;
    // 显示索引(可能不同)
    FNameEntryId DisplayIndex;
    // 编号(用于区分同名对象)
    int32 Number;
};

而GName实际上是一个TNameEntryArray类型的全局数组,存储着所有的FNameEntry对象,每个FNameEntry包含实际的字符串数据。我们的目标就是找到这个全局数组在内存中的位置。

2. 准备工作与环境配置

在开始逆向之前,确保你准备好了以下工具和环境:

必需工具清单:

  • Cheat Engine 7.4+ - 我们的主要逆向工具
  • IDA Pro 或 Ghidra - 用于静态分析(可选但推荐)
  • UE4 4.22或更早版本的游戏 - 作为分析目标
  • Process Hacker 或 Process Explorer - 查看进程信息
  • 文本编辑器 - 记录偏移和地址

目标游戏选择建议: 对于初学者,我推荐选择以下类型的游戏作为练习目标:

  1. 单机UE4游戏 - 没有反作弊,分析更简单
  2. 小体量独立游戏 - 加载快,结构相对简单
  3. 已知使用UE4 4.22或更早版本的游戏 - 确保方法适用

注意:请仅在你有合法权限的游戏上进行逆向分析,比如自己购买的游戏或明确允许修改的游戏。尊重开发者的劳动成果和版权。

Cheat Engine基础配置:

  1. 下载并安装最新版Cheat Engine
  2. 首次运行可能需要关闭一些杀毒软件的实时保护(CE有时会被误报)
  3. 熟悉CE的基本操作:附加进程、内存扫描、查看内存区域、下断点

实战前的心理准备: 逆向工程很少有一次成功的情况。你可能会遇到游戏更新导致偏移变化、反调试保护、或者自己理解错误的情况。保持耐心,把每次失败都当作学习的机会。我刚开始时,为了找一个简单的偏移花了整整两天时间,但现在回想起来,那些挫折让我对内存布局有了更深的理解。

3. 使用Cheat Engine定位GName的实战步骤

现在进入实战环节。我将以《BattleRoyaleTrainer》(一个UE4单机训练游戏)为例,展示完整的定位过程。你可以用任何UE4 4.23以下的游戏跟随操作。

3.1 第一步:定位关键字符串特征

UE4引擎在初始化时会注册大量硬编码的名称,如"None"、"ByteProperty"、"IntProperty"等。这些字符串在内存中的位置与GName有直接关系,是我们寻找GName的重要线索。

操作步骤:

  1. 启动游戏和Cheat Engine
  2. 在CE中选择游戏进程并附加
  3. 点击"Memory View"打开内存查看器
  4. 在内存查看器中右键选择"Search for → String references"
  5. 在弹出的对话框中输入"ByteProperty"(不含引号)
  6. 确保搜索选项设置为"Case sensitive"和"Entire block"

你会看到类似这样的结果:

地址                    反汇编
00007FF6A1B3D120        push rbx
00007FF6A1B3D122        sub rsp, 20h
00007FF6A1B3D126        mov rbx, rcx
00007FF6A1B3D129        lea rcx, [00007FF6A3A8F2D0h] ; "ByteProperty"
00007FF6A1B3D130        call 00007FF6A1B3CF00

关键技巧:

  • 不要只看第一个结果,往下滚动查找在代码段(.text)中的引用
  • 注意那些在函数开头附近被引用的字符串,很可能是初始化函数
  • 记录下字符串地址,比如上面的00007FF6A3A8F2D0

3.2 第二步:分析字符串引用函数

找到字符串引用后,我们需要查看是哪个函数在使用它。在内存查看器中双击字符串地址,CE会跳转到引用该字符串的代码位置。

常见的GName相关函数特征:

  1. FName::ToString() - 将FName转换为字符串
  2. FName::GetDisplayNameEntry() - 获取名称条目
  3. 静态初始化函数 - 在游戏启动时注册硬编码名称

以"ByteProperty"为例,我们可能会看到这样的代码模式:

; 典型的GName初始化代码模式
mov     rcx, [GName地址]      ; 加载GName指针
lea     rdx, [字符串地址]      ; 加载"ByteProperty"地址
call    RegisterNameFunction   ; 调用注册函数

实际操作:

  1. 在字符串引用处按Ctrl+D查看反汇编
  2. 向上滚动查看函数开头(通常有push rbp, mov rbp, rsp等序言)
  3. 寻找对全局变量的访问指令,如mov rax, [7FF6A3B45000h]

识别GName访问模式: GName通常以以下形式被访问:

  • 直接内存访问:mov rax, [GName地址]
  • 相对RIP访问:lea rax, [rip+GName偏移]
  • 通过函数获取:call GetGNameFunction

3.3 第三步:追踪GName的交叉引用

一旦找到可能初始化或使用GName的函数,我们需要追踪它的交叉引用。在CE中,你可以:

  1. 在函数内的GName访问指令上右键
  2. 选择"Find out what accesses this address"
  3. CE会记录所有访问该地址的代码

实际案例追踪: 假设我们在00007FF6A1B3D130处找到了对"ByteProperty"的引用,向上分析函数:

; 函数开头
00007FF6A1B3D120  push    rbx
00007FF6A1B3D122  sub     rsp, 20h
00007FF6A1B3D126  mov     rbx, rcx
00007FF6A1B3D129  lea     rcx, [00007FF6A3A8F2D0h] ; "ByteProperty"
00007FF6A1B3D130  call    00007FF6A1B3CF00         ; 注册函数
00007FF6A1B3D135  mov     rcx, rbx
00007FF6A1B3D138  add     rsp, 20h
00007FF6A1B3D13C  pop     rbx
00007FF6A1B3D13D  ret

这个函数看起来是在注册名称。我们需要找到它被谁调用,以及GName在哪里被初始化。在函数开头00007FF6A1B3D120处右键选择"Find references to this address"。

如果幸运的话,你可能会找到一个像这样的初始化函数:

; GName初始化函数(简化版)
GName_Init:
    push    rbp
    mov     rbp, rsp
    sub     rsp, 40h
    mov     [rbp+var_18], rbx
    mov     [rbp+var_10], rsi
    mov     [rbp+var_8], rdi
    
    ; 初始化GName数组
    lea     rax, GName            ; 关键!这里加载GName地址
    mov     [rax], 0
    mov     [rax+8], 0
    ...
    
    ; 注册硬编码名称
    lea     rcx, [Str_None]
    call    RegisterName
    lea     rcx, [Str_ByteProperty]  ; 我们找到的字符串
    call    RegisterName
    ...

3.4 第四步:验证找到的GName地址

找到可能的GName地址后,我们需要验证它是否正确。GName在内存中通常表现为一个结构体数组,具有以下特征:

GName数组的典型内存布局:

地址          数据
GName+0x00:   指向第一个FNameEntry块的指针
GName+0x08:   指向第二个FNameEntry块的指针
GName+0x10:   指向第三个FNameEntry块的指针
...

验证方法:

  1. 在CE的十六进制查看器中转到疑似GName的地址
  2. 查看该地址存储的值(应该是一个指针)
  3. 跟随该指针(右键→"Follow in dump")
  4. 你应该看到类似这样的结构:
FNameEntry内存块示例:
偏移   数据              ASCII
0x00:  00 00 00 00 00 00 00 00  ........
0x08:  4E 6F 6E 65 00 00 00 00  None....
0x10:  42 79 74 65 50 72 6F 70  ByteProp
0x18:  65 72 74 79 00 00 00 00  erty....

快速验证脚本: 你可以在CE中编写一个简单的Lua脚本来验证GName:

function verifyGName(gnameAddr)
  local blockPtr = readPointer(gnameAddr)
  if blockPtr == nil or blockPtr == 0 then
    print("无效的GName地址")
    return false
  end
  
  -- 读取第一个名称(应该是"None")
  local nameEntry = blockPtr + 0x8  -- 跳过头部信息
  local name = readString(nameEntry, 50, true)
  
  if name == "None" then
    print(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值