2024年京津冀大学生信息安全网络攻防大赛(首届)|by_Flooc

reverse

看图标

py打包的exe,解包,反编译

image-20240907100119482

本来想用z3爆破,仔细看了看,a的每两位数就是flag的ascii码。直接打印:

1
2
3
4
5
6
7
8
result = 0x28F811AAE094D40A82BE51B5F4BBB6924300A1D9E72C9AA02E19B8A123BD7A9EED4849CEA7F9395CE507E8C9AF284EA74EB436CD9810A3C876C150

p = "666C61677B63393461626261652D353263302D346461302D616666312D3838393737303538393563377D666164616477617364617764617764631B"

a = 0x666C61677B63393461626261652D353263302D346461302D616666312D383839373730353839356337A7
for i in range(len(p) // 2):
print(chr(int(p[2 * i:2 * i + 2], 16)),end='')
# for i in range(len(str(p))):

beijing

打开发现用的都是同一个函数sub_8048460,传入的一个参数,这个参数就是代表后面switch case的情况,将异或的结果打印出来,不对,在数据段看到异或的第二个操作数,发现是flag的ascii码,手搓出来,打印

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
print(chr(0x66), end='')
print(chr(0x6c), end='')
print(chr(0x61), end='')
print(chr(0x67), end='')
print(chr(0x7b), end='')
print(chr(0x61), end='')
print(chr(0x6d), end='')
print(chr(0x61), end='')
print(chr(0x7a), end='')
print(chr(0x69), end='')
print(chr(0x6e), end='')
print(chr(0x67), end='')
print(chr(0x5f), end='')
print(chr(0x62), end='')
print(chr(0x65), end='')
print(chr(0x69), end='')
print(chr(0x6a), end='')
print(chr(0x69), end='')
print(chr(0x6e), end='')
print(chr(0x67), end='')
print(chr(0x7D), end='')

image-20240907100757678

swag

分析

首先jadx打开,分析发现主要逻辑在so层,改apk后缀为zip,解压,找到lib下的四个so文件,我这里看的是x86的so,比较清晰。分析完的函数重命名一下:

image-20240908160939935

首先是convert_to_matrix函数,这里可以看一下arm64-v8a下的so:

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
_QWORD *__fastcall sub_D60(unsigned __int8 *a1)
{
_QWORD *v2; // x20
_DWORD *v3; // x21
_DWORD *v4; // x22
_DWORD *v5; // x23
_DWORD *v6; // x24
_DWORD *v7; // x9
_QWORD *result; // x0
_DWORD *v9; // x8

v2 = malloc(48u);
v3 = malloc(0x18u);
*v2 = v3;
v4 = malloc(0x18u);
v2[1] = v4;
v5 = malloc(0x18u);
v2[2] = v5;
v6 = malloc(0x18u);
v2[3] = v6;
v2[4] = malloc(0x18u);
v2[5] = malloc(0x18u);
v7 = (_DWORD *)v2[4];
result = v2;
*v3 = *a1;
v3[1] = a1[1];
v3[2] = a1[2];
v3[3] = a1[3];
v3[4] = a1[4];
v3[5] = a1[5];
*v4 = a1[6];
v4[1] = a1[7];
v4[2] = a1[8];
v4[3] = a1[9];
v4[4] = a1[10];
v4[5] = a1[11];
*v5 = a1[12];
v5[1] = a1[13];
v5[2] = a1[14];
v5[3] = a1[15];
v5[4] = a1[16];
v5[5] = a1[17];
*v6 = a1[18];
v6[1] = a1[19];
v6[2] = a1[20];
v6[3] = a1[21];
v6[4] = a1[22];
v6[5] = a1[23];
*v7 = a1[24];
v7[1] = a1[25];
v7[2] = a1[26];
v7[3] = a1[27];
v7[4] = a1[28];
v7[5] = a1[29];
v9 = (_DWORD *)v2[5];
*v9 = a1[30];
v9[1] = a1[31];
v9[2] = a1[32];
v9[3] = a1[33];
v9[4] = a1[34];
v9[5] = a1[35];
return result;
}

可以理解为将输入的36个字符转换成了一个6*6的矩阵,或者二位列表

然后是turn_over_by_main_diagonal,作用是将这个矩阵沿着它的主对角线翻转(应该是这么说的,线代忘得差不多了。。

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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
int __cdecl sub_D20(_DWORD *a1)
{
_DWORD *v1; // edx
int v2; // esi
_DWORD *v3; // edx
int v4; // esi
_DWORD *v5; // edx
int v6; // esi
_DWORD *v7; // edx
int v8; // esi
_DWORD *v9; // edx
int v10; // esi
int v11; // ecx
int v12; // edx
int v13; // esi
int v14; // ecx
int v15; // edx
int v16; // esi
int v17; // ecx
int v18; // edx
int v19; // esi
int v20; // ecx
int v21; // edx
int v22; // esi
int v23; // ecx
int v24; // edx
int v25; // esi
int v26; // ecx
int v27; // edx
int v28; // esi
int v29; // ecx
int v30; // edx
int v31; // esi
int v32; // ecx
int v33; // edx
int v34; // esi
int v35; // ecx
int v36; // edx
int v37; // esi
int v38; // ecx
int result; // eax
int v40; // edx

v1 = (_DWORD *)a1[1];
v2 = *(_DWORD *)(*a1 + 4);
*(_DWORD *)(*a1 + 4) = *v1;
*v1 = v2;
v3 = (_DWORD *)a1[2];
v4 = *(_DWORD *)(*a1 + 8);
*(_DWORD *)(*a1 + 8) = *v3;
*v3 = v4;
v5 = (_DWORD *)a1[3];
v6 = *(_DWORD *)(*a1 + 12);
*(_DWORD *)(*a1 + 12) = *v5;
*v5 = v6;
v7 = (_DWORD *)a1[4];
v8 = *(_DWORD *)(*a1 + 16);
*(_DWORD *)(*a1 + 16) = *v7;
*v7 = v8;
v9 = (_DWORD *)a1[5];
v10 = *(_DWORD *)(*a1 + 20);
*(_DWORD *)(*a1 + 20) = *v9;
*v9 = v10;
v11 = a1[1];
v12 = a1[2];
v13 = *(_DWORD *)(v11 + 8);
*(_DWORD *)(v11 + 8) = *(_DWORD *)(v12 + 4);
*(_DWORD *)(v12 + 4) = v13;
v14 = a1[1];
v15 = a1[3];
v16 = *(_DWORD *)(v14 + 12);
*(_DWORD *)(v14 + 12) = *(_DWORD *)(v15 + 4);
*(_DWORD *)(v15 + 4) = v16;
v17 = a1[1];
v18 = a1[4];
v19 = *(_DWORD *)(v17 + 16);
*(_DWORD *)(v17 + 16) = *(_DWORD *)(v18 + 4);
*(_DWORD *)(v18 + 4) = v19;
v20 = a1[1];
v21 = a1[5];
v22 = *(_DWORD *)(v20 + 20);
*(_DWORD *)(v20 + 20) = *(_DWORD *)(v21 + 4);
*(_DWORD *)(v21 + 4) = v22;
v23 = a1[2];
v24 = a1[3];
v25 = *(_DWORD *)(v23 + 12);
*(_DWORD *)(v23 + 12) = *(_DWORD *)(v24 + 8);
*(_DWORD *)(v24 + 8) = v25;
v26 = a1[2];
v27 = a1[4];
v28 = *(_DWORD *)(v26 + 16);
*(_DWORD *)(v26 + 16) = *(_DWORD *)(v27 + 8);
*(_DWORD *)(v27 + 8) = v28;
v29 = a1[2];
v30 = a1[5];
v31 = *(_DWORD *)(v29 + 20);
*(_DWORD *)(v29 + 20) = *(_DWORD *)(v30 + 8);
*(_DWORD *)(v30 + 8) = v31;
v32 = a1[3];
v33 = a1[4];
v34 = *(_DWORD *)(v32 + 16);
*(_DWORD *)(v32 + 16) = *(_DWORD *)(v33 + 12);
*(_DWORD *)(v33 + 12) = v34;
v35 = a1[3];
v36 = a1[5];
v37 = *(_DWORD *)(v35 + 20);
*(_DWORD *)(v35 + 20) = *(_DWORD *)(v36 + 12);
*(_DWORD *)(v36 + 12) = v37;
v38 = a1[4];
result = a1[5];
v40 = *(_DWORD *)(v38 + 20);
*(_DWORD *)(v38 + 20) = *(_DWORD *)(result + 16);
*(_DWORD *)(result + 16) = v40;
return result;
}

这部分代码分成两份看,第一部分到62行这里:

image-20240908161500362

从二维列表的角度看,这里a1[n]可以看成第n行的第一个元素,而*(a1 + 4 * n)可以看成第一行也就是a0的第n个元素,至于为什么是4 * n,我也不太清楚,前面有个DW标识,表示它后面跟的那个变量是什么类型的,DW是双字,也就是四字节,一次交换四个字节,虽然一个char是一个字节,但是这个exe中存储的是四个字节,从后面密文的存储结构可以看出来。

所以从矩阵角度来看,就是将m(0,n)与m(n,0)交换。

第二部分同上分析,也可看出是将m(a,b)与m(b,a)交换。

这样两者结合起来,就是沿主对角线翻转。

最后就是这个加密函数了,其实不算加密,只是计算了乘积的和。

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
_DWORD *__cdecl sub_EA0(int a1, _DWORD *a2)
{
_DWORD *v2; // edi
int i; // eax
_DWORD *v4; // ecx
_DWORD *v6; // [esp+8h] [ebp-14h]

v6 = malloc(0x18u);
v2 = malloc(0x18u);
*v6 = v2;
v6[1] = malloc(0x18u);
v6[2] = malloc(0x18u);
v6[3] = malloc(0x18u);
v6[4] = malloc(0x18u);
v6[5] = malloc(0x18u);
for ( i = -5; ; ++i )
{
v4 = *(_DWORD **)(a1 + 4 * i + 20);
*v2 = *v4 * *a2 + v4[1] * a2[6] + v4[2] * a2[12] + v4[3] * a2[18] + v4[4] * a2[24] + v4[5] * a2[30];
v2[1] = *v4 * a2[1] + v4[1] * a2[7] + v4[2] * a2[13] + v4[3] * a2[19] + v4[4] * a2[25] + v4[5] * a2[31];
v2[2] = *v4 * a2[2] + v4[1] * a2[8] + v4[2] * a2[14] + v4[3] * a2[20] + v4[4] * a2[26] + v4[5] * a2[32];
v2[3] = *v4 * a2[3] + v4[1] * a2[9] + v4[2] * a2[15] + v4[3] * a2[21] + v4[4] * a2[27] + v4[5] * a2[33];
v2[4] = *v4 * a2[4] + v4[1] * a2[10] + v4[2] * a2[16] + v4[3] * a2[22] + v4[4] * a2[28] + v4[5] * a2[34];
v2[5] = *v4 * a2[5] + v4[1] * a2[11] + v4[2] * a2[17] + v4[3] * a2[23] + v4[4] * a2[29] + v4[5] * a2[35];
if ( !i )
break;
v2 = (_DWORD *)v6[i + 6];
}
return v6;
}

6次循环,每次循环处理翻转后的一行,这里比较简单,用z3解。

脚本

首先将密文转换为DW类型并导出,我是这样处理的:按几下d,然后将这段文本复制,写个脚本处理下:

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
import re

txt = """
DCD 0x4E36B ; DATA XREF: sub_11B4+60↑o
.rodata:000000000000166C DCD 0x362D6
.rodata:0000000000001670 DCD 0x3D5F1
.rodata:0000000000001674 dword_1674 DCD 0x63C4C ; DATA XREF: sub_11B4+A4↑r
.rodata:0000000000001678 dword_1678 DCD 0x66AF7 ; DATA XREF: sub_11B4+B4↑r
.rodata:000000000000167C dword_167C DCD 0x418B7 ; DATA XREF: sub_11B4+C4↑r
.rodata:0000000000001680 DCD 0x4BE2E
.rodata:0000000000001684 DCD 0x35571
.rodata:0000000000001688 DCD 0x3DA7F
.rodata:000000000000168C DCD 0x60D4A
.rodata:0000000000001690 DCD 0x6423A
.rodata:0000000000001694 DCD 0x3FC18
.rodata:0000000000001698 DCD 0x3A3B6
.rodata:000000000000169C DCD 0x2FBEE
.rodata:00000000000016A0 DCD 0x38F5B
.rodata:00000000000016A4 DCD 0x509E4
.rodata:00000000000016A8 DCD 0x57DAE
.rodata:00000000000016AC DCD 0x37D25
.rodata:00000000000016B0 DCD 0x2E69A
.rodata:00000000000016B4 DCD 0x28B2A
.rodata:00000000000016B8 DCD 0x363B1
.rodata:00000000000016BC DCD 0x41DAE
.rodata:00000000000016C0 DCD 0x49FA8
.rodata:00000000000016C4 DCD 0x2D536
.rodata:00000000000016C8 DCD 0x3B440
.rodata:00000000000016CC DCD 0x28D5B
.rodata:00000000000016D0 DCD 0x3AF48
.rodata:00000000000016D4 DCD 0x51F80
.rodata:00000000000016D8 DCD 0x59294
.rodata:00000000000016DC DCD 0x30E5F
.rodata:00000000000016E0 DCD 0x47CF0
.rodata:00000000000016E4 DCD 0x34F47
.rodata:00000000000016E8 DCD 0x33520
.rodata:00000000000016EC DCD 0x547A8
.rodata:00000000000016F0 DCD 0x581E0
.rodata:00000000000016F4 DCD 0x3E875

"""

result = re.findall(r'DCD\s+(0x[0-9A-Z]+)',txt)
print(', '.join(result))

处理后,得到这种DW的数据,然后就是z3解:

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
cipher = [0x4E36B, 0x362D6, 0x3D5F1, 0x63C4C, 0x66AF7, 0x418B7, 0x4BE2E, 0x35571, 0x3DA7F, 0x60D4A, 0x6423A, 0x3FC18, 0x3A3B6, 0x2FBEE, 0x38F5B, 0x509E4, 0x57DAE, 0x37D25, 0x2E69A, 0x28B2A, 0x363B1, 0x41DAE, 0x49FA8, 0x2D536, 0x3B440, 0x28D5B, 0x3AF48, 0x51F80, 0x59294, 0x30E5F, 0x47CF0, 0x34F47, 0x33520, 0x547A8, 0x581E0, 0x3E875
]

table = [0x106, 0x245, 0x9C, 0x1E2, 0x224, 0x27A, 0x112, 0x0AE, 0x323,
0x3C4, 0x370, 0x0DC, 0x387, 0x1E, 0x0B6, 0x3D8, 0x35D, 0x13A,
0x2B9, 0x162, 0x83, 0x225, 0x57, 0x18C, 0x109, 0x21B, 0x319,
0x0EE, 0x2C1, 0x1D5, 0x23A, 0x19A, 0x145, 0x25E, 0x32A, 0x1D6]

from z3 import *


counts = 0
while counts < 6:
s = Solver()
a,b,c,d,e,f = Ints("a b c d e f")
s.add(a * table[0] + b * table[6] + c * table[12] + d * table[18] + e * table[24] + f * table[30] == cipher[6 * counts + 0])
s.add(a * table[1] + b * table[7] + c * table[13] + d * table[19] + e * table[25] + f * table[31] == cipher[6 * counts + 1])
s.add(a * table[2] + b * table[8] + c * table[14] + d * table[20] + e * table[26] + f * table[32] == cipher[6 * counts + 2])
s.add(a * table[3] + b * table[9] + c * table[15] + d * table[21] + e * table[27] + f * table[33] == cipher[6 * counts + 3])
s.add(a * table[4] + b * table[10] + c * table[16] + d * table[22] + e * table[28] + f * table[34] == cipher[6 * counts + 4])
s.add(a * table[5] + b * table[11] + c * table[17] + d * table[23] + e * table[29] + f * table[35] == cipher[6 * counts + 5])
print("第" + str(counts) + "部分:",end='')
print(s.check())
print(s.model())

counts += 1

最后是将这个矩阵沿主对角线翻转然后输出,我这里直接输出了,因为是6*6的矩阵,行和列代表的index互换一下就行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
tmp = [{'a': 109, 'b': 99, 'c': 116, 'd': 98, 'e': 107, 'f': 111},
{'a': 97, 'b': 101, 'c': 104, 'd': 100, 'e': 110, 'f': 114},
{'a': 121, 'b': 98, 'c': 89, 'd': 56, 'e': 121, 'f': 50},
{'a': 70, 'b': 101, 'c': 48, 'd': 54, 'e': 122, 'f': 54},
{'a': 48, 'b': 119, 'c': 85, 'd': 50, 'e': 105, 'f': 102},
{'a': 114, 'b': 49, 'c': 99, 'd': 106, 'e': 111, 'f': 102}]

combined_list = []
for part in tmp:
combined_list.extend(part.values())

for i in range(6):
for j in range(6):
print(chr(combined_list[i + j * 6]),end='')

image-20240908163212463

最后虽然有点flag的形状,但也不太确定,在apk中提交一下,已经变成flag的形状了(

image-20240908163244332