2024TencentGameAndroid初赛复现
0x0 引言
通过查看这两位师傅的文章代码学习,进行复现
https://www.52pojie.cn/forum.php?mod=viewthread&tid=1930752
http://www.yxfzedu.com/article/10613
0x1 dump ue4 SDK
搜索ue4+

得到ue4.27版本
https://github.com/hackcatml/frida-ue4dump
下载dump 工具

frida-ue4dump可以直接获得GNames,但是获取GUObject出错了
GNames是0xB171CC0
libUE4.so搜索Max UObject count is invalid. It must be a number that is greater than 0.

用交叉引用找到sub_6FE10CC ,再查sub_6FE10CC的交叉引用,看到sub_6FE4C54。这里给sub_6FE10CC的a1传参是&dword_B1B5F98因此GUObject是0xB1B5F98

同理,在虚幻引擎源代码项目文件夹和libUE4.so中搜索SeamlessTravel FlushLevelStreaming


GNames是0xB171CC0
GWorld的是0xB32D8A8
GUObject是0xB1B5F98修改frida-ue4dump中的script.js片段,手工指定GUObject的偏移即可获取SDK
function findGUObjectArray1(moduleBase) {
        console.log("[*] Using predefined GUObjectArray offset.");
        // 已知的偏移地址
        var offset = ptr(0xB1B5F98); // 直接使用已知的偏移
        // 计算 GUObjectArray 的实际地址
        GUObjectArray = moduleBase.add(offset);
        
        if (GUObjectArray.isNull()) {
            console.log("[!] GUObjectArray is null, please check if the address is correct.");
            return;
        }
        console.log(`[*] Got GUObjectArray at: ${GUObjectArray}`);
    
}输出内容很多 重定向输出到文本中
frida -U -f com.tencent.ace.match2024 -l .\script.js > sdkdump.txt
https://github.com/revercc/UE4Dumper?tab=readme-ov-file
也可以用ue4dumper来dump

0x2 关卡内容

总共4关
0x3 section0_逃出房间
尝试走出房间,会重生回房间,多试几次观察细节发现是生命值被清空了

找到人物血量内存位置 修改血量
SDK搜索FirstPersonCharacter 找到人物属性

对照两份看,发现 float widestr 是生命值 偏移0x510

编写frida hook脚本
var GName_Offset = 0xB171CC0;
var GWorld_Offset = 0xB32D8A8;
var GUObjectArray_Offset = 0xB1B5F98;
var playerName = "FirstPersonCharacter_C";
var moduleBase;
var GWorld;
var GName;
var GUObjectArray;
// 对象在GUObjectArray中的序号
var offset_UObject_InternalIndex =0xC;
// 对象的类
var offset_UObject_ClassPrivate = 0X10;
// 对象名称在FName表中的索引
var offset_UObject_FNameIndex = 0x18;
// 指向包含该对象的外部对象,表示层次关系
var offset_UObject_OuterPrivate = 0x20;
var offset_UObject_InternalIndex = 0xC;
//指向描述对象类的 UClass 对象
var offset_UObject_ClassPrivate = 0x10;
//对象名称在 FName 表中的索引
var offset_UObject_FNameIndex = 0x18;
//指向包含该对象的外部对象,表示层次关系。
var offset_UObject_OuterPrivate = 0x20;
var UObject={
 getClass: function(obj){
   var classPrivate=ptr(obj).add(offset_UObject_ClassPrivate).readPointer();//读取指针
   //console.log(`classPrivate: ${classPrivate}`);
   return classPrivate;
 },
getNameId: function(obj){
 //console.log(`obj:${obj}`);
 try{
   var nameId=ptr(obj).add(offset_UObject_FNameIndex).readU32();//读取4字节。
   //console.log(`nameId:${nameId}`);
   return nameId;
 }catch(e){
   console.log("error")
   return 0;
 }
},
getName: function(obj) {
 if (this.isValid(obj)){
     return getFNameFromID(this.getNameId(obj));
 } else {
     return "None";
 }
},
getClassName: function(obj) {
 if (this.isValid(obj)) {
     var classPrivate = this.getClass(obj);
     return this.getName(classPrivate);
 } else {
     return "None";
 }
},
isValid: function(obj) {
 var isValid = (ptr(obj) > 0 && this.getNameId(obj) > 0 && this.getClass(obj) > 0);
 // console.log(`isValid: ${isValid}`);
 return isValid;
}
}
function getFNameFromID(index) {
    // FNamePool相关偏移量和步长
    var FNameStride = 0x2;                   // FNameEntry 的步长,每个FNameEntry占用2字节
    var offset_GName_FNamePool = 0x30;       // GName 到 FNamePool 的偏移量
    var offset_FNamePool_Blocks = 0x10;      // FNamePool 到 Blocks 的偏移量
    // FNameEntry相关偏移量和位
    var offset_FNameEntry_Info = 0;          // FNameEntry 到 Info 的偏移量
    var FNameEntry_LenBit = 6;               // FNameEntry 长度位
    var offset_FNameEntry_String = 0x2;      // FNameEntry 到字符串部分的偏移量
    // 计算块和偏移量
    var Block = index >> 16;                 // 块索引
    var Offset = index & 65535;              // 块内偏移量
    // 获取FNamePool的起始地址
    var FNamePool = GName.add(offset_GName_FNamePool);
    // console.log(`FNamePool: ${FNamePool}`);
    // console.log(`Block: ${Block}`);
    // 获取特定块的地址
    var NamePoolChunk = FNamePool.add(offset_FNamePool_Blocks + Block * 8).readPointer();
    // console.log(`NamePoolChunk: ${NamePoolChunk}`);
    // 计算FNameEntry的地址
    var FNameEntry = NamePoolChunk.add(FNameStride * Offset);
    // console.log(`FNameEntry: ${FNameEntry}`);
    try {
        // 读取FNameEntry的Header
        if (offset_FNameEntry_Info !== 0) {
            var FNameEntryHeader = FNameEntry.add(offset_FNameEntry_Info).readU16();     
        } else {
            var FNameEntryHeader = FNameEntry.readU16();
        }
    } catch(e) {
        // 捕捉读取异常并返回空字符串
        // console.log(e);
        return "";
    }
    // console.log(`FNameEntryHeader: ${FNameEntryHeader}`);
    // 获取字符串地址
    var str_addr = FNameEntry.add(offset_FNameEntry_String);
    // console.log(`str_addr: ${str_addr}`);
    // 计算字符串长度和宽度
    var str_length = FNameEntryHeader >> FNameEntry_LenBit; // 计算字符串长度
    var wide = FNameEntryHeader & 1;                       // 判断字符串是否为宽字符
    // 如果是宽字符,返回 "widestr"
    if (wide) return "widestr";
    // 如果字符串长度合理,读取并返回UTF-8字符串
    if (str_length > 0 && str_length < 250) {
        var str = str_addr.readUtf8String(str_length);
        return str;
    } else {
        return "None"; // 长度不合理,返回 "None"
    }
}
//获取对象实例
function getActorAddr(str){
    var player_addr;
    var actorsAddr=getActorsAddr();
    for(var key in actorsAddr){
      if(key==str){
        console.log(actorsAddr[key]);
        player_addr=actorsAddr[key];
      }
    }
    if(player_addr==null)
      {
        console.log("null pointer!");
      }
    return player_addr;
  }
  function getActorsAddr(){
    var Level_Offset=0x30//偏移
    var Actors_Offset=0x98
    var Level=GWorld.add(Level_Offset).readPointer()//读取GWorld的level指针
    var Actors=Level.add(Actors_Offset).readPointer()//读取Actors的指针
    var Actors_Num=Level.add(Actors_Offset).add(8).readU32()//获取Actor的数量
    var actorsAddr={};//空对象,下面的实现类似字典
    for(var index=0;index<Actors_Num;index++)
      {
        var actor_addr=Actors.add(index*8).readPointer()//读取当前索引处的Actor地址
        var actorName=UObject.getName(actor_addr)//通过地址获取字符串名字
        actorsAddr[actorName]=actor_addr;//以字符串名字对应地址值
        //console.log(`actors[${index}]`,actorName);
      }
      return actorsAddr;
  }
function setPlayerHP(hp = 1000000){
    //生命属性偏移
    getActorAddr(playerName).add(0x510).writeFloat(hp);
}
function set(moduleName){
    //获取libUE4的基地址
    moduleBase=Module.findBaseAddress(moduleName);
  
    //UE4基地址加上在IDA中获取的GName偏移获取GName地址
    GName=moduleBase.add(GName_Offset);
  
    //同上获取GUObjectArray的地址
    GUObjectArray=moduleBase.add(GUObjectArray_Offset);
  
  //读取GWorld的指针
    GWorld =moduleBase.add(GWorld_Offset).readPointer();
  
  }成功走出房间

0x4 section1_空中flag
出来抬头可以看见空中有flag{}

获取自己位置
//坐标
class Vector{
    constructor(x,y,z){
        this.x = x;
        this.y = y;
        this.z = z;
    }
    toString(){
        return `(${this.x}, ${this.y}, ${this.z})`;
    }
}
function dumpVector(addr){
    // dumpAddr('firstPersion_RootComponent',firstPersion_RootComponent_ptr,0x152)
    // 从地址空间中读取三个浮点数
    const values = Memory.readByteArray(addr, 3 * 4);  // 3个float共占12个字节
    // 解析浮点数并初始化 Vector 对象
    const vec = new Vector(
        new Float32Array(values, 0, 1)[0],  // 读取第一个浮点数
        new Float32Array(values, 4, 1)[0],  // 读取第二个浮点数
        new Float32Array(values, 8, 1)[0]   // 读取第三个浮点数
    );
    console.log('[+] 坐标:',vec);//打出坐标。
  }
//用ue4 api获取坐标
function getActorLocation(actor_addr){
    actor_addr = ptr(actor_addr)
    var buf = Memory.alloc(0x100);
    var f_addr = moduleBase.add(0x965ddf8);//ue4中的api K2_GetActorLocation的偏移
      // 将目标函数地址转换为JavaScript函数
    var getLocationFunc = new NativeFunction(f_addr, 'void', ['pointer','pointer','pointer']);
  
    // 调用目标函数并传递内存地址作为参数
    try{
        getLocationFunc(actor_addr,buf,buf);
        dumpVector(buf);
        return buf;
        //info(ptr(actor_addr).add(0x130).readPointer().add(0x14c).readU8()&32 != 0);
    }
    catch (e){
    }
}[Pixel 6::com.tencent.ace.match2024 ]-> getActorLocation(getActorAddr(playerName))
0x6e6c4d7120
[+] 坐标: (-1264.872314453125, -347.19818115234375, 268.3692321777344)
"0x71c7474aa0"function setActorLocation(actor_addr,x,y,z){
    actor_addr = ptr(actor_addr)
    var f_addr = moduleBase.add(0x8C3181C);//加上偏移获取目标函数的偏移 K2_SetActorLocation
      // 将目标函数地址转换为JavaScript函数
    var setLocationFunc = new NativeFunction(f_addr, 'bool', ['pointer','bool','pointer','bool','float','float','float']);
    // 调用目标函数并传递内存地址作为参数
    setLocationFunc(actor_addr,0,ptr(0),0,x,y,z);
  
}第一个房间得到提示,天空flag被隐藏了

搜索找到设置隐藏的api

function setActorVisibility(actor_addr,NewHidden=1,bPropagateToChildren=2){
    var f_addr = moduleBase.add(0x8E619BC);
    let setActorHiddenFunc = new NativeFunction(f_addr, 'void', ['pointer','char','char']);
    setActorHiddenFunc(ptr(actor_addr).add(0x130).readPointer(),NewHidden,bPropagateToChildren);
}
function dumpActorInstances(){
    GWorld = moduleBase.add(GWorld_Offset).readPointer();
    var Level_Offset = 0x30
    var Actors_Offset = 0x98
 
    var Level = GWorld.add(Level_Offset).readPointer()
    var Actors = Level.add(Actors_Offset).readPointer()
    var Actors_Num = Level.add(Actors_Offset).add(8).readU32()
    var actorsInstances = {};
    for(var index = 0; index < Actors_Num; index++){
        var actor_addr = Actors.add(index * 8).readPointer()
        var actorName = UObject.getName(actor_addr)
        actorsInstances[index] = actorName;
        //info(`actors[${index}]:${actor_addr}`,actorName);
        getActorLocation(actor_addr);
        try{
            setActorVisibility(actor_addr)
        }
        catch(e){
 
        }
    }
}遍历全部Actor全部显示

0x5 section2_让立方体变得不可穿透
使物品有碰撞属性
Class: StaticMeshComponent.MeshComponent.PrimitiveComponent.SceneComponent.ActorComponent.Object调用它基类PrimitiveComponent.SceneComponent.ActorComponent.Object的SetCollisionEnabled方法
function SetCollisionEnable(actor_addr,dr=3) {
  var f_addr=moduleBase.add(0x933b300);
  let CallFunc=new NativeFunction(f_addr,'void',['pointer','int']);
  CallFunc(ptr(actor_addr).add(0x130).readPointer(),dr);
}
0x6 section3_查找黄色小球的类

SDK中搜索flag字样

发现一个getlastflag函数

调用了libplay.so中的get_last_flag
异或出来发现base表

回到开头,这里应该是密文和一个数组

sub_192c进去

经过计算可能会跳转到ea8 或 ed8 都进入查看

a8是异或

d8是返回
那我们将上面的数据base64变表解密再异或解密尝试
最终得到 _Anti_Cheat_Expert
flag
FLAG{8939008_Anti_Cheat_Expert}
 
                     
                     
                        
                        