UE4Dumper64 源码阅读

536 阅读3分钟

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 // 解析类中的函数

function

pvm

这里作者做了一个取巧的方法,没有使用sys/uio.h提供的vm_read/vm_write。而是通过不同指令集下vm_read/vm_write函数在syscall调用列表的number来使用syscall调用完成vm_read/vm_write调用。

arm64 syscall number

unistd.h

x86

syscall_32.tbl

x86_64

syscall_64.tbl

/*
 * 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;
}

Reference

UE4Dumper

syscall number helper