二、UE4四件套获取

一、UWorld

  • 字符串SeamlessTravel FlushLevelStreaming源代码如下:
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
GWorld = LoadedWorld;    //特征
if (!LoadedWorld->bIsWorldInitialized)
{
LoadedWorld->InitWorld();
}
bWorldChanged = true;
// Track session change on seamless travel.
NETWORK_PROFILER(GNetworkProfiler.TrackSessionChange(true, LoadedWorld->URL));
checkSlow((LoadedWorld->GetNetMode() == NM_Client) == bIsClient);
if (bCreateNewGameMode)
{
LoadedWorld->SetGameMode(PendingTravelURL);
}
// if we've already switched to entry before and this is the transition to the new map, re-create the gameinfo
if (bSwitchedToDefaultMap && !bIsClient)
{
if (FAudioDevice* AudioDevice = LoadedWorld->GetAudioDeviceRaw())
{
AudioDevice->SetDefaultBaseSoundMix(LoadedWorld->GetWorldSettings()->DefaultBaseSoundMix);
}
// Copy cheat flags if the game info is present
// @todo UE4 FIXMELH - see if this exists, it should not since it's created in GameMode or it's garbage info
if (LoadedWorld->NetworkManager != nullptr)
{
LoadedWorld->NetworkManager->bHasStandbyCheatTriggered = bHasStandbyCheatTriggered;
}
}
// Make sure "always loaded" sub-levels are fully loaded
{
SCOPE_LOG_TIME_IN_SECONDS(TEXT(" SeamlessTravel FlushLevelStreaming "), nullptr) //特征
LoadedWorld->FlushLevelStreaming(EFlushLevelStreamingType::Visibility);
}

document_image_rId4

document_image_rId5

  • Count

Uworld -> ULevel -> Count

  • 调试符号Gworld

跳过去直接拿

二、GName

  • 字符串ByteProperty

源代码如下:

1
2
3
4
5
6
7
8
9
10
static FNamePool& GetNamePool()
{
if (bNamePoolInitialized)
{
return *(FNamePool*)NamePoolData;
}
FNamePool* Singleton = new (NamePoolData) FNamePool;
bNamePoolInitialized = true;
return *Singleton;
}

document_image_rId6

  • 算法逆推
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//
// Version:4.22
//
string NameStore::GetName(int i)
{
int Id = (int)(i / (int)GameInst::ChunkSize); //0 旧版本中未加密游戏的ChunkSize固定为16384
int Idtemp = (int)(i % (int)GameInst::ChunkSize);//1
auto NamePtr = Process::Read<uint64_t>(Names + Id * 8); //Names已经空读了一层
auto Name = Process::Read<uint64_t>(NamePtr + 8 * Idtemp);
char name[0x100] = {0};
if (Process::ReadMemory(PVOID(Name + 0x10), name, 0x100)) // 0xC需要手动测
{
return name;
}
return string();
}
  • UE中,每个ID都会对应一个属于自己的字符串。比如下表
Id String
0 None
1 ByteProperty
2 IntProperty
3 BoolProperty
4 FloatProperty
….. …..

那么,可以利用算法进行反推出Gname。那么假设ID=1,则Id = 1 / 16384 = 0Idtemp = 1 % 16384 = 1ChunkSize固定为0x4000(16384)
搜索ByteProperty

document_image_rId7

将结果逐个Ctrl+B进行查看,直到找得到如下图内容。

document_image_rId8

然后将字符串None的地址减掉??后的地址(offset = ②地址 - ①地址 = 0xC)。

document_image_rId9

然后将ByteProperty地址剪掉offset后进行搜索。

document_image_rId10

排除掉最后两个,然后将第一个地址减掉Idtemp后进行搜索。

document_image_rId11

只有一个结果,由于id=0,所以在搜索一次,得到GName。

document_image_rId12

  • 地址遍历
1
2
3
4
5
6
7
8
9
10
11
12
uint64_t Base = GetModuleHandleA("xxxxx.exe");
int i = 0;
do
uint64_t gname = Mem::Read<uint64_t>(Base + 0x8 * i);
if(gname != NULL)
{
char* byteProperty = GetName(gname,1);
if(!strcmp(byteProperty,"ByteProeprty"))
break;
}
while(true);
printf("offset_gname=%x\n",i*8);

直接从进程.exe开始+8或者+4进行遍历,每一次遍历就将遍历到的地址假设为GName,然后配合算法计算1返回的字符串,若返回的字符串为ByteProperty,则表明本次遍历到的地址为GName。

  • 调试符号:FName::GetNames

    document_image_rId13

三、GObjectArray

  • 字符串GObject: CanvasObject

document_image_rId14

document_image_rId15

由于GObjectArray是全局,然后C++编译器直接把GObjectArray的Num成员以地址形式输出。查看NumElements偏移然后计算即可。

document_image_rId16

  • 构造特征

首先简单认识一下UObject的结构和一些属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class UObject
{
/** virtual function table */
void* vtf;
/** Flags used to track and report various object states. This needs to be 8 byte aligned on 32-bit
platforms to reduce memory waste */
EObjectFlags ObjectFlags;
/** Index into GObjectArray...very private. */
int32 InternalIndex; //表明该对象在GObjectArray中的第几个
/** Class the object belongs to. */
UClass* ClassPrivate;
/** Name of this object */
FName NamePrivate;
/** Object this object resides in. */
UObject* OuterPrivate;
}

还有GObjectArray结构。

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
class FUObjectItem
{
// Pointer to the allocated object
class UObjectBase* Object;
// Internal flags
int32 Flags;
// UObject Owner Cluster Index
int32 ClusterRootIndex;
// Weak Object Pointer Serial number associated with the object
int32 SerialNumber;
}
class TUObjectArray
{
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 FUObjectArray
{
int32 ObjFirstGCIndex;
/** Index pointing to last object created in range disregarded for GC. */
int32 ObjLastNonGCIndex;
/** Maximum number of objects in the disregard for GC Pool */
int32 MaxObjectsNotConsideredByGC;
/** If true this is the intial load and we should load objects int the disregarded for GC range. */
bool OpenForDisregardForGC;
/** Array of all live objects. */
TUObjectArray ObjObjects;
}

手动查看GObject的第一个对象,Index = 0

document_image_rId17

第二个对象,Index = 1

document_image_rId18

那么可以构造一个临时特征码。

document_image_rId19

特征码进行模糊处理后得到:?? ?? ?? ?? ?? 7F 00 00 ?? ?? ?? ?? 00 00 00 00

这个时候就不断地尝试假设Index的值,然后到CE中搜索这个特征码,看看Index为几的时候结果是最少的。

document_image_rId20

经过测试得到Index = 0xAC的结果最少,然后排除掉绿的地址后将剩下四个拉下来后逐个搜索,经过测试只有地址0x25CC2DC8D40能搜索到结果。

document_image_rId21

将地址拿下来后逐个进行结构分析,查看FObjectItem的大小,结构如下图。

document_image_rId22

经过排查最终只有下列地址符合我们的需求。

document_image_rId23

现在假设sizeof(FObjectItem)=0x10。然后Index=0xAC,那么该Object在GObjectArray的偏移就等于0xAC * 0x10 = 0xAC0,我们将第一个地址减掉0xAC0得到0x25CC47CFF00,然后搜索这个地址。

document_image_rId24

发现没有结果,说明这个地址不是来自GObject,换下一个搜索。

document_image_rId25

同样为0,说明sizeof(FObjectItem) != 0x10。那么同样的操作假设sizeof(FObjectItem)=0x20,再重复一遍刚刚的操作,后搜索。

document_image_rId26

发现有结果,那么0x25开头的地址拿下来搜索。

document_image_rId27

GObject到手。

UEngine

字符串Create GEngine下拉

image-20230614002449493