看了虚幻引擎游戏开发教程(https://www.youtube.com/watch?v=k7w66PGppaU&list=PLY9cHlxw3OgjuLmBX1rYPhRwcDfCza1_5&index=3),自己搭建了虚幻引擎4.27.2版本的示例游戏,尝试dump SDK、函数勾引、篡改……
示例游戏已上传至Google Drive,如有需要请下载并练习。
- Android arm64 虚幻引擎测试游戏
https://drive.google.com/file/d/1pflCQaUsyhdi274BQaU8lvTJOCCbIa2R/view?usp=sharing
■UE4Dumper构建
Android Unreal Engine dumper 可以从下面的 github 地址下载。
git clone https://github.com/kp7742/UE4Dumper.git
在 Android Studio 中打开项目。
如果只是build,正常的UE4.27.2版本是不会dump的,所以需要修改Offset.h文件(InitOffsets_64,patchUE423_64函数修改,offset值可能不正确。)
void initOffsets_64() {
//Global
PointerSize = 0x8;
FUObjectItemPad = 0x0;
FUObjectItemSize = 0x18;
//---------SDK-----------
//Class: FNameEntry
FNameEntryToNameString = 0x10; // Only Used if < UE4.23
//Class: FUObjectArray
FUObjectArrayToTUObjectArray = 0x10;
//Class: TUObjectArray
TUObjectArrayToNumElements = 0xC;
//Class: UObject
UObjectToInternalIndex = 0xC;
UObjectToClassPrivate = 0x10;
UObjectToFNameIndex = 0x18;
UObjectToOuterPrivate = 0x20;
//Class: UField
UFieldToNext = 0x28;
//Class: UStruct
UStructToSuperStruct = 0x40;
UStructToChildren = 0x48;
//Class: UFunction
UFunctionToFunctionFlags = 0xB0;
UFunctionToFunc= 0xD8;
//Class: UProperty
UPropertyToElementSize = 0x38;
UPropertyToPropertyFlags = 0x40;
UPropertyToOffsetInternal = 0x4C;
//Class: UBoolProperty
UBoolPropertyToFieldSize = 0x88;
UBoolPropertyToByteOffset = 0x89;
UBoolPropertyToByteMask = 0x8a;
UBoolPropertyToFieldMask = 0x8b;
//Class: UObjectProperty
UObjectPropertyToPropertyClass = 0x78;
//Class: UClassProperty
UClassPropertyToMetaClass = 0x78;
//Class: UInterfaceProperty
UInterfacePropertyToInterfaceClass = 0x70;
//Class: UArrayProperty
UArrayPropertyToInnerProperty = 0x70;
//Class: UMapProperty
UMapPropertyToKeyProp = 0x70;
UMapPropertyToValueProp = 0x78;
//Class: USetProperty
USetPropertyToElementProp = 0x70;
//Class: UStructProperty
UStructPropertyToStruct = 0x78;
//Class: UWorld
UWorldToPersistentLevel = 0x30;
//Class: ULevel
ULevelToAActors = 0x98;
ULevelToAActorsCount = 0xA0;
}
void patchUE423_64() {
//Class: FNamePool
FNameStride = 0x2;
GNamesToFNamePool = 0x30;
FNamePoolToCurrentBlock = 0x8;
FNamePoolToCurrentByteCursor = 0xC;
FNamePoolToBlocks = 0x10;
//Class: FNameEntry
FNameEntryToLenBit = 0x6;
FNameEntryToString = 0x2;
//Class: TUObjectArray
TUObjectArrayToNumElements = 0x14;
//Class: UStruct
UStructToChildProperties = 0x50;
//Class: FField
FFieldToClass = 0x8;
FFieldToNext = 0x20;
FFieldToName = 0x28;
}
在该项目目录中使用 ndk-build 构建。
如果用 adb push 推送到终端运行,可以看到需要提供 GNames 和 GUObject 指针地址值作为 SDK dump 的参数。
在这里,GNames 是一个名为 NamePoolData 的全局变量,其中包含分配给游戏中所有对象的名称。
GUObject 是一个名为 GUObjectArray 的全局变量,其中包含有关游戏中所有对象的信息。
■查找NamePoolData、GUObjectArray
(1)查找NamePoolData Offset 根据
UE4源码,UnrealName.cpp文件中FNamePool类的FNamePool构造函数中硬编码了“DuplicatedHardcodedName”字符串。
该字符串是从 IDA 中检索的。该字符串是 FNamePool::FNamePool() 所在的位置。
因为它是一个示例构建,所以函数名称似乎是可见的。这通常显示为 sub_xxx 。
※ 在虚幻游戏中,libUE4.so 文件的大小是数百兆字节,尽管是一个简单的游戏。因此,IDA 可能需要很长时间才能完成自动分析。
现在,如果我们追溯调用 FNamePool 构造函数的位置,则传递了 &unk_B07E0c0,即 NamePoolData 的偏移地址值(0xB07E0c0)。
(2) Find GUObjectArray Offset
搜索字符串“uobject count is invalid”。包含该字符串的位置是 FUObjectArray::AllocateObjectPool() 。
由于它是一个示例构建,因此函数名称似乎是可见的。这通常显示为 sub_xxx 。
如果追溯FUObjectArray::AllocateObjectPool函数的调用位置,可以看到GUObjectArray(0xB0C2398)的偏移地址值被传递。
由于正在导出 GUObjectArray,您可以使用 Frida 检查 Offset 值。
■ 运行SDK 转储
游戏应用程序并在终端中运行ue4dumper64。
上面得到的 NamePoolData 和 GUObjectArray 的偏移地址值作为参数传递。
转储文件保存为 /sdcard/SDK.txt 文件。
如果打开文件,可以查看主函数名和偏移地址值。void AddCoin() 等函数的偏移量被正确提取,但可能是 Offset 的偏移量设置值。
■弗里达挂钩
我们想通过挂钩 EndlessRunnerGameModeBase 类的 AddCoin 函数来操纵获得的硬币数量。
转到 AddCoin 函数的偏移地址。红框内的代码似乎是币数加1的逻辑。
如果您查看该部分的汇编语言,您可以通过在 0x69a4478 偏移处挂钩 x9 寄存器值来操纵获得的硬币数量。
因此,我编写了一个Frida脚本,将获得的硬币数量增加10,如下所示。
function traceReg(reg_addr){
var module_base = Module.findBaseAddress("libUE4.so"); // get base addr from module
var reg_realaddr = module_base.add(reg_addr); // add function offset
console.log("Tracing " + ptr(reg_addr));
Interceptor.attach(reg_realaddr, { // set hook
onEnter: function (args) {
// console.warn("[+] " + ptr(reg_addr) + " called");
if(reg_addr == "0x69A4478"){
this.context.x9 = this.context.x9.add(0x9);
}
},
// onLeave: function (retval) {
// console.warn("[-] " + ptr(reg_addr) + " Exiting"); // after call
// }
});
}
traceReg(0x69A4478)
附上Frida后玩游戏,可以看到金币数量增加了10。
这几天制作和分析了Unreal游戏,感觉难度比Unity游戏高不少,但也很好玩。
由于游戏引擎的重要性似乎在未来由于元宇宙和其他一切而变得越来越重要,因此将 Unreal 或 Unity 开发和分析作为一种爱好也不错。
※ 来源
https://github.com/guttir14/UnrealDumper-4.25/blob/main/Dumper/engine.cpp (Windows Unreal Dumper)
https://github.com/kp7742/UE4Dumper (Android Unreal Dumper)
https://shhoya. github.io/ue_dumper.html (Unreal Analytics)
https://github.com/Shhoya/Shh0yaUEDumper (Windows Unreal Dumper)
https://www.youtube.com/watch?v=P1OqJ3vZQeo (GName, GUObject Concept Explained Youtube)
https://www.youtube.com/watch?v=hXu2BXIMiX0(YouTube解释了如何找到 NamePoolData、ObjObjects 的偏移和签名以使用 UnrealDumper-4.25 Windows)