之前看到过很多游戏都是用虚幻引擎写的,就萌生了研究它的兴趣,但是发现网上关于如何Dump出SDK没有很好的资料,正好研究具体原理并整理一下。本文基于UnrealEngine4.27版本,对于4.25之前的版本需要针对UProperty和FProperty的变动部分进行修改。
U/FProperty: 表示 C++ 中的属性,即类或结构体的成员变量。UEnum: 表示 C++ 中的枚举,内部保存了一个 TMap,维护了 Name、Value、Index 三大信息的对应关系。UStruct: 表示 C++ 中的复杂类型,包含函数、类、结构体三种。内部维护了所表示类型的所有 UProperty。UFunction: 表示 C++ 中的函数,内部维护了函数指针、栈帧、参数返回值信息,还提供了反射执行所表示函数的方法。UClass:表示 C++ 中的类,在 UStruct 的基础上扩展了 UFunction 的保存与查找方法。UScriptStruct: 表示 C++ 中的结构体,只是在 UStruct 的基础上增加了一些工具方法而已。
可以发现即使能够直接通过UObject::FName Name来获取类/函数/属性的名称,解析FName同样不是一件简单的事情。因为FName实际并未存储实际的字符串,而是存储了字符串的引用。在虚幻引擎中,FName为静态字符串类型,类似于存储与.rodata段的const char*字符串。在FName中实际上存储的是一个Index,即FNameEntryId。
通过FNameEntryId即可从FNamePool GName中查找到实际的字符串值。GName的具体逆向方法这里不做详细介绍,这里介绍如何通过GName和FNameEntryId来访问具体的字符串。查看FNamePool的定义,可以发现其中即FNameEntryAllocator,而其中存在这一个指针数组,数组中存储着指向FNameEntry的指针,名为Blocks。
查看FNameEntry可以发现其中存储了字符串本体,因此我们根据FNamePool和FName中的FNameEntryId查表即可获取到字符串本体。
查表方式借鉴虚幻引擎FNameEntryAllocator下的Resolve方法,FNameEntryHandle其实是通过移位分裂FNameEntryId的方式获得的,代码如下。
至此便可以通过FNamePool GName和对应的FName获得对象的名称了,具体的实现代码请查看源码Dump.cpp下的GetNameByIndex函数。
在了解了解析UClass等反射数据的方法和通过FName获取具体字符串的操作后,现在只需要获取到相应的类对象即可进行dump了。
#define CLASS_TYPENAME "Class /Script/CoreUObject.Class"#define ENUM_TYPENAME "Class /Script/CoreUObject.Enum"#define SCRIPTSTRUCT_TYPENAME "Class /Script/CoreUObject.ScriptStruct"
虽然上文将我们感兴趣的UObject分为了三类,但实际上需要处理的只有两类:UEnum和UStruct,因为UClass和UScriptStruct皆继承自UStruct,且仅通过UStruct结构就可提取出类的属性和方法等所有生成Sdk的信息。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
veryluckko 没记错的话好像只有netvar才能dump