首页
社区
课程
招聘
[原创]UE4.27SDK-Dump
发表于: 2024-8-11 19:45 4784

[原创]UE4.27SDK-Dump

2024-8-11 19:45
4784

近期学了学UE4相关知识,并且自己动手写了写SDK dump,特此记录一下。相关代码已传至github,使用了FridaTypeScript,解决了FridaJs下代码挤压在同一个文件的问题,同时使用C++去写结构体并得到偏移,能够更好的维护。

在阅读代码之前,就必须去了解一下UE4的命名约定,具体的自己去查看官网文档,下面是一些基本需要知道的:

在UE4引擎中全局定义了UWorldProxy对象,此对象在Engine\Source\Runtime\Engine\Classes\Engine\World.h定义,这个类中有一个变量UWorld* World;

官网定义中可以看到,UWorld是地图或沙盒的顶级对象,其中会有Actor等信息

image-20240714172715346

根据继承关系找到最顶级的类UobjectBase,此类在Engine\Source\Runtime\CoreUObject\Public\UObject\UObjectBase.h定义,在这个类中有一个成员变量FName,由注释可知此变量代表对象的名称。

image-20240715111610611

其字段含义如下

这里值得一提的是,普遍认为UObject是是UE4中所有对象的基类,但是从这里可以发现真正的基类是UObjectBase。认为UObject是基类也并无道理,因为继承自UObjectBaseUObject没有新增任何成员,从VS提供的内存视图可知,下文也会延续这一说法。

image-20240723095540028

正是因为UE4中所有对象都继承自UObject,而UObject又有成员FName提供名字,因此UE4的反射系统才能方便得知一个对象的名字,甚至是字段、函数参数等名字。这也给逆向工作提供了可乘之机,那么就不得不研究一下UE4是怎么通过这个FName得到的名字的。

FNameEngine\Source\Runtime\Core\Public\UObject\NameTypes.h定义,这个类中有一个成员函数ToString(),还存储了字符串索引FNameEntryId ComparisonIndex;

image-20240715114813804

见名思义,调用FName::ToString即可得到名字,返回一个FString类型。

查看FString类,在Engine\Source\Runtime\Core\Public\Containers\UnrealString.h定义

image-20240714173747698

可见字符串相关数据存储在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的值

image-20240714212028986

这个类主要就是存放字符串在GName中的索引

还有一些关键类会在后续边解析边提及。

此函数实现于Engine\Source\Runtime\Core\Private\UObject\UnrealNames.cpp

image-20240729172018231

可知该函数先调用了GetDisplayNameEntry函数

GetDisplayNameEntry此函数实现就在ToString函数上方

image-20240729173300599

可知此函数调用了三个函数,依次查看

此函数在Engine\Source\Runtime\Core\Private\UObject\UnrealNames.cpp中定义

image-20240714205547097

这是一个单例模式的函数,即在So中存在唯一的全局静态变量NamePoolData,查看NamePoolData的类型FNamePool

这个模式保证了即使在多线程环境下,FNamePool也只会被初始化一次。它不使用C++11中的魔法静态(magic statics)或单例模式中的锁,以减少运行时的开销。通过这种方式,FNamePool类的实例在第一次调用GetNamePool时被创建,并在随后的调用中直接返回,避免了重复初始化。

此函数实现于Engine\Source\Runtime\Core\Public\UObject\NameTypes.h

image-20240729173437588

进入GetDisplayIndexFast函数,此函数定义在相同目录下

image-20240729173455037

查看相关宏定义,

image-20240714211337676

由注释可知此宏只用于编辑器,而在实际运行中不会启用

因此GetDisplayIndexFast实际返回ComparisonIndexComparisonIndex有两处被定义,其中一处是作为FName的成员变量,FNameEntryId前文也有解析,它里面就只存储了uint32_t的值,作为字符串在GName中索引。

image-20240715111926364

如果还没忘记的话,FName是基类UObject的成员之一,即可以通过NamePrivate获得此Index

此函数定义于Engine\Source\Runtime\Core\Private\UObject\UnrealNames.cpp。在ToString函数中,Resolve参数是GetDisplayIndex函数的返回值,其类型是FNameEntryId

image-20240714183707060

而查看Resolve的定义

image-20240715112332385

其参数类型却是FNameEntryHandle,即说明FNameEntryHandle的构造函数存在相关转换

转至相关定义可以发现,确实有以FNameEntryId为参数的构造函数

image-20240714212916118

这里FNameBlockOffsetBits在上方也有定义,是常量

查看Resolve相关实现

image-20240715112302267

这里的Blocks就是前文提及的重要四个变量之一,位于FNameEntrlAllocator下,FNameEntrlAllocator则是FNamePool唯一成员。

稍微总结一下,调用GetNamePool得到GName,调用GetDisplayIndex得到索引,调用ResolveGName上根据索引找到一个FNameEntry类型变量,最后返回此变量。

查看GetDisplayNameEntry函数返回类型FNameEntry

image-20240715123755799

里同样有一个只在编译运行时才有效的宏,因此这里的成员只有FNameEntryHeader Header和一个联合体,这两个成员都会参与后续字符串转换。

image-20240715124124435

这里就指示了是否为宽字符以及字符长度。

在调用完GetDisplayNameEntry就会接着调用GetPlainNameString,将FNameEntry类型转换为FString

image-20240729174905865

如下是GetPlainNameString函数定义

image-20240729174848325

这里的Header即为FNameEntryHeader 中的Header,他用于判断是否为宽字符。再返回对应的类型字符串。

GetUnterminatedName也仅仅只是返回FNameEntry中存储的字符串而已。

image-20240729175127534

ToString函数实现会调用两个关键函数GetDisplayNameEntryGetPlainNameStringGetDisplayNameEntry又依次调用GetNamePoolGetDisplayIndexResolve三个函数。

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有多少个ActorsActor地址都在哪,Frida代码如下(GWorld需要通过其他手段得到)

在得到Actor之后,自然就是要从Actor开始解析,这也简单,因为Actor也是继承自UObject

image-20240729185341826

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 IdComparisonIndex转化为FNameEntryHandle

image-20240714212916118

前面已经得到FNameEntryAllocatorBlocks

查看Resolve相关实现

image-20240715112302267

得到FNameEntry

得到FNameEntry后,就相当于得到了真正的字符串

FNameEntryHeader只是存储了字符串是否为宽字符,长度为多少而已,真正字符串就在后面的AnsiNameWideName

整合一下就是

效果

image-20240729194413303

名字解析只是最基本的,复杂的还是成员字段、函数签名等信息。在UE4中,还有一个关键全局变量GUObjectArray,这个全局变量存储了当前所有的对象

image-20240729194922327

FUObjectArray部分定义如下

这里面重要的就是TUObjectArray TUObjectArrayTUObjectArray类型就是FChunkedFixedUObjectArray

image-20240729195250191

定义如下

显然就存储了当前所有最大元素数量,当前存活的元素数量等信息

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

image-20240723101628057

这个类就是UObject中的成员,它继承UStruct,新增很多成员,不过实际使用的还是UStruct中已经定义的成员

image-20240729203535618

回顾一下,从GUObjectArray可以根据索引一步一步得到UObject,而UObject中又有UClass成员,它由继承自UStruct,那么从这个成员就可以得到UStruct,进一步得到 UField *Children; // 结构体中的方法
FField *ChildProperties; // 结构体中的属性

得到 FField *ChildProperties就可以根据具体的类型进行解析,在UE4中定义了许多具体的类型,如FInterfacePropertyFStructPropertyFEnumPropertyFIntProperty等,他们均继承自FField

FInterfaceProperty

image-20240729204945870

FStructProperty

image-20240729204757146

FEnumProperty

image-20240729205045137

这些Property自身可能还会有字段比如UStriptStruct* Struct来进一步描述信息,也有可能仅依赖于FProperty而不需要新字段去描述信息。

实际上,在逆向中根据一步一步偏移得到的FField就已经是具体的每一个Property了,即通过这个函数

得到的FField,要么已经是FInterfaceProperty,要么就是FStructProperty,总之是一种具体的类型。因此会在得到此指针后再增加一个sizeof_FProperty用于得到真正的xproperty

image-20240729210426658

image-20240729210441118

一些特殊property还多加了一个指针偏移,比如FMapProperty,则是有指针存储了不同信息

image-20240729210637754

image-20240729210714428

FEnumProperty则多存储了一个UnderlyingProp指针,需要加上这个指针大小才能指向UEnum

image-20240729210856037

image-20240730092610660

而要在内存中区分这些Property,则依赖于FField中的字段 FFieldClass *ClassPrivate; // 类名,用于区分FProperty类型。同样也能通过字段FName NamePrivate得到这个属性的名字

在从UStruct得到FField后,就能通过FFieldFFieldClass *ClassPrivate得到具体property类型,再强制转化为该property类型指针,这一强转正确性是由FProperty继承FField,而具体property又是继承FProperty,相当于把父类指针强转为了子类指针。

UEnum继承自UFieldUField则是继承自UObject,相比于UObjectUField仅仅是多了一个UField* next指针用于迭代,而UEnum相比于UField则是多了几个成员,其中最重要的是TArray<TPair<FName, int64_t>> Names,这是一个键值对,用于记录名字和值的对应。

image-20240730094916263

TArray是模板类,第一个成员是模板指针,第二第三个成员则分别描述当前有几个这样的模板指针,最大有多少个这样的模板指针。而这里的模板则是一个键值对。

image-20240730093944056

从内存试图可以比较清楚得知FByteProperty仅仅只是比FProperty多一个UENum*指针,而关键也是要解析这个指针。第一步当然是要得到这个指针

在得到UEnum*指针后就可以开始解析这个指针,也就是解析里面的TArray模板类,这也很简单。这里再贴出TArray定义

大体步骤就是先得到TArray<TPair<FName, int64_t>> Names这个指针,读出他指向的数组起始地址,也就是一个个TPair组成的数组。通过数组元素大小即TPair<FName, int64_t>大小进行遍历,而元素数量上则是由

对于一些符复合类型则可能需要走一下小递归,以MapProperty为例,他会有两个FProperty类型来分别描述keyvalue类型,这也就意味着如果需要知道keyvalue是什么类型,就必须再一次进行FProperty解析

这里以resolveProp来处理这种递归的解析

可以看到resolveProp(GName, recurrce, UMapProperty.getKeyProp(prop))就负责解析key的类型。而resolveProp大体上也是Property解析的流程

其余的Property也是类似这样去解析即可。

除了类属性之外,还有类成员函数需要解析,这一步会简单许多,UFunction继承自UStructUStruct继承于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`等迭代方法
 
![image-20240723100142226](https://yring-me.oss-cn-beijing.aliyuncs.com/test/202408111922690.png)
 
### 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`等迭代方法
 
![image-20240723100142226](https://yring-me.oss-cn-beijing.aliyuncs.com/test/202408111922690.png)
 
### FField
 

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

最后于 2024-8-11 23:36 被yring编辑 ,原因:
收藏
免费 5
支持
分享
最新回复 (1)
雪    币: 225
活跃值: (146)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
顶顶顶
2025-1-24 14:45
0
游客
登录 | 注册 方可回帖
返回