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.flappybird 1|walleye:/ # cat /proc/15423/maps | grep 2cpp c02fb000-c06db000 r--s 00000000 103:1d 2917159 /data/media/0/Android/data/com.personal.flappybird/files/il2cpp/Metadata/global-metadata.dat c16cb000-c1dd1000 r-xp 00000000 103:1d 2310173 /data/app/com.personal.flappybird-Ro_Mv8KtyMyikogWcojKtw==/lib/arm/libil2cpp.so c1dd2000-c1e03000 r--p 00706000 103:1d 2310173 /data/app/com.personal.flappybird-Ro_Mv8KtyMyikogWcojKtw==/lib/arm/libil2cpp.so c1e03000-c1e22000 rw-p 00737000 103:1d 2310173 /data/app/com.personal.flappybird-Ro_Mv8KtyMyikogWcojKtw==/lib/arm/libil2cpp.so c1e53000-c1e54000 rw-p 00755000 103:1d 2310173 /data/app/com.personal.flappybird-Ro_Mv8KtyMyikogWcojKtw==/lib/arm/libil2cpp.so c1e54000-c1e5a000 r-xp 00755000 103:1d 2310173 /data/app/com.personal.flappybird-Ro_Mv8KtyMyikogWcojKtw==/lib/arm/libil2cpp.so c1e5a000-c1e5c000 rw-p 0075a000 103:1d 2310173 /data/app/com.personal.flappybird-Ro_Mv8KtyMyikogWcojKtw==/lib/arm/libil2cpp.so d3dda000-d3e2d000 r--s 00000000 103:1d 2917157 /data/media/0/Android/data/com.personal.flappybird/files/il2cpp/Resources/mscorlib.dll-resources.dat
1 2 3 4 5 6 7 walleye:/ # cat /proc/15423/maps | grep unity c335a000-c3e14000 r-xp 00000000 103:1d 2310168 /data/app/com.personal.flappybird-Ro_Mv8KtyMyikogWcojKtw==/lib/arm/libunity.so c3e15000-c3e2f000 r--p 00aba000 103:1d 2310168 /data/app/com.personal.flappybird-Ro_Mv8KtyMyikogWcojKtw==/lib/arm/libunity.so c3e2f000-c3e41000 rw-p 00ad4000 103:1d 2310168 /data/app/com.personal.flappybird-Ro_Mv8KtyMyikogWcojKtw==/lib/arm/libunity.so c3ef5000-c3ef7000 rw-p 00ae5000 103:1d 2310168 /data/app/com.personal.flappybird-Ro_Mv8KtyMyikogWcojKtw==/lib/arm/libunity.so c3ef7000-c3efc000 r-xp 00ae6000 103:1d 2310168 /data/app/com.personal.flappybird-Ro_Mv8KtyMyikogWcojKtw==/lib/arm/libunity.so c3efc000-c3efe000 rw-p 00aea000 103:1d 2310168 /data/app/com.personal.flappybird-Ro_Mv8KtyMyikogWcojKtw==/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.datdump下来。
记下 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:\CTF\MATCH\TP2021\Final\Android\FlappyBird\lib\armeabi-v7a\libil2cpp.so D:\CTF\MATCH\TP2021\Final\Android\FlappyBird\assets\bin\Data\Managed\Metadata\global-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:\projects\il2cppdumper\Il2CppDumper\Il2Cpp\Metadata.cs:line 78 at Il2CppDumper.Program.Init(String il2cppPath, String metadataPath, Metadata& metadata, Il2Cpp& il2Cpp) in C:\projects\il2cppdumper\Il2CppDumper\Program.cs:line 126 at Il2CppDumper.Program.Main(String[] args) in C:\projects\il2cppdumper\Il2CppDumper\Program.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 # 部分crash log 09-12 10:19:23.802 10761 15384 E CRASH : #00 pc 0058dde4 /data/app/com.personal.flappybird-JK0jL3qOSXAgeknqh2UNyA==/lib/arm/libil2cpp.so 09-12 10:19:23.802 10761 15384 E CRASH : #01 pc 002a6480 /data/app/com.personal.flappybird-JK0jL3qOSXAgeknqh2UNyA==/lib/arm/libunity.so 09-12 10:19:23.802 10761 15384 E CRASH : #02 pc 002a63a4 /data/app/com.personal.flappybird-JK0jL3qOSXAgeknqh2UNyA==/lib/arm/libunity.so 09-12 10:19:23.802 10761 15384 E CRASH : #03 pc 002a16b4 /data/app/com.personal.flappybird-JK0jL3qOSXAgeknqh2UNyA==/lib/arm/libunity.so 09-12 10:19:23.802 10761 15384 E CRASH : #04 pc 004cd144 /data/app/com.personal.flappybird-JK0jL3qOSXAgeknqh2UNyA==/lib/arm/libunity.so 09-12 10:19:23.802 10761 15384 E CRASH : #05 pc 004cce40 /data/app/com.personal.flappybird-JK0jL3qOSXAgeknqh2UNyA==/lib/arm/libunity.so 0
分析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 (0x553034) il2cpp_image_get_class (0x58DDE4) il2cpp_class_get_method_from_name (0x55311C)
其中,前两个函数是在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 即可。