read
ue4dumper是一款针对于吃鸡手游的逆向工具,本文针对ue4dumper64 2.3.0+ 的逻辑分析,从而达到可以写出针对于ue4 2.6.2版本的ue4 android sdk dumper工具。
ue4dumper usage for dump sdk in android
./ue4dumper64 --sdkw --newue --gname 0xE524D00 --gworld 0xE6B8208 --package com.des.test
map
- main
- Offsets::initOffsets_64();
jni\kmods.cpp
- Offsets::patchUE423_64();
- Offsets::patchCustom_64();
- find_pid
- get_module_base
- DumpSDKW
- gworld = getPtr(UWorld::getGWorld());
- UObject::getName
- GetFNameFromID(getNameID(object))
- writeStruct(sdk, UObject::getClass(gworld)); // 将GWorld的结构解析
- writeStructChild423 // 解析类中的property
- writeStructChild423_Func // 解析类中的函数
- Offsets::initOffsets_64();
function
pvm
这里作者做了一个取巧的方法,没有使用
sys/uio.h
提供的vm_read/vm_write。而是通过不同指令集下vm_read/vm_write
函数在syscall
调用列表的number来使用syscall
调用完成vm_read/vm_write
调用。
arm64 syscall number
x86
x86_64
/*
* https://man7.org/linux/man-pages/man2/process_vm_readv.2.html
* Syscall Implementation of process_vm_readv & process_vm_writev
*/
bool pvm(void *address, void *buffer, size_t size, bool iswrite) {
struct iovec local[1];
struct iovec remote[1];
local[0].iov_base = buffer;
local[0].iov_len = size;
remote[0].iov_base = address;
remote[0].iov_len = size;
if (target_pid < 0) {
return false;
}
#if defined(__arm__)
int process_vm_readv_syscall = 376;
int process_vm_writev_syscall = 377;
#elif defined(__aarch64__)
int process_vm_readv_syscall = 270;
int process_vm_writev_syscall = 271;
#elif defined(__i386__)
int process_vm_readv_syscall = 347;
int process_vm_writev_syscall = 348;
#else
int process_vm_readv_syscall = 310;
int process_vm_writev_syscall = 311;
#endif
ssize_t bytes = syscall((iswrite ? process_vm_writev_syscall : process_vm_readv_syscall),
target_pid, local, 1, remote, 1, 0);
//printf("process_vm_readv reads %zd bytes from PID: %d\n", bytes, target_pid);
return bytes == size;
}
vm_readv
bool vm_readv(void *address, void *buffer, size_t size) {
return pvm(address, buffer, size, false);
}
Read
template<typename T>
T Read(kaddr address) {
T data;
vm_readv(reinterpret_cast<void *>(address), reinterpret_cast<void *>(&data), sizeof(T));
return data;
}
GetFNameFromID
string GetFNameFromID(uint32 index) {
if (isUE423) {
// UE4 2.3.0+
// 通过index计算Block和Offset的逻辑,具体逻辑在 Engine\Source\Runtime\Core\Private\UObject\UnrealNames.cpp
// 中struct FNameEntryHandle构造函数部分
uint32 Block = index >> 16;
uint16 Offset = index & 65535;
// 计算 FNamePool 单例在内存中的实际地址
kaddr FNamePool = getRealOffset(Offsets::GNames) + Offsets::GNamesToFNamePool;
// NamePoolChunk的实际内存地址 = FNamePool + FNamePoolToBlocks在FNamePool中的偏移 + BlockIndex * 64位下指针的offset
kaddr NamePoolChunk = getPtr(
FNamePool + Offsets::FNamePoolToBlocks + (Block * Offsets::PointerSize));
// FNameEntry的实际内存地址 = NamePoolChunk的地址 + FNameStride * Offset
// 具体逻辑在 Engine\Source\Runtime\Core\Private\UObject\UnrealNames.cpp [FNameEntry& Resolve(FNameEntryHandle Handle) const]函数中
// return *reinterpret_cast<FNameEntry*>(Blocks[Handle.Block] + Stride * Handle.Offset);
// 不过在ue4 2.3.0的源码中,FNameStride的大小为4,而这里为2
kaddr FNameEntry = NamePoolChunk + (Offsets::FNameStride * Offset);
// 读取FNameEntryHeader的信息,具体查看在 Engine\Source\Runtime\Core\Public\UObject\NameTypes.h 中
// FNameEntry以及FNameEntryHeader结构体的定义
int16 FNameEntryHeader = Read<int16>(FNameEntry);
// FNameEntry中第一个元素为FNameEntryHeader,第二个元素就是字符串存储的位置,因此:
// 字符串实际存储地址 = FNameEntry地址 + sizeof(FNameEntryHeader)
// 而 Offsets::FNameEntryToString == sizeof(FNameEntryHeader) == 0x2
kaddr StrPtr = FNameEntry + Offsets::FNameEntryToString;
// FNameEntryHeader在内存中实际是一个16bit的内存,后10bit用来表示字符串的长度
int StrLength = FNameEntryHeader >> Offsets::FNameEntryToLenBit;
///Unicode Dumping Not Supported Yet
if (StrLength > 0 && StrLength < 250) {
bool wide = FNameEntryHeader & 1;
// 从内存中读取 宽字符/窄字符 类型的实际字符串
if (wide) {
return WideStr::getString(StrPtr, StrLength);
} else {
return ReadStr2(StrPtr, StrLength);
}
} else {
return "None";
}
} else {
static kaddr TNameEntryArray;
if (TNameEntryArray) {//As usual caching ;)
goto gotGName;
}
if (isPtrDec) {
if (isPGLite) {
uint32 modeSel = Read<uint32>(getRealOffset(Offsets::PGLEncSelect));
if (modeSel) {
kaddr blockSlice = getPtr(getRealOffset(Offsets::PGLBlockSlice1));
if (blockSlice) {
kaddr block = getPtr(blockSlice + (Offsets::PointerSize * 5));
uint8 shift = Read<uint8>(getRealOffset(Offsets::PGLBlockShift));
kaddr offset = Offsets::PointerSize * (shift + 5);
kaddr encGName = getPtr(block + offset);
#if defined(__LP64__)
TNameEntryArray = encGName ^ 0x7878787878787878;
#else
TNameEntryArray = encGName ^ 0x78787878;
#endif
} else {
return "None";
}
} else {
kaddr blockSlice = getRealOffset(Offsets::PGLBlockSlice2);
if (blockSlice && Read<int>(blockSlice + 0x4)) {
kaddr block = getPtr(blockSlice + 0x8);
uint32 shift = Read<uint32>(blockSlice);
uint32 offset = (Offsets::PointerSize * 2) * (((shift - 0x64) / 0x3) - 1);
TNameEntryArray = getPtr(block + offset);
} else {
return "None";
}
}
} else {
kaddr blockSlice = getRealOffset(Offsets::GNames);
if (blockSlice) {
kaddr block = getPtr(blockSlice + 0x8);
uint32 shift = Read<uint32>(blockSlice);
uint32 offset = (Offsets::PointerSize * 2) * (((shift - 0x64) / 0x3) - 1);
TNameEntryArray = getPtr(block + offset);
} else {
return "None";
}
}
} else {
if (deRefGNames) {
TNameEntryArray = getPtr(getRealOffset(Offsets::GNames));
} else {
TNameEntryArray = getRealOffset(Offsets::GNames);
}
}
gotGName:
kaddr FNameEntryArr = getPtr(
TNameEntryArray + ((index / 0x4000) * Offsets::PointerSize));
kaddr FNameEntry = getPtr(
FNameEntryArr + ((index % 0x4000) * Offsets::PointerSize));
return ReadStr(FNameEntry + Offsets::FNameEntryToNameString, MAX_SIZE);
}
}
UObject::getNameID
// 与其说是getNameId 不如说是get UObject.NamePrivate中的字符串实际在全局UObject字符串表中的index
static uint32 getNameID(kaddr object) {
// UObject的基址+在UObject中 NamePrivate.ComparisonIndex.Value 的偏移
return Read<uint32>(object + Offsets::UObjectToFNameIndex);
}
UObejct::getName
GetFNameFromID
getStructClassPath
找到类的继承关系。
static string getStructClassPath(kaddr clazz)
{
string classname = UObject::getName(clazz);
kaddr superclass = getSuperClass(clazz);
while (superclass)
{
classname += ".";
classname += UObject::getName(superclass);
superclass = getSuperClass(superclass);
}
return classname;
}
writeStruct
writeStruct 遍历该类的结构,并将该结构写入到 SDK.txt 输入流中
void writeStruct(ofstream &sdk, kaddr clazz) {
list<kaddr> recurrce;
kaddr currStruct = clazz;
while (UObject::isValid(currStruct)) {
string name = UObject::getName(currStruct);
// 无效的class, break
if (isEqual(name, "None") || isContain(name, "/Game/") || isContain(name, "_png") ||
name.empty()) {
break;
}
uint32 NameID = UObject::getNameID(currStruct);
// 该class是否已经被解析过
if (!isScanned(NameID)) {
//Verbose Output
if (isVerbose) {
cout << "Name: " << name << endl;
cout << "Class: " << UStruct::getStructClassPath(currStruct) << endl;
cout << endl;
}
//Dumping
// 将当前的NameID保存
structIDMap.push_back(NameID);
// 类的继承关系
sdk << "Class: " << UStruct::getStructClassPath(currStruct) << endl;
if (isUE423) {
// 拿到class的proterpty信息
// writeStructChild423: 遍历每一个filed,判断其类型并将其写入到 SDK.txt 流中
recurrce.merge(writeStructChild423(sdk, UStruct::getChildProperties(currStruct)));
// 拿到class的func信息
// writeStructChild423_Func: 遍历每一个filed, 判断其类型并将其写入到 SDK.txt 流中
recurrce.merge(writeStructChild423_Func(sdk, UStruct::getChildren(currStruct)));
} else {
recurrce.merge(writeStructChild(sdk, UStruct::getChildren(currStruct)));
}
sdk << "\n--------------------------------" << endl;
classCount++;
}
currStruct = UStruct::getSuperClass(currStruct);
}
for (auto it = recurrce.begin(); it != recurrce.end(); ++it)
writeStruct(sdk, *it);
}
DumpSDKW
void DumpSDKW(string out) {
ofstream sdk(out + "/SDK.txt", ofstream::out);
if (sdk.is_open()) {
cout << "Dumping SDK List" << endl;
clock_t begin = clock();
// 拿到参数输入的 GWorld 的address
kaddr gworld = getPtr(UWorld::getGWorld());
cout << "UWorld: " << setbase(16) << gworld << setbase(10) << " | Name: "
<< UObject::getName(gworld) << endl;
if (UObject::isValid(gworld)) {
//Iterate World
// 对GWorld结构进行解析
writeStruct(sdk, UObject::getClass(gworld));
//Iterate Entities
// UWorld 成员变量: class ULevel* PersistentLevel; 偏移为 0x30
kaddr level = getPtr(gworld + Offsets::UWorldToPersistentLevel);
// ULevel 成员变量: TArray<AActor*> Actors; 偏移为 0x98
kaddr actorList = getPtr(level + Offsets::ULevelToAActors);
int actorsCount = Read<int>(level + Offsets::ULevelToAActorsCount);
// 遍历所有的Actor,对每一个Actor执行和GWorld同样的操作
for (int i = 0; i < actorsCount; i++) {
kaddr actor = getPtr(actorList + (i * Offsets::PointerSize));
if (UObject::isValid(actor)) {
writeStruct(sdk, UObject::getClass(actor));
}
}
}
sdk.close();
clock_t end = clock();
double elapsed_secs = double(end - begin) / CLOCKS_PER_SEC;
cout << classCount << " Items Dumped in SDK in " << elapsed_secs << "S" << endl;
}
}
Offsets::initOffsets_64()
void initOffsets_64()
{
// 类中成员的偏移
// Global
PointerSize = 0x8; // 64位下指针类型所占内存大小
FUObjectItemPadd = 0x0;
FUObjectItemSize = 0x18;
//---------SDK-----------
// Class: FNameEntry
FNameEntryToNameString = 0x10; // TODO
// Class: FUObjectArray
// FUObjectArray 定义在 Engine\Source\Runtime\CoreUObject\Public\UObject\UObjectArray.h
FUObjectArrayToTUObjectArray = 0x10; // TUObjectArray ObjObjects; 4+4+4+1+padding(3) = 0x10
// Class: TUObjectArray
TUObjectArrayToNumElements = 0xC; // TODO
// Class: UObject
// UObject定义在 Engine\Source\Runtime\CoreUObject\Public\UObject\Object.h
// vtable + offset(objectFlags) = 0xc
UObjectToInternalIndex = 0xC; // int32 InternalIndex; 0xc sizeof(int32) = 0x4
UObjectToClassPrivate = 0x10; // UClass* ClassPrivate; 0xc+4 = 0x10 sizeof(UClass*) = 0x8
UObjectToFNameIndex = 0x18; // FName NamePrivate; 0x10+0x8 = 0x18 sizeof(FName) = 0x8 FName.ComparisonIndex.Value
UObjectToOuterPrivate = 0x20; // UObject* OuterPrivate; 0x18+0x8 = 0x20 sizeof(UObject*) = 0x8
// Class: UField
// UField定义在 Engine\Source\Runtime\CoreUObject\Public\UObject\Class.h
UFieldToNext = 0x28; // UField* Next; 0x0 + sizeof(UObject) = 0x28
// Class: UStruct
UStructToSuperStruct = 0x30; // UStruct* SuperStruct;
UStructToChildren = 0x38; // UField* Children;
// Class: UFunction
UFunctionToFunctionFlags = 0x88; // EFunctionFlags FunctionFlags;
UFunctionToFunc = 0xB0; // FNativeFuncPtr Func;
// Class: UProperty
UPropertyToElementSize = 0x34; // int32 ElementSize;
UPropertyToPropertyFlags = 0x38; // EPropertyFlags PropertyFlags;
UPropertyToOffsetInternal = 0x44; // int32 Offset_Internal;
// Class: UBoolProperty
UBoolPropertyToFieldSize = 0x70; // uint8 FieldSize;
UBoolPropertyToByteOffset = 0x71; // uint8 ByteOffset;
UBoolPropertyToByteMask = 0x72; // uint8 ByteMask;
UBoolPropertyToFieldMask = 0x73; // uint8 FieldMask;
// Class: UObjectProperty
UObjectPropertyToPropertyClass = 0x70; // class UClass* PropertyClass;
// Class: UClassProperty
UClassPropertyToMetaClass = 0x78; // class UClass* MetaClass;
// Class: UInterfaceProperty
UInterfacePropertyToInterfaceClass = 0x78; // class UClass* InterfaceClass;
// Class: UArrayProperty
UArrayPropertyToInnerProperty = 0x70; // UProperty* Inner;
// Class: UMapProperty
UMapPropertyToKeyProp = 0x70; // UProperty* KeyProp;
UMapPropertyToValueProp = 0x78; // UProperty* ValueProp;
// Class: USetProperty
USetPropertyToElementProp = 0x70; // UProperty* ElementProp;
// Class: UStructProperty
UStructPropertyToStruct = 0x70; // class UScriptStruct* Struct;
// Class: UWorld
UWorldToPersistentLevel = 0x30; // class ULevel* PersistentLevel;
// Class: ULevel
ULevelToAActors = 0x98; // TArray<AActor*> Actors;
ULevelToAActorsCount = 0xA0;
}