-
-
[原创]UE4.27SDK-Dump
-
发表于: 2024-8-11 19:45 4784
-
近期学了学UE4相关知识,并且自己动手写了写SDK dump,特此记录一下。相关代码已传至github,使用了Frida
与TypeScript
,解决了Frida
在Js
下代码挤压在同一个文件的问题,同时使用C++
去写结构体并得到偏移,能够更好的维护。
在阅读代码之前,就必须去了解一下UE4的命名约定,具体的自己去查看官网文档,下面是一些基本需要知道的:
在UE4引擎中全局定义了UWorldProxy
对象,此对象在Engine\Source\Runtime\Engine\Classes\Engine\World.h
定义,这个类中有一个变量UWorld* World;
在官网定义中可以看到,UWorld
是地图或沙盒的顶级对象,其中会有Actor
等信息
根据继承关系找到最顶级的类UobjectBase
,此类在Engine\Source\Runtime\CoreUObject\Public\UObject\UObjectBase.h
定义,在这个类中有一个成员变量FName
,由注释可知此变量代表对象的名称。
其字段含义如下
这里值得一提的是,普遍认为UObject
是是UE4中所有对象的基类,但是从这里可以发现真正的基类是UObjectBase
。认为UObject
是基类也并无道理,因为继承自UObjectBase
的UObject
没有新增任何成员,从VS提供的内存视图可知,下文也会延续这一说法。
正是因为UE4中所有对象都继承自UObject
,而UObject
又有成员FName
提供名字,因此UE4的反射系统才能方便得知一个对象的名字,甚至是字段、函数参数等名字。这也给逆向工作提供了可乘之机,那么就不得不研究一下UE4是怎么通过这个FName
得到的名字的。
FName
在Engine\Source\Runtime\Core\Public\UObject\NameTypes.h
定义,这个类中有一个成员函数ToString()
,还存储了字符串索引FNameEntryId ComparisonIndex;
见名思义,调用FName::ToString
即可得到名字,返回一个FString
类型。
查看FString
类,在Engine\Source\Runtime\Core\Public\Containers\UnrealString.h
定义
可见字符串相关数据存储在TArray
容器中,容器类型是TCHAR
,而TCHAR
类型就是wchar_t
类型。
TArray是一个模板类,定义也比较简单
由此逻辑已经清晰,要在UE4中获得一个对象的名称,即该对象的字符串,只需要访问UObjectBase
的成员变量NamePrivate
,调用该变量的ToString
函数,该函数返回一个FSting
类型,这个类型里面就有所要字符串的地址。
以上过程是正向开发的调用过程,但在逆向的时候情况就不会有这么简单,当然也可以直接查找调用ToString
函数偏移来得到对象名字,这一步可以通过IDA
等手段完成。必要地,还是要看一下ToString
函数是怎么实现的。这一点会在后文进行。
此类型在Engine\Source\Runtime\Core\Private\UObject\UnrealNames.cpp
中定义,这里只关注第一个成员
这个类型定义了一个全局静态变量NamePoolData
数组,这个NamePoolData
就是常说的GName
,因为它就存储了全局的字符串,相当于一个字符串池,后续的ToString
函数也高度依赖这个数组。
此类型在Engine\Source\Runtime\Core\Private\UObject\UnrealNames.cpp
中定义,这类中有四个相当重要的成员变量
此类型在Engine\Source\Runtime\Core\Public\UObject\NameTypes.h
定义,用于存储实际的字符串。FNameEntryHeader
结构比较简单,后面会有提及。
此类型中,只有一个uint32
的类型变量,其余均是函数,因此可简单理解为ComparisonIndex
就是一个uint32
的值
这个类主要就是存放字符串在GName
中的索引
还有一些关键类会在后续边解析边提及。
此函数实现于Engine\Source\Runtime\Core\Private\UObject\UnrealNames.cpp
中
可知该函数先调用了GetDisplayNameEntry
函数
GetDisplayNameEntry此函数实现就在ToString
函数上方
可知此函数调用了三个函数,依次查看
此函数在Engine\Source\Runtime\Core\Private\UObject\UnrealNames.cpp
中定义
这是一个单例模式的函数,即在So中存在唯一的全局静态变量NamePoolData
,查看NamePoolData
的类型FNamePool
。
这个模式保证了即使在多线程环境下,FNamePool
也只会被初始化一次。它不使用C++11中的魔法静态(magic statics)或单例模式中的锁,以减少运行时的开销。通过这种方式,FNamePool
类的实例在第一次调用GetNamePool
时被创建,并在随后的调用中直接返回,避免了重复初始化。
此函数实现于Engine\Source\Runtime\Core\Public\UObject\NameTypes.h
进入GetDisplayIndexFast
函数,此函数定义在相同目录下
查看相关宏定义,
由注释可知此宏只用于编辑器,而在实际运行中不会启用
因此GetDisplayIndexFast
实际返回ComparisonIndex
,ComparisonIndex
有两处被定义,其中一处是作为FName
的成员变量,FNameEntryId
前文也有解析,它里面就只存储了uint32_t
的值,作为字符串在GName
中索引。
如果还没忘记的话,FName
是基类UObject
的成员之一,即可以通过NamePrivate
获得此Index
。
此函数定义于Engine\Source\Runtime\Core\Private\UObject\UnrealNames.cpp
。在ToString
函数中,Resolve
参数是GetDisplayIndex
函数的返回值,其类型是FNameEntryId
而查看Resolve
的定义
其参数类型却是FNameEntryHandle
,即说明FNameEntryHandle
的构造函数存在相关转换
转至相关定义可以发现,确实有以FNameEntryId
为参数的构造函数
这里FNameBlockOffsetBits
在上方也有定义,是常量
查看Resolve
相关实现
这里的Blocks
就是前文提及的重要四个变量之一,位于FNameEntrlAllocator
下,FNameEntrlAllocator
则是FNamePool
唯一成员。
稍微总结一下,调用GetNamePool
得到GName
,调用GetDisplayIndex
得到索引,调用Resolve
在GName
上根据索引找到一个FNameEntry
类型变量,最后返回此变量。
查看GetDisplayNameEntry
函数返回类型FNameEntry
里同样有一个只在编译运行时才有效的宏,因此这里的成员只有FNameEntryHeader Header
和一个联合体,这两个成员都会参与后续字符串转换。
这里就指示了是否为宽字符以及字符长度。
在调用完GetDisplayNameEntry
就会接着调用GetPlainNameString
,将FNameEntry
类型转换为FString
如下是GetPlainNameString
函数定义
这里的Header
即为FNameEntryHeader
中的Header
,他用于判断是否为宽字符。再返回对应的类型字符串。
GetUnterminatedName
也仅仅只是返回FNameEntry
中存储的字符串而已。
ToString
函数实现会调用两个关键函数GetDisplayNameEntry
和GetPlainNameString
,GetDisplayNameEntry
又依次调用GetNamePool
,GetDisplayIndex
,Resolve
三个函数。
GetNamePool
函数主要用于初始化和返回FNamePool NamePoolData
,其中FNamePool
类的构造函数中会注册一大堆硬编码字符串,这里可通过搜索字符串并交叉引用的方式查找NamePoolData
GetDisplayIndex
主要用于返回对象名称的索引,其返回类型是FNameEntryId
,它的成员变量只有一个uint32 Value
。此函数会继续调用GetDisplayIndexFast
来得到ComparisonIndex
,此变量也在FName
中有存储。即可以通过UobjectBase
下的FName NamePrivate
成员来得到此Index
。
Resolve
根据返回的索引继续返回名称目录项,它的返回类型是FNameEntry
,这个类有一个比特位域用于判断是否为宽字符,有一个联合体用于名称字符串。
最终调用Resolve
返回类型FNameEntry
的方法GetPlainNameString
来返回FString
字符串
在实际运行环境中UWorld
自己的第一个成员就是ULevel* PersistentLevel
,可表示成这样
根据官方注释可知,PersistentLevel
存储了world
信息
继续跟进ULevel
类
此时就可以看到存储了所有Actors
的数组,根据以上关系就可以轻松得知当前Level
有多少个Actors
,Actor
地址都在哪,Frida代码如下(GWorld
需要通过其他手段得到)
在得到Actor
之后,自然就是要从Actor
开始解析,这也简单,因为Actor
也是继承自UObject
而UObject
中就存放了FName NamePrivate
这一字段,通过他就可以得到名字,接下来要做的就是手动实现ToString
这一函数。
首先需要获得FName NamePrivate
这一字段,通过偏移很容易得到
得到FName
后就是实现那几个函数。这里稍微提一下为什么actor.add(FName_Offset)
之后不用在readPointer
,是因为UObject
里面直接存放的就是FName
这一结构,而不是FName*
指针,所以不需要再readPointer
这一步有两种方式,第一种通过IDA
等方式先找到全局变量GName
,第二种通过特征码等方式在内存中找到。如何找不在这里展开,默认已经找到。找到之后需要使用的是FNamePool
的第一个成员FNameEntryAllocator
中的Blocks
,
这里有一个点需要注意, FNameEntryAllocator
第一个成员是Lock
,这android平台对应的是pthread_rwlock_t
类型,windows平台对应的是SRWLOCK
类型,
在32位安卓平台上此成员大小应该是0x28
,在64位安卓平台上应该是0x38
,那么从FNamePool
得到Blocks
就是
这一步很简单
第一步是将FNameEntryId Id
即ComparisonIndex
转化为FNameEntryHandle
前面已经得到FNameEntryAllocator
的Blocks
查看Resolve
相关实现
得到FNameEntry
得到FNameEntry
后,就相当于得到了真正的字符串
FNameEntryHeader
只是存储了字符串是否为宽字符,长度为多少而已,真正字符串就在后面的AnsiName
或WideName
中
整合一下就是
效果
名字解析只是最基本的,复杂的还是成员字段、函数签名等信息。在UE4中,还有一个关键全局变量GUObjectArray
,这个全局变量存储了当前所有的对象
FUObjectArray
部分定义如下
这里面重要的就是TUObjectArray TUObjectArray
,TUObjectArray类型
就是FChunkedFixedUObjectArray
定义如下
显然就存储了当前所有最大元素数量,当前存活的元素数量等信息
FUObjectItem
就是一个一个具体的Object
,定义也简单
从GUOjbectArray
得到MaxElements
就是
然而UObject的储存并不是平坦的,而是分块的,这点从Chunked可以看出来,同样的可以查看虚幻引擎的源码(UObjectArray.h)来得知如何访问FChunkedFixedUObjectArray来获取UObject*
转成Frida
在得到UObejct
后就可以进一步开始解析,比如先解析得到所属类,这个对象名字。解析FName
应该是很简单的事了·。
``is
/**
*
* @param obj
* @returns UClass* Returns the UClass that defines the fields of this object
*/
getClass: function (obj: UObjectPointer) {
var classPrivate = obj.add(OFFSET.offset_UObject_ClassPrivate).readPointer(); //得到所属类
// console.log(classPrivate: ${classPrivate}
);
return classPrivate;
},
这个类继承FField
,有更详细的信息描述
这个类比较重要,记录了成员、函数信息的指针,它继承于UField
主要关注Children,SuperStruct和ChildProperties
这个类就是UObject
中的成员,它继承UStruct
,新增很多成员,不过实际使用的还是UStruct
中已经定义的成员
回顾一下,从GUObjectArray
可以根据索引一步一步得到UObject
,而UObject
中又有UClass
成员,它由继承自UStruct
,那么从这个成员就可以得到UStruct
,进一步得到 UField *Children; // 结构体中的方法
和FField *ChildProperties; // 结构体中的属性
得到 FField *ChildProperties
就可以根据具体的类型进行解析,在UE4中定义了许多具体的类型,如FInterfaceProperty
,FStructProperty
,FEnumProperty
,FIntProperty
等,他们均继承自FField
FInterfaceProperty
FStructProperty
FEnumProperty
这些Property
自身可能还会有字段比如UStriptStruct* Struct
来进一步描述信息,也有可能仅依赖于FProperty
而不需要新字段去描述信息。
实际上,在逆向中根据一步一步偏移得到的FField
就已经是具体的每一个Property
了,即通过这个函数
得到的FField
,要么已经是FInterfaceProperty
,要么就是FStructProperty
,总之是一种具体的类型。因此会在得到此指针后再增加一个sizeof_FProperty
用于得到真正的xproperty
一些特殊property
还多加了一个指针偏移,比如FMapProperty
,则是有指针存储了不同信息
FEnumProperty
则多存储了一个UnderlyingProp
指针,需要加上这个指针大小才能指向UEnum
而要在内存中区分这些Property
,则依赖于FField
中的字段 FFieldClass *ClassPrivate; // 类名,用于区分FProperty类型
。同样也能通过字段FName NamePrivate
得到这个属性的名字
在从UStruct
得到FField
后,就能通过FField
中FFieldClass *ClassPrivate
得到具体property
类型,再强制转化为该property
类型指针,这一强转正确性是由FProperty
继承FField
,而具体property
又是继承
自FProperty
,相当于把父类指针强转为了子类指针。
UEnum
继承自UField
,UField
则是继承自UObject
,相比于UObject
,UField
仅仅是多了一个UField* next
指针用于迭代,而UEnum
相比于UField
则是多了几个成员,其中最重要的是TArray<TPair<FName, int64_t>> Names
,这是一个键值对,用于记录名字和值的对应。
TArray
是模板类,第一个成员是模板指针,第二第三个成员则分别描述当前有几个这样的模板指针,最大有多少个这样的模板指针。而这里的模板则是一个键值对。
从内存试图可以比较清楚得知FByteProperty
仅仅只是比FProperty
多一个UENum*
指针,而关键也是要解析这个指针。第一步当然是要得到这个指针
在得到UEnum*
指针后就可以开始解析这个指针,也就是解析里面的TArray
模板类,这也很简单。这里再贴出TArray
定义
大体步骤就是先得到TArray<TPair<FName, int64_t>> Names
这个指针,读出他指向的数组起始地址,也就是一个个TPair
组成的数组。通过数组元素大小即TPair<FName, int64_t>
大小进行遍历,而元素数量上则是由
对于一些符复合类型则可能需要走一下小递归,以MapProperty
为例,他会有两个FProperty
类型来分别描述key
和value
类型,这也就意味着如果需要知道key
和value
是什么类型,就必须再一次进行FProperty
解析
这里以resolveProp
来处理这种递归的解析
可以看到resolveProp(GName, recurrce, UMapProperty.getKeyProp(prop))
就负责解析key
的类型。而resolveProp
大体上也是Property
解析的流程
其余的Property
也是类似这样去解析即可。
除了类属性之外,还有类成员函数需要解析,这一步会简单许多,UFunction
继承自UStruct
,UStruct
继承于UField
,也就是说UFunction
实际是继承于UField
,这点与property
继承于FField
有所不同。
在UStruct
中,也有字段存储UField
自然也是通过UField
强转得到UFuncion
指针。
既然UFunction
最终继承自UObject
,那么也自然是通过UObject
得到函数名字
在此之后则是通过类名字进行判断是否为函数
如果是函数,则可以通过UStruct
得到参数属性(UStruct
继承自UField
,故这里也是把UField
给强转为了UStruct
)
那么参数解析自然走得就是之前property
的解析步骤了。
当然UE4
还定义了一些属性flag,用于标识函数类型或者参数类型,比如native
函数,out
型参数。
class
UObjectBase
{
public
:
EObjectFlags ObjectFlags;
//对象属性
int32_t InternalIndex;
//对象GUObjectArray序号
UClass* ClassPrivate;
//对象的类
FName NamePrivate;
//对象的名字
UObject* OuterPrivate;
//对象所在UPackage
};
class
UObjectBase
{
public
:
EObjectFlags ObjectFlags;
//对象属性
int32_t InternalIndex;
//对象GUObjectArray序号
UClass* ClassPrivate;
//对象的类
FName NamePrivate;
//对象的名字
UObject* OuterPrivate;
//对象所在UPackage
};
template
<
typename
ElementType>
class
TArray
{
public
:
ElementType *Allocator;
int32_t ArrayNum;
int32_t ArrayMax;
};
template
<
typename
ElementType>
class
TArray
{
public
:
ElementType *Allocator;
int32_t ArrayNum;
int32_t ArrayMax;
};
class
FNamePool
{
public
:
FNameEntryAllocator Entries;
…………
};
class
FNamePool
{
public
:
FNameEntryAllocator Entries;
…………
};
class
FNameEntryAllocator
{
public
:
enum
{
Stride = alignof(FNameEntry)
};
enum
{
BlockSizeByte = Stride * FNameBlockOffsets
};
mutable
PVOID
Lock;
uint32_t CurrenBlock;
uint32_t CurrentByteCursor;
FNameEntry *Blocks[FNameMaxBlocks];
};
class
FNameEntryAllocator
{
public
:
enum
{
Stride = alignof(FNameEntry)
};
enum
{
BlockSizeByte = Stride * FNameBlockOffsets
};
mutable
PVOID
Lock;
uint32_t CurrenBlock;
uint32_t CurrentByteCursor;
FNameEntry *Blocks[FNameMaxBlocks];
};
class
FNameEntry
{
public
:
FNameEntryHeader Header;
union
{
char
AnsiName[NAME_SIZE];
wchar_t
WideName[NAME_SIZE];
};
};
class
FNameEntry
{
public
:
FNameEntryHeader Header;
union
{
char
AnsiName[NAME_SIZE];
wchar_t
WideName[NAME_SIZE];
};
};
static
constexpr uint32 FNameMaxBlockBits = 13;
static
constexpr uint32 FNameBlockOffsetBits = 16;
static
constexpr uint32 FNameMaxBlocks = 1 << FNameMaxBlockBits;
static
constexpr uint32 FNameBlockOffsets = 1 << FNameBlockOffsetBits;
static
constexpr uint32 FNameMaxBlockBits = 13;
static
constexpr uint32 FNameBlockOffsetBits = 16;
static
constexpr uint32 FNameMaxBlocks = 1 << FNameMaxBlockBits;
static
constexpr uint32 FNameBlockOffsets = 1 << FNameBlockOffsetBits;
class
FNetworkNotify
{
uint64_t VTable;
};
class
UWorld :
public
UObject,
public
FNetworkNotify
{
public
:
/** Persistent level containing the world info, default brush and actors spawned during gameplay among other things */
ULevel *PersistentLevel;
…………
};
class
FNetworkNotify
{
uint64_t VTable;
};
class
UWorld :
public
UObject,
public
FNetworkNotify
{
public
:
/** Persistent level containing the world info, default brush and actors spawned during gameplay among other things */
ULevel *PersistentLevel;
…………
};
class
IInterface_AssetUserData
{
uint64_t dummy;
};
template
<
typename
ElementType>
class
TArray
{
public
:
ElementType *Allocator;
int32_t ArrayNum;
int32_t ArrayMax;
};
class
ULevel :
public
UObject,
public
IInterface_AssetUserData
{
public
:
char
dummyURL[104];
TArray<AActor *> Actors;
…………
};
class
IInterface_AssetUserData
{
uint64_t dummy;
};
template
<
typename
ElementType>
class
TArray
{
public
:
ElementType *Allocator;
int32_t ArrayNum;
int32_t ArrayMax;
};
class
ULevel :
public
UObject,
public
IInterface_AssetUserData
{
public
:
char
dummyURL[104];
TArray<AActor *> Actors;
…………
};
var
Level = GWorld.add(OFFSET.offset_UWorld_PersistentLevel).readPointer()
console.log(
"Level :"
, Level)
var
Actors = Level.add(OFFSET.offset_ULevel_Actors).readPointer()
console.log(
"Actors Array :"
, Actors)
var
Actors_Num = Level.add(OFFSET.offset_ULevel_Actors).add(8).readU32()
console.log(
"Actors_num :"
, Actors_Num)
var
Actors_Max = Level.add(OFFSET.offset_ULevel_Actors).add(0xc).readU32()
console.log(
"Actors_Max :"
, Actors_Max)
var
Level = GWorld.add(OFFSET.offset_UWorld_PersistentLevel).readPointer()
console.log(
"Level :"
, Level)
var
Actors = Level.add(OFFSET.offset_ULevel_Actors).readPointer()
console.log(
"Actors Array :"
, Actors)
var
Actors_Num = Level.add(OFFSET.offset_ULevel_Actors).add(8).readU32()
console.log(
"Actors_num :"
, Actors_Num)
var
Actors_Max = Level.add(OFFSET.offset_ULevel_Actors).add(0xc).readU32()
console.log(
"Actors_Max :"
, Actors_Max)
var
FName_Offset = 0x18
var
FName = actor.add(FName_Offset);
var
FName_Offset = 0x18
var
FName = actor.add(FName_Offset);
var
FNameEntryAllocator = GNames
var
Blocks = GNames.add(0x40)
var
FNameEntryAllocator = GNames
var
Blocks = GNames.add(0x40)
var
ComparisonIndex = FName.add(0).readU32()
var
ComparisonIndex = FName.add(0).readU32()
var
FNameBlockOffsetBits = 16
var
FNameBlockOffsets = 65536
var
Block = ComparisonIndex >> FNameBlockOffsetBits
var
Offset = ComparisonIndex & (FNameBlockOffsets - 1)
var
FNameBlockOffsetBits = 16
var
FNameBlockOffsets = 65536
var
Block = ComparisonIndex >> FNameBlockOffsetBits
var
Offset = ComparisonIndex & (FNameBlockOffsets - 1)
var
Blocks = GNames.add(0x40)
var
FNameEntryAllocator = GNames
var
Blocks = GNames.add(0x40)
var
FNameEntryAllocator = GNames
var
FNameEntry = Blocks.add(Block * 8).readPointer().add(Offset * 2)
var
FNameEntry = Blocks.add(Block * 8).readPointer().add(Offset * 2)
class
FNameEntry
{
public
:
FNameEntryHeader Header;
union
{
char
AnsiName[NAME_SIZE];
wchar_t
WideName[NAME_SIZE];
};
};
class
FNameEntry
{
public
:
FNameEntryHeader Header;
union
{
char
AnsiName[NAME_SIZE];
wchar_t
WideName[NAME_SIZE];
};
};
var
FNameEntryHeader = FNameEntry.readU16()
var
isWide = FNameEntryHeader & 1
var
Len = FNameEntryHeader >> 6
//最大长度就是1024,所以需要右移6位
if
(0 == isWide) {
console.log(`\x1b[32m[+] actor ${actor}: ${FNameEntry.add(2).readCString(Len)}\x1b[0m`)
}
var
FNameEntryHeader = FNameEntry.readU16()
var
isWide = FNameEntryHeader & 1
var
Len = FNameEntryHeader >> 6
//最大长度就是1024,所以需要右移6位
if
(0 == isWide) {
console.log(`\x1b[32m[+] actor ${actor}: ${FNameEntry.add(2).readCString(Len)}\x1b[0m`)
}
export
function
dumpActorName(GWorld: NativePointer, GNames: NativePointer) {
var
Level = GWorld.add(OFFSET.offset_UWorld_PersistentLevel).readPointer()
console.log(
"Level :"
, Level)
var
Actors = Level.add(OFFSET.offset_ULevel_Actors).readPointer()
console.log(
"Actors Array :"
, Actors)
var
Actors_Num = Level.add(OFFSET.offset_ULevel_Actors).add(8).readU32()
console.log(
"Actors_num :"
, Actors_Num)
var
Actors_Max = Level.add(OFFSET.offset_ULevel_Actors).add(0xc).readU32()
console.log(
"Actors_Max :"
, Actors_Max)
for
(
var
index = 0; index < Actors_Num; index++) {
var
actor = Actors.add(index * 8).readPointer()
if
(actor == NULL) {
continue
; }
//console.log("actor", actor)
//通过角色actor获取其成员变量FName
var
FNameEntryAllocator = GNames
var
FName_Offset = 0x18
var
FName = actor.add(FName_Offset);
var
ComparisonIndex = FName.add(0).readU32()
// console.log("ComparisonIndex:", ComparisonIndex);
var
FNameBlockOffsetBits = 16
var
FNameBlockOffsets = 65536
var
Block = ComparisonIndex >> FNameBlockOffsetBits
var
Offset = ComparisonIndex & (FNameBlockOffsets - 1)
var
Blocks_Offset = 0x40
var
Blocks = FNameEntryAllocator.add(Blocks_Offset)
var
FNameEntry = Blocks.add(Block * 8).readPointer().add(Offset * 2)
// console.log("FNameEntry:", FNameEntry)
var
FNameEntryHeader = FNameEntry.readU16()
var
isWide = FNameEntryHeader & 1
var
Len = FNameEntryHeader >> 6
if
(0 == isWide) {
console.log(`\x1b[32m[+] actor ${actor}: ${FNameEntry.add(2).readCString(Len)}\x1b[0m`)
}
}
export
function
dumpActorName(GWorld: NativePointer, GNames: NativePointer) {
var
Level = GWorld.add(OFFSET.offset_UWorld_PersistentLevel).readPointer()
console.log(
"Level :"
, Level)
var
Actors = Level.add(OFFSET.offset_ULevel_Actors).readPointer()
console.log(
"Actors Array :"
, Actors)
var
Actors_Num = Level.add(OFFSET.offset_ULevel_Actors).add(8).readU32()
console.log(
"Actors_num :"
, Actors_Num)
var
Actors_Max = Level.add(OFFSET.offset_ULevel_Actors).add(0xc).readU32()
console.log(
"Actors_Max :"
, Actors_Max)
for
(
var
index = 0; index < Actors_Num; index++) {
var
actor = Actors.add(index * 8).readPointer()
if
(actor == NULL) {
continue
; }
//console.log("actor", actor)
//通过角色actor获取其成员变量FName
var
FNameEntryAllocator = GNames
var
FName_Offset = 0x18
var
FName = actor.add(FName_Offset);
var
ComparisonIndex = FName.add(0).readU32()
// console.log("ComparisonIndex:", ComparisonIndex);
var
FNameBlockOffsetBits = 16
var
FNameBlockOffsets = 65536
var
Block = ComparisonIndex >> FNameBlockOffsetBits
var
Offset = ComparisonIndex & (FNameBlockOffsets - 1)
var
Blocks_Offset = 0x40
var
Blocks = FNameEntryAllocator.add(Blocks_Offset)
var
FNameEntry = Blocks.add(Block * 8).readPointer().add(Offset * 2)
// console.log("FNameEntry:", FNameEntry)
var
FNameEntryHeader = FNameEntry.readU16()
var
isWide = FNameEntryHeader & 1
var
Len = FNameEntryHeader >> 6
if
(0 == isWide) {
console.log(`\x1b[32m[+] actor ${actor}: ${FNameEntry.add(2).readCString(Len)}\x1b[0m`)
}
}
class
FUObjectArray
{
public
:
uint32_t ObjFirstGCIndex;
uint32_t ObjLastNonGCIndex;
uint32_t MaxObjectsNotConsideredByGC;
bool
OpenForDisregardForGC;
TUObjectArray TUObjectArray;
…………
};
class
FUObjectArray
{
public
:
uint32_t ObjFirstGCIndex;
uint32_t ObjLastNonGCIndex;
uint32_t MaxObjectsNotConsideredByGC;
bool
OpenForDisregardForGC;
TUObjectArray TUObjectArray;
…………
};
class
FChunkedFixedUObjectArray
{
enum
{
NumElementsPerChunk = 64 * 1024,
};
/** Master table to chunks of pointers **/
FUObjectItem** Objects;
/** If requested, a contiguous memory where all objects are allocated **/
FUObjectItem* PreAllocatedObjects;
/** Maximum number of elements **/
int32 MaxElements;
/** Number of elements we currently have **/
int32 NumElements;
/** Maximum number of chunks **/
int32 MaxChunks;
/** Number of chunks we currently have **/
int32 NumChunks;
}
class
FChunkedFixedUObjectArray
{
enum
{
NumElementsPerChunk = 64 * 1024,
};
/** Master table to chunks of pointers **/
FUObjectItem** Objects;
/** If requested, a contiguous memory where all objects are allocated **/
FUObjectItem* PreAllocatedObjects;
/** Maximum number of elements **/
int32 MaxElements;
/** Number of elements we currently have **/
int32 NumElements;
/** Maximum number of chunks **/
int32 MaxChunks;
/** Number of chunks we currently have **/
int32 NumChunks;
}
class FUObjectItem
{
class UObject *Object;
uint32_t Flags;
// UObject Owner Cluster Index
uint32_t ClusterRootIndex;
// Weak Object Pointer Serial number associated with the object
uint32_t SerialNumber;
};
class FUObjectItem
{
class UObject *Object;
uint32_t Flags;
// UObject Owner Cluster Index
uint32_t ClusterRootIndex;
// Weak Object Pointer Serial number associated with the object
uint32_t SerialNumber;
};
export
function
getObjectCount(GUObjectArray: NativePointer) {
var
GUObjectElementCount = GUObjectArray.add(OFFSET.GAME_FUObjectArray_TUObjectArray_OFFSET).add(OFFSET.GAME_TUObjectArray_NumElements_OFFSET).readU32()
console.log(`\x1b[32m[+] GUObjectElementCount: ${GUObjectElementCount}\x1b[0m`)
return
GUObjectElementCount;
}
export
function
getObjectCount(GUObjectArray: NativePointer) {
var
GUObjectElementCount = GUObjectArray.add(OFFSET.GAME_FUObjectArray_TUObjectArray_OFFSET).add(OFFSET.GAME_TUObjectArray_NumElements_OFFSET).readU32()
console.log(`\x1b[32m[+] GUObjectElementCount: ${GUObjectElementCount}\x1b[0m`)
return
GUObjectElementCount;
}
FORCEINLINE_DEBUGGABLE FUObjectItem
const
* GetObjectPtr(int32 Index)
const
TSAN_SAFE
{
const
int32 ChunkIndex = Index / NumElementsPerChunk;
const
int32 WithinChunkIndex = Index % NumElementsPerChunk;
FUObjectItem* Chunk = Objects[ChunkIndex];
return
Chunk + WithinChunkIndex;
}
FORCEINLINE_DEBUGGABLE FUObjectItem
const
* GetObjectPtr(int32 Index)
const
TSAN_SAFE
{
const
int32 ChunkIndex = Index / NumElementsPerChunk;
const
int32 WithinChunkIndex = Index % NumElementsPerChunk;
FUObjectItem* Chunk = Objects[ChunkIndex];
return
Chunk + WithinChunkIndex;
}
export
function
getUObjectBaseObjectFromId(GUObjectArray: NativePointer, index: number): UObjectPointer {
var
FUObjectItem = GUObjectArray.add(OFFSET.GAME_FUObjectArray_TUObjectArray_OFFSET).readPointer();
//定位到第一个chunk
var
chunkIndex = Math.floor(index / 0x10000) * Process.pointerSize;
var
WithinChunkIndex = (index % 0x10000) * OFFSET.GAME_FUOBJECT_ITEM_SIZE;
var
chunk = FUObjectItem.add(chunkIndex);
var
FUObjectItemObjects = chunk.readPointer();
//定位到当前chunk的第一个位置
var
UObjectBaseObject = FUObjectItemObjects.add(WithinChunkIndex).readPointer();
//这里直接返回Uobject
return
UObjectBaseObject;
}
export
function
getUObjectBaseObjectFromId(GUObjectArray: NativePointer, index: number): UObjectPointer {
var
FUObjectItem = GUObjectArray.add(OFFSET.GAME_FUObjectArray_TUObjectArray_OFFSET).readPointer();
//定位到第一个chunk
var
chunkIndex = Math.floor(index / 0x10000) * Process.pointerSize;
var
WithinChunkIndex = (index % 0x10000) * OFFSET.GAME_FUOBJECT_ITEM_SIZE;
var
chunk = FUObjectItem.add(chunkIndex);
var
FUObjectItemObjects = chunk.readPointer();
//定位到当前chunk的第一个位置
var
UObjectBaseObject = FUObjectItemObjects.add(WithinChunkIndex).readPointer();
//这里直接返回Uobject
return
UObjectBaseObject;
}
/
*
*
*
*
@param GName
*
@param obj
*
@returns string Returns the logical name of this
object
*
/
getName: function (GName: NativePointer, obj: UObjectPointer) {
if
(this.isValid(obj)) {
return
getFNameFromID(GName, this.getNameId(obj));
}
else
{
return
"None"
;
}
},
/
*
*
*
*
@param GName
*
@param obj
*
@returns string Returns the logical name of this
object
*
/
getName: function (GName: NativePointer, obj: UObjectPointer) {
if
(this.isValid(obj)) {
return
getFNameFromID(GName, this.getNameId(obj));
}
else
{
return
"None"
;
}
},
在进一步之前,还需要了解更多的关键类。
## dumpSdk所需类
### UField
它继承自`UObject`,但仅仅只多一个`UField
*
Next
`迭代指针,这个类主要是用于`UClass`等迭代方法

### FField
在UE4.
25
以后,使用`UField`子集`FField`来描述属性信息,且`FField`没有继承任何一个类,这样大幅度减少了属性对象的占用。
```cpp
class
FFieldClass
{
public:
FName Name;
/
/
uint64_t
Id
;
uint64_t CastFlags;
uint64_t ClassFlags;
FFieldClass
*
SuperClass;
FField
*
DefaultObject;
};
class
FFieldVariant
{
public:
union FFieldObjectUnion
{
FField
*
Field;
UObject
*
Object
;
} Container;
uint64_t bIsUObject;
};
#pragma pack(4)
class
FField
{
public:
uint64_t VTable;
FFieldClass
*
ClassPrivate;
/
/
类名,用于区分FProperty类型
FFieldVariant Owner;
FField
*
Next
;
/
/
指向下一个FField
FName NamePrivate;
/
/
属性名称
uint32_t FlagsPrivate;
};
#pragma pack()
在进一步之前,还需要了解更多的关键类。
## dumpSdk所需类
### UField
它继承自`UObject`,但仅仅只多一个`UField
*
Next
`迭代指针,这个类主要是用于`UClass`等迭代方法

### FField
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课