TP 2021 Android Final
修复so 和预赛一样,决赛的 .so 文件的 .text,.rodata 段被加密。由于 dd 指令 dump 内存太慢,决赛我选择 gameguardian(GameGuardian APK Download ) Dump内存,dump的内存地址从 /proc/xxxx/maps
读取。
1 2 3 4 5 6 7 8 9 10 11 12 u0_a285 15423 777 1363396 110672 ep_poll 0 S com.personal.flappybird1 |walleye:/ # cat /proc/15423 /maps | grep 2 cppc02fb000 -c06 db000 r--s 00000000 103 :1 d 2917159 /data/media/0 /Android/data/com.personal.flappybird/files/il2 cpp/Metadata/global-metadata.datc16cb000 -c1 dd1000 r-xp 00000000 103 :1 d 2310173 /data/app/com.personal.flappybird-Ro_Mv8 KtyMyikogWcojKtw==/lib/arm/libil2 cpp.soc1dd2000 -c1 e03000 r--p 00706000 103 :1 d 2310173 /data/app/com.personal.flappybird-Ro_Mv8 KtyMyikogWcojKtw==/lib/arm/libil2 cpp.soc1e03000 -c1 e22000 rw-p 00737000 103 :1 d 2310173 /data/app/com.personal.flappybird-Ro_Mv8 KtyMyikogWcojKtw==/lib/arm/libil2 cpp.soc1e53000 -c1 e54000 rw-p 00755000 103 :1 d 2310173 /data/app/com.personal.flappybird-Ro_Mv8 KtyMyikogWcojKtw==/lib/arm/libil2 cpp.soc1e54000 -c1 e5 a000 r-xp 00755000 103 :1 d 2310173 /data/app/com.personal.flappybird-Ro_Mv8 KtyMyikogWcojKtw==/lib/arm/libil2 cpp.soc1e5a000 -c1 e5 c000 rw-p 0075 a000 103 :1 d 2310173 /data/app/com.personal.flappybird-Ro_Mv8 KtyMyikogWcojKtw==/lib/arm/libil2 cpp.sod3dda000 -d3 e2 d000 r--s 00000000 103 :1 d 2917157 /data/media/0 /Android/data/com.personal.flappybird/files/il2 cpp/Resources/mscorlib.dll-resources.dat
1 2 3 4 5 6 7 walleye :/ # cat /proc/15423 /maps | grep unityc335a000 -c3 e14000 r-xp 00000000 103 :1 d 2310168 /data/app/com.personal.flappybird-Ro_Mv8 KtyMyikogWcojKtw==/lib/arm/libunity.soc3e15000 -c3 e2 f000 r--p 00 aba000 103 :1 d 2310168 /data/app/com.personal.flappybird-Ro_Mv8 KtyMyikogWcojKtw==/lib/arm/libunity.soc3e2f000 -c3 e41000 rw-p 00 ad4000 103 :1 d 2310168 /data/app/com.personal.flappybird-Ro_Mv8 KtyMyikogWcojKtw==/lib/arm/libunity.soc3ef5000 -c3 ef7000 rw-p 00 ae5000 103 :1 d 2310168 /data/app/com.personal.flappybird-Ro_Mv8 KtyMyikogWcojKtw==/lib/arm/libunity.soc3ef7000 -c3 efc000 r-xp 00 ae6000 103 :1 d 2310168 /data/app/com.personal.flappybird-Ro_Mv8 KtyMyikogWcojKtw==/lib/arm/libunity.soc3efc000 -c3 efe000 rw-p 00 aea000 103 :1 d 2310168 /data/app/com.personal.flappybird-Ro_Mv8 KtyMyikogWcojKtw==/lib/arm/libunity.so
dump后,使用 010 editor 将 dump下的数据覆盖 两个 .so 的 .text 和 .rodata。
观察资源文件,发现 global-metadata.dat
被加密。分析il2cpp.so,发现加载global-metadata.dat
的函数被hook到sec2021.so。
上图是还原后的加载函数。
为了获取 global-metadata.dat
文件,我们可以选择从全局变量 s_GlobalMetadata
找到内存中的 global-metadata.dat
dump下来。
记下 s_GlobalMetadata
在il2cpp.so的偏移,通过 /proc/xxxx/maps
获取il2cpp.so的基址,加上偏移后,用GG修改器dump下来。
dump后,发现dat文件与正常的dat文件差距较大。分析 il2cpp::vm::MetadataCache::Initialize
函数,发现 dat 文件的字符串被加密了,写个CPP解密一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 char g_pDecoutput[20000000 ];extern unsigned char hexData[];int __fastcall GetStringFromIndex (int idx) { char *pOutBase=g_pDecoutput; int pRet; int *v3; char *data=(char *)hexData; printf ("%d:" ,idx); if ( !*(char *)(g_pDecoutput + idx) ) { if ( 222636 > idx ) { do { if ( !data[idx] ) break ; pOutBase[idx] = idx & 0x7F ^ (data[idx] - 0x7F ) ^ 0x5C ; printf ("%c" ,pOutBase[idx]); ++idx; } while ( idx < 222636 ); } } puts ("" ); return idx++; }
解密后,可以看到字符串,下面是末尾的一部分字符串。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 222120 :medalSparkle 222133 :flappy 222140 :ready 222146 :newBool 222154 :gameScore 222164 :<GameOver>c__Iterator0 222187 :<i>__1 222194 : $locvar0 222203 : $locvar1 222212 :<StartGame>c__Iterator1 222236 :MainMenu 222245 :ChangeScene 222257 :RateGame 222266 :<StartGame>c__Iterator0 222290 :ObstacleBehaviour 222308 :moveSpeed 222318 :ObstacleSpawner 222334 :OnTriggerEnter2D 222351 :obstaclePrefabs 222367 :tempTime 222376 :PlayerController 222393 :OnCollisionEnter2D 222412 :KillPlayer 222423 :thrust 222430 :minTiltSmooth 222444 :maxTiltSmooth 222458 :hoverDistance 222472 :hoverSpeed 222483 :tiltSmooth 222494 :playerRigid 222506 :downRotation 222519 :upRotation 222530 :Scrolling 222540 :scrollSpeed 222552 :isMainMenu 222563 :quadRenderer 222576 :SoundManager 222589 :tempName 222598 :PlayTheAudio 222611 :swoosh 222618 :flap 222623 :playAudio
尝试使用il2cppdumper解析,但报错了。
1 2 3 4 5 6 7 8 9 Il2CppDumper.exe D:\C TF\M ATCH\T P2021\F inal\A ndroid\F lappyBird\l ib\a rmeabi-v7a\l ibil2cpp.so D:\C TF\M ATCH\T P2021\F inal\A ndroid\F lappyBird\a ssets\b in\D ata\M anaged\M etadata\g lobal-metadata.dat .\ Initializing metadata... System.ArgumentException: An item with the same key has already been added. Key: 119 at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior) at System.Linq.Enumerable.ToDictionary[TSource,TKey](TSource[] source, Func`2 keySelector, IEqualityComparer`1 comparer) at Il2CppDumper.Metadata..ctor(Stream stream) in C:\p rojects\i l2cppdumper\I l2CppDumper\I l2Cpp\M etadata.cs:line 78 at Il2CppDumper.Program.Init(String il2cppPath, String metadataPath, Metadata& metadata, Il2Cpp& il2Cpp) in C:\p rojects\i l2cppdumper\I l2CppDumper\P rogram.cs:line 126 at Il2CppDumper.Program.Main(String[] args) in C:\p rojects\i l2cppdumper\I l2CppDumper\P rogram.cs:line 100 Press any key to exit...
模拟sec2021.so sec2021 的功能与初赛大致相似,都导出了2个数组:
分析p_array中的函数
第0个函数大概与保护相关,在fread,fwrite等函数都有调用。
h_open_meatdata
函数实现加载 metadata。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 void __fastcall dec_Metadata (int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, __int64 a9, __int64 a10, int a11) { int v11; int v12; char v13; int v14; int v15; int sem; int v17; a8 = 0xEECF7326 ; ((void (__fastcall *)(int *, int , int , int ))((char *)off_4C704 + v14))(&a8, v11, -1 , v12); a9 = 0LL ; a10 = 0LL ; LOBYTE (a11) = v13; v15 = ((int (*)(void ))((char *)off_4C708 + v14))(); sem = ((int (__fastcall *)(int , __int64 *))((char *)0x340d8 + v14))(v15, &a9); a8 = 0xEECF7326 ; ((void (__fastcall *)(int *, int , int , int ))((char *)0x003202c + v14))(&a8, v11, g_metadata_size, sem); a8 = 0xEECF7326 ; ((void (__fastcall *)(int *, int ))((char *)off_4C714 + v14))(&a8, 3539 ); v17 = ((int (__fastcall *)(int *))((char *)off_4C718 + v14))(&dword_4EDA1); hooked_il2cpp_func[0 ] = (int (*)(void ))(v17 + 0x553034 ); hooked_il2cpp_func[1 ] = (int (*)(void ))(v17 + 0x58DDE4 ); hooked_il2cpp_func[2 ] = (int (*)(void ))(v17 + 0x55311C ); __asm { POPEQ {R4-R11,PC}; Pop registers } }
和初赛一样,我选择模拟 sec2021.so。将模拟的 sec2021.so、解密后的 unity.so,il2cpp.so、解密后的 global-metadata.dat
覆盖原文件打包后,发现程序crash。
1 2 3 4 5 6 7 8 09 -12 10 :19 :23 .802 10761 15384 E CRASH : #00 pc 0058 dde4 /data/app/com.personal.flappybird-JK0 jL3 qOSXAgeknqh2 UNyA==/lib/arm/libil2 cpp.so09 -12 10 :19 :23 .802 10761 15384 E CRASH : #01 pc 002 a6480 /data/app/com.personal.flappybird-JK0 jL3 qOSXAgeknqh2 UNyA==/lib/arm/libunity.so09 -12 10 :19 :23 .802 10761 15384 E CRASH : #02 pc 002 a63 a4 /data/app/com.personal.flappybird-JK0 jL3 qOSXAgeknqh2 UNyA==/lib/arm/libunity.so09 -12 10 :19 :23 .802 10761 15384 E CRASH : #03 pc 002 a16 b4 /data/app/com.personal.flappybird-JK0 jL3 qOSXAgeknqh2 UNyA==/lib/arm/libunity.so09 -12 10 :19 :23 .802 10761 15384 E CRASH : #04 pc 004 cd144 /data/app/com.personal.flappybird-JK0 jL3 qOSXAgeknqh2 UNyA==/lib/arm/libunity.so09 -12 10 :19 :23 .802 10761 15384 E CRASH : #05 pc 004 cce40 /data/app/com.personal.flappybird-JK0 jL3 qOSXAgeknqh2 UNyA==/lib/arm/libunity.so0
分析crash部分,发现是 il2cpp_method_get_name
发生了异常。
这个导出函数显得很奇怪,相当于是 R0=*(0+12)。结合 p_array
中有 hook_dlsym,怀疑是sec2021.so 中的 dlsym
将这个函数解析到真正的地址,而 il2cpp.so
中的原导出地址是假的。
分析 hook_dlsym,发现很多 BX 指令,干扰了IDA的分析。将BXPatch成NOP,勉强分析一下。
第一个函数是根据index获取字符串,有很多BX BLX之类的混淆指令,patch成Nop后,勉强能看,但循环、条件判断之类的都无法看出了,还好通过查找SBox的交叉引用,看到其他函数的未经混淆的解密函数,写CPP解密一下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 int __fastcall GetString_31A60 (int *a1, int ID) { int v2; unsigned __int8 size; char *pBuf; int v5; int *v6; _DWORD *v7; char cur_xor; _DWORD v10[3 ]; char buf4[4 ]; int ID_1; int a1a; v2 = *a1; ID_1 = ID; Set0 (buf4); sub_30D78 ((int )v10, (int )&dword_4DFC4, (int )&ID_1); sub_30DA0 ((int )buf4, v10); Set0to0_0 ((int )v10, (int )&dword_4DFC4); sub_30DD4 (buf4, v10); cur_xor = SBox[ID_1]; size = SBox[ID_1 + 1 ] ^ cur_xor; pBuf = (char *)malloc (size + 1 ); v10[2 ] = pBuf; a1a = v2; clear_mem ((int )&a1a, (int )pBuf, -1 ); a1a = v2; copy ((int )&a1a, (int )pBuf, -1 , &SBox[ID_1 + 2 ], size); a1a = v2; do_xor ((int )&a1a, (int )pBuf, size, cur_xor); a1a = v2; v5 = getCheckSum (&a1a, pBuf); v6 = (_DWORD *)&byte_4; v7 = (_DWORD *)(unsigned __int8)SBox[size + 2 + ID_1]; if ( (_DWORD *)v5 == v7 ) v6 = &dword_14; sub_31054 (0 , v7, v6); sub_31038 (&dword_4DFC4, 0 ); return v10[2 ]; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 int getxor (int id) { int checksum=0xff ; int _0=sbox[id]; int _1 = sbox[id+1 ]; int cur_size=_0 ^ _1; unsigned char key[9 ]; key[0 ]=_0; for (int i = 1 ; i < 9 ; ++i) { key[i]= ((unsigned __int8)(key[i-1 ] + 17 ) >> 5 ) | (8 * (key[i-1 ] + 17 )); } puts ("" ); for (int i = 0 ; i < cur_size; ++i) { printf ("%c" ,key[i%9 ]^sbox[id+2 +i]); } puts ("" ); return 0 ; }int main () { getxor (264 ); getxor (297 ); getxor (327 ); getxor (363 ); getxor (390 ); }
第二个是strcmp,再结合汇编分析,
不难想到,hook_dlsym 将这几个函数解析到真正的地址。
1 2 3 4 5 il2cpp_method_get_name il2cpp_class_get_name il2cpp_class_get_methods (0 x553034 )il2cpp_image_get_class (0 x58 DDE4 )il2cpp_class_get_method_from_name (0 x55311 C)
其中,前两个函数是在sec2021.so中实现,且只有2个指令。
没有必要模拟,直接在 il2cpp.so中的对应位置 patch 成正确的指令即可。
分析后三条指令:
1 2 3 4 5 6 7 8 9 10 11 12 int h_il2cpp_class_get_methods () { return hooked_il2cpp_func[0 ](); }int h_il2cpp_image_get_class () { return hooked_il2cpp_func[1 ](); }int h_il2cpp_class_get_method_from_name () { return hooked_il2cpp_func[2 ](); }
找 hooked_il2cpp_func
的引用,发现在加载 metadata 函数被赋值。
不难猜到,v17 和后面的数字分别是il2cpp的基址和真正函数的偏移。
将后三个函数的导出地址的第一条指令,patch成 B 0xXXXXXX,即可让这三个函数跳转到真正函数地址。
实现无敌破解 global-metadata.dat
的格式与常规的差很多,无法使用工具解析。
在GitHub上搜关键字 gameScoreText flappy
,可以找到这个游戏的源代码。
找到判断障碍物的逻辑
下载后,编译成apk
,使用 il2cppdumper
分析,可以定位到关键逻辑函数,方便对比分析。
其中,UnityEngine_Component__CompareTag
函数存在特征字符串”UnityEngine.GameObject::CompareTag(System.String)”
在题目的so中找到特征字符串、查找引用,定位到题目对应函数。
为了实现无敌,将 BNE
patch 成 B
即可。