腾讯游戏安全竞赛2024初赛体验

腾讯游戏安全竞赛2024初赛

安卓赛道

安卓APK拿到先反编译拆包看看

非常明显的虚幻4打包,那么主要逻辑就都在libUE4.so里面了,这里还隐去了游戏引擎版本

游戏本身对于libUE4.so没有做什么反编译操作,直接拖到IDA里(好大,好慢)

section0

进入游戏会发现到门口会自动开门,但是任何与墙体的碰撞都会导致血量清空重生,而门的宽度又不足以在没有精准外挂的情况下通过,所以大概有四种思路通过门

  1. 干掉碰撞逻辑,碰了等于没碰
  2. 锁血,随便你碰反正血掉不了一点
  3. 世界渲染把门口距离改大(或者直接移除墙体),直接过去
  4. 外挂直接控制角色移动

下一步就是去找这些操作所对应的函数进行hook,那么可以去反编译的内容中找找特定函数名字符串尝试进一步寻找到函数地址进行hook

发现游戏内相关函数都是匿名的,那么搜索相关字符串

门相关字符串,交叉引用一下

找到三个引用点,依次进去查看

发现这个奇怪的结构

x地址为函数名地址,x+8为函数地址,继续交叉引用对应名字位置

发现这个函数

其中结尾处调用了函数名地址,后面跟了一个数字,而这个数字和之前奇怪结构里面存在的函数数量刚好相同,盲猜一波这个是从头开始注册函数的一个东西,而又了解到UE4的蓝图使用了一些类反射相关的技术,那么来hook一下sub_6E733A8

hook脚本如下,hook了dlopen是因为发现直接spawn启动太早UE4还没加载

function hook_ue4(){
    const moduleName = 'libUE4.so'
    let baseAddr = Module.findBaseAddress(moduleName)
    let sub_fun = baseAddr.add(0x6E733A8)
    Interceptor.attach(sub_fun, {
        onEnter: function (args) {
        	

        },
        onLeave: function (retval) {
            // console.log(retval);
            // console.log(retval++)

        },
    })
}
function hook_dlopen(){
    var dlopen = Module.findExportByName(null, "android_dlopen_ext")
    var hookingUE4=false
    Interceptor.attach(dlopen,{
        onEnter: function (args) {
            var a=ptr(args[0]).readCString().toString()
            if(a.indexOf("libUE4.so")!=-1){
                hookingUE4=true
                console.log("hook_dlopen: ",ptr(args[0]).readCString());
            }
        },
        onLeave:function (rev){
            if(hookingUE4){
                hook_ue4()
                hookingUE4=false
            }
        }
    })
}
hook_dlopen()

可以看到打印出了大量的函数名,鉴于之前的结构我们可以直接将函数地址找到,修改对应hook脚本如下

onEnter: function (args) {
    var stadd=args[1]
    var funcadd=stadd.add(8).readPointer()
    for(var i=1;i<=args[2].toInt32();i++){
        console.log(stadd.readPointer().readCString().toString())
        console.log(funcadd.sub(baseAddr))
        stadd=stadd.add(16)
        funcadd=stadd.add(8).readPointer()
    }
},

这里有一个很有意思的点,部分函数在角色死亡重生后会再次进入这个类似函数加载的函数,并且其中的属性也与角色高度绑定,所以可以先从这些和角色有关的函数下手

可以搜索到一个叫ReceiveHit的函数,直接干掉他看能不能干掉撞墙逻辑,这里要去对应的0x5e95000地址和0x5e958e4看一下参数和返回值逻辑,但是鉴于复活后只加载了后者所以我们进后者

                if(stadd.readPointer().readCString().toString().indexOf("ReceiveHit")!=-1){
                    hook_hit(stadd.readPointer().readCString().toString(),funcadd,baseAddr)
                }

function hook_hit(funcName,func,baseAddr){
    console.log(funcName,func.sub(baseAddr),"hook_hit")
    Interceptor.replace(func,new NativeCallback(function(a1,a2){
    },"void",["int64","pointer"]))
}

然后就出来了

section1

思路是hook掉世界加载渲染相关函数

section2

拿GWorld拿Object改属性(脚本没找到)

section3

直接搜就能搜到一个函数,在libplay.so里面

跟出大量异或操作,尝试打印附近内存

function hook_play(){
    let baseAddr = Process.findModuleByName("libplay.so").base
    let dataaddr = ptr(baseAddr).add(0x3DE0)
    console.log(hexdump(dataaddr))
}

base64换表解码后异或![截屏2024-04-14 23.39.54](/Users/jlan/Library/Application Support/typora-user-images/截屏2024-04-14 23.39.54.png)