-
-
[原创]2023腾讯游戏安全大赛-安卓赛道初赛wp
-
2023-4-17 19:46 31842
-
说明
本文档是进行完相关分析之后的总结回忆, 所以可能有的地方的花指令是被去除了再截图的, 函数名和数据结构被重命名了. vm算法没有逆出来
flag获取分析
根据题目提示要求, flag会在1000分的时候出现. 打开安装包发现是unity打包的游戏, 用010editor打开global-metadata.dat文件发现头文件没有被加密, 字符串也完好, 拖入il2cppdumper提示失败. ida打开libil2cpp.so查看init.array段, 第一个函数中看到了module_base: %p, g_unpacker: %p
字符串, off_13BAFF0
则是g_sec2023_p_array
, 猜测是动态加载的时候会被libsec2023.so解密出来.
粗略看了一下解密挺复杂的, 所以首先尝试从内存中dump出来libil2cpp.so, 然后再使用il2cppdumper生成类信息, 这次可以直接成功. 观察数据结构类名函数名等信息, 在相关排查后确定老鼠的相关控制逻辑在MouseController
类中, 其中有一个private void CollectCoin(Collider2D coinCollider)
函数, 参数是碰撞器, 使用ida打开这个函数并且导入结构体类名信息, 其伪代码中出现了一个特殊的数字1000, 题目提到了分数多于1000即会出现flag, 估计关键点在这里.
其关键位置原本的汇编指令是CMP W0, #0x3E8
, 这里使用gg修改器修改为CMP W0, #0
, 然后随便吃个金币后成功得到flag.
反调试分析
前面分析大概能够猜测到libsec2023.so中大概率会存在反调试的功能, 首先在手机端开启frida_server, 结果并没有附加到游戏上打开游戏便被检测到了. 首先对其字符串操作函数进行hook看看有没有关键信息, hook掉strcmp, strncmp, strlen函数能够发现存在字符串re.frida.server的对比, 以及sscanf函数对maps文件的处理.
因为我并没有注入游戏就被检测了, 那么关于这种检测常见的有:
检测/data/local/tmp文件夹下的re.frida.server
检测27042端口
检测dbus
查阅资料发现解决第一个问题需要重新编译frida, 这有点太麻烦了, 所以我直接打开frida_server的二进制文件patch掉re.frida.server字符串, 并且使用./fs -l 0.0.0.0:23948
命令切换监听端口, 这样一来就能够成功过掉检测.
在这之后我继续分析libil2cpp.so内的功能流程, 我发现frida hook它是没问题的, 但是只要hook libsec2023.so就会崩掉, 刚开始我是怀疑crc检测, 检测到了就调用kill
或者exit
等函数, 但是我hook掉退出函数发现并没有被调用. 对于这些检测函数, 其大概的逻辑是新开一个线程不间断扫描内存段, 并且计算出一个值, 如果值改变了那么就说明程序段被篡改了.
针对上面这个逻辑, 我首先想到了frida的异常hook, 对指定的地址修改内存属性为只能执行, 读取后进入异常打印堆栈信息. 结果发现要么崩溃要么没有效果, 所以只能另外想办法.
我这边正好有一个修改完内核之后的手机, 可以使用rwProcMem33开源项目中的硬件断点, 所以我使用这个工具尝试一下能否找到关键点. 结果成功定位到crc的关键函数, 下图中的libsec2023.so的基址是0x7d02bc8000
命中地址0x7d02bff704
减去基址得到偏移为0x37704
, 为了保险还对其他的函数下了相同的内存断点, 发现断下来的地址只有这一个, 并且如果游戏进程没有重开, 返回值都相同, 跟进ida中查看是一个很简单的计算函数, 他的调用者sub_370AC
很有可能就是进行相关操作的检测函数, 对其进行分析:
1 2 3 4 5 6 7 8 9 10 11 | .text: 00000000000371C8 41 01 00 94 BL sub_376CC ;调用的计算函数 .text: 00000000000371C8 .text: 00000000000371CC C8 1A 40 B9 LDR W8, [X22, #0x18] .text: 00000000000371D0 09 07 80 52 MOV W9, #0x38 ; '8' .text: 00000000000371D4 1F 00 08 6B CMP W0, W8 ;对返回值进行判断 .text: 00000000000371D8 08 05 80 52 MOV W8, #0x28 ; '(' .text: 00000000000371DC 08 01 89 9A CSEL X8, X8, X9, EQ ;根据判断结果进行赋值 .text: 00000000000371E0 A8 6A 68 F8 LDR X8, [X21,X8] .text: 00000000000371E4 09 3E 91 52 49 F8 A6 72 MOV W9, #0x37C289F0 .text: 00000000000371EC 08 01 09 8B ADD X8, X8, X9 .text: 00000000000371F0 01 00 00 14 B loc_371F4 |
因为前面分析知道sub_376CC
正常情况的返回值都是相等的, 所以我们进行inlinehook之后会导致该返回值修改, 走到崩溃路线, 因此关键在cmp语句或者下面的cesl语句, 这句csel语句的意思是x8 = w0==w8 ? x8 : x9
, x8与x9相差0x10, 这就导致了控制流的改变. 值得指出的是371F0这里的语句是错误的, 在内存dump下来的libsec2023.so中这里的值是BR X8
. 将371DC处的值改为NOP
之后x8的值就不会被修改为x9了, 也就绕过了inlinehook检测. 至此frida就能够完全正常使用了
除了上面说到的反调试, 在libsec2023.so中还存在大量的花指令为csel...br reg形式的指令, 能够使得函数不被ida正常解析出来, 其br之后实际还是继续执行下一条指令. 在这些花指令中还藏着正常的选择语句块, 或者跳过几条语句的埋坑, 所以不方便用脚本批量还原掉, 我是踩完坑被误导之后才决定分析到哪里再配合frida-trace手动去除的.
例如下面两个截图是还原前后的效果
程序算法流程分析
分析算法首先需要找到输入的值是从哪里传入的, 观察类名发现有一个SmallKeyboard
类, 其中有被混淆过的参数, 经过一个个查看发现iI1Ii
函数存在关键逻辑, 主要是有三种按键: 数字, 删除, 回车确定. 查看伪代码能够发现关键逻辑, 使用frida hook System_Convert__ToUInt64_8763844
得到其返回值确实就是我们的输入值. 为了方便, 我输入的值是666666, 对应的16进制值是0xa2c2a
跟进SmallKeyboard__iI1Ii_4610736
函数发现其最后jumpout到别的地方去了, 用frida hook后得到其跳转的位置是libsec2023.so + 0x31164处, 从这里开始有比较误导性的混淆, 我的截图都是去除混淆之后加了注释的.
sub_31164
函数调用了sub_3B8CC
之后拿到一个函数指针跳转出去了, 经过动态调试能够知道跳转到il2cpp中去了. 这里跟进看看sub_3B8CC
做了什么事情.
经过验证这个函数存在错误的b跳转, 伪代码因此是存在错误的, 按照截图中最后的返回值恒为0, 这显然是错误的. 但是可以看出来大概做了什么事情:
sub_3B9D4处理输入值
sub_3A924处理第一步输出值的一部分
sub_3A924处理第一步输出值的另一部分
返回
libsec2023.so ==> sub_3B9D4函数
sub_3B9D4函数看起来很简单, 按照伪代码的逻辑, 对于输入值0, 我们应该返回(0x85-24)<<24, 也就是0x6d000000, 但最后的结果是0x6d94cae4. 那这个伪代码就没用了, 需要查看汇编代码是什么情况, 为了方便分析, 使用frida-trace对该函数进行一遍trace得到对应的log记录, 进行汇编分析, 汇编分析时以输入参数为发力点, 观察数据变化.
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | 3b9e4 csel x10, xzr, x9, lo ; x10 = 0xa2c5a - - > 0x0 (null) 3b9e8 adrp x9, #0x7d03375000 ; x9 = 0x28 --> 0x7d03375000 (�fC���>) 3b9ec add x9, x9, #0xc40 ; x9 = 0x7d03375000 --> 0x7d03375c40 q3�|) 3b9f0 ldr x10, [x9, x10] ; x10 = 0x0 - - > 0x7c8f337108 3b9f4 mov w11, #0x78fc ; x11 = 0xb400007bb22885a0 --> 0x78fc 3b9f8 movk w11, #0x7400, lsl #16 ; x11 = 0x78fc --> 0x740078fc 3b9fc add x10, x10, x11 ; x10 = 0x7c8f337108 - - > 0x7d0333ea04 (j) 3ba00 br x10 ;必定跳转 3ba04 mov w10, #3 ; x10 = 0x7d0333ea04 --> 0x3 3ba08 mov w11, #0x18 ; x11 = 0x740078fc --> 0x18 3ba0c cmp x10, #0 ; x10 = 0x3 3ba10 mov w12, #0x10 ; x12 = 0x0 --> 0x10 3ba14 csel x11, x12, x11, ge ; x11 = 0x18 - - > 0x10 3ba18 ldr x11, [x9, x11] ; x11 = 0x10 - - > 0x7c8f337138 3ba1c mov w12, #0x78fc ; x12 = 0x10 --> 0x78fc 3ba20 movk w12, #0x7400, lsl #16 ; x12 = 0x78fc --> 0x740078fc 3ba24 str wzr, [sp, #0xc] ; str = 0 3ba28 add x12, x11, x12 ; x12 = 0x740078fc - - > 0x7d0333ea34 (xh�� 3 ) 3ba2c mov w11, #0x18 ; x11 = 0x7c8f337138 --> 0x18 3ba30 br x12 ;必定跳转 ;我称前面是第一个代码块 3ba34 ldr w12, [x0, x8, lsl #2] ; x12 = 0x7d0333ea34 --> 0x0 (null)取参数值, 也就是高32位, 即0 3ba38 add x13, sp, #0xc ; x13 = 0x6430bee2 --> 0x7cfe68653c 3ba3c mov w14, #0x18 ; x14 = 0x14da99d881a188 --> 0x18 从高到低选择字节 3ba40 lsr w12, w12, w11 ; 取字节 3ba44 eor w12, w12, w10 ; x12 = 0x0 - - > 0x3 和字节序号做异或 3ba48 strb w12, [x13, x10] ; 存到[sp, #0xc]的位置去, 此时顺序还是原来 ... ;我称这为第二个代码块, 主要做的事情是取 666666 ( 64 位)的高 32 位, 和字节序号异或存回去, 省略掉的部分是其他字节相同操作 3ba74 ldrb w11, [sp, #0xf] ; x11 = 0xfffffff8 --> 0x3 第4字节 3ba78 ldrb w13, [sp, #0xd] ; x13 = 0x740078fc --> 0x1 第2字节 3ba7c mov w12, #0x86 ; x12 = 0x7d0333ea74 --> 0x86 3ba80 ldrb w14, [sp, #0xe] ; x14 = 0x18 --> 0x2 第3字节 3ba84 eor w11, w11, w12 ; x11 = 0x3 - - > 0x85 byte 4 和 0x86 异或 3ba88 mov w12, #0xd3 ; x12 = 0x86 --> 0xd3 3ba8c eor w12, w13, w12 ; x12 = 0xd3 - - > 0xd2 0xd3 和byte 2 异或 3ba90 ldrb w13, [sp, #0xc] ; x13 = 0x1 --> 0x0 (null) 第1字节 3ba94 strb w11, [sp, #0xf] ; 也就是最高字节和0x86异或存回去 byte[3] ^= 0x86 3ba98 sub w11, w14, #0x5e ; x11 = 0x85 --> 0xffffffa4 3ba9c strb w11, [sp, #0xe] ; byte[2] -= 0x5e 3baa0 mov w11, #3 ; x11 = 0xffffffa4 --> 0x3 3baa4 sub w13, w13, #0x1c ; x13 = 0x0 --> 0xffffffe4 3baa8 strb w12, [sp, #0xd] ; byte[1] ^= 0xd3 3baac mov w12, #8 ; x12 = 0xd2 --> 0x8 3bab0 strb w13, [sp, #0xc] ; byte[0] -= 0x1c 最低字节-0x1c存回去 3bab4 mov w13, #0x20 ; x13 = 0xffffffe4 --> 0x20 3bab8 cmp x11, #0 ; x11 = 0x3 3babc str wzr, [x0, x8, lsl #2] ; str = 0 3bac0 csel x12, x13, x12, ge ; x12 = 0x8 - - > 0x20 3bac4 ldr x12, [x9, x12] ; x12 = 0x20 - - > 0x7c8f3371e4 3bac8 mov w13, #0x78fc ; x13 = 0x20 --> 0x78fc 3bacc movk w13, #0x7400, lsl #16 ; x13 = 0x78fc --> 0x740078fc 3bad0 mov w10, wzr ; x10 = 0xffffffffffffffff - - > 0x0 (null) 3bad4 add x13, x12, x13 ; x13 = 0x740078fc - - > 0x7d0333eae0 (� 3 ) 3bad8 mov w12, #0x18 ; x12 = 0x7c8f3371e4 --> 0x18 3badc br x13 ; ;我称这为第三个代码块, 主要做的事情是每个字节分别进行对应加减法 3bae0 add x13, sp, #0xc ; x13 = 0x7d0333eae0 --> 0x7cfe68653c (�Ҥ�) 3bae4 ldrb w14, [x13, x11] ; x14 = 0x2 - - > 0x85 取最高字节 3bae8 sub w14, w14, w12 ; x14 = 0x85 - - > 0x6d 3baec strb w14, [x13, x11] ;最高字节 - 0x18 存回去 byte[ 3 ] - = 0x18 3baf0 and w14, w14, #0xff ; 3baf4 lsl w14, w14, w12 ; x14 = 0x6d - - > 0x6d000000 3baf8 sub x11, x11, #1 ; x11 = 0x3 --> 0x2 3bafc mov w13, #8 ; x13 = 0x7cfe68653c --> 0x8 3bb00 add w10, w14, w10 ; x10 = 0x0 - - > 0x6d000000 3bb04 mov w14, #0x20 ; x14 = 0x6d000000 --> 0x20 3bb08 cmp x11, #0 ; x11 = 0x2 3bb0c str w10, [x0, x8, lsl #2] ; str = 0x6d000000 *result = byte[3]<<24 3bb10 csel x13, x14, x13, ge ; x13 = 0x8 - - > 0x20 3bb14 ldr x13, [x9, x13] ; x13 = 0x20 - - > 0x7c8f3371e4 3bb18 mov w14, #0x78fc ; x14 = 0x20 --> 0x78fc 3bb1c movk w14, #0x7400, lsl #16 ; x14 = 0x78fc --> 0x740078fc 3bb20 sub w12, w12, #8 ; x12 = 0x18 --> 0x10 3bb24 add x13, x13, x14 ; x13 = 0x7c8f3371e4 - - > 0x7d0333eae0 (� 3 ) 3bb28 br x13 ;循环节 ... ;我称这为第四个代码块, 主要做的事情是对每个字节分别进行byte[i] - = 8 * i; ;在这之后代码会取低 32 位进行相同的操作 |
分析完以上的汇编代码后可以还原对应的算法
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 | void sub_3b9d4(unsigned char * byte) { / / 输入参数指针指向的区域是 00 00 00 00 2a 2c 0a 00 / / 进行完修改之后数据变为e4 ca 94 6d 0e f6 9a 6d for ( int i = 3 ;i> = 0 ;i - - ) byte[i] ^ = i; byte[ 0 ] - = 0x1c ; byte[ 1 ] ^ = 0xd3 ; byte[ 2 ] - = 0x5e ; byte[ 3 ] ^ = 0x86 ; for ( int i = 3 ;i> = 0 ;i - - ) byte[i] - = 8 * i; byte + = 4 ; for ( int i = 3 ;i> = 0 ;i - - ) byte[i] ^ = i; byte[ 0 ] - = 0x1c ; byte[ 1 ] ^ = 0xd3 ; byte[ 2 ] - = 0x5e ; byte[ 3 ] ^ = 0x86 ; for ( int i = 3 ;i> = 0 ;i - - ) byte[i] - = 8 * i; } |
使用frida hook来验证结果是否正确, 确实无误
之后来编写对应的逆算法为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | void sub_3b9d4_reverse(unsigned char * byte) { for ( int i = 3 ; i > = 0 ; i - - ) byte[i] + = 8 * i; byte[ 0 ] + = 0x1c ; byte[ 1 ] ^ = 0xd3 ; byte[ 2 ] + = 0x5e ; byte[ 3 ] ^ = 0x86 ; for ( int i = 3 ; i > = 0 ; i - - ) byte[i] ^ = i; byte + = 4 ; for ( int i = 3 ; i > = 0 ; i - - ) byte[i] + = 8 * i; byte[ 0 ] + = 0x1c ; byte[ 1 ] ^ = 0xd3 ; byte[ 2 ] + = 0x5e ; byte[ 3 ] ^ = 0x86 ; for ( int i = 3 ; i > = 0 ; i - - ) byte[i] ^ = i; } |
libsec2023.so ==> sub_3A924
在这个函数中有很多复杂且奇怪的函数, 并且也出现了函数指针+1000多的数字, 因此可以猜想这里主要做的事情是准备java层环境, 之后调用java函数, 将伪代码中JNIEnv*参数类型标注出来. 刷新伪代码后能够很明显看到先new了一个java层的bytearray, 然后findclass, GetStaticMethodID得到函数, 最后在sub_3ABBC中调用这个函数.
这里我首先对findclass进行hook, 发现类名是空的, 静态函数名称能得到是encrypt, 基本可以确定无误. 用jadx打开dex发现并没有这个函数, 因此怀疑是走RegisterNatives动态注册的. 使用frida对RegisterNatives进行hook, 发现了一条记录
1 | [RegisterNatives] java_class: com.tencent.games.sec2023.Sec2023Application name: initialize sig: ()I fnPtr: 0x7cfe0d5abc fnOffset: 0x7cfe0d5abc libsec2023.so! 0xfabc callee: 0x7cfe0d5ed0 libsec2023.so!JNI_OnLoad + 0x3b0 |
但是经过排查很明显不是上面的encrypt函数. 思考一下, 除了动态注册以外, 有可能是动态加载了dex, 于是使用frida-dexdump将内存中的dex dump下来. 果然在某个dex中发现了该函数.
但是其控制流也被混淆了, 好在函数本身并不复杂, 因此可以将伪代码直接复制新建一个java工程, 测试功能完好之后对其进行调试分析, 将函数逻辑梳理一下得到其逻辑代码如下
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 | char * encrypt(char * var0) { / / 输入 109 , - 108 , - 54 , - 28 / / 输出 - 6 , 23 , - 40 , 16 / / 输入 109 , - 102 , - 10 , 14 / / 输出 46 , 23 , - 52 , 119 int var5 = 0 ; int var6 = 0 ; var6 = (var0[ 3 ] & 0xff ) + (var0[ 0 ] << 24 & - 0x1000000 ) + (var0[ 1 ] << 16 & 0xff0000 ) + (var0[ 2 ] << 8 & 0xff00 ); var5 = ((unsigned int )var6 >> 7 ) | (var6 & 0x7f ) << 25 ; char * var1 = new char[ 4 ]; var1[ 0 ] = (unsigned char)(var5 >> 24 & 255 ); var1[ 1 ] = (unsigned char)(var5 >> 16 & 255 ); var1[ 2 ] = (unsigned char)(var5 >> 8 & 255 ); var1[ 3 ] = (unsigned char)(var5 & 255 ); unsigned char data[] = { 50 , - 51 , - 1 , - 104 , 25 , - 78 , 124 , - 102 }; for ( int i = 0 ; i < 4 ; i + + ) { var1[i] ^ = data[i % 8 ]; var1[i] = (unsigned char)(var1[i] + i); } return var1; } |
经过分析也能得出对应的逆算法为如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | char * decrypt(char * var0) { unsigned char data[] = { 50 , - 51 , - 1 , - 104 , 25 , - 78 , 124 , - 102 }; for ( int i = 0 ; i < 4 ; i + + ) { var0[i] = (unsigned char)(var0[i] - i); var0[i] ^ = data[i % 8 ]; } int var5 = (var0[ 3 ] & 0xff ) + (var0[ 2 ] << 8 & 0xff00 ) + (var0[ 1 ] << 16 & 0xff0000 ) + (var0[ 0 ] << 24 & - 0x1000000 ); var5 = ((unsigned int )var5 << 7 ) | ((unsigned int )var5 >> 25 ); char * var1 = new char[ 4 ]; var1[ 0 ] = (unsigned char)(var5 >> 24 & 255 ); var1[ 1 ] = (unsigned char)(var5 >> 16 & 255 ); var1[ 2 ] = (unsigned char)(var5 >> 8 & 255 ); var1[ 3 ] = (unsigned char)(var5 & 255 ); return var1; } |
使用frida hook得到的结果与上面算法计算得到的结果一致
梳理完这个函数那么算法在libsec2023.so的部分就已经结束了, 按照动态流程分析执行完sub_31164后会跳回到libil2cpp.so + 0x465AB4中, 从java层这个encrypt返回的结果与0x465AB4所拦截的结果是一致的. 所以libsec2023.so部分到此结束.
libil2cpp.so ==> sub_465AB4
这个函数是从libsec2023.so返回所进入的函数, 首先根据伪代码观察其流程, 主要做的事情是初始化了一些数组和对象, 将上一个encrypt的结果传入sub_46AD44函数中进行加密, 然后在一个do_while循环中进行第二次加密. 最后如果v28等于0并且v27与token的值相等就开启mod成功, 这里手动patch判断语句可以实现作弊功能, 所以整体思路是对的.
libil2cpp.so ==> sub_46AD44
首先来分析第一个sub_46AD44函数, 这个函数非常复杂, 主要是重要的字段名信息还有函数名信息都被混淆掉了.
整个类中做了什么事情, 首先会初始化一些参数, 最重要的是某个迭代值(类内偏移0x2c), action词典(类内偏移0x38), 码表(类内偏移0x10). 该类在初始化的时候会注册0x1-0x16的值与某个操作函数对应关系在action词典中, 然后在一个大循环中随着迭代值的变化在码表中选择不同的action, 类似于状态机的实现. 下面这个sub_46AD44函数内的循环即是主要逻辑.
最重要的事情是无论输入值是多少, 该状态机的码表和操作序列是一样的. 因此可以dump码表和操作序列, dump出来的码表和操作序列如下
1 2 3 | unsigned char mabiaoData[] = { 0x09 , 0x00 , 0x18 , 0x00 , 0x0d , 0x00 , 0x02 , 0x00 , 0x0b , 0x00 , 0x00 , 0x00 , 0x0b , 0x00 , 0x02 , 0x00 , 0x13 , 0x00 , 0x09 , 0x00 , 0xff , 0x00 , 0x15 , 0x00 , 0x0b , 0x00 , 0x02 , 0x00 , 0x09 , 0x00 , 0x08 , 0x00 , 0x02 , 0x00 , 0x0d , 0x00 , 0x02 , 0x00 , 0x0b , 0x00 , 0x02 , 0x00 , 0x09 , 0x00 , 0x00 , 0x00 , 0x04 , 0x00 , 0x08 , 0x00 , 0x04 , 0x00 , 0x09 , 0x00 , 0x1b , 0x00 , 0x02 , 0x00 , 0x0d , 0x00 , 0x04 , 0x00 , 0x09 , 0x00 , 0xc2 , 0x00 , 0x16 , 0x00 , 0x0d , 0x00 , 0x05 , 0x00 , 0x09 , 0x00 , 0xa8 , 0x00 , 0x01 , 0x00 , 0x0d , 0x00 , 0x06 , 0x00 , 0x09 , 0x00 , 0x36 , 0x00 , 0x16 , 0x00 , 0x0d , 0x00 , 0x07 , 0x00 , 0x09 , 0x00 , 0x04 , 0x00 , 0x0d , 0x00 , 0x02 , 0x00 , 0x09 , 0x00 , 0x00 , 0x00 , 0x0d , 0x00 , 0x03 , 0x00 , 0x09 , 0x00 , 0x00 , 0x00 , 0x0d , 0x00 , 0x00 , 0x00 , 0x0b , 0x00 , 0x02 , 0x00 , 0x0a , 0x00 , 0x0b , 0x00 , 0x03 , 0x00 , 0x16 , 0x00 , 0x0b , 0x00 , 0x03 , 0x00 , 0x14 , 0x00 , 0x09 , 0x00 , 0xff , 0x00 , 0x0b , 0x00 , 0x03 , 0x00 , 0x14 , 0x00 , 0x15 , 0x00 , 0x0b , 0x00 , 0x00 , 0x00 , 0x01 , 0x00 , 0x0d , 0x00 , 0x00 , 0x00 , 0x0b , 0x00 , 0x02 , 0x00 , 0x09 , 0x00 , 0x01 , 0x00 , 0x01 , 0x00 , 0x0d , 0x00 , 0x02 , 0x00 , 0x0b , 0x00 , 0x03 , 0x00 , 0x09 , 0x00 , 0x08 , 0x00 , 0x01 , 0x00 , 0x0d , 0x00 , 0x03 , 0x00 , 0x0b , 0x00 , 0x03 , 0x00 , 0x09 , 0x00 , 0x19 , 0x00 , 0x04 , 0x00 , 0x07 , 0x00 , 0x3a , 0x00 , 0x09 , 0x00 , 0x18 , 0x00 , 0x0d , 0x00 , 0x02 , 0x00 , 0x0b , 0x00 , 0x01 , 0x00 , 0x0b , 0x00 , 0x02 , 0x00 , 0x13 , 0x00 , 0x09 , 0x00 , 0xff , 0x00 , 0x15 , 0x00 , 0x0b , 0x00 , 0x02 , 0x00 , 0x09 , 0x00 , 0x08 , 0x00 , 0x02 , 0x00 , 0x0d , 0x00 , 0x02 , 0x00 , 0x0b , 0x00 , 0x02 , 0x00 , 0x09 , 0x00 , 0x00 , 0x00 , 0x04 , 0x00 , 0x08 , 0x00 , 0x67 , 0x00 , 0x09 , 0x00 , 0x2f , 0x00 , 0x02 , 0x00 , 0x0d , 0x00 , 0x04 , 0x00 , 0x09 , 0x00 , 0xb6 , 0x00 , 0x16 , 0x00 , 0x0d , 0x00 , 0x05 , 0x00 , 0x09 , 0x00 , 0x37 , 0x00 , 0x01 , 0x00 , 0x0d , 0x00 , 0x06 , 0x00 , 0x09 , 0x00 , 0x98 , 0x00 , 0x16 , 0x00 , 0x0d , 0x00 , 0x07 , 0x00 , 0x09 , 0x00 , 0x04 , 0x00 , 0x0d , 0x00 , 0x02 , 0x00 , 0x09 , 0x00 , 0x00 , 0x00 , 0x0d , 0x00 , 0x03 , 0x00 , 0x09 , 0x00 , 0x00 , 0x00 , 0x0d , 0x00 , 0x01 , 0x00 , 0x0b , 0x00 , 0x02 , 0x00 , 0x0a , 0x00 , 0x0b , 0x00 , 0x03 , 0x00 , 0x01 , 0x00 , 0x0b , 0x00 , 0x03 , 0x00 , 0x14 , 0x00 , 0x09 , 0x00 , 0xff , 0x00 , 0x0b , 0x00 , 0x03 , 0x00 , 0x14 , 0x00 , 0x15 , 0x00 , 0x0b , 0x00 , 0x01 , 0x00 , 0x01 , 0x00 , 0x0d , 0x00 , 0x01 , 0x00 , 0x0b , 0x00 , 0x02 , 0x00 , 0x09 , 0x00 , 0x01 , 0x00 , 0x01 , 0x00 , 0x0d , 0x00 , 0x02 , 0x00 , 0x0b , 0x00 , 0x03 , 0x00 , 0x09 , 0x00 , 0x08 , 0x00 , 0x01 , 0x00 , 0x0d , 0x00 , 0x03 , 0x00 , 0x0b , 0x00 , 0x03 , 0x00 , 0x09 , 0x00 , 0x19 , 0x00 , 0x04 , 0x00 , 0x07 , 0x00 , 0x9d , 0x00 , 0x12 , 0x00 }; void * stepPointer[]{ (void * )sub_46b578,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b3f8,(void * )sub_46b040,(void * )sub_46b578,(void * )sub_46b0bc,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46aecc,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46b1c4,(void * )sub_46b32c,(void * )sub_46b3f8,(void * )sub_46b3f8,(void * )sub_46b040,(void * )sub_46b578,(void * )sub_46b0bc,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46aecc,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46b1c4,(void * )sub_46b32c,(void * )sub_46b3f8,(void * )sub_46b3f8,(void * )sub_46b040,(void * )sub_46b578,(void * )sub_46b0bc,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46aecc,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46b1c4,(void * )sub_46b32c,(void * )sub_46b3f8,(void * )sub_46b3f8,(void * )sub_46b040,(void * )sub_46b578,(void * )sub_46b0bc,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46aecc,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46b1c4,(void * )sub_46b32c,(void * )sub_46b578,(void * )sub_46aecc,(void * )sub_46b4e8,(void * )sub_46b578,(void * )sub_46b13c,(void * )sub_46b4e8,(void * )sub_46b578,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b578,(void * )sub_46b13c,(void * )sub_46b4e8,(void * )sub_46b578,(void * )sub_46b4e8,(void * )sub_46b578,(void * )sub_46b4e8,(void * )sub_46b578,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b394,(void * )sub_46b3f8,(void * )sub_46b13c,(void * )sub_46b3f8,(void * )sub_46afc4,(void * )sub_46b578,(void * )sub_46b3f8,(void * )sub_46afc4,(void * )sub_46b0bc,(void * )sub_46b3f8,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46b1c4,(void * )sub_46b2c4,(void * )sub_46b3f8,(void * )sub_46b394,(void * )sub_46b3f8,(void * )sub_46b13c,(void * )sub_46b3f8,(void * )sub_46afc4,(void * )sub_46b578,(void * )sub_46b3f8,(void * )sub_46afc4,(void * )sub_46b0bc,(void * )sub_46b3f8,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46b1c4,(void * )sub_46b2c4,(void * )sub_46b3f8,(void * )sub_46b394,(void * )sub_46b3f8,(void * )sub_46b13c,(void * )sub_46b3f8,(void * )sub_46afc4,(void * )sub_46b578,(void * )sub_46b3f8,(void * )sub_46afc4,(void * )sub_46b0bc,(void * )sub_46b3f8,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46b1c4,(void * )sub_46b2c4,(void * )sub_46b3f8,(void * )sub_46b394,(void * )sub_46b3f8,(void * )sub_46b13c,(void * )sub_46b3f8,(void * )sub_46afc4,(void * )sub_46b578,(void * )sub_46b3f8,(void * )sub_46afc4,(void * )sub_46b0bc,(void * )sub_46b3f8,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46b1c4,(void * )sub_46b2c4,(void * )sub_46b578,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b3f8,(void * )sub_46b040,(void * )sub_46b578,(void * )sub_46b0bc,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46aecc,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46b1c4,(void * )sub_46b32c,(void * )sub_46b3f8,(void * )sub_46b3f8,(void * )sub_46b040,(void * )sub_46b578,(void * )sub_46b0bc,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46aecc,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46b1c4,(void * )sub_46b32c,(void * )sub_46b3f8,(void * )sub_46b3f8,(void * )sub_46b040,(void * )sub_46b578,(void * )sub_46b0bc,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46aecc,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46b1c4,(void * )sub_46b32c,(void * )sub_46b3f8,(void * )sub_46b3f8,(void * )sub_46b040,(void * )sub_46b578,(void * )sub_46b0bc,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46aecc,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46b1c4,(void * )sub_46b32c,(void * )sub_46b578,(void * )sub_46aecc,(void * )sub_46b4e8,(void * )sub_46b578,(void * )sub_46b13c,(void * )sub_46b4e8,(void * )sub_46b578,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b578,(void * )sub_46b13c,(void * )sub_46b4e8,(void * )sub_46b578,(void * )sub_46b4e8,(void * )sub_46b578,(void * )sub_46b4e8,(void * )sub_46b578,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b394,(void * )sub_46b3f8,(void * )sub_46ae50,(void * )sub_46b3f8,(void * )sub_46afc4,(void * )sub_46b578,(void * )sub_46b3f8,(void * )sub_46afc4,(void * )sub_46b0bc,(void * )sub_46b3f8,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46b1c4,(void * )sub_46b2c4,(void * )sub_46b3f8,(void * )sub_46b394,(void * )sub_46b3f8,(void * )sub_46ae50,(void * )sub_46b3f8,(void * )sub_46afc4,(void * )sub_46b578,(void * )sub_46b3f8,(void * )sub_46afc4,(void * )sub_46b0bc,(void * )sub_46b3f8,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46b1c4,(void * )sub_46b2c4,(void * )sub_46b3f8,(void * )sub_46b394,(void * )sub_46b3f8,(void * )sub_46ae50,(void * )sub_46b3f8,(void * )sub_46afc4,(void * )sub_46b578,(void * )sub_46b3f8,(void * )sub_46afc4,(void * )sub_46b0bc,(void * )sub_46b3f8,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46b1c4,(void * )sub_46b2c4,(void * )sub_46b3f8,(void * )sub_46b394,(void * )sub_46b3f8,(void * )sub_46ae50,(void * )sub_46b3f8,(void * )sub_46afc4,(void * )sub_46b578,(void * )sub_46b3f8,(void * )sub_46afc4,(void * )sub_46b0bc,(void * )sub_46b3f8,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46ae50,(void * )sub_46b4e8,(void * )sub_46b3f8,(void * )sub_46b578,(void * )sub_46b1c4,(void * )sub_46b2c4 }; |
其中修改data的函数总共执行了48次, 且看起来非常有规律性, 因此一种可行方案是对这里用到的总共13种操作函数进行重写, 实现一个和游戏分离离线的状态机, 然后再对该算法进行逆向. 时间和工作量原因我这里并没有对该算法实现相应的逆向.
libil2cpp.so ==> sub_465C80
虽然上一个算法并没有实现对应的逆向算法, 但是其输入输出是一一对应的, 所以这个部分也能够继续进行. 这个算法大部分数据的来源都是已知的, 除了其中的某个数组, 这个数组可以用frida获取, 并且其值是锁定的, 不会改变
v29本身的+8是跳过其他结构, 到数据区, 可以看到数组的下标一定是[0, 1, 2, 3]中的一个, 所以把数组的这四项dump下来即可实现该算法. 经过研究其等价的c代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | int Fun1(unsigned int v27, unsigned int v28) { / / 输入值 0x3e90dddf 0x713a9ff / / 输出值 0xD2FB5C27 unsigned int data[] = { 0x7b777c63 , 0xc56f6bf2 , 0x2b670130 , 0x76abd7fe }; unsigned int v33 = 0xBEEFBEEF , v34 = 0x9D9D7DDE , v35 = 64 ; do { unsigned int v36 = (v34 >> 13 ) & 3 ; v27 + = (v33 - data[(v33 & 3 )]) ^ (((v28 << 7 ) ^ (v28 >> 8 )) + v28); - - v35; v33 - = 0x21524111 ; v28 + = (v34 + data[v36]) ^ (((v27 << 8 ) ^ (v27 >> 7 )) - v27); v34 - = 0x21524111 ; } while (v35); return v27; } |
那么它的逆算法也能给出是这样的, 经过验算无误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void Fun1_reverse(unsigned int &v27, unsigned int &v28, int in_v27) { / / 输入 v27 = 0xd2fb5c27 , v28 = 0xcf361bed / / 输出 v27 = 0x3e90dddf , v28 = 0x713a9ff v27 = in_v27; unsigned int v33 = 0x6a5f7aaf , v34 = 0x490d399e , v35 = 64 ; unsigned int data[] = { 0x7b777c63 , 0xc56f6bf2 , 0x2b670130 , 0x76abd7fe }; do { v34 + = 0x21524111 ; unsigned int v36 = (v34 >> 13 ) & 3 ; v28 - = (v34 + data[v36]) ^ (((v27 << 8 ) ^ (v27 >> 7 )) - v27); v33 + = 0x21524111 ; v27 - = (v33 - data[(v33 & 3 )]) ^ (((v28 << 7 ) ^ (v28 >> 8 )) + v28); - - v35; } while (v35); } |
总结
到此为止成功获取了flag, 过掉反调试使得frida能够正常工作, 之后静态分析加动态调试还原了除vm的算法以外的其余算法.
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课