2025第十届游戏安全竞赛-初赛-安卓客户端安全

  1. 2025第十届游戏安全竞赛-初赛-安卓客户端安全
    1. 一、前言
    2. 二、开始
      1. 异常点 1
      2. 异常点 2、3
      3. 修复

2025第十届游戏安全竞赛-初赛-安卓客户端安全

一、前言

启动游戏,可以明显发现游戏存在加速、自瞄、透视等问题。同时看 apk 图标也可知这是用虚幻引擎写的。那么首先先判断虚幻引擎版本,可以直接在 AndroidManifest.xml 中找到 UE4.27
image-20250723100202404

对所有 so 文件进行分析。首先用偷懒的方法,将所有文件通过 Virustotal 计算一下 hash 查看上次上传时间可知,除 libUE4.so 和 libGame.so 都曾经上传过,是标准库,可以不用看,那么就分析这两个文件就好了 。

参考文章,分别找到libUE4.so中三个核心参数的偏移

GWorld 0xAFAC398
GName 0xADF07C0
GUObject 0xAE34A98

然后使用UE4Dumper对SDK进行提取,再进一步分析

二、开始

对 libGame.so 进行分析,可以看到该文件对函数使用了控制流平坦化混淆,利用 IDA 的插件 D-810 即可有效去除混淆进行分析。留意到有几个异或函数对字符串进行了加密,直接手动解出来并进行标注。

这个 so 文件通过 .init_array 调用函数,通过 pthread_create 创建新线程,在 0x1B9C 通过 /proc/self/maps 等方法获取 libUE4.so 的基址,然后在下面通过基址 + 偏移计算得到 UE4 中关键参数的地址。

void __fastcall sub_1B9C(__int64 a1)
{
  int n19629; // w8
  __int64 *v2; // x8
  int n18756; // w8
  __int64 *v4; // x8
  int n26308; // w8
  int n11323; // w8
  int n22929; // w8
  int n2306; // w8
  int n28745; // w8
  int n26777; // w8
  int n4031; // w8
  __int64 *v12; // x9
  int n22355; // w8
  __int64 *v14; // x9
  _BYTE v15[4]; // [xsp+0h] [xbp-150h] BYREF
  int n15350_1; // [xsp+4h] [xbp-14Ch]
  __int64 *v17; // [xsp+8h] [xbp-148h]
  __int64 v18; // [xsp+10h] [xbp-140h]
  int n15350; // [xsp+1Ch] [xbp-134h]
  unsigned __int64 *v20; // [xsp+20h] [xbp-130h]
  unsigned int *v21; // [xsp+28h] [xbp-128h]
  unsigned int *v22; // [xsp+30h] [xbp-120h]
  _BYTE *v23; // [xsp+38h] [xbp-118h]
  _QWORD *v24; // [xsp+40h] [xbp-110h]
  __int64 *v25; // [xsp+48h] [xbp-108h]
  int v26; // [xsp+50h] [xbp-100h]
  bool v27; // [xsp+57h] [xbp-F9h]
  __int64 ____; // [xsp+58h] [xbp-F8h]
  bool v29; // [xsp+67h] [xbp-E9h]
  __int64 baseAddr; // [xsp+68h] [xbp-E8h]
  __int64 *GWorld_Addr; // [xsp+70h] [xbp-E0h]
  __int64 GWorld_Addr1; // [xsp+78h] [xbp-D8h]
  __int64 level; // [xsp+80h] [xbp-D0h]
  bool v34; // [xsp+8Fh] [xbp-C1h]
  unsigned __int64 n0x10000; // [xsp+90h] [xbp-C0h]
  bool v36; // [xsp+9Fh] [xbp-B1h]
  __int64 v37; // [xsp+A0h] [xbp-B0h] BYREF
  unsigned int i; // [xsp+B4h] [xbp-9Ch]
  unsigned int actorsCount; // [xsp+B8h] [xbp-98h]
  bool v40; // [xsp+BFh] [xbp-91h]
  unsigned int v41; // [xsp+CCh] [xbp-84h]
  bool v42; // [xsp+DFh] [xbp-71h]
  int v43; // [xsp+F0h] [xbp-60h]
  bool v44; // [xsp+F7h] [xbp-59h]
  unsigned int v45; // [xsp+120h] [xbp-30h]
  unsigned int v46; // [xsp+124h] [xbp-2Ch]
  __int64 v47; // [xsp+148h] [xbp-8h]

  v17 = &v37;
  v18 = a1;
  v47 = *(_ReadStatusReg(TPIDR_EL0) + 0x28);
  v20 = &v15[-16];
  n15350 = 15350;
  while ( 1 )
  {
    while ( 1 )
    {
      n15350_1 = n15350;
      if ( n15350 != 1150 )
        break;
      *v22 = v46;
      n15350 = 16941;
    }
    switch ( n15350_1 )
    {
      case 2082:
        if ( v36 )
          n22929 = 22929;
        else
          n22929 = 16541;
        n15350 = n22929;
        break;
      case 2306:
        v17[4] = *v20;
        v41 = *v22;
        n15350 = 31673;
        break;
      case 3430:
        usleep(0x2710u);
        n15350 = 31107;
        break;
      case 3548:
        if ( v27 )
          n19629 = 19629;
        else
          n19629 = 3430;
        n15350 = n19629;
        break;
      case 4031:
        n15350 = 12052;
        break;
      case 4639:
        n15350 = 29658;
        break;
      case 4833:
        v17[1] = *v17;                          // 拿到 ActorsCount 指针
        n15350 = 31115;
        break;
      case 4966:
        GWorld_Addr1 = *GWorld_Addr;
        n15350 = 7376;
        break;
      case 5021:
        if ( v42 )
          n28745 = 28745;
        else
          n28745 = 26924;
        n15350 = n28745;
        break;
      case 5097:
        if ( v44 )
          n4031 = 4031;
        else
          n4031 = 16512;
        n15350 = n4031;
        break;
      case 5537:
        *v20 = *(v17[17] + 0x98);               // Actors
        n15350 = 21538;
        break;
      case 5829:
        if ( *v24 - v17[19] == 0xA63BE28 )
          n26777 = 26777;
        else
          n26777 = 4031;
        n15350 = n26777;
        break;
      case 6270:
        *v24 = v17[9];
        n15350 = 5829;
        break;
      case 7376:
        v4 = v17;
        v17[18] = GWorld_Addr1;
        if ( v4[18] >= 0x10000 )
          n26308 = 26308;
        else
          n26308 = 13931;
        n15350 = n26308;
        break;
      case 9161:
        if ( v17[12] >= 0x10000 )
          n22355 = 22355;
        else
          n22355 = 18636;
        n15350 = n22355;
        break;
      case 9930:
        v40 = i < actorsCount;
        n15350 = 13977;
        break;
      case 11323:
        n15350 = 31101;
        break;
      case 11840:
        GWorld_Addr = (baseAddr + 0xAFAC398);   // GWorld
        n15350 = 4966;
        break;
      case 12052:
        v45 = *v22;
        n15350 = 27350;
        break;
      case 12623:
        ____ = sub_B80(0xFFFFFFFF, &unk_B658);  // 获取基址
        n15350 = 24084;
        break;
      case 13290:
        v12 = v17;
        *v25 = *(*v23 + 0x288LL);
        v12[12] = *v25;
        n15350 = 9161;
        break;
      case 13931:
        n15350 = 31101;
        break;
      case 13966:
        n15350 = 3430;
        break;
      case 13977:
        if ( v40 )
          n2306 = 2306;
        else
          n2306 = 21724;
        n15350 = n2306;
        break;
      case 15006:
        v24 = v15;
        v25 = &v15[-16];
        v17[20] = v18;
        n15350 = 31101;
        break;
      case 15350:
        v21 = v15;
        v22 = v15;
        v23 = &v15[-16];
        n15350 = 15006;
        break;
      case 15573:
        v44 = v43 != 0;
        n15350 = 5097;
        break;
      case 15574:
        *(v17[15] + 0x18C) = 0x4E6E6B28;
        dword_B61C = 1;
        n15350 = 21724;
        break;
      case 16118:
        v36 = n0x10000 < 0x10000;
        n15350 = 2082;
        break;
      case 16512:
        v17[11] = *v23 + 0x538LL;
        n15350 = 23986;
        break;
      case 16541:
        *v17 = v17[17] + 0xA0;                  // ActorsCount
        n15350 = 4833;
        break;
      case 16941:
        n15350 = 29658;
        break;
      case 16944:
        v17[17] = level;
        n15350 = 32439;
        break;
      case 18636:
        n15350 = 12052;
        break;
      case 18756:
        baseAddr = v17[19];
        n15350 = 11840;
        break;
      case 19072:
        v17[9] = *v17[8];
        n15350 = 6270;
        break;
      case 19629:
        sub_30A0(&unk_B658, &unk_890);          // 异或解出来是 libUE4.so
        n15350 = 12623;
        break;
      case 19954:
        if ( v29 )
          n18756 = 18756;
        else
          n18756 = 13966;
        n15350 = n18756;
        break;
      case 21538:
        n0x10000 = *v20;
        n15350 = 16118;
        break;
      case 21724:
        n15350 = 13966;
        break;
      case 22355:
        v17[13] = *v25 + 0x1A0;
        n15350 = 24767;
        break;
      case 22386:
        *v23 = *(v17[4] + 8 * v17[6]);          // actor = actors[i]
        v42 = *v23 < 0x10000u;
        n15350 = 5021;
        break;
      case 22704:
        actorsCount = *v21;
        n15350 = 9930;
        break;
      case 22929:
        n15350 = 31101;
        break;
      case 23655:
        v14 = v17;
        *v17[14] = 0x4E6E6B28;
        v14[15] = *v25;
        n15350 = 15574;
        break;
      case 23986:
        *v17[11] = 0;
        n15350 = 13290;
        break;
      case 24084:
        v2 = v17;
        v17[19] = ____;
        v29 = v2[19] > 0x10000;
        n15350 = 19954;
        break;
      case 24393:
        v27 = v26 == 1;
        n15350 = 3548;
        break;
      case 24626:
        if ( v34 )
          n11323 = 11323;
        else
          n11323 = 5537;
        n15350 = n11323;
        break;
      case 24767:
        v17[14] = v17[13];
        n15350 = 23655;
        break;
      case 26308:
        level = *(v17[18] + 0x30);              // level
        n15350 = 16944;
        break;
      case 26777:
        v43 = dword_B61C;
        n15350 = 15573;
        break;
      case 26924:
        v17[8] = *v23;
        n15350 = 19072;
        break;
      case 27350:
        v46 = v45 + 1;
        n15350 = 1150;
        break;
      case 28745:
        n15350 = 12052;
        break;
      case 29658:
        i = *v22;
        n15350 = 22704;
        break;
      case 31101:
        v26 = dword_B618;
        n15350 = 24393;
        break;
      case 31107:
        n15350 = 31101;
        break;
      case 31115:
        *v21 = *v17[1];                         // 拿到 ActorsCount 的值
        *v22 = 0;
        n15350 = 4639;
        break;
      case 31673:
        v17[6] = v41;
        n15350 = 22386;
        break;
      case 32439:
        v34 = v17[17] < 0x10000;
        n15350 = 24626;
        break;
    }
  }
}

在下面通过遍历 Actors 中的元素,找到所要的 Actor 后,通过偏移计算找到对应要修改的参数

case 11840:
    GWorld_Addr = (baseAddr + 0xAFAC398);   // GWorld
    n15350 = 4966;

case 4966:
    GWorld_Addr1 = *GWorld_Addr;
    n15350 = 7376;

case 7376:
    v17[18] = GWorld_Addr1;
    if ( v17[18] >= 0x10000 )
        n15350 = 26308;
    else
        n15350 = 13931;

case 26308:
    level = *(v17[18] + 0x30);              // 获取 Level
    n15350 = 16944;

case 16944:
    v17[17] = level;
    n15350 = 32439;

case 32439:
    v34 = v17[17] < 0x10000;
    n15350 = 24626;

case 24626:
    if ( v34 )
        n15350 = 11323;
    else
        n15350 = 5537;

case 5537:
    *v20 = *(v17[17] + 0x98);               // Actors: TArray<AActor*>*
    n15350 = 21538;

case 21538:
    n0x10000 = *v20;                        // TArray.data
    n15350 = 16118;

case 16118:
    v36 = n0x10000 < 0x10000;
    n15350 = 2082;

case 2082:
    if ( v36 )
        n15350 = 16541;
    else
        n15350 = 22929;

case 16541:
    *v17 = v17[17] + 0xA0;                  // ActorsCount
    n15350 = 4833;

case 4833:
    v17[1] = *v17;                          // 拿到 ActorsCount 指针
    n15350 = 31115;

case 31115:
    *v21 = *v17[1];                         // 拿到 ActorsCount 的值
    *v22 = 0;
    n15350 = 4639;

case 4639:
    n15350 = 29658;

case 29658:
    v38 = *v22;                             // 当前索引 i
    n15350 = 22704;

case 22704:
    v39 = *v21;                             // ActorsCount
    n15350 = 9930;

case 9930:
    v40 = v38 < v39;                        // i < count ?
    n15350 = 13977;

case 13977:
    if ( v40 )
        n15350 = 2306;                      // 是,继续遍历
    else
        n15350 = 21724;

case 2306:
    v17[4] = *v20;                          // TArray<AActor*>.data
    v41 = *v22;                             // i
    n15350 = 31673;

case 31673:
    v17[6] = v41;
    n15350 = 22386;

case 22386:
    *v23 = *(v17[4] + 8 * v17[6]);          // actor = actors[i]
    v42 = *v23 < 0x10000u;
    n15350 = 5021;

case 5021:
    if ( v42 )
        n15350 = 28745;
    else
        n15350 = 26924;

case 28745:
    n15350 = 12052;

case 12052:
    v45 = *v22;
    n15350 = 27350;

case 27350:
    v46 = v45 + 1;
    n15350 = 1150;

case 1150:
    *v22 = v46;                             // i++
    n15350 = 16941;

case 16941:
    n15350 = 29658;                         // 继续循环

image-20250723145621085

image-20250723145632530

异常点 1

根据偏移查找SDK可以得知是开枪时的后坐力,后续通过Frida修改为其他值也可以进一步验证

image-20250723150117275

image-20250723150239782

异常点 2、3

同理,根据偏移去查找对应的参数,可知是人物的速度和加速度
image-20250723150720666

image-20250723150902498

修复

这里选择将三个地方的 STR 赋值汇编 NOP 掉,然后patch,使用 MT 管理器替换 so 文件 将 apk 重新打包签名,即可修复
image-20250723151132269

image-20250723151425313

自瞄这个异常点也是需要先去分析 SDK ,然后用 Frida 去进行 Hook 验证猜想,接着再去观察开枪和不开枪时堆栈的区别,最后定位到某一行代码,nop掉,就可以修复了。

还有两个异常点,子弹乱飞,渲染问题。由于没接触过虚幻引擎这方面的开发,修复不下去了…


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1621925986@qq.com

💰

×

Help us with donation