CISCN 2022 华北赛后复盘

reverse

rtMaze

首先打开题目发现只有一个linux脚本和bin文件. 根据脚本里的QEMU以及对bin进行file后, 可以推断是一个固件逆向之类的题目, 且架构为ARM.

1
2
3
4
5
if [ ! -f "sd.bin" ]; then
dd if=/dev/zero of=sd.bin bs=1024 count=65536
fi

qemu-system-arm -M vexpress-a9 -smp cpus=2 -kernel rtMaze.bin -serial stdio -sd sd.bin -show-cursor

运行qemu.sh后, 输入get_flag会发现要先解一个迷宫, 我输入一个’a’发现就可以, 但是到后面会发现这样投机取巧是不行的, 正确的解法如下:

使用IDA打开rmMaze.bin, 将基址设为0x60010000.

LDR PC, =sub_6001E684可知固件入口函数是在sub_6001E684.
固件里的函数太多, 直接搜索flag字符串:

在字符串input your flag: />这里, 发现有xref, 跳转到sub_600100B4:

基本可以判断sub_600100B4就是我们要找的核心函数:

伪代码如下:

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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
int __fastcall sub_600100B4(int a1)
{
char *v2; // r5
int v3; // r4
bool v4; // zf
unsigned int v5; // r0
unsigned int v6; // r3
signed int v7; // r8
signed int v8; // r4
int v9; // r4
int *v10; // r5
unsigned int *v11; // r10
int v12; // r3
int *v13; // r10
int v14; // r4
int v15; // r0
_WORD *v16; // r11
int v17; // r6
int v18; // r4
unsigned int v19; // r3
int *v20; // r0
int v21; // r1
int v22; // r3
unsigned int v23; // r3
int *v24; // r3
int v25; // r2
int v26; // r0
int v27; // t1
int v28; // t1
unsigned int v30[2]; // [sp+0h] [bp-98h] BYREF
int v31[4]; // [sp+8h] [bp-90h] BYREF
int v32[10]; // [sp+18h] [bp-80h] BYREF
_WORD Str_v34[20]; // [sp+40h] [bp-58h] BYREF
int key_v35[11]; // [sp+68h] [bp-30h] BYREF
char v35; // [sp+97h] [bp-1h] BYREF
_DWORD Str[64]; // [sp+98h] [bp+0h] BYREF

v2 = &v35;
sub_6005A394(Str, 0, 256);
v32[0] = 0xBD7CA3FA;
v32[1] = 0xAF0E7E23;
v32[2] = 0xDFCE4E3A;
v32[3] = 0x9099574E;
v32[4] = 0x56EF0BC5;
v32[5] = 0xC4E49E3F;
v32[6] = 0x4D222426;
v32[7] = 0x63C96BE5;
v32[8] = 0x3D60CE03;
v32[9] = 0xE64EEFA1;
printf_60013F3C("input your flag: />");
while ( 1 )
{
v3 = (unsigned __int8)getchar_60029EAC();
v4 = v3 == 0xD;
if ( v3 != 0xD )
v4 = v3 == 0xA;
if ( v4 )
break;
printf_60013F3C("%c");
*++v2 = v3;
}
printf_60013F3C("\n", v3);
if ( strlen_6005AA98((int)Str) == 40 )
{
v5 = strlen_6005AA98(a1);
v6 = v5;
if ( v5 <= 47 )
{
v7 = 48 - v5;
v8 = 0;
while ( 1 )
{
++v8;
*(_WORD *)(a1 + v6) = 0x61;
if ( v7 <= v8 )
break;
v6 = strlen_6005AA98(a1);
}
}
v9 = 0;
v10 = (int *)Str_v34;
v11 = (unsigned int *)Str_v34;
do
{
v12 = Str[v9++];
key_v35[0] = v12;
*v11++ = sub_60010B2C((int)key_v35);
}
while ( v9 != 10 );
v13 = key_v35;
v14 = 0;
do
{
v15 = *(_DWORD *)(a1 + 4 * v14++);
v31[0] = v15;
*v13++ = sub_60010B2C((int)v31);
}
while ( v14 != 12 );
v16 = Str_v34;
v17 = 0;
do
{
v18 = v17 + 4;
v19 = *((_DWORD *)v16 + 1);
v20 = v31;
v21 = v17;
v30[0] = *(_DWORD *)&Str_v34[v17];
v30[1] = v19;
do
{
v22 = v21 - 12 * (((int)((unsigned __int64)(v21 * (__int64)(int)&MEMORY[0x2AAAAAAB]) >> 32) >> 1) - (v21 >> 31));
++v21;
*v20++ = key_v35[v22];
}
while ( v18 != v21 );
sub_60010AB8(v30, (int)v31);
v23 = v30[1];
v16 += 4;
*(_DWORD *)&Str_v34[v17] = v30[0];
v17 += 4;
*((_DWORD *)v16 - 1) = v23;
}
while ( v18 != 20 );
v24 = v32;
v25 = 1;
do
{
v27 = *v10++;
v26 = v27;
v28 = *v24++;
if ( v26 != v28 )
v25 = 0;
}
while ( v10 != key_v35 );
if ( v25 )
return printf_60013F3C("Right!\nThe flag is: flag{%s}\n", (const char *)Str);
else
return printf_60013F3C("Sorry, it is wrong~\n");
}
else
{
printf_60013F3C("Wrong length!\n");
return 0;
}
}

首先是读取我们的输入到Str, 如下:

1
2
3
4
5
6
7
8
9
10
11
12
printf_60013F3C("input your flag: />");
while ( 1 )
{
v3 = (unsigned __int8)getchar_60029EAC();
v4 = v3 == 0xD;
if ( v3 != 0xD )
v4 = v3 == 0xA;
if ( v4 )
break;
printf_60013F3C("%c");
*++v2 = v3;
}

然后会判断Str长度是否为40:

1
2
3
4
5
6
7
8
9
if ( strlen_6005AA98((int)Str) == 40 )
{
// ......
}
else
{
printf_60013F3C("Wrong length!\n");
return 0;
}

下一步是用0x61填充我们在解迷宫那一步输入的key为48字节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
v5 = strlen_6005AA98(a1);
v6 = v5;
if ( v5 <= 47 )
{
v7 = 48 - v5;
v8 = 0;
while ( 1 )
{
++v8;
*(_WORD *)(a1 + v6) = 0x61;
if ( v7 <= v8 )
break;
v6 = strlen_6005AA98(a1);
}
}

接下来的两个do-while循环在动态调试后发现就是一个memcpy:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
v9 = 0;
v10 = (int *)Str_v34;
v11 = (unsigned int *)Str_v34;
do
{
v12 = Str[v9++];
key_v35[0] = v12;
*v11++ = sub_60010B2C((int)key_v35);
}
while ( v9 != 10 );
v13 = key_v35;
v14 = 0;
do
{
v15 = *(_DWORD *)(a1 + 4 * v14++);
v31[0] = v15;
*v13++ = sub_60010B2C((int)v31);
}
while ( v14 != 12 );

接下来就是最最核心的加密环节:

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
v16 = Str_v34;
v17 = 0;
do
{
v18 = v17 + 4;
v19 = *((_DWORD *)v16 + 1);
v20 = v31;
v21 = v17;
v30[0] = *(_DWORD *)&Str_v34[v17];
v30[1] = v19;
do
{
v22 = v21 - 12 * (((int)((unsigned __int64)(v21 * (__int64)(int)&MEMORY[0x2AAAAAAB]) >> 32) >> 1) - (v21 >> 31));
++v21;
*v20++ = key_v35[v22];
}
while ( v18 != v21 );
sub_60010AB8(v30, (int)v31);
v23 = v30[1];
v16 += 4;
*(_DWORD *)&Str_v34[v17] = v30[0];
v17 += 4;
*((_DWORD *)v16 - 1) = v23;
}
while ( v18 != 20 );

其中, v22 = v21 - 12 * (((int)((unsigned __int64)(v21 * (__int64)(int)&MEMORY[0x2AAAAAAB]) >> 32) >> 1) - (v21 >> 31)); 其实就是v22 = v12 % 12;因为我改了一下某个数组的lvar type影响这里识别不出来了.

很明显这里的sub_60010AB8是加密函数, 在加密之前的do-while会将key_v35里的数据复制到v31中, 每次sub_60010AB8加密Str_v34的两个DWORD, 可以大胆猜测sub_60010AB8里面和TEA算法有关.

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
ROM:60010AB8             ; unsigned int *__fastcall sub_60010AB8(unsigned int *result, int)
ROM:60010AB8 sub_60010AB8 ; CODE XREF: sub_600100B4+1A4↑p
ROM:60010AB8 30 40 2D E9 PUSH {R4,R5,LR}
ROM:60010ABC 20 57 03 E3 MOVW R5, #0x3720
ROM:60010AC0 10 40 90 E8 LDM R0, {R4,LR}
ROM:60010AC4 EF 56 4C E3 MOVT R5, #0xC6EF
ROM:60010AC8 00 30 A0 E3 MOV R3, #0
ROM:60010ACC
ROM:60010ACC loc_60010ACC ; CODE XREF: sub_60010AB8+68↓j
ROM:60010ACC 03 20 03 E2 AND R2, R3, #3
ROM:60010AD0 AE C2 A0 E1 MOV R12, LR,LSR#5
ROM:60010AD4 0E C2 2C E0 EOR R12, R12, LR,LSL#4
ROM:60010AD8 02 21 91 E7 LDR R2, [R1,R2,LSL#2]
ROM:60010ADC 0E C0 8C E0 ADD R12, R12, LR
ROM:60010AE0 02 20 83 E0 ADD R2, R3, R2
ROM:60010AE4 9E 34 83 E2 ADD R3, R3, #0x9E000000
ROM:60010AE8 DD 39 83 E2 ADD R3, R3, #0x374000
ROM:60010AEC 0C 20 22 E0 EOR R2, R2, R12
ROM:60010AF0 E6 3D 83 E2 ADD R3, R3, #0x3980
ROM:60010AF4 02 40 84 E0 ADD R4, R4, R2
ROM:60010AF8 39 30 83 E2 ADD R3, R3, #0x39 ; '9'
ROM:60010AFC D3 25 E1 E7 UBFX R2, R3, #0xB, #2
ROM:60010B00 A4 C2 A0 E1 MOV R12, R4,LSR#5
ROM:60010B04 04 C2 2C E0 EOR R12, R12, R4,LSL#4
ROM:60010B08 05 00 53 E1 CMP R3, R5
ROM:60010B0C 02 21 91 E7 LDR R2, [R1,R2,LSL#2]
ROM:60010B10 04 C0 8C E0 ADD R12, R12, R4
ROM:60010B14 02 20 83 E0 ADD R2, R3, R2
ROM:60010B18 0C 20 22 E0 EOR R2, R2, R12
ROM:60010B1C 02 E0 8E E0 ADD LR, LR, R2
ROM:60010B20 E9 FF FF 1A BNE loc_60010ACC
ROM:60010B24 10 40 80 E8 STM R0, {R4,LR}
ROM:60010B28 30 80 BD E8 POP {R4,R5,PC}
ROM:60010B28 ; End of function sub_60010AB8

这里就不得不吐槽一下IDA的F5, 我第一次也是直接用F5看的加密函数sub_60010AB8, 但是从a2 + 4 * ((i >> 11) & 3))可以发现a2必须是一个13个元素的DWORD数组, 这就很反直觉, 在动态调试时候也证实a2是一个4元素的DWORD数组.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned int *__fastcall sub_60010AB8(unsigned int *result, int a2)
{
unsigned int v2; // r4
unsigned int v3; // lr
unsigned int i; // r3

v2 = *result;
v3 = result[1];
for ( i = 0; i != 0xC6EF3720; v3 += (i + *(_DWORD *)(a2 + 4 * ((i >> 11) & 3))) ^ (((v2 >> 5) ^ (16 * v2)) + v2) )
{
v2 += (i + *(_DWORD *)(a2 + 4 * (i & 3))) ^ (((v3 >> 5) ^ (16 * v3)) + v3);
i -= -0x9E3779B9;
}
*result = v2;
result[1] = v3;
return result;
}

所以我通过动态调试和读汇编代码(主要还是猜), 逆出了正确的加密方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void encrypt(uint32_t *v, uint32_t *k)
{
uint32_t v0 = v[0], v1 = v[1];
uint32_t sum = 0x0;

while (1)
{
v0 += (sum + *(k + (sum & 3))) ^ (((v1 >> 5) ^ (v1 << 4)) + v1);
sum -= 0x61C88647;
if (sum == 0xC6EF3720)
{
break;
}
v1 += (sum + *(k + ((sum >> 11) & 3))) ^ (((v0 >> 5) ^ (v0 << 4)) + v0);
}
v[0] = v0;
v[1] = v1;
}

在加密结束之后, 就到了判断和输出结果的地方了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
v24 = v32;
v25 = 1;
do
{
v27 = *v10++;
v26 = v27;
v28 = *v24++;
if ( v26 != v28 )
v25 = 0;
}
while ( v10 != key_v35 );
if ( v25 )
return printf_60013F3C("Right!\nThe flag is: flag{%s}\n", (const char *)Str);
else
return printf_60013F3C("Sorry, it is wrong~\n");

可以看到是将加密后的结果和v32进行比较, v32在前面已经进行初始化了:

1
2
3
4
5
6
7
8
9
10
v32[0] = 0xBD7CA3FA;
v32[1] = 0xAF0E7E23;
v32[2] = 0xDFCE4E3A;
v32[3] = 0x9099574E;
v32[4] = 0x56EF0BC5;
v32[5] = 0xC4E49E3F;
v32[6] = 0x4D222426;
v32[7] = 0x63C96BE5;
v32[8] = 0x3D60CE03;
v32[9] = 0xE64EEFA1;

由此, 大体的逻辑我们以及找到, 下面就是解密环节了:

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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#include <stdio.h>
#include <stdint.h>
#include <string.h>

uint32_t flag[11] = {
0x64646464,
0x64646464,
0x64646464,
0x64646464,
0x64646464,
0x64646464,
0x64646464,
0x64646464,
0x64646464,
0x64646464,
0x0};

uint32_t dec_flag[11] = {
0xBD7CA3FA,
0xAF0E7E23,
0xDFCE4E3A,
0x9099574E,
0x56EF0BC5,
0xC4E49E3F,
0x4D222426,
0x63C96BE5,
0x3D60CE03,
0xE64EEFA1,
0x0};
// dddwwawwwwaasdsasawawdwaaasawassssdwdsddssasddw
uint8_t key[] = {
'd',
'd',
'd',
'w',
'w',
'a',
'w',
'w',
'w',
'w',
'a',
'a',
's',
'd',
's',
'a',
's',
'a',
'w',
'a',
'w',
'd',
'w',
'a',
'a',
'a',
's',
'a',
'w',
'a',
's',
's',
's',
's',
'd',
'w',
'd',
's',
'd',
'd',
's',
's',
'a',
's',
'd',
'd',
'w',
'a',
};

void encrypt(uint32_t *v, uint32_t *k)
{
uint32_t v0 = v[0], v1 = v[1];
uint32_t sum = 0x0;
while (1)
{
v0 += (sum + *(k + (sum & 3))) ^ (((v1 >> 5) ^ (v1 << 4)) + v1);
sum -= 0x61C88647;
if (sum == 0xC6EF3720)
{
break;
}
v1 += (sum + *(k + ((sum >> 11) & 3))) ^ (((v0 >> 5) ^ (v0 << 4)) + v0);
}
v[0] = v0;
v[1] = v1;
}

void decrypt(uint32_t *v, uint32_t *k)
{
uint32_t v0 = v[0], v1 = v[1];
uint32_t sum = 0xC6EF3720;
while (1)
{
if (sum == 0)
{
break;
}
v1 -= (sum + *(k + ((sum >> 11) & 3))) ^ (((v0 >> 5) ^ (v0 << 4)) + v0);
sum += 0x61C88647;
v0 -= (sum + *(k + (sum & 3))) ^ (((v1 >> 5) ^ (v1 << 4)) + v1);
}
v[0] = v0;
v[1] = v1;
}

int main(int argc, const char *argv[])
{
uint32_t round_key[14] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
uint32_t *v;

int i = 0, j = 0;
do
{
j = i + 4;
int l = 0;
int n = i;
uint32_t *rk = round_key;
v = &dec_flag[i / 2];
do
{
l = n % 12;
++n;
*rk++ = ((uint32_t*)key)[l];
} while (j != n);
printf("k: %s\n", round_key);
decrypt(v, round_key);
i += 4;
} while (j != 20);
printf("%s", dec_flag);
return 0;
}

计算出flag:

完成: