找关键结构体
https://www.cnblogs.com/revercc/p/17641855.html
找GWORLD
https://bbs.kanxue.com/thread-280042.htm
可以发现是 TEXT包裹的,utf-16编码,ida alt + b搜索 53 00 65 00 61 00 6D 00 6C 00 65 00 73 00 73 00 54 00 72 00
即可
网上翻即可找到 GWorld
对应地址: 0x00000000B32D8A8
找GNAME
直接搜 ByteProperty 字符串即可,函数的第一个参数即为GNAME,交叉应用一下即可拿到GNAME
对应地址: 0x00000000B171CC0
找GUObjectArray
直接搜 CloseDisregardForGC
字符串即可,下面就是GUObjectArray
0x00000000B1B5F98
dump关键信息
查看ue4版本
参考: https://www.52pojie.cn/thread-1838396-1-1.html
然鹅并没有查到版本,仿佛题目作者专门抹去了ue4的版本号,不过经过测试可以坑底的是一个较高版本的ue4
事后看了别人的wp,原来得搜utf16编码的字符串才能看到

点击setup后勾选这个:

就能看到了
设置之后,前面定位关键信息都好定位了
dump 相关信息
使用这个工具: https://github.com/revercc/UE4Dumper
要dump出以下三种信息才好逆向:
# dump sdk:
# ./ue4dumper64 --package com.tencent.ace.match2024 --newue+ --sdkw --gworld 0x00000000B32D8A8 --gname 0x00000000B171CC0 --output /data/local/tmp/111# dump objs: # 获取类地址、对象地址
# ./ue4dumper64 --package com.tencent.ace.match2024 --newue+ --objs --gname 0x00000000B171CC0 --guobj 0x00000000B1B5F98 --output /data/local/tmp/111# dump actors
# ./ue4dumper64 --package com.tencent.ace.match2024 --newue+ --actors --gworld 0x00000000B32D8A8 --gname 0x00000000B171CC0 --output /data/local/tmp/111
SDK.txt就是dump出的SDK结构体信息,Object.txt是dump的游戏中所有的对象名以及所属类的信息,actor.txt是游戏中所有Actor的相关信息。
相关重要操作
frida遍历 64位 ue4的Actor
参考:
https://github.com/revercc/UE4Dumper
https://www.cnblogs.com/revercc/p/17629584.html
function pt_all_actor(){var libUE4_module = Module.findBaseAddress("libUE4.so")// console.log("libUE4_module is :", libUE4_module)var GWorld_Offset = 0x00000000B32D8A8var GName_Offset = 0x00000000B171CC0var GName = libUE4_module.add(GName_Offset);var GWorld = libUE4_module.add(GWorld_Offset).readPointer()var Level_Offset = 0x30var Level = GWorld.add(Level_Offset).readPointer()// console.log("Level :", Level)var Actors_Offset = 0x98var Actors = Level.add(Actors_Offset).readPointer()// console.log("Actors Array :", Actors)var Actors_Num = Level.add(Actors_Offset).add(8).readU32()// console.log("Actors_num :", Actors_Num)var ct = 0for(var index = 0; index < Actors_Num; index++){var actor = Actors.add(index * 8).readPointer()//通过角色actor获取其成员变量FNamevar FName_Offset = 0x18var FName = actor.add(FName_Offset);var FNameEntryAllocator = GNamevar FNamePool = FNameEntryAllocator.add(0x38) // FNamePool//手动解析FNamePoolvar ComparisonIndex = FName.add(0).readU32()var FNameBlockOffsetBits = 16var FNameBlockOffsets = 65536var Block = ComparisonIndex >> FNameBlockOffsetBitsvar Offset = ComparisonIndex & (FNameBlockOffsets - 1)var NamePoolChunk = FNamePool.add(0x8 + Block * 8).readPointer()var FNameEntry = NamePoolChunk.add(Offset * 2)var FNameEntryHeader = FNameEntry.readU16()var isWide = FNameEntryHeader & 1var Len = FNameEntryHeader >> 6// console.log("actor : ", actor, " ", FNameEntry.add(2).readCString(Len))if(0 == isWide){var Name = FNameEntry.add(2).readCString(Len)var actor_addr = actor;console.log("actor : ", actor, " ", FNameEntry.add(2).readCString(Len))}}
}
exec包裹的关键函数
我在hook / 主动调用的时候发现不管是函数参数个数还是参数类型都对不上,一度没有思路,甚至想下载ue4编译个demo研究研究。
后来想到2022年final中有一个32位的有符号的 libUE4.so文件,于是拿出来研究了一下
发现SDK中指向的函数一般都是 exec开头的函数,而这种函数一般有两种实现: 带exec头与不带exec头,而且exec开头的函数,其参数都是 AActor *this, FFrame *a2, void* a3
这种的形式,于是我猜测,可能execSetActorHiddenInGame函数只是在 SetActorHiddenInGame 上套了个壳,并猜测下图的 *this+0x160
指针指向的就是 SetActorHiddenInGame 函数,实际上也确实如此。
实际上我们直接调用 内部的不包裹exec的函数即可。
参考: https://www.cnblogs.com/wodehao0808/p/6163673.html
section0
只要碰到障碍我,人物就会死亡。正常情况下不可能走出房屋。
way1
经过我的尝试,发现了 0x5e93094 偏移处的ReceiveHit函数,只要与障碍物解除就会调用这个函数,那我直接动态重写这个函数,把函数开头patch成 ret指令,即可逃脱房间
function section0_way1(){var base_addr = get_so_base("libUE4.so")var hook_list = [//0x6fcd294,0x5e93094, // ReceiveHit,当碰到墙壁时就会触发这个函数]Memory.protect(base_addr.add(0x5e93094), 0x1000, 'rwx');base_addr.add(0x5e93094).writeByteArray([0xC0, 0x03, 0x5F, 0xD6])
}
way2
修改血量 FirstPersonCharacter Actor的生命值
原本的生命值是100.0,那我们直接给一个极大的值:
function hook_find_actor( find_actor_name,cnt=0){var libUE4_module = Module.findBaseAddress("libUE4.so")// console.log("libUE4_module is :", libUE4_module)var GWorld_Offset = 0x00000000B32D8A8var GName_Offset = 0x00000000B171CC0var GName = libUE4_module.add(GName_Offset);var GWorld = libUE4_module.add(GWorld_Offset).readPointer()var Level_Offset = 0x30var Level = GWorld.add(Level_Offset).readPointer()// console.log("Level :", Level)var Actors_Offset = 0x98var Actors = Level.add(Actors_Offset).readPointer()// console.log("Actors Array :", Actors)var Actors_Num = Level.add(Actors_Offset).add(8).readU32()// console.log("Actors_num :", Actors_Num)var ct = 0for(var index = 0; index < Actors_Num; index++){var actor = Actors.add(index * 8).readPointer()//通过角色actor获取其成员变量FNamevar FName_Offset = 0x18var FName = actor.add(FName_Offset);var FNameEntryAllocator = GNamevar FNamePool = FNameEntryAllocator.add(0x38) // FNamePool//手动解析FNamePoolvar ComparisonIndex = FName.add(0).readU32()var FNameBlockOffsetBits = 16var FNameBlockOffsets = 65536var Block = ComparisonIndex >> FNameBlockOffsetBitsvar Offset = ComparisonIndex & (FNameBlockOffsets - 1)var NamePoolChunk = FNamePool.add(0x8 + Block * 8).readPointer()var FNameEntry = NamePoolChunk.add(Offset * 2)var FNameEntryHeader = FNameEntry.readU16()var isWide = FNameEntryHeader & 1var Len = FNameEntryHeader >> 6// console.log("actor : ", actor, " ", FNameEntry.add(2).readCString(Len))if(0 == isWide){var Name = FNameEntry.add(2).readCString(Len)var actor_addr = actor;if (Name === find_actor_name){if(ct == cnt){// console.log("actor : ", actor, " ", FNameEntry.add(2).readCString(Len))return actor_addr}else{ct++}}}}
}function section0_way2(){var TriggerBox_addr = hook_find_actor("FirstPersonCharacter_C")TriggerBox_addr.add(0x510).writeFloat(1000000.0)
}
way3
后续找到了生命值碰墙的扣血函数:
ReceiveHit(offset: 0x5e93094) ---> no_exec_ReceiveHit(offset: 0x5E843D4) ---> Hp_sub(offset: 0x5E840C8)
直接nop掉0x5E840F8处的指令即可:
function section0_way3(){var base_addr = get_so_base("libUE4.so")Memory.protect(base_addr.add(0x000000005E840F8), 0x1000, 'rwx');base_addr.add(0x000000005E840F8).writeByteArray([0x1F, 0x20, 0x03, 0xD5])
}
way4
锁定生命值,类似于CE的锁定值功能,还不会实现。。。
section1
感觉这一section最难,很难猜到作者让干什么,我想了很多情况,试了好多次才成功。
走的弯路
感觉主要的难点是猜不到对应的对象以及对应的类,那就不好解析字段,只能对Actor对象进行一个个尝试。到最后才发现,既然都继承了Actor字段,那直接从Actor字段就可以设置大部分的属性。(也并不是绝对的,比如section2设置了Actor全局的碰撞属性并没影响到组件的相关属性)
一开始我猜测被隐藏的字符串是TextRenderActor
类的对象,因此我开始我走向弯路:
-
猜测文字渲染被透明化了,因此我通过调用
Set_Color
函数来吧字体变得透明。这里让所有的Actor都主动调用了这个方法(实际上让 只上所有TextRenderActor
类的对象调用即可,其他的类的对象调用巧不巧还会出错,得设置好异常处理函数)代码实现如下:
function set_Color(Actor_Name,color){ try{var base_addr = get_so_base()var Actor_addr = hook_find_actor(Actor_Name)var TextRenderComponent = Actor_addr.add(0x220).readPointer()// 需要注意的是,如果直接修改颜色字段而不调用相关函数,则需要刷新一下才能实现,刷新方法可以通过下面这种方法实现,我的评价是不如直接调用SetTextRenderColor()函数// => set_Color()// => call_SetActorHiddenInGame("TextRenderActor",1,0)// => Thread.sleep(1); // 暂停 3 秒// => call_SetActorHiddenInGame("TextRenderActor",0,0)// var old_color = TextRenderComponent.add(0x474).readU32(color)// console.log(hex(old_color))// TextRenderComponent.add(0x474).writeU32(color)var no_exec_SetTextRenderColor = base_addr.add(0x000000008EAC0F0)const SetTextRenderColor = new NativeFunction(no_exec_SetTextRenderColor, 'void', ['pointer','uint']);SetTextRenderColor(TextRenderComponent,color)}catch(e){console.log(e)}}
事实上,就算让所有的Actor都主动调用了
SetTextRenderColor
函数也是没有作用。 -
获取相关文字,到这里我还是不信邪,因此主动调用了
TextRenderActor
类的所有文字,并手动解析了FText结构体// class FTextData { // public: // char pad_0x0000[0x28]; //0x0000 // wchar_t* Name; //0x0028 // __int32 Length; //0x0030 // }; // struct FText { // FTextData* Data; // char UnknownData[0x10]; // wchar_t* Get() const { // if (Data) // return Data->Name; // return nullptr; // } // }; function get_FText_mess(Actor_Name){ // 确实能找到所有字符串,但没啥用try{var base_addr = get_so_base()var Actor_addr = hook_find_actor(Actor_Name)var TextRenderComponent = Actor_addr.add(0x220).readPointer()var FText = TextRenderComponent.add(0x448)// console.log(`FText: ${hexdump(FText.readByteArray(0x20))}`)var FTextData = FText.add(0).readPointer()// console.log(`FTextData: ${hexdump(FTextData.readByteArray(0x50))}`)var FText_Name_pointer = FTextData.add(0x28).readPointer()var FText_len = FTextData.add(0x30).readU32()var real_string = FText_Name_pointer.readUtf16String()console.log(`FText_Name: ${hexdump(FText_Name_pointer.readByteArray(0x50))}`)console.log(`FText_len: ${FText_len}`)console.log(`=> ${real_string}`)console.log("\n")}catch(e){console.log(e)} }
事实上,读取了所有字符串,根本没有有用的。
-
于是决定转变思路。"被隐藏的可能并不是
TextRenderActor
类的对象"。后来分析了Actor.txt与Objects.txt,发现绝大部分对象都属于
StaticMeshActor
类或者继承了这个类。因此我打算从这个类入手如下方代码所示,我在
StaticMeshComponent
类中找到了两个api:SetHiddenInGame
、SetActorHiddenInGame
,我让所有的Actor都call了这两个api,然鹅没什么用,后来检查了 behidden 字段,发现基本所有的对象基本上hidden
字段都为0,也就是都没被隐藏。既然都没被隐藏,那字符串是怎么没的呢?一时间我陷入了迷茫。function check_behidden(Actor_Name){var Actor_addr = hook_find_actor(Actor_Name)var behidden = Actor_addr.add(0x58).readU8()console.log(`${Actor_Name}: hidden: ${(behidden >> 5) & 1}`) }function call_SetActorHiddenInGame(Actor_Name,behidden,sel = 0){ // 确实能隐藏物体,比如说Cubetry{var Actor_addr = hook_find_actor(Actor_Name,sel)var a1_0 = Actor_addr.add(0).readPointer()var no_exec_SetActorHiddenInGame = a1_0.add(0x310).readPointer()const SetActorHiddenInGame = new NativeFunction(no_exec_SetActorHiddenInGame, 'void', ['pointer','bool']);SetActorHiddenInGame(Actor_addr,behidden)}catch(e){console.log(`${Actor_Name} error: ${e}`)}} function StaticMeshActor_call_SetHiddenInGame(Actor_Name,NewHidden,bPropagateToChildren,sel = 0){ // 并没用try{var base_addr = get_so_base()var Actor_addr = hook_find_actor(Actor_Name,sel)var StaticMeshComponent = Actor_addr.add(0x220).readPointer() // StaticMeshComponent.MeshComponent.PrimitiveComponent.SceneComponent.ActorComponent.Objectvar no_exec_SetHiddenInGame = base_addr.add(0x000000008E61C70)const SetHiddenInGame = new NativeFunction(no_exec_SetHiddenInGame, 'void', ['pointer','bool','bool']);SetHiddenInGame(StaticMeshComponent,NewHidden,bPropagateToChildren)console.log(`${Actor_Name} call_SetHiddenInGame`)}catch(e){console.log(`${Actor_Name} error: ${e}`)}}function section1(){try{// StaticMeshActor.Actor.Object// call_SetActorHiddenInGame("Cube",1)// var list = ["TemplateLabel","SkySphereBlueprint","AtmosphericFog","SphereReflectionCapture","NetworkPlayerStart","LightSource","PostProcessVolume","SkyLight","EditorCube8","EditorCube9","EditorCube10","EditorCube11","EditorCube12","EditorCube13","EditorCube14","EditorCube15","EditorCube16","EditorCube17","EditorCube18","Floor","Wall1","Wall2","Wall3","Wall4","BigWall","BigWall2","Wall_400x400","Wall_400x401","Wall_400x402","Wall_400x403","Wall_400x404","PointLight","Floor_400x400","Floor_400x401","TextRenderActor","TextRenderActor2","TextRenderActor3","Wall_Door_400x400","SM_Door","TriggerBox","TextRenderActor4","Wall_400x405","Wall_400x406","SM_MERGED_Shape_Pipe_Flag","Plane_Blueprint","Cube","Cube2","Cube3","Wall_400x407","Wall_400x408","TextRenderActor6","TextRenderActor10","Wall_400x409","Wall_400x410","TextRenderActor12","TextRenderActor13","TextRenderActor14","TextRenderActor15","Actor","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","SM_MERGED_Shape_Pipe_Flag_1_Blueprint2","SM_MERGED_Shape_Pipe_Flag_1_Blueprint3","SM_MERGED_Shape_Pipe_Flag_1_Blueprint","EditorCube19","Shape_Pipe_Flag","TextRenderActor8","TextRenderActor9","TextRenderActor11","TextRenderActor16","Wall_400x411","TextRenderActor17","DefaultPhysicsVolume","FirstPersonGameMode_C","GameSession","ParticleEventManager","GameNetworkManager","FirstPersonExampleMap_C","FirstPersonCharacter_C","GameStateBase","PlayerController","PlayerState","PlayerCameraManager","CameraActor","FirstPersonHUD_C"]// var cnt = -1// for(var i in list){// const name = list[i].trim()// if(name === "Shape_Pipe_Flag"){// cnt += 1// call_SetActorHiddenInGame(name,0,cnt)// }else{// call_SetActorHiddenInGame(name,0)// }// }// call_ StaticMeshActor_call_SetHiddenInGame ,不可以// var list = ["TemplateLabel","SkySphereBlueprint","AtmosphericFog","SphereReflectionCapture","NetworkPlayerStart","LightSource","PostProcessVolume","SkyLight","EditorCube8","EditorCube9","EditorCube10","EditorCube11","EditorCube12","EditorCube13","EditorCube14","EditorCube15","EditorCube16","EditorCube17","EditorCube18","Floor","Wall1","Wall2","Wall3","Wall4","BigWall","BigWall2","Wall_400x400","Wall_400x401","Wall_400x402","Wall_400x403","Wall_400x404","PointLight","Floor_400x400","Floor_400x401","TextRenderActor","TextRenderActor2","TextRenderActor3","Wall_Door_400x400","SM_Door","TriggerBox","TextRenderActor4","Wall_400x405","Wall_400x406","SM_MERGED_Shape_Pipe_Flag","Plane_Blueprint","Cube","Cube2","Cube3","Wall_400x407","Wall_400x408","TextRenderActor6","TextRenderActor10","Wall_400x409","Wall_400x410","TextRenderActor12","TextRenderActor13","TextRenderActor14","TextRenderActor15","Actor","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","SM_MERGED_Shape_Pipe_Flag_1_Blueprint2","SM_MERGED_Shape_Pipe_Flag_1_Blueprint3","SM_MERGED_Shape_Pipe_Flag_1_Blueprint","EditorCube19","Shape_Pipe_Flag","TextRenderActor8","TextRenderActor9","TextRenderActor11","TextRenderActor16","Wall_400x411","TextRenderActor17","DefaultPhysicsVolume","FirstPersonGameMode_C","GameSession","ParticleEventManager","GameNetworkManager","FirstPersonExampleMap_C","FirstPersonCharacter_C","GameStateBase","PlayerController","PlayerState","PlayerCameraManager","CameraActor","FirstPersonHUD_C"]// var cnt = -1// for(var i in list){// const name = list[i].trim()// if(name === "Shape_Pipe_Flag"){// cnt += 1// StaticMeshActor_call_SetHiddenInGame(name,0,1,cnt)// }else{// StaticMeshActor_call_SetHiddenInGame(name,0,1)// }// }// 检测是否被隐藏,不是这样的作法,不过通过这个我发现triggerbox就是本就存在的东西// var list = ["TemplateLabel","SkySphereBlueprint","AtmosphericFog","SphereReflectionCapture","NetworkPlayerStart","LightSource","PostProcessVolume","SkyLight","EditorCube8","EditorCube9","EditorCube10","EditorCube11","EditorCube12","EditorCube13","EditorCube14","EditorCube15","EditorCube16","EditorCube17","EditorCube18","Floor","Wall1","Wall2","Wall3","Wall4","BigWall","BigWall2","Wall_400x400","Wall_400x401","Wall_400x402","Wall_400x403","Wall_400x404","PointLight","Floor_400x400","Floor_400x401","TextRenderActor","TextRenderActor2","TextRenderActor3","Wall_Door_400x400","SM_Door","TriggerBox","TextRenderActor4","Wall_400x405","Wall_400x406","SM_MERGED_Shape_Pipe_Flag","Plane_Blueprint","Cube","Cube2","Cube3","Wall_400x407","Wall_400x408","TextRenderActor6","TextRenderActor10","Wall_400x409","Wall_400x410","TextRenderActor12","TextRenderActor13","TextRenderActor14","TextRenderActor15","Actor","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","SM_MERGED_Shape_Pipe_Flag_1_Blueprint2","SM_MERGED_Shape_Pipe_Flag_1_Blueprint3","SM_MERGED_Shape_Pipe_Flag_1_Blueprint","EditorCube19","Shape_Pipe_Flag","TextRenderActor8","TextRenderActor9","TextRenderActor11","TextRenderActor16","Wall_400x411","TextRenderActor17","DefaultPhysicsVolume","FirstPersonGameMode_C","GameSession","ParticleEventManager","GameNetworkManager","FirstPersonExampleMap_C","FirstPersonCharacter_C","GameStateBase","PlayerController","PlayerState","PlayerCameraManager","CameraActor","FirstPersonHUD_C"]// for(var i in list){// const name = list[i].trim()// // console.log(name)// check_behidden(name)// }}catch(e){console.log(e)}}
正解
后来找到了 SetVisibility
API。并通过询问deepseek得到了这样的信息
通过Actor主动调用SetVisibility
函数或者直接设置 bVisible
属性并刷新,即可显示出flag
function set_bVisible(Actor_Name,sel=0){try{var base_addr = get_so_base()var Actor_addr = hook_find_actor(Actor_Name,sel)var StaticMeshComponent = Actor_addr.add(0x220).readPointer()var old_val = StaticMeshComponent.add(0x14c).readU8()StaticMeshComponent.add(0x14c).writeU8(old_val | 0x10)console.log(`${Actor_Name} set_bVisible`)}catch(e){console.log(`${Actor_Name} error: ${e}`)}}function StaticMeshActor_call_SetVisibility(Actor_Name,bNewVisibility,bPropagateToChildren,sel=0){try{var base_addr = get_so_base()var Actor_addr = hook_find_actor(Actor_Name,sel)var StaticMeshComponent = Actor_addr.add(0x220).readPointer()var no_exec_SetVisibility = base_addr.add(0x000000008E619BC)const SetVisibility = new NativeFunction(no_exec_SetVisibility, 'void', ['pointer','bool','bool']);SetVisibility(StaticMeshComponent,bNewVisibility,bPropagateToChildren)console.log(`${Actor_Name} call_SetVisibility`)}catch(e){console.log(`${Actor_Name} error: ${e}`)}}function section1(){try{// call_SetVisibility// var list = ["TemplateLabel","SkySphereBlueprint","AtmosphericFog","SphereReflectionCapture","NetworkPlayerStart","LightSource","PostProcessVolume","SkyLight","EditorCube8","EditorCube9","EditorCube10","EditorCube11","EditorCube12","EditorCube13","EditorCube14","EditorCube15","EditorCube16","EditorCube17","EditorCube18","Floor","Wall1","Wall2","Wall3","Wall4","BigWall","BigWall2","Wall_400x400","Wall_400x401","Wall_400x402","Wall_400x403","Wall_400x404","PointLight","Floor_400x400","Floor_400x401","TextRenderActor","TextRenderActor2","TextRenderActor3","Wall_Door_400x400","SM_Door","TriggerBox","TextRenderActor4","Wall_400x405","Wall_400x406","SM_MERGED_Shape_Pipe_Flag","Plane_Blueprint","Cube","Cube2","Cube3","Wall_400x407","Wall_400x408","TextRenderActor6","TextRenderActor10","Wall_400x409","Wall_400x410","TextRenderActor12","TextRenderActor13","TextRenderActor14","TextRenderActor15","Actor","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","SM_MERGED_Shape_Pipe_Flag_1_Blueprint2","SM_MERGED_Shape_Pipe_Flag_1_Blueprint3","SM_MERGED_Shape_Pipe_Flag_1_Blueprint","EditorCube19","Shape_Pipe_Flag","TextRenderActor8","TextRenderActor9","TextRenderActor11","TextRenderActor16","Wall_400x411","TextRenderActor17","DefaultPhysicsVolume","FirstPersonGameMode_C","GameSession","ParticleEventManager","GameNetworkManager","FirstPersonExampleMap_C","FirstPersonCharacter_C","GameStateBase","PlayerController","PlayerState","PlayerCameraManager","CameraActor","FirstPersonHUD_C"]// var cnt = -1// for(var i in list){// const name = list[i].trim()// if(name === "Shape_Pipe_Flag"){// cnt += 1;// StaticMeshActor_call_SetVisibility(name,1,1,cnt)// }else{// StaticMeshActor_call_SetVisibility(name,1,1)// }// }// set_Visible(),这个需要刷新一下才可以,与上面的call_SetHiddenInGame 连用就ok// var list = ["TemplateLabel","SkySphereBlueprint","AtmosphericFog","SphereReflectionCapture","NetworkPlayerStart","LightSource","PostProcessVolume","SkyLight","EditorCube8","EditorCube9","EditorCube10","EditorCube11","EditorCube12","EditorCube13","EditorCube14","EditorCube15","EditorCube16","EditorCube17","EditorCube18","Floor","Wall1","Wall2","Wall3","Wall4","BigWall","BigWall2","Wall_400x400","Wall_400x401","Wall_400x402","Wall_400x403","Wall_400x404","PointLight","Floor_400x400","Floor_400x401","TextRenderActor","TextRenderActor2","TextRenderActor3","Wall_Door_400x400","SM_Door","TriggerBox","TextRenderActor4","Wall_400x405","Wall_400x406","SM_MERGED_Shape_Pipe_Flag","Plane_Blueprint","Cube","Cube2","Cube3","Wall_400x407","Wall_400x408","TextRenderActor6","TextRenderActor10","Wall_400x409","Wall_400x410","TextRenderActor12","TextRenderActor13","TextRenderActor14","TextRenderActor15","Actor","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","Shape_Pipe_Flag","SM_MERGED_Shape_Pipe_Flag_1_Blueprint2","SM_MERGED_Shape_Pipe_Flag_1_Blueprint3","SM_MERGED_Shape_Pipe_Flag_1_Blueprint","EditorCube19","Shape_Pipe_Flag","TextRenderActor8","TextRenderActor9","TextRenderActor11","TextRenderActor16","Wall_400x411","TextRenderActor17","DefaultPhysicsVolume","FirstPersonGameMode_C","GameSession","ParticleEventManager","GameNetworkManager","FirstPersonExampleMap_C","FirstPersonCharacter_C","GameStateBase","PlayerController","PlayerState","PlayerCameraManager","CameraActor","FirstPersonHUD_C"]// var cnt = -1// for(var i in list){// const name = list[i].trim()// if(name === "Shape_Pipe_Flag"){// console.log(name)// cnt += 1// set_bVisible(name,cnt)// }else{// set_bVisible(name)// }// }}catch(e){console.log(e)}}

我这里的初衷是让 所有StaticMeshActor
类的对象主动调用 SetVisibility
函数,事后,读别人wp发现,似乎直接让Actor类的 RootComponent
成员直接调用 SetVisibility
函数更好,只要设置好 bPropagateToChildren
属性,其所有子组件都能显现出来。用法更宽泛一些。
得到section1: 8939
section2
题目提示了物体为立方体。那我们就直接定位对象名与对应的类。
关注这个类StaticMeshActor
即可,查询大模型,如何设置物体不可穿越属性,让三个Cube(Cube、Cube2、Cube3)一块主动调用SetCollisionResponseToAllChannels
与SetCollisionEnabled
函数即可(实际上只调用SetCollisionEnabled
就ok )。代码实现如下:
function hook_find_actor( find_actor_name,cnt=0){var libUE4_module = Module.findBaseAddress("libUE4.so")// console.log("libUE4_module is :", libUE4_module)var GWorld_Offset = 0x00000000B32D8A8var GName_Offset = 0x00000000B171CC0var GName = libUE4_module.add(GName_Offset);var GWorld = libUE4_module.add(GWorld_Offset).readPointer()var Level_Offset = 0x30var Level = GWorld.add(Level_Offset).readPointer()// console.log("Level :", Level)var Actors_Offset = 0x98var Actors = Level.add(Actors_Offset).readPointer()// console.log("Actors Array :", Actors)var Actors_Num = Level.add(Actors_Offset).add(8).readU32()// console.log("Actors_num :", Actors_Num)var ct = 0for(var index = 0; index < Actors_Num; index++){var actor = Actors.add(index * 8).readPointer()//通过角色actor获取其成员变量FNamevar FName_Offset = 0x18var FName = actor.add(FName_Offset);var FNameEntryAllocator = GNamevar FNamePool = FNameEntryAllocator.add(0x38) // FNamePool//手动解析FNamePoolvar ComparisonIndex = FName.add(0).readU32()var FNameBlockOffsetBits = 16var FNameBlockOffsets = 65536var Block = ComparisonIndex >> FNameBlockOffsetBitsvar Offset = ComparisonIndex & (FNameBlockOffsets - 1)var NamePoolChunk = FNamePool.add(0x8 + Block * 8).readPointer()var FNameEntry = NamePoolChunk.add(Offset * 2)var FNameEntryHeader = FNameEntry.readU16()var isWide = FNameEntryHeader & 1var Len = FNameEntryHeader >> 6// console.log("actor : ", actor, " ", FNameEntry.add(2).readCString(Len))if(0 == isWide){var Name = FNameEntry.add(2).readCString(Len)var actor_addr = actor;if (Name === find_actor_name){if(ct == cnt){// console.log("actor : ", actor, " ", FNameEntry.add(2).readCString(Len))return actor_addr}else{ct++}}}}
}function set_Collision(Actor_Name){try{var base_addr = get_so_base("libUE4.so")var TriggerBox_addr = hook_find_actor(Actor_Name)var ShapeComponent = TriggerBox_addr.add(0x220).readPointer()var a1_0 = ShapeComponent.add(0).readPointer()var no_exec_SetCollisionResponseToAllChannels = a1_0.add(0x850).readPointer()// console.log(`no_exec_SetCollisionResponseToAllChannels ${no_exec_SetCollisionResponseToAllChannels.sub(base_addr)}`) const SetCollisionResponseToAllChannels_pointer = new NativeFunction(no_exec_SetCollisionResponseToAllChannels, 'void', ['pointer','uint8']);const ECR_Block = 2SetCollisionResponseToAllChannels_pointer(ShapeComponent,ECR_Block); // 0x98eb590const QueryAndPhysics = 3const no_exec_SetCollisionEnabled = a1_0.add(0x660).readPointer()const SetCollisionEnabled_pointer = new NativeFunction(no_exec_SetCollisionEnabled,"void",['pointer','uint8'])SetCollisionEnabled_pointer(ShapeComponent,QueryAndPhysics)// console.log(`no_exec_SetCollisionEnabled ${no_exec_SetCollisionEnabled.sub(base_addr)}`) var no_exec_K2_IsQueryCollisionEnabled = a1_0.add(0x510).readPointer()const K2_IsQueryCollisionEnabled = new NativeFunction(no_exec_K2_IsQueryCollisionEnabled,"bool",['pointer'])var ret1 = K2_IsQueryCollisionEnabled(ShapeComponent)// console.log(`K2_IsQueryCollisionEnabled & K2_IsPhysicsCollisionEnabled & K2_IsCollisionEnabled : ${ret1}`)}catch(e){console.log(e)}}
function section2(){try{set_Collision("Cube")set_Collision("Cube2")set_Collision("Cube3")console.log("section2 ~")}catch(e){console.log(e)}
}

得到section2: 008
另外,直接调用SetActorEnableCollision
并没有实现,看来全局Actor的碰撞属性影响不到局部的碰撞属性
section3

函数采用了字符串混淆 + br混淆。
unidbg trace一下后手动patch即可恢复控制流
package com.Tencent_game;import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.debugger.DebuggerType;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;
import com.github.unidbg.virtualmodule.android.AndroidModule;
import java.io.*;public class pri_2024 {public final AndroidEmulator emulator;public final VM vm;public final Memory memory;public final Module module;public pri_2024(){emulator = AndroidEmulatorBuilder.for64Bit().build();memory = emulator.getMemory();memory.setLibraryResolver(new AndroidResolver(23));emulator.getSyscallHandler().setEnableThreadDispatcher(true);vm = emulator.createDalvikVM();new AndroidModule(emulator,vm).register(memory);DalvikModule dalvikModule = vm.loadLibrary(new File("C:\\Users\\27236\\Desktop\\2024_tencent\\mobile_pri\\gamesec2024_signed\\lib\\arm64-v8a\\libplay.so"), true);module = dalvikModule.getModule();
// vm.callJNI_OnLoad(emulator,module);}public static void main(String[] args){pri_2024 mainActivity = new pri_2024();mainActivity.debugger();}public void debugger(){
// emulator.traceCode(0x40000000,0x40010000);emulator.attach(DebuggerType.CONSOLE).addBreakPoint(module.base+ 0x000000000001710);module.callFunction(emulator,0x000000000001458,"tlsn22334455667788");}
}
实际上就一换表后的魔改base64 + xor
给出解密脚本:
def enc():N_str = bytes.fromhex(" 0A 0C 0E 00 51 16 27 38 49 1A 3B 5C 2D 4E 6F FA FC FE")inp = b"tlsn22334455667788"cip1 = []for i in range(len(N_str)):cip1.append(inp[i] ^ N_str[i])cip1 = bytes(cip1)base64en = b"ACE0BDFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456789+/"inp = cip1enc = bytearray(b"\x00" * 0x18)idx = 0v10 = 0cnt = 0lenn = len(inp)while v10+1 < lenn:v10 = idx + 2;v8 = (inp[idx ] << 16) | (inp[idx + 1] << 8) | inp[idx + 2];v9 = base64en[(v8 >> 12) & 0x3F];enc[cnt*4+0] = base64en[v8 >> 0x12];enc[cnt*4+1] = v9;enc[cnt*4+2] = base64en[(v8 >> 6) & 0x3F];enc[cnt*4+3] = base64en[v8 & 0x3F];idx += 3cnt += 1assert enc == bytearray(b'elC9alLjDAs9Kf5oF3gXybSF')def dec(cip):base64en = b"ACE0BDFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456789+/"debase64 = []for i in range(0,len(cip),4):x1 = base64en.find(cip[i+0])x2 = base64en.find(cip[i+1])x3 = base64en.find(cip[i+2])x4 = base64en.find(cip[i+3])v8 = (x1 << 0x12) | (x2 << 12) | (x3 << 6) | x4debase64.append( (v8 >> 16) & 0xff )debase64.append( (v8 >> 8) & 0xff )debase64.append( v8 & 0xff )N_str = bytes.fromhex(" 0A 0C 0E 00 51 16 27 38 49 1A 3B 5C 2D 4E 6F FA FC FE")inp = debase64cip1 = []for i in range(len(N_str)):cip1.append(inp[i] ^ N_str[i])cip1 = bytes(cip1)return cip1enc()
assert dec(b"elC9alLjDAs9Kf5oF3gXybSF") == b"tlsn22334455667788"m = dec(b"UT1fc0gIYDArdz80Z0Xem46J")
print(m)
print(len(m))
得到section3: _Anti_Cheat_Expert
最后的flag: FLAG{8939008_Anti_Cheat_Expert}
我的问题
-
frida UE4反射,如果能反射的话,主动调用就很简单了。
-
自动化去br混淆
-
如何用frida锁定某内存值