近期学了学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应该是很简单的事了·。
在进一步之前,还需要了解更多的关键类。
它继承自UObject,但仅仅只多一个UField* Next迭代指针,这个类主要是用于UClass等迭代方法

在UE4.25以后,使用UField子集FField来描述属性信息,且FField没有继承任何一个类,这样大幅度减少了属性对象的占用。
这个类继承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;
UClass* ClassPrivate;
FName NamePrivate;
UObject* OuterPrivate;
};
class UObjectBase
{
public:
EObjectFlags ObjectFlags;
int32_t InternalIndex;
UClass* ClassPrivate;
FName NamePrivate;
UObject* OuterPrivate;
};
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:
ULevel *PersistentLevel;
…………
};
class FNetworkNotify
{
uint64_t VTable;
};
class UWorld : public UObject, public FNetworkNotify
{
public:
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
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
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; }
var FNameEntryAllocator = GNames
var FName_Offset = 0x18
var FName = actor.add(FName_Offset);
var ComparisonIndex = FName.add(0).readU32()
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)
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; }
var FNameEntryAllocator = GNames
var FName_Offset = 0x18
var FName = actor.add(FName_Offset);
var ComparisonIndex = FName.add(0).readU32()
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)
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,
};
FUObjectItem** Objects;
FUObjectItem* PreAllocatedObjects;
int32 MaxElements;
int32 NumElements;
int32 MaxChunks;
int32 NumChunks;
}
class FChunkedFixedUObjectArray
{
enum
{
NumElementsPerChunk = 64 * 1024,
};
FUObjectItem** Objects;
FUObjectItem* PreAllocatedObjects;
int32 MaxElements;
int32 NumElements;
int32 MaxChunks;
int32 NumChunks;
}
class FUObjectItem
{
class UObject *Object;
uint32_t Flags;
uint32_t ClusterRootIndex;
uint32_t SerialNumber;
};
class FUObjectItem
{
class UObject *Object;
uint32_t Flags;
uint32_t ClusterRootIndex;
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();
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();
var UObjectBaseObject = FUObjectItemObjects.add(WithinChunkIndex).readPointer();
return UObjectBaseObject;
}
export function getUObjectBaseObjectFromId(GUObjectArray: NativePointer, index: number): UObjectPointer {
var FUObjectItem = GUObjectArray.add(OFFSET.GAME_FUObjectArray_TUObjectArray_OFFSET).readPointer();
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();
var UObjectBaseObject = FUObjectItemObjects.add(WithinChunkIndex).readPointer();
return UObjectBaseObject;
}
getClass: function (obj: UObjectPointer) {
var classPrivate = obj.add(OFFSET.offset_UObject_ClassPrivate).readPointer();
return classPrivate;
},
getName: function (GName: NativePointer, obj: UObjectPointer) {
if (this.isValid(obj)) {
return getFNameFromID(GName, this.getNameId(obj));
} else {
return "None";
}
},
getClass: function (obj: UObjectPointer) {
var classPrivate = obj.add(OFFSET.offset_UObject_ClassPrivate).readPointer();
return classPrivate;
},
getName: function (GName: NativePointer, obj: UObjectPointer) {
if (this.isValid(obj)) {
return getFNameFromID(GName, this.getNameId(obj));
} else {
return "None";
}
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2025-5-8 03:04
被yring编辑
,原因: 更正一个笔误