ACTF2022 复现 2

kcov

附件

  • launch.sh
  • bzImage
  • rootfs.cpio.gz

    分析

附件里的 bzImage 和 rootfs.cpio.gz 很明显是个 Linux 内核文件和Linux文件系统. 看了一下 launch.sh 的文件内容就是一个 QEMU 的启动脚本, 直接运行试试.

发现运行后可以看到kcov程序运行, 但是输出了个文件无法打开的错误就退出了, 这种情况就直接从 rootfs.cpio.gz 里面找到 kcov 程序然后提取出来(7-Zip、tar命令都可以).

main

使用 IDA 打开, 定位到main函数, 且使用Findcrypt发现了AES.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// main 函数
void __fastcall __noreturn sub_401AB9(__int64 a1, __int64 a2)
{
int i; // [rsp+14h] [rbp-ACh]
__int64 (__fastcall *v3)(); // [rsp+28h] [rbp-98h]
__int64 v4[16]; // [rsp+30h] [rbp-90h] BYREF
_QWORD v5[2]; // [rsp+B0h] [rbp-10h] BYREF

v5[1] = __readfsqword(0x28u);
memset(v4, 0, sizeof(v4));
sub_400B6D(v5, a2, v4);
sub_400BCD(v5);
for ( i = 0; i <= 15; ++i )
{
v3 = off_6BE3C0[sub_411920(v5) & 0xF];
sub_400CCF(v5);
((void (__fastcall *)(_QWORD *))v3)(v5);
v4[i] = *(_QWORD *)qword_6C17C8;
}
sub_4017D2(v4);
sub_410DF0(1LL);
}

其中, sub_400BCD 函数是从文件中读取内容, 并没有找到使用键盘输入.
由于不需要键盘输入, kcov 运行后发现要读取的文件不存在就会自动退出, 动调的时候可以通过修改寄存器绕过 sub_400BCD 的判断.

然后在 sub_4017D2 中发现了有调用加密函数:

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
unsigned __int64 __fastcall sub_4017D2(__int64 a1)
{
unsigned __int64 result; // rax
unsigned __int64 i; // [rsp+18h] [rbp-148h]
unsigned __int64 j; // [rsp+20h] [rbp-140h]
unsigned __int64 k; // [rsp+28h] [rbp-138h]
char v5[192]; // [rsp+30h] [rbp-130h] BYREF
_BYTE v6[16]; // [rsp+F0h] [rbp-70h] BYREF
char v7[80]; // [rsp+100h] [rbp-60h] BYREF
unsigned __int64 v8; // [rsp+158h] [rbp-8h]

v8 = __readfsqword(0x28u);
for ( i = 0LL; i <= 0xF; ++i )
*(_QWORD *)(a1 + 8 * i) = *(_QWORD *)(8 * i + a1) / 0x64uLL;
if ( sub_4015F8(a1) )
{
sub_401760((__int64)&unk_495E70, 0x27u);
v7[0] = 0x9E;
v7[1] = 0x69;
v7[2] = 0x36;
v7[3] = 0x12;
v7[4] = 0x7B;
v7[5] = 0xB0;
v7[6] = 4;
v7[7] = 0x4D;
v7[8] = 0xB9;
v7[9] = 0x2A;
v7[10] = 0x13;
v7[11] = 0x6E;
v7[12] = 0x37;
v7[13] = 0xD1;
v7[14] = 0xD6;
v7[15] = 0x20;
v7[16] = 0xA5;
v7[17] = 0x2B;
v7[18] = 0xB8;
v7[19] = 0xA2;
v7[20] = 0x9A;
v7[21] = 0x92;
v7[22] = 0xA2;
v7[23] = 0x24;
v7[24] = 0x9F;
qmemcpy(&v7[25], "89", 2);
v7[27] = 0xFF;
v7[28] = 0x55;
v7[29] = 0x66;
v7[30] = 0x6D;
v7[31] = 0xCE;
v7[32] = 0x39;
v7[33] = 0xEA;
v7[34] = 0x75;
v7[35] = 60;
v7[36] = 50;
v7[37] = 69;
v7[38] = 0xF3;
v7[39] = 0x67;
v7[40] = 0x84;
v7[41] = 0x8E;
v7[42] = 73;
v7[43] = 123;
v7[44] = 0xB8;
v7[45] = 0xAA;
v7[46] = 107;
v7[47] = 31;
v7[48] = 85;
v7[49] = 0xFC;
v7[50] = 0x96;
v7[51] = 30;
v7[52] = 119;
v7[53] = 0x90;
v7[54] = 61;
v7[55] = 0xB4;
v7[56] = 0xCB;
v7[57] = 105;
v7[58] = 62;
v7[59] = 0xF9;
v7[60] = 43;
v7[61] = 56;
v7[62] = 0x9E;
v7[63] = 118;
v7[64] = 109;
v7[65] = 0xB0;
v7[66] = 0xC9;
v7[67] = 0x9F;
v7[68] = 41;
v7[69] = 45;
v7[70] = 11;
v7[71] = 0xE7;
v7[72] = 12;
v7[73] = 0xED;
v7[74] = 0x8D;
v7[75] = 40;
v7[76] = 71;
v7[77] = 0x8C;
v7[78] = 94;
v7[79] = 0xCD;
memset(v6, 0, sizeof(v6));
for ( j = 0LL; j <= 0xF; ++j )
v6[j] = *(_QWORD *)(8 * j + a1);
sub_401E7D((__int64)v5, (__int64)v6);
for ( k = 0LL; k <= 4; ++k )
sub_402D5A(v5, &v7[16 * k]);
sub_413250(v7);
}
else
{
sub_401760((__int64)&byte_495E98, 0x27u);
}
result = __readfsqword(0x28u) ^ v8;
if ( result )
sub_44F0D0();
return result;
}

for ( i = 0LL; i <= 0xF; ++i ) 循环将 a1 进行了依次除 100 操作, 随后 sub_4015F8 对 a1 进行检查:

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
_BOOL8 __fastcall sub_4015F8(__int64 a1)
{
_BOOL8 result; // rax
unsigned __int64 i; // [rsp+18h] [rbp-58h]
char v3[16]; // [rsp+30h] [rbp-40h] BYREF
char v4[16]; // [rsp+40h] [rbp-30h] BYREF
_BYTE v5[16]; // [rsp+50h] [rbp-20h] BYREF
unsigned __int64 v6; // [rsp+68h] [rbp-8h]

v6 = __readfsqword(0x28u);
memset(v3, 0, sizeof(v3));
for ( i = 0LL; i <= 0xF; ++i )
v3[4 * (i & 3) + (i >> 2)] = *(_QWORD *)(8 * i + a1);
v4[0] = 44;
v4[1] = 4;
v4[2] = 23;
v4[3] = 13;
v4[4] = 2;
v4[5] = 45;
v4[6] = 57;
v4[7] = 51;
v4[8] = 7;
v4[9] = 22;
v4[10] = 52;
v4[11] = 24;
v4[12] = 29;
v4[13] = 26;
qmemcpy(&v4[14], "\v(", 2);
v5[1] = 0;
*(_WORD *)&v5[2] = 0;
*(_DWORD *)&v5[4] = 0;
*(_QWORD *)&v5[8] = 0LL;
sub_401503((__int64)v4, (__int64)v3, (__int64)v5, 4u);
result = v5[10] + v5[5] + v5[0] + v5[15] == 4 && v5[10] * v5[5] * v5[0] * v5[15] == 1;
if ( __readfsqword(0x28u) != v6 )
sub_44F0D0();
return result;
}

for ( i = 0LL; i <= 0xF; ++i ) 循环将 a1 打乱放入 v3 中, 然后 sub_401503 将 v3 和 v4 进行计算, 计算结果放入 v5 . 且 v3 v4 v5 都是 16 个元素.

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
__int64 __fastcall sub_401503(__int64 a1, __int64 a2, __int64 a3, unsigned __int8 a4)
{
unsigned __int8 v4; // al
__int64 result; // rax
unsigned __int8 v8; // [rsp+27h] [rbp-19h]
unsigned __int64 i; // [rsp+28h] [rbp-18h]
unsigned __int64 j; // [rsp+30h] [rbp-10h]
unsigned __int64 k; // [rsp+38h] [rbp-8h]

for ( i = 0LL; ; ++i )
{
result = a4;
if ( i >= a4 )
break;
for ( j = 0LL; j < a4; ++j )
{
v8 = 0;
for ( k = 0LL; k < a4; ++k )
{
v4 = sub_401465(*(unsigned __int8 *)(k + i * a4 + a1), *(unsigned __int8 *)(j + k * a4 + a2));
v8 = sub_4014DE(v8, v4);
}
*(_BYTE *)(a3 + j + i * a4) = v8;
}
}
return result;
}

这个三层循环嵌套很像矩阵乘法的操作, 通过 https://gist.github.com/meagtan/dc1adff8d84bb895891d8fd027ec9d8c 可以看到和 GF(2^8) 上的矩阵乘法很像, 但是不同点是 GF(2^8) 在乘法时是 AND 0x80, kcov中是 AND 0x40, 所以这里应该是 GF(2^6), 并且最后有一个 MOD 91的操作.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__int64 __fastcall sub_401465(char a1, unsigned __int8 a2)
{
unsigned __int8 v3; // [rsp+Dh] [rbp-Bh]
unsigned __int64 i; // [rsp+10h] [rbp-8h]

v3 = 0;
for ( i = 0LL; i < 6; ++i )
{
if ( (a2 & 1) != 0 )
v3 ^= a1;
a2 >>= 1;
a1 *= 2;
if ( (a1 & 0x40) != 0 )
a1 ^= 0x5Bu;
}
return mod91_4013E8(v3);
}

又根据 result = v5[10] + v5[5] + v5[0] + v5[15] == 4 && v5[10] * v5[5] * v5[0] * v5[15] == 1;, 可以猜测 v5 最后需要是一个单位矩阵, 所以我们需要找到一个矩阵是 v4 的逆矩阵. 可以使用 matlab 计算一下:

计算完成后可以得到[0, 5, 5, 14, 21, 8, 9, 1, 10, 6, 12, 3, 6, 7, 12, 4], 此时还需要对上述的v3[4 * (i & 3) + (i >> 2)] = *(_QWORD *)(8 * i + a1); 打乱操作进行还原,最后可得:key = [0, 21, 10, 6, 5, 8, 6, 7, 5, 9, 12, 12, 14, 1, 3, 4].

然后回到 sub_4017D2 , 可以发现 sub_402D5A 是一个 AES_ECB 加密过程, 加密密钥就是上面的key .

解密脚本

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
from Crypto.Cipher import AES
key = [0, 5, 5, 14, 21, 8, 9, 1, 10, 6, 12, 3, 6, 7, 12, 4]
tmp_key = [0] * 16

for i in range(0x10):
tmp_key[i] = key[(((i & 3) << 2) + (i >> 2))]

key = tmp_key
print(key)


aes = AES.new(bytes(key), AES.MODE_ECB)
flag = [0] * 80
flag[0] = 0x9E
flag[1] = 0x69
flag[2] = 0x36
flag[3] = 0x12
flag[4] = 0x7B
flag[5] = 0xB0
flag[6] = 4
flag[7] = 0x4D
flag[8] = 0xB9
flag[9] = 0x2A
flag[10] = 0x13
flag[11] = 0x6E
flag[12] = 0x37
flag[13] = 0xD1
flag[14] = 0xD6
flag[15] = 0x20
flag[16] = 0xA5
flag[17] = 0x2B
flag[18] = 0xB8
flag[19] = 0xA2
flag[20] = 0x9A
flag[21] = 0x92
flag[22] = 0xA2
flag[23] = 0x24
flag[24] = 0x9F
flag[25] = ord('8')
flag[26] = ord('9')
flag[27] = 0xFF
flag[28] = 0x55
flag[29] = 0x66
flag[30] = 0x6D
flag[31] = 0xCE
flag[32] = 0x39
flag[33] = 0xEA
flag[34] = 0x75
flag[35] = 60
flag[36] = 50
flag[37] = 69
flag[38] = 0xF3
flag[39] = 0x67
flag[40] = 0x84
flag[41] = 0x8E
flag[42] = 73
flag[43] = 123
flag[44] = 0xB8
flag[45] = 0xAA
flag[46] = 107
flag[47] = 31
flag[48] = 85
flag[49] = 0xFC
flag[50] = 0x96
flag[51] = 30
flag[52] = 119
flag[53] = 0x90
flag[54] = 61
flag[55] = 0xB4
flag[56] = 0xCB
flag[57] = 105
flag[58] = 62
flag[59] = 0xF9
flag[60] = 43
flag[61] = 56
flag[62] = 0x9E
flag[63] = 118
flag[64] = 109
flag[65] = 0xB0
flag[66] = 0xC9
flag[67] = 0x9F
flag[68] = 41
flag[69] = 45
flag[70] = 11
flag[71] = 0xE7
flag[72] = 12
flag[73] = 0xED
flag[74] = 0x8D
flag[75] = 40
flag[76] = 71
flag[77] = 0x8C
flag[78] = 94
flag[79] = 0xCD
for i in range(len(flag)):
flag[i] &= 0xff

flag = aes.decrypt(bytes(flag))
print(flag)