某校园题材二游逆向记录
受朋友委托,对某二游进行逆向工程。
经了解,目标游戏使用Unity引擎开发,同时登录PC、Android、iOS平台。这里为了方便绕过反作弊和反调试,选用Android端作为逆向对象。
初步分析
针对客户端进行解包,可以看到非常熟悉的Unity il2cpp打包结构:
对于这种情况,不必多说,先尝试直接使用il2cppdumper开干:
不过很显然事情并不会这么简单,意料之中地报错了,从报错信息中可以得到,libil2cpp.so本身应该不存在保护,问题主要集中在global-metadata.dat。分析错误可知崩溃点在MapVATR读CodeRegistration/MetadataRegistration的数组时抛EndOfStreamException,这基本是非常明显的il2cpp元数据结构魔改特征。
不过也不必先急于对魔改后的结构进行完整逆向,我们可以使用容错能力更强的cpp2il在加载StrippedCodeRegSupport插件的情况下跑diffable-cs和isildump得到结构和机器码:
metadata usage token布局分析
虽然cpp2il已经能跑出diffable-cs和isildump,但il2cppdumper本身仍然无法正常解析。既然崩溃点和metadata usage解析强相关,那就继续顺着相关调用往下看。
先照重定位表随手还原几个metadata usage slot,再顺着读对应位置的内容,可以拿到一些看起来像metadata usage token的值。照常见il2cpp解法来解token:
kind = token >> 29;
index = token & 0x1FFFFFFF;
结果明显异常,有的index直接越界,有的虽然没越界,但解出来也是无关文本:
可以判断这次魔改动的就是token的编码方式,跟通用公式对不上。
继续分析相关汇编,可以很容易发现相关位置在赋值前会先调0x6353FAC去解这些metadata usage,那就直接跟进该函数:
这层只是个跳板,顺着sub_6363BF4的尾调用进到sub_63E2AA0,关键逻辑就在这里:
unsigned __int64 __fastcall sub_63E2AA0(unsigned __int64 *a1, char a2, int a3, int a4, int a5, int a6, int a7, int a8)
{
unsigned __int64 v9; // x20
__int64 v10; // x21
__int64 v11; // x0
unsigned int *v12; // x20
int *v13; // x10
unsigned __int64 v14; // x0
unsigned __int64 *v15; // x9
unsigned __int64 v16; // x8
int v17; // w9
unsigned int *v18; // x20
__int64 v19; // x0
int v21; // [xsp+0h] [xbp-30h]
char v22[8]; // [xsp+8h] [xbp-28h] BYREF
int v23[2]; // [xsp+10h] [xbp-20h]
void *v24; // [xsp+18h] [xbp-18h]
do
v9 = __ldaxr(a1);
while ( __stlxr(v9, a1) );
__dmb(0xBu);
if ( (v9 & 1) != 0 )
{
v10 = ((unsigned int)v9 >> 1) & 0xFFFFFFF;
switch ( (unsigned int)v9 >> 29 )
{
case 1u:
v11 = sub_63E2C60((unsigned int)v10, a2 & 1);
goto LABEL_17;
case 2u:
v9 = *(_QWORD *)(*(_QWORD *)(qword_FDCE5E8 + 56) + 8LL * (unsigned int)v10);
if ( !v9 )
return v9;
goto LABEL_19;
case 3u:
case 6u:
v11 = sub_63E2DA8((unsigned int)v9);
goto LABEL_17;
case 4u:
v12 = (unsigned int *)(qword_FDCE5F0 + *(int *)(qword_FDCE5F8 + 184) + 8LL * (unsigned int)v10);
v9 = *(_QWORD *)(sub_63E2C60(*v12, 1) + 128) + 32LL * (int)v12[1];
if ( !v9 )
return v9;
goto LABEL_19;
case 5u:
v9 = *(_QWORD *)(qword_FDCE628 + 8LL * (unsigned int)v10);
if ( v9 )
goto LABEL_18;
v13 = (int *)(qword_FDCE5F0 + *(int *)(qword_FDCE5F8 + 8) + 8 * v10);
v14 = il2cpp_string_new_len_0(
(int)qword_FDCE5F0 + *(_DWORD *)(qword_FDCE5F8 + 16) + v13[1],
*v13,
a3,
a4,
a5,
a6,
a7,
a8,
v21,
v22[0],
v23[0],
v24);
v9 = v14;
v15 = (unsigned __int64 *)(qword_FDCE628 + 8 * v10);
break;
case 7u:
v18 = (unsigned int *)(qword_FDCE5F0 + *(int *)(qword_FDCE5F8 + 184) + 8LL * (unsigned int)v10);
v19 = sub_63E2C60(*v18, 1);
v11 = sub_63E2DF4(*(_QWORD *)(v19 + 128) + 32LL * (int)v18[1], v22);
LABEL_17:
v9 = v11;
LABEL_18:
if ( v9 )
goto LABEL_19;
return v9;
default:
return 0;
}
while ( 1 )
{
v16 = __ldaxr(v15);
if ( v16 )
break;
if ( !__stlxr(v14, v15) )
{
v17 = 1;
goto LABEL_22;
}
}
__clrex();
v17 = 0;
LABEL_22:
__dmb(0xBu);
if ( v17 )
{
sub_63D38A4(qword_FDCE628 + 8 * v10);
if ( !v9 )
return v9;
}
else
{
v9 = v16;
if ( !v16 )
return v9;
}
LABEL_19:
__dmb(0xBu);
*a1 = v9;
}
return v9;
}
剥出关键逻辑:
// 最低位为0:已解析真实指针,直接返回
if ((token & 1) == 0)
return token;
// 最低位为1:待解析token
kind = (token >> 29) & 0x7;
index = (token >> 1) & 0x0FFFFFFF;
switch (kind) {
case 1: // 类型/类引用
resolved = sub_63E2C60(index, a2 & 1);
break;
case 2: // 全局缓存表里直接取
resolved = *(_QWORD *)(*(_QWORD *)(qword_FDCE5E8 + 56) + 8 * index);
break;
case 3:
case 6: // 走特殊解析分支
resolved = sub_63E2DA8(token);
break;
case 4: // 表里定位类型+成员索引,再算槽位
entry = qword_FDCE5F0 + *(int *)(qword_FDCE5F8 + 184) + 8 * index;
klass = sub_63E2C60(*(uint32_t *)entry, 1);
resolved = *(_QWORD *)(klass + 128) + 32 * *(int *)(entry + 4);
break;
case 5: // 字符串字面量,带CAS缓存
resolved = qword_FDCE628[index];
if (!resolved) {
entry = qword_FDCE5F0 + *(int *)(qword_FDCE5F8 + 8) + 8 * index;
new_string = il2cpp_string_new_len_0(
qword_FDCE5F0 + *(int *)(qword_FDCE5F8 + 16) + entry->offset,
entry->length
);
// CAS抢写,避免多线程重复覆盖
if (atomic_compare_exchange(&qword_FDCE628[index], 0, new_string)) {
resolved = new_string;
sub_63D38A4(&qword_FDCE628[index]);
} else {
resolved = qword_FDCE628[index];
}
}
break;
case 7: // 在case 4基础上再解一层成员
entry = qword_FDCE5F0 + *(int *)(qword_FDCE5F8 + 184) + 8 * index;
klass = sub_63E2C60(*(uint32_t *)entry, 1);
resolved = sub_63E2DF4(*(_QWORD *)(klass + 128) + 32 * *(int *)(entry + 4), tmp);
break;
default:
return 0;
}
if (resolved)
*a1 = resolved; // 回写真实指针,对应开头直接返回
return resolved;
可见真正的token布局是这样:
kind = (token >> 29) & 0x7;
index = (t
...(内容过长,已截断)
---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-291406.htm
[原创]某校园题材二游逆向记录
1 浏览
0 回复
暂无回复,快来抢沙发吧!