This page looks best with JavaScript enabled

UE Dumper

 ·  ☕ 6 min read · 👀... views

What is known to us is that UnrealEngine uses reflection to achieve various dynamic features, such as object management, garbage collection systems, etc. UnrealEngine generates reflection data during compilation, including class names, class methods, class fields and other class properties. And these data are packed and saved. In this situation, we can retrieve class information and symbols through reverse engineering. Usually, we refer to this process as Dump SDK.

In this blog, I downloaded UE4 4.25 source code and compiled a demo program UE4Game-Win64-Shipping.exe. I will dump symbols from this program to introduce the UE Dump SDK.

GNames

A golbal variable named NamePoolData is a important varible, which introduce UE reflect data structure. And all of programs compiled by UnrealEngine have this varible. Lots of people refer to it as GNames. The type of NamePoolData is FNamePool

0:000> dt FNamePool
UE4Game_Win64_Shipping!FNamePool
   +0x000 Entries          : FNameEntryAllocator
   +0x10040 ComparisonShards : [16] FNamePoolShard<1>
   +0x10440 ENameToEntry     : [702] FNameEntryId
   +0x10f38 LargestEnameUnstableId : Uint4B
   +0x10f40 EntryToEName     : TMap<FNameEntryId,enum EName,TInlineSetAllocator<512,TSetAllocator<TSparseArrayAllocator<TSizedDefaultAllocator<32>,TSizedDefaultAllocator<32> >,TSizedDefaultAllocator<32>,2,8,4>,2,4>,TDefaultMapHashableKeyFuncs<FNameEntryId,enum EName,0> >

0:000> dt FNameEntryAllocator
UE4Game_Win64_Shipping!FNameEntryAllocator
   +0x000 Lock             : FWindowsRWLock
   +0x008 CurrentBlock     : Uint4B
   +0x00c CurrentByteCursor : Uint4B
   +0x010 Blocks           : [8192] Ptr64 UChar

And we can browse memory region about NamePoolData。
NamePoolData

We should notice a pointer array FNamePool->FNameEntryAllocator->Blocks, which saved pointer of block. And FNameEntries were stores in these blocks. These are structures definition:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
typedef struct FNameEntryHeader
{
	uint16_t bIsWide : 1;
	uint16_t LowercaseProbeHash : 5;
	uint16_t Len : 10;
} FNameEntryHeader;

class FNameEntry {

	FNameEntryHeader Header;

	union
	{
		char AnsiName[1024];
		wchar_t	WideName[1024];
	};
}

FNameBlock

Okay, we can enumerate Blocks and FNameEntry to retrieve all the FName string.

Sometimes, certain games encrypt FNameEntry.FNameBuffer. However, UnrealEngine needs the original FName to achieve the goal of reflection. Therefore these games encrypt FNameEntry.FNameBuffer and decrypt these data when programs use. We can disassemble FNameEntry::AppendNameToString to analyze the encryption algorithm.

GObjects

There is a global variable named GUObjectArray that stores all instances of the UObject in the UnrealEngine program. We commonly refer it as GObjects. The type of GUObjectArray is FUObjectArray.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class FUObjectArray
{
public:
	uint32_t ObjFirstGCIndex; //0x0000
	uint32_t ObjLastNonGCIndex; //0x0004
	uint32_t MaxObjectsNotConsideredByGC; //0x0008
	uint32_t OpenForDisregardForGC; //0x000C

	FChunkedFixedUObjectArray ObjObjects; //0x0010
};
class FUObjectArray GUObjectArray;

class FChunkedFixedUObjectArray
{
public:
	FUObjectItem* Objects; //0x0000
	uint64_t  PreAllocatedObjects;//0x0008
	uint32_t MaxElements; //0x0010
	uint32_t NumElements; //0x0014
	uint32_t MaxChunks;//0x18
	uint32_t NumChunks;//0x1C
};

class FUObjectItem
{
public:
	class UObject* Object;     //0x0000
   uint32_t Flags;            // 0x0008
   uint32_t ClusterRootIndex; // 0x000C
	uint32_t SerialNumber;     //0x0010
};

We can access all UObject pointers by GUObjectArray->ObjObjects->Objects[i]->Object
Objects

Total objects in the UnrealEngine program are derived from UObject, including UField, UEnum, UStruct, UScriptStruct, UFunction, UClass, and more. Therefore, we can dump symbols if we can solve the following problems:

  1. Identifying the type of any object.
  2. Retrieving the name of any object from NamePoolData

In UnrealEngine, we can obtain the name of any object from NamePoolData. Let’s take a closer look at the UObject structure.

0:000> dt UObject
UE4Game_Win64_Shipping!UObject
   +0x000 __VFN_table : Ptr64 
   +0x008 ObjectFlags      : EObjectFlags
   +0x00c InternalIndex    : Int4B
   +0x010 ClassPrivate     : Ptr64 UClass
   +0x018 NamePrivate      : FName
   +0x020 OuterPrivate     : Ptr64 UObject

There is a field named NamePrivate. We can use this field to obtain the name of the object. However, the type of UObject->NamePrivate is FName, which does not directly store the string. We know that symbols of the UnrealEngine program are stored in the structure FNameEntry.

0:000> dt FName
UE4Game_Win64_Shipping!FName
   +0x000 ComparisonIndex  : FNameEntryId
   +0x004 Number           : Uint4B

We can disassemble and analyze FName::GetEntry to obtain the conversion relationship between FName and FNameEntry.
FNameBlock

I imple this method using C language.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
constexpr uint16_t FNameEntryHeader_Stride = 2;
FNameEntryHandle(uint32_t id) : Block(id >> 16), Offset(id & 65535) {};
FNameEntry* FNamePool::GetEntry(FNameEntryHandle handle) const 
{
	if (handle.Block >= 8192)
	{
		return nullptr;
	}
	return reinterpret_cast<FNameEntry*>(Blocks[handle.Block] + FNameEntryHeader_Stride * static_cast<uint64_t>(handle.Offset));
}

We need to take note of a feature in UnrealEngine called subobject. This means that each UObject can have an optional “Outer Object”. Therefore, when trying to obtain the accurate name of an object, we should iterate through of its outer objects.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
UEObject UEObject::GetOuter() const
{
	
	auto Outer =Process::Read<decltype(UObject::Outer)>(object->Outer);
	if (!Outer)
		return 0;
	
	return UEObject(reinterpret_cast<UObject*>(Outer));
}

std::string UEObject::GetFullName() const
{
	if (GetClass().IsValid())
	{
		std::string temp;
		for (auto outer = GetOuter(); outer.IsValid(); outer = outer.GetOuter())
		{
			temp = outer.GetName() + "." + temp;
		}

		std::string name = GetClass().GetName();

		name += " ";
		name += temp;
		name += GetName();

		return name;
	}

	return std::string("(null)");
}

Okay, let’s discuss how to identify the type of any objects. Before we talk about how to retrieve an object’s type, we first need to understand UnrealEngine Types.
UE_Type

It is clearly that all UE Objects based on UObject, including UFunction, UClass, UEnum, and so on. We need to distinguish whether the object belongs to UClass, UEnum, or UFunction.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

UEClass ObjectsStore::FindClass(const std::string& name) 
{
	static auto f = []() ->auto
	{
		static auto Objects = ObjectsStore::GetReference();
		std::unordered_map<string, UEObject> map;
		for (size_t i = 0; i < Objects.GetNumElements(); i++)
		{
			auto obj = Objects[i];
			if (!obj.IsValid()) continue;
			map[obj.GetFullName()] = obj;
		}
		return map;
	}();
	if (f.count(name) != 0)
	{
		return f[name].Cast<UEClass>();
	}

	return UEClass(nullptr);
}

static auto ustruct = ObjectsStore::FindClass("Class CoreUObject.Struct");
static auto ufunction = ObjectsStore::FindClass("Class CoreUObject.Function");
static auto uclass = ObjectsStore::FindClass("Class CoreUObject.Class");
static auto uenum = ObjectsStore::FindClass("Class CoreUObject.Enum");
static auto ufield = ObjectsStore::FindClass("Class CoreUObject.Field");

for (auto super = obj.class; obj != nullptr ; super = super.SueprField )
{
	switch(super)
	{
		case ustruct:
			// Is UEStruct
			break;
		case ufunction:
			// Is UFunction
			break;
		case uclass:
			// Is UClass
			break;
		case uenum:
			// Is UEnum
			break;
		case ufield:
			// Is UField
			break;
	}
}

After that, we can cast the object pointer to ue type and retrieve information from the object fields.

Reference

  1. https://docs.unrealengine.com/4.27/en-US/API/Runtime/CoreUObject/UObject/

  2. https://bbs.kanxue.com/thread-278264.htm

  3. https://www.52pojie.cn/thread-1838396-1-1.html

Share on

Qfrost
WRITTEN BY
Qfrost
CTFer, Anti-Cheater, LLVM Committer