2024 腾讯游戏安全大赛 mobile 初赛 wp

找关键结构体

https://www.cnblogs.com/revercc/p/17641855.html

找GWORLD

https://bbs.kanxue.com/thread-280042.htm

image-20250322111902706

可以发现是 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即可

image-20250324102128421

网上翻即可找到 GWorld

image-20250324102208236

对应地址: 0x00000000B32D8A8

找GNAME

直接搜 ByteProperty 字符串即可,函数的第一个参数即为GNAME,交叉应用一下即可拿到GNAME

image-20250324102327051

对应地址: 0x00000000B171CC0

找GUObjectArray

直接搜 CloseDisregardForGC字符串即可,下面就是GUObjectArray

image-20250324102242125

0x00000000B1B5F98

dump关键信息

查看ue4版本

参考: https://www.52pojie.cn/thread-1838396-1-1.html

image-20250324105327482

然鹅并没有查到版本,仿佛题目作者专门抹去了ue4的版本号,不过经过测试可以坑底的是一个较高版本的ue4


做完事后,看了别人的wp,原来得搜utf16编码的字符串才能看到

image-20250324170343593

点击setup后勾选这个:

image-20250324170314353

就能看到了

image-20250324170236418

设置之后,前面定位关键信息也都能直接搜到了

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 函数,实际上也确实如此。

image-20250324110324555

image-20250324110927126

实际上我们直接调用 内部的不包裹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的生命值

image-20250324111300908

原本的生命值是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)

image-20250324111729118

直接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类或者继承了这个类。因此我打算从这个类入手

    image-20250324145753332

    如下方代码所示,我在 StaticMeshComponent类中找到了两个api: SetHiddenInGameSetActorHiddenInGame,我让所有的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得到了这样的信息

image-20250324150355600

通过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)
        
    }

}
image-20250324151004760

我这里的初衷是让 所有StaticMeshActor类的对象主动调用 SetVisibility函数,事后,读别人wp发现,似乎直接让Actor类的 RootComponent成员直接调用 SetVisibility函数更好,只要设置好 bPropagateToChildren属性,其所有子组件都能显现出来。用法更宽泛一些。

image-20250324152101081

image-20250324151948748

得到section1: 8939

section2

题目提示了物体为立方体。那我们就直接定位对象名与对应的类。

image-20250324152457291

image-20250324152509648

关注这个类StaticMeshActor即可,查询大模型,如何设置物体不可穿越属性,让三个Cube(Cube、Cube2、Cube3)一块主动调用SetCollisionResponseToAllChannelsSetCollisionEnabled函数即可(实际上只调用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)
    }
}
image-20250324152757946

得到section2: 008

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

section3

image-20250324155657518

image-20250324155707746

image-20250324155604018

image-20250324155818362

image-20250324164601190

函数采用了字符串混淆 + 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");

    }
}

image-20250324164707815

image-20250324164718296

image-20250324164738575

实际上就一换表后的魔改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锁定某内存值

posted @ 2025-03-24 17:19  TLSN  阅读(306)  评论(0)    收藏  举报