[WP]2019红帽杯_easyRE

本文最后更新于:3 years ago

最近在做逆向题目时,遇到一道比较难的题,其中一个小细节第一遍做的时候忽略了,所以这里记录一下。

0x01 查壳和详细信息

拿到 attachment.elf 文件
首先,先查看基本信息


无壳,ELF文件,64位。

0x02 分析文件

既然是ELF文件的话,Windows环境下也运行不了,懒得打开Linux系统了。
所以这里直接就拖进IDA中静态调试。

看着这么多未被识别的函数,黄豆流汗。


先查找字符串,发现了有用的字符串


仔细查看汇编代码段的话


我在这里看出了Base64加密,所以立马解密了。
在解密十多次后,发现是一篇地址的网站,以为这么简单就拿到Flag了,
但最后发现只是一篇文章。被骗了…

回到IDA,接着通过交叉引用,可以找到对应的函数。贴出伪代码:

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
146
147
signed __int64 sub_4009C6()
{
signed __int64 result; // rax
__int64 v1; // ST10_8
__int64 v2; // ST18_8
__int64 v3; // ST20_8
__int64 v4; // ST28_8
__int64 v5; // ST30_8
__int64 v6; // ST38_8
__int64 v7; // ST40_8
__int64 v8; // ST48_8
__int64 v9; // ST50_8
int i; // [rsp+Ch] [rbp-114h]
char v11; // [rsp+60h] [rbp-C0h]
char v12; // [rsp+61h] [rbp-BFh]
char v13; // [rsp+62h] [rbp-BEh]
char v14; // [rsp+63h] [rbp-BDh]
char v15; // [rsp+64h] [rbp-BCh]
char v16; // [rsp+65h] [rbp-BBh]
char v17; // [rsp+66h] [rbp-BAh]
char v18; // [rsp+67h] [rbp-B9h]
char v19; // [rsp+68h] [rbp-B8h]
char v20; // [rsp+69h] [rbp-B7h]
char v21; // [rsp+6Ah] [rbp-B6h]
char v22; // [rsp+6Bh] [rbp-B5h]
char v23; // [rsp+6Ch] [rbp-B4h]
char v24; // [rsp+6Dh] [rbp-B3h]
char v25; // [rsp+6Eh] [rbp-B2h]
char v26; // [rsp+6Fh] [rbp-B1h]
char v27; // [rsp+70h] [rbp-B0h]
char v28; // [rsp+71h] [rbp-AFh]
char v29; // [rsp+72h] [rbp-AEh]
char v30; // [rsp+73h] [rbp-ADh]
char v31; // [rsp+74h] [rbp-ACh]
char v32; // [rsp+75h] [rbp-ABh]
char v33; // [rsp+76h] [rbp-AAh]
char v34; // [rsp+77h] [rbp-A9h]
char v35; // [rsp+78h] [rbp-A8h]
char v36; // [rsp+79h] [rbp-A7h]
char v37; // [rsp+7Ah] [rbp-A6h]
char v38; // [rsp+7Bh] [rbp-A5h]
char v39; // [rsp+7Ch] [rbp-A4h]
char v40; // [rsp+7Dh] [rbp-A3h]
char v41; // [rsp+7Eh] [rbp-A2h]
char v42; // [rsp+7Fh] [rbp-A1h]
char v43; // [rsp+80h] [rbp-A0h]
char v44; // [rsp+81h] [rbp-9Fh]
char v45; // [rsp+82h] [rbp-9Eh]
char v46; // [rsp+83h] [rbp-9Dh]
char v47[32]; // [rsp+90h] [rbp-90h]
int v48; // [rsp+B0h] [rbp-70h]
char v49; // [rsp+B4h] [rbp-6Ch]
char v50; // [rsp+C0h] [rbp-60h]
char v51; // [rsp+E7h] [rbp-39h]
char v52; // [rsp+100h] [rbp-20h]
unsigned __int64 v53; // [rsp+108h] [rbp-18h]

v53 = __readfsqword(0x28u);
v11 = 73;
v12 = 111;
v13 = 100;
v14 = 108;
v15 = 62;
v16 = 81;
v17 = 110;
v18 = 98;
v19 = 40;
v20 = 111;
v21 = 99;
v22 = 121;
v23 = 127;
v24 = 121;
v25 = 46;
v26 = 105;
v27 = 127;
v28 = 100;
v29 = 96;
v30 = 51;
v31 = 119;
v32 = 125;
v33 = 119;
v34 = 101;
v35 = 107;
v36 = 57;
v37 = 123;
v38 = 105;
v39 = 121;
v40 = 61;
v41 = 126;
v42 = 121;
v43 = 76;
v44 = 64;
v45 = 69;
v46 = 67;
memset(v47, 0, sizeof(v47));
v48 = 0;
v49 = 0;
sub_4406E0(0LL, v47, 37LL);
v49 = 0;
if ( sub_424BA0(v47) == 36 )
{
for ( i = 0; i < (unsigned __int64)sub_424BA0(v47); ++i )
{
if ( (unsigned __int8)(v47[i] ^ i) != *(&v11 + i) )
{
result = 4294967294LL;
goto LABEL_13;
}
}
sub_410CC0("continue!");
memset(&v50, 0, 0x40uLL);
v52 = 0;
sub_4406E0(0LL, &v50, 64LL);
v51 = 0;
if ( sub_424BA0(&v50) == 39 )
{
v1 = sub_400E44(&v50);
v2 = sub_400E44(v1);
v3 = sub_400E44(v2);
v4 = sub_400E44(v3);
v5 = sub_400E44(v4);
v6 = sub_400E44(v5);
v7 = sub_400E44(v6);
v8 = sub_400E44(v7);
v9 = sub_400E44(v8);
sub_400E44(v9);
if ( !(unsigned int)sub_400360() )
{
sub_410CC0("You found me!!!");
sub_410CC0("bye bye~");
}
result = 0LL;
}
else
{
result = 4294967293LL;
}
}
else
{
result = 0xFFFFFFFFLL;
}
LABEL_13:
if ( __readfsqword(0x28u) != v53 )
sub_444020();
return result;
}

__readfsqword(0x28u) 这个函数是用来防止调试的。

当我们查看 sub_4406E0 sub_424BA0 sub_424BA0 … 会发现这些函数都很难去分析。
但,既然有关键字 'continue!' 的话,那么肯定上面的函数确实存在有相应的信息。

其实突破点还是比较好找的,就在Continue上面

1
2
3
4
5
if ( (unsigned __int8)(v53[i] ^ i) != *(&v17 + i) )
{
result = 4294967294LL;
goto LABEL_13;
}

通过这段代码,发现,v53这个数组会与i进行异或并与v17数组比较,如果想往下继续执行的话,两者就必须相等。
v17这个数组,其实就是v17-v52的值。(我第一次写的时候,并没有发现这点,后面的字符串时猜的),这里猜测v53和输入有关系

然后就可以写EXP了:

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
# EXP1
flag_exp = ''
v = [0]*37
v[0] = 73
v[1] = 111
v[2] = 100
v[3] = 108
v[4] = 62
v[5] = 81
v[6] = 110
v[7] = 98
v[8] = 40
v[9] = 111
v[10] = 99
v[11] = 121
v[12] = 127
v[13] = 121
v[14] = 46
v[15] = 105
v[16] = 127
v[17] = 100
v[18] = 96
v[19] = 51
v[20] = 119
v[21] = 125
v[22] = 119
v[23] = 101
v[24] = 107
v[25] = 57
v[26] = 123
v[27] = 105
v[28] = 121
v[29] = 61
v[30] = 126
v[31] = 121
v[32] = 76
v[33] = 64
v[34] = 69
v[35] = 67
for i in range(36):
for char in range(32,127):
if i ^ char == v[i]:
flag_exp += chr(char)
print(flag_exp)

# 输出
Info:The first four chars are `flag`

其实输出的这段提示,就是我第一遍没有得到的信息,是靠猜前四个字符是flag。

接着看到这串代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
if ( v5 == 39 )
{
v1 = sub_400E44(&v50);
v2 = sub_400E44(v1);
v3 = sub_400E44(v2);
v4 = sub_400E44(v3);
v5 = sub_400E44(v4);
v6 = sub_400E44(v5);
v7 = sub_400E44(v6);
v8 = sub_400E44(v7);
v9 = sub_400E44(v8);
sub_400E44(v9);
if ( !(unsigned int)sub_400360() )
{
sub_410CC0("You found me!!!");
sub_410CC0("bye bye~");
}
result = 0LL;
}
else
{
result = 4294967293LL;
}

发现 sub_400E44 一直在将v50进行不断处理,那就康康这个函数,贴出代码:

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
__int64 __fastcall sub_400E44(const __m128i *a1)
{
__int64 v1; // rax
__int64 v2; // rax
__int64 v3; // rax
int v5; // [rsp+18h] [rbp-28h]
int v6; // [rsp+1Ch] [rbp-24h]
signed __int64 v7; // [rsp+20h] [rbp-20h]
__int64 v8; // [rsp+30h] [rbp-10h]
__int64 v9; // [rsp+38h] [rbp-8h]

LODWORD(v1) = sub_424BA0(a1);
v8 = v1;
if ( v1 == 3 * (((unsigned __int128)(6148914691236517206LL * (signed __int128)v1) >> 64) - (v1 >> 63)) )
v2 = ((unsigned __int128)(6148914691236517206LL * (signed __int128)v1) >> 64) - (v1 >> 63);
else
v2 = ((unsigned __int128)(6148914691236517206LL * (signed __int128)v1) >> 64) - (v1 >> 63) + 1;
v7 = 4 * v2;
v9 = sub_41EF60(4 * v2 + 1);
*(_BYTE *)(v7 + v9) = 0;
v5 = 0;
v6 = 0;
while ( v5 < v7 - 2 )
{
*(_BYTE *)(v9 + v5) = aAbcdefghijklmn[(unsigned __int8)(*((_BYTE *)a1->m128i_i64 + v6) >> 2)];
*(_BYTE *)(v9 + v5 + 1LL) = aAbcdefghijklmn[16 * (*((_BYTE *)a1->m128i_i64 + v6) & 3) | (unsigned __int8)(*((_BYTE *)a1->m128i_i64 + v6 + 1) >> 4)];
*(_BYTE *)(v9 + v5 + 2LL) = aAbcdefghijklmn[4 * (*((_BYTE *)a1->m128i_i64 + v6 + 1) & 0xF) | (unsigned __int8)(*((_BYTE *)a1->m128i_i64 + v6 + 2) >> 6)];
*(_BYTE *)(v9 + v5 + 3LL) = aAbcdefghijklmn[*((_BYTE *)a1->m128i_i64 + v6 + 2) & 0x3F];
v6 += 3;
v5 += 4;
}
v3 = v8 - 3 * (((unsigned __int128)(6148914691236517206LL * (signed __int128)v8) >> 64) - (v8 >> 63));
if ( v3 == 1 )
{
*(_BYTE *)(v5 - 2LL + v9) = 61;
*(_BYTE *)(v5 - 1LL + v9) = 61;
}
else if ( v3 == 2 )
{
*(_BYTE *)(v5 - 1LL + v9) = 61;
}
return v9;
}

额,这不就是Base64嘛。。。和上面呼应起来了,就是我开始解密的十遍的那个字符串嘛
可想而知,这条线索是没有用的。

这时没办法了,看了WP

我们先来到调用加密过的数据的地方,发现还有两个 byte_6CC0A0byte_6CC0A3 是数组,其实就是一组数据。


来到调用这组数据的函数

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
__int64 __fastcall sub_400D35(__int64 a1, __int64 a2)
{
__int64 v2; // rdi
__int64 result; // rax
unsigned __int64 v4; // rt1
unsigned int v5; // [rsp+Ch] [rbp-24h]
signed int i; // [rsp+10h] [rbp-20h]
signed int j; // [rsp+14h] [rbp-1Ch]
unsigned int v8; // [rsp+24h] [rbp-Ch]
unsigned __int64 v9; // [rsp+28h] [rbp-8h]

v9 = __readfsqword(0x28u);
v2 = 0LL;
v5 = sub_43FD20(0LL) - qword_6CEE38;
for ( i = 0; i <= 1233; ++i )
{
v2 = v5;
sub_40F790(v5);
sub_40FE60();
sub_40FE60();
v5 = (unsigned __int64)sub_40FE60() ^ 0x98765432;
}
v8 = v5;
if ( ((unsigned __int8)v5 ^ byte_6CC0A0[0]) == 102 && (HIBYTE(v8) ^ (unsigned __int8)byte_6CC0A3) == 103 )
{
for ( j = 0; j <= 24; ++j )
{
v2 = (unsigned __int8)(byte_6CC0A0[j] ^ *((_BYTE *)&v8 + j % 4));
sub_410E90(v2);
}
}
v4 = __readfsqword(0x28u);
result = v4 ^ v9;
if ( v4 != v9 )
sub_444020(v2, a2);
return result;
}

找到涉及到这段数据的代码:

1
2
3
4
5
6
7
8
9
v8 = v5;
if ( ((unsigned __int8)v5 ^ byte_6CC0A0[0]) == 'f' && (HIBYTE(v8) ^ (unsigned __int8)byte_6CC0A3) == 'g' )
{
for ( j = 0; j <= 24; ++j )
{
v2 = (unsigned __int8)(byte_6CC0A0[j] ^ *((_BYTE *)&v8 + j % 4));
sub_410E90(v2);
}
}

v5赋值给v8,所以这里v5和v8是一样的。
这里解释一下,HIBYTE()是什么意思,HIBYTE()函数的作用是获取高字节也就是数组的最后一位(),同时还有BYTE()、BYTE1()、BYTE2()第一个是获取数组的第一位,第二个就是获取第二位,依次类推。

所以这里的 HIBYTE(v8) 其实就是int型v5的第七八位的十六进制,也就是一个字符十六进制,需要等于 ‘g’
这里的第一二位需要等于 ‘f’, 所以我猜测就是前四个字符是 ‘flag’

分析到这里,大概基本上就出来了,for循环是不断将 byte_6CC0A0 数组,与v8的前四个(j%4)字符进行异或,v8前四个能够通过 flag 来逆出来。

0x03 EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
s = [0x40, 0x35, 0x20, 0x56]
flag_Demo = 'flag'
a = ''

for i in range(4):
a += chr(s[i] ^ ord(flag_Demo[i]))
# print(ord(flag_Demo[i]))
print(a)

b = [0x40,0x35,0x20,0x56,0x5D,0x18,0x22,0x45,0x17,0x2F,0x24,0x6E,0x62,0x3C,0x27,0x54,0x48,0x6C,0x24,0x6E,0x72,0x3C,0x32,0x45,0x5B]

flag = ''

for i in range(len(b)):
flag += chr(b[i] ^ ord(a[i%4]))
print(flag)

===============================
参考文章:
2019 红帽杯 easyRE


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!