2025第十届游戏安全竞赛-初赛-安卓客户端安全
一、前言
启动游戏,可以明显发现游戏存在加速、自瞄、透视等问题。同时看 apk 图标也可知这是用虚幻引擎写的。那么首先先判断虚幻引擎版本,可以直接在 AndroidManifest.xml 中找到 UE4.27 。
对所有 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; // 继续循环
异常点 1
根据偏移查找SDK可以得知是开枪时的后坐力,后续通过Frida修改为其他值也可以进一步验证
异常点 2、3
同理,根据偏移去查找对应的参数,可知是人物的速度和加速度
修复
这里选择将三个地方的 STR 赋值汇编 NOP 掉,然后patch,使用 MT 管理器替换 so 文件 将 apk 重新打包签名,即可修复
自瞄这个异常点也是需要先去分析 SDK ,然后用 Frida 去进行 Hook 验证猜想,接着再去观察开枪和不开枪时堆栈的区别,最后定位到某一行代码,nop掉,就可以修复了。
还有两个异常点,子弹乱飞,渲染问题。由于没接触过虚幻引擎这方面的开发,修复不下去了…
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 1621925986@qq.com