2024 腾讯游戏安全大赛 mobile 初赛 wp
找关键结构体
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 = 0x00000000B32D8A8
var GName_Offset = 0x00000000B171CC0
var GName = libUE4_module.add(GName_Offset);
var GWorld = libUE4_module.add(GWorld_Offset).readPointer()
var Level_Offset = 0x30
var Level = GWorld.add(Level_Offset).readPointer()
// console.log("Level :", Level)
var Actors_Offset = 0x98
var 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 = 0
for(var index = 0; index < Actors_Num; index++){
var actor = Actors.add(index * 8).readPointer()
//通过角色actor获取其成员变量FName
var FName_Offset = 0x18
var FName = actor.add(FName_Offset);
var FNameEntryAllocator = GName
var FNamePool = FNameEntryAllocator.add(0x38) // FNamePool
//手动解析FNamePool
var ComparisonIndex = FName.add(0).readU32()
var FNameBlockOffsetBits = 16
var FNameBlockOffsets = 65536
var Block = ComparisonIndex >> FNameBlockOffsetBits
var 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 & 1
var 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 = 0x00000000B32D8A8
var GName_Offset = 0x00000000B171CC0
var GName = libUE4_module.add(GName_Offset);
var GWorld = libUE4_module.add(GWorld_Offset).readPointer()
var Level_Offset = 0x30
var Level = GWorld.add(Level_Offset).readPointer()
// console.log("Level :", Level)
var Actors_Offset = 0x98
var 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 = 0
for(var index = 0; index < Actors_Num; index++){
var actor = Actors.add(index * 8).readPointer()
//通过角色actor获取其成员变量FName
var FName_Offset = 0x18
var FName = actor.add(FName_Offset);
var FNameEntryAllocator = GName
var FNamePool = FNameEntryAllocator.add(0x38) // FNamePool
//手动解析FNamePool
var ComparisonIndex = FName.add(0).readU32()
var FNameBlockOffsetBits = 16
var FNameBlockOffsets = 65536
var Block = ComparisonIndex >> FNameBlockOffsetBits
var 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 & 1
var 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){ // 确实能隐藏物体,比如说Cube try{ 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.Object var 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 = 0x00000000B32D8A8
var GName_Offset = 0x00000000B171CC0
var GName = libUE4_module.add(GName_Offset);
var GWorld = libUE4_module.add(GWorld_Offset).readPointer()
var Level_Offset = 0x30
var Level = GWorld.add(Level_Offset).readPointer()
// console.log("Level :", Level)
var Actors_Offset = 0x98
var 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 = 0
for(var index = 0; index < Actors_Num; index++){
var actor = Actors.add(index * 8).readPointer()
//通过角色actor获取其成员变量FName
var FName_Offset = 0x18
var FName = actor.add(FName_Offset);
var FNameEntryAllocator = GName
var FNamePool = FNameEntryAllocator.add(0x38) // FNamePool
//手动解析FNamePool
var ComparisonIndex = FName.add(0).readU32()
var FNameBlockOffsetBits = 16
var FNameBlockOffsets = 65536
var Block = ComparisonIndex >> FNameBlockOffsetBits
var 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 & 1
var 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 = 2
SetCollisionResponseToAllChannels_pointer(ShapeComponent,ECR_Block); // 0x98eb590
const QueryAndPhysics = 3
const 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 = cip1
enc = bytearray(b"\x00" * 0x18)
idx = 0
v10 = 0
cnt = 0
lenn = 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 += 3
cnt += 1
assert 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) | x4
debase64.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 = debase64
cip1 = []
for i in range(len(N_str)):
cip1.append(inp[i] ^ N_str[i])
cip1 = bytes(cip1)
return cip1
enc()
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锁定某内存值