本文档是进行完相关分析之后的总结回忆, 所以可能有的地方的花指令是被去除了再截图的, 函数名和数据结构被重命名了. vm算法没有逆出来
根据题目提示要求, 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处理第一步输出值的另一部分
返回
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;
}
|
在这个函数中有很多复杂且奇怪的函数, 并且也出现了函数指针+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部分到此结束.
这个函数是从libsec2023.so返回所进入的函数, 首先根据伪代码观察其流程, 主要做的事情是初始化了一些数组和对象, 将上一个encrypt的结果传入sub_46AD44函数中进行加密, 然后在一个do_while循环中进行第二次加密. 最后如果v28等于0并且v27与token的值相等就开启mod成功, 这里手动patch判断语句可以实现作弊功能, 所以整体思路是对的.
首先来分析第一个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种操作函数进行重写, 实现一个和游戏分离离线的状态机, 然后再对该算法进行逆向. 时间和工作量原因我这里并没有对该算法实现相应的逆向.
虽然上一个算法并没有实现对应的逆向算法, 但是其输入输出是一一对应的, 所以这个部分也能够继续进行. 这个算法大部分数据的来源都是已知的, 除了其中的某个数组, 这个数组可以用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的算法以外的其余算法.
更多【2023腾讯游戏安全大赛-安卓赛道初赛wp】相关视频教程:www.yxfzedu.com