首页
社区
课程
招聘
[原创]2023腾讯游戏安全大赛-安卓赛道初赛wp
2023-4-17 19:46 31842

[原创]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文件的处理.

 

 

因为我并没有注入游戏就被检测了, 那么关于这种检测常见的有:

  1. 检测/data/local/tmp文件夹下的re.frida.server

  2. 检测27042端口

  3. 检测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, 这显然是错误的. 但是可以看出来大概做了什么事情:

  1. sub_3B9D4处理输入值

  2. sub_3A924处理第一步输出值的一部分

  3. sub_3A924处理第一步输出值的另一部分

  4. 返回

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 40x86异或
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直播授课

最后于 2023-4-17 20:18 被juice4fun编辑 ,原因:
收藏
点赞4
打赏
分享
最新回复 (2)
雪    币: 1744
活跃值: (8728)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
你瞒我瞒 2023-4-18 09:15
2
0
66666
雪    币: 19431
活跃值: (29097)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-4-29 12:23
3
0
tql
游客
登录 | 注册 方可回帖
返回