2020-10-12

特难题折腾#1

[2019红帽杯]xx

1.

exeinfo查看信息,无壳,然后放入ida64位。

2.

找到关键字符串,跟进看伪代码。

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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
int64 __fastcall sub_1400011A0(__int64 a1, __int64 a2)
{
signed __int64 v2; // rbx
signed __int64 v3; // rax
__int64 v4; // rax
__int64 v5; // r11
__int128 *v6; // r14
int v7; // edi
_BYTE *v8; // rsi
char v9; // r10
int v10; // edx
__int64 v11; // r8
unsigned __int64 v12; // rcx
signed __int64 v13; // rcx
unsigned __int64 v14; // rax
unsigned __int64 i; // rax
__int64 v16; // rax
size_t v17; // rsi
_BYTE *v18; // rbx
_BYTE *v19; // r9
signed int v20; // er11
char *v21; // r8
signed __int64 v22; // rcx
char v23; // al
signed __int64 v24; // r9
signed __int64 v25; // rdx
__int64 v26; // rax
size_t Size; // [rsp+20h] [rbp-48h]
__int128 v29; // [rsp+28h] [rbp-40h]
int v30; // [rsp+38h] [rbp-30h]
int v31; // [rsp+3Ch] [rbp-2Ch]
int Code[4]; // [rsp+40h] [rbp-28h]
int v33; // [rsp+50h] [rbp-18h]

*(_OWORD *)Code = 0i64;
v33 = 0;
sub_1400018C0(std::cin, a2, Code);
v2 = -1i64;
v3 = -1i64;
do
++v3;
while ( *((_BYTE *)Code + v3) );
if ( v3 != 19 )
{
sub_140001620(std::cout, "error\n");
_exit((unsigned __int64)Code);
}
v4 = sub_140001E5C(5ui64);
v5 = *(_QWORD *)&::Code;
v6 = (__int128 *)v4;
v7 = 0;
v8 = (_BYTE *)v4;
do
{
v9 = v8[(_QWORD)Code - v4];
v10 = 0;
*v8 = v9;
v11 = 0i64;
v12 = -1i64;
do
++v12;
while ( *(_BYTE *)(v5 + v12) );
if ( v12 )
{
do
{
if ( v9 == *(_BYTE *)(v5 + v11) )
break;
++v10;
++v11;
}
while ( v10 < v12 );
}
v13 = -1i64;
do
++v13;
while ( *(_BYTE *)(v5 + v13) );
if ( v10 == v13 )
_exit(v5);
++v8;
}
while ( (signed __int64)&v8[-v4] < 4 );
*(_BYTE *)(v4 + 4) = 0;
do
++v2;
while ( *((_BYTE *)Code + v2) );
v14 = 0i64;
v29 = *v6;
while ( *((_BYTE *)&v29 + v14) )
{
if ( !*((_BYTE *)&v29 + v14 + 1) )
{
++v14;
break;
}
if ( !*((_BYTE *)&v29 + v14 + 2) )
{
v14 += 2i64;
break;
}
if ( !*((_BYTE *)&v29 + v14 + 3) )
{
v14 += 3i64;
break;
}
v14 += 4i64;
if ( v14 >= 0x10 )
break;
}
for ( i = v14 + 1; i < 0x10; ++i )
*((_BYTE *)&v29 + i) = 0;
v16 = sub_140001AB0(Code, v2, &v29, &Size);
v17 = Size;
v18 = (_BYTE *)v16;
v19 = (_BYTE *)sub_140001E5C(Size);
v20 = 1;
*v19 = v18[2];
v21 = v19 + 1;
v19[1] = *v18;
v19[2] = v18[3];
v19[3] = v18[1];
v19[4] = v18[6];
v19[5] = v18[4];
v19[6] = v18[7];
v19[7] = v18[5];
v19[8] = v18[10];
v19[9] = v18[8];
v19[10] = v18[11];
v19[11] = v18[9];
v19[12] = v18[14];
v19[13] = v18[12];
v19[14] = v18[15];
v19[15] = v18[13];
v19[16] = v18[18];
v19[17] = v18[16];
v19[18] = v18[19];
v19[19] = v18[17];
v19[20] = v18[22];
v19[21] = v18[20];
v19[22] = v18[23];
for ( v19[23] = v18[21]; v20 < v17; ++v21 )
{
v22 = 0i64;
if ( v20 / 3 > 0 )
{
v23 = *v21;
do
{
v23 ^= v19[v22++];
*v21 = v23;
}
while ( v22 < v20 / 3 );
}
++v20;
}
*(_QWORD *)&v29 = -4569681940847739698i64;
v24 = v19 - (_BYTE *)&v29;
*((_QWORD *)&v29 + 1) = 3819887636644928495i64;
v25 = 0i64;
v30 = -939386845;
v31 = -95004953;
do
{
if ( *((_BYTE *)&v29 + v25) != *((_BYTE *)&v29 + v25 + v24) )
_exit(v7 * v7);
++v7;
++v25;
}
while ( v25 < 24 );
v26 = sub_140001620(std::cout, "You win!");
std::basic_ostream<char,std::char_traits<char>>::operator<<(v26, sub_1400017F0);
return 0i64;
}

真的是很难了…

检索网上的资料,需要用IDA的findcrypt3插件。这个插件可以识别代码的特征,看出是什么类型的加密。

然而插件安装及其复杂,折腾了好久。想知道会不会有神仙能直接肉眼识别出加密类型呢?

3.

根据findcrypt插件查明为tea加密,进函数内部发现为xxtea加密。(查阅资料明白的,我觉得可以单独拿出来学一遍了)

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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
__int64 __fastcall sub_1400011A0(__int64 a1, __int64 a2)
{
unsigned __int64 v2; // rbx
signed __int64 v3; // rax
__int128 *v4; // rax
__int64 v5; // r11
__int128 *v6; // r14
int v7; // edi
__int128 *v8; // rsi
char v9; // r10
int v10; // edx
__int64 v11; // r8
unsigned __int64 v12; // rcx
signed __int64 v13; // rcx
unsigned __int64 v14; // rax
unsigned __int64 i; // rax
_BYTE *v16; // rax
size_t v17; // rsi
_BYTE *v18; // rbx
_BYTE *v19; // r9
signed int v20; // er11
char *v21; // r8
signed __int64 v22; // rcx
char v23; // al
signed __int64 v24; // r9
signed __int64 v25; // rdx
__int64 v26; // rax
size_t Size; // [rsp+20h] [rbp-48h]
__int128 v29; // [rsp+28h] [rbp-40h]
int v30; // [rsp+38h] [rbp-30h]
int v31; // [rsp+3Ch] [rbp-2Ch]
int user_input[4]; // [rsp+40h] [rbp-28h]
int v33; // [rsp+50h] [rbp-18h]

*(_OWORD *)user_input = 0i64;
v33 = 0;
sub_1400018C0(std::cin, a2, user_input); // 读取用户输入
v2 = -1i64;
v3 = -1i64;
do
++v3;
while ( *((_BYTE *)user_input + v3) ); // 计算user_input(原为Code)的长度,即v3
if ( v3 != 19 ) // 输入的长度要等于19
{
sub_140001620(std::cout, "error\n");
_exit((unsigned __int64)user_input);
}
v4 = (__int128 *)sub_140001E5C(5ui64); // v4为动态分配出的一块长度为5的空间
v5 = *(_QWORD *)&Code; // v5为内存中存储的数据
//为"qwertyuiopasdfghjklzxcvbnm1234567890"
v6 = v4;
v7 = 0;
v8 = v4; // v6=v8=v4
do
{
v9 = *((_BYTE *)v8 + (char *)user_input - (char *)v4);// v9 = user_input
v10 = 0;
*(_BYTE *)v8 = v9;
v11 = 0i64;
v12 = -1i64;
do
++v12;
while ( *(_BYTE *)(v5 + v12) ); // v12 计算内存中存储数据的长度
if ( v12 )
{
do
{
if ( v9 == *(_BYTE *)(v5 + v11) ) // 输入的字符,是在内存存储的数据里中的一个元素
break;
++v10;
++v11;
}
while ( v10 < v12 );
}
v13 = -1i64;
do
++v13;
while ( *(_BYTE *)(v5 + v13) ); // v12 计算内存中存储数据的长度
if ( v10 == v13 )
_exit(v5);
v8 = (__int128 *)((char *)v8 + 1);
}
while ( (char *)v8 - (char *)v4 < 4 ); // 54~83行
// 输入数据的前四个元素能在内存中存储的数据中找到对应
*((_BYTE *)v4 + 4) = 0; // v4这块动态分配的内存空间中存放着 输入的前四个元素和一个0
do
++v2;
while ( *((_BYTE *)user_input + v2) ); // v2为输入字符串的长度
v14 = 0i64;
v29 = *v6; // v6等于v4 85行
while ( *((_BYTE *)&v29 + v14) )
{
if ( !*((_BYTE *)&v29 + v14 + 1) )
{
++v14;
break;
}
if ( !*((_BYTE *)&v29 + v14 + 2) )
{
v14 += 2i64;
break;
}
if ( !*((_BYTE *)&v29 + v14 + 3) )
{
v14 += 3i64;
break;
}
v14 += 4i64;
if ( v14 >= 16 )
break;
}
for ( i = v14 + 1; i < 16; ++i )
*((_BYTE *)&v29 + i) = 0; // 将四个字符之后的置零
v16 = sub_140001AB0((__int64)user_input, v2, (unsigned __int8 *)&v29, &Size); //xxtea加密
v17 = Size; // 加密后的长度
v18 = v16;
v19 = sub_140001E5C(Size); // 动态分配一块加密后字符串长度给v19
v20 = 1;
*v19 = v18[2];
v21 = v19 + 1;
v19[1] = *v18;
v19[2] = v18[3];
v19[3] = v18[1];
v19[4] = v18[6];
v19[5] = v18[4];
v19[6] = v18[7];
v19[7] = v18[5];
v19[8] = v18[10];
v19[9] = v18[8];
v19[10] = v18[11];
v19[11] = v18[9];
v19[12] = v18[14];
v19[13] = v18[12];
v19[14] = v18[15];
v19[15] = v18[13];
v19[16] = v18[18];
v19[17] = v18[16];
v19[18] = v18[19];
v19[19] = v18[17];
v19[20] = v18[22];
v19[21] = v18[20];
v19[22] = v18[23]; // 排序
for ( v19[23] = v18[21]; v20 < v17; ++v21 ) // v21=v19+1
{
v22 = 0i64;
if ( v20 / 3 > 0 )
{
v23 = *v21;
do
{
v23 ^= v19[v22++];
*v21 = v23;
}
while ( v22 < v20 / 3 );
}
++v20;
}
*(_QWORD *)&v29 = 0xC0953A7C6B40BCCEi64;
v24 = v19 - (_BYTE *)&v29;
*((_QWORD *)&v29 + 1) = 0x3502F79120209BEFi64;
v25 = 0i64;
v30 = 0xC8021823;
v31 = 0xFA5656E7;
do
{
if ( *((_BYTE *)&v29 + v25) != *((_BYTE *)&v29 + v25 + v24) )
_exit(v7 * v7);
++v7;
++v25;
}
while ( v25 < 24 );
v26 = sub_140001620(std::cout, "You win!");
std::basic_ostream<char,std::char_traits<char>>::operator<<(v26, sub_1400017F0);
return 0i64;
}

*添加了注释的伪代码

流程为

  1. 先判断输入的字符串是否都在程序实现存储的数据Code中(实际上我的IDA直接识别出在Code里了)

  2. 然后取前四个字符作为xxtea的密钥(因为格式是flag{},所以密钥是flag,不满位数右端补零)

  3. 然后对输入的字符串进行加密(进行xxTEA加密,原理没搞懂,但是python有解密脚本)

  4. 之后对加密的字符串打乱顺序(伪代码排序部分)

  5. 之后异或操作(所以再异或一遍)

  6. 再与存储的数据进行比对(为CEBC406B7C3A95C0EF9B202091F70235231802C8E75656FA)

    存储的数据也需要一定的知识,v29,v29+1,v30,v31在栈上存放的位置相连。小端序存放,需将其反过来写。

    (感觉自己的学习跨度有点大了,以上东西理解了很久。涉及了许多知识。)

4.

于是写python脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
data0 = "CEBC406B7C3A95C0EF9B202091F70235231802C8E75656FA"#为提取出来的v29,v29+1,v30,v31
data = []
for i in range(0,len(data0),2):
data.append(int(data0[i]+data0[i+1],16))#每两位为整体,将16进制转换为10进制
print(data)
for i in range(len(data)-1,-1,-1):
for j in range(i//3):
data[i] ^= data[j]
#进行的异或操作
print(data)
biao = [2,0,3,1,6,4,7,5,10,8,11,9,14,12,15,13,18,16,19,17,22,20,23,21]#置换表
shuju = [1]*24
for i in range(24):
shuju[biao[i]] = data[i]#将其按照一定顺序置换
print(shuju)
print(len(shuju))

然后把置换回来的数据(参考博客原作者突然又用C)

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
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include "encode.h"
int main()
{
#if 0
int i,j,data[] = { 188, 165, 206, 64, 244, 178, 178, 231, 169, 18, 157, 18, 174, 16, 200, 91, 61, 215, 6, 29, 220, 112, 248, 220 };
for (i = 1; i <= 6; i++)//导出加密的数据,将其转换为十六进制并用小端序来表示
{
printf("0x");
for (j = i * 4 - 1; j > (i - 1) * 4 - 1; j--)
printf("%x", data[j]);
printf(",");
}
printf("\n0x");
int key[] = { 'f','l','a','g' };//导出密钥,将其转换为十六进制并用小端序来表示
for (i = 3; i >= 0; i--)
printf("%x", key[i]);
printf(",");
for (i = 0; i < 3; i++)//密钥为4个32位的数,1个字符4bit,4个字符为32bit,还差3个32bit的数
printf("0x0,");

uint32_t enc[6] = { (unsigned int)0x40cea5bc,(unsigned int)0xe7b2b2f4,(unsigned int)0x129d12a9,(unsigned int)0x5bc810ae,(unsigned int)0x1d06d73d,(unsigned int)0xdcf870dc };
int n = -6;
uint32_t const key[4] = { (unsigned int)0x67616c66,(unsigned int)0x0,(unsigned int)0x0,(unsigned int)0x0 };
btea(enc, n, key);
for (int i = 0; i < 6; i++)
printf("%x", enc[i]);
#endif
char string[] = "67616c665858437b646e615f742b2b5f7d6165";
int i = 1,j=0;
for (;i<6;i++)
{
for (j = 0; j < 8; j += 2)
{
if (j == 0 && i == 5)
continue;
printf("%c%c", string[i * 8 - j - 2], string[i * 8 - j - 1]);
}
}

}

下面是python脚本,但是我看得不是很懂。

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
enc = 'CEBC406B7C3A95C0EF9B202091F70235231802C8E75656FA'.decode('hex')

dec1 = ''
for i in range(len(enc)/3):
j = len(enc)/3 - i - 1
res = ''
for k in enc[j*3:j*3+3]:
tmp = ord(k)
for l in range(j):
tmp ^= ord(enc[l])
res += chr(tmp)
dec1 = res + dec1
print dec1.encode('hex')

dec2 = [''] * len(dec1)
for i in range(len(dec1)):
dec2[i] = dec1[i]

box = [1,3,0,2,5,7,4,6,9,11,8,10,13,15,12,14,17,19,16,18,21,23,20,22]
print len(box)

for i in range(len(box)):
dec2[i] = dec1[box[i]]
dec3 = ''.join(dec2)
print dec3.encode('hex') #先异或再换位置

import struct

_DELTA = 0x9E3779B9

def _long2str(v, w):
n = (len(v) - 1) << 2
if w:
m = v[-1]
if (m < n - 3) or (m > n): return ''
n = m
s = struct.pack('<%iL' % len(v), *v)
return s[0:n] if w else s

def _str2long(s, w):
n = len(s)
m = (4 - (n & 3) & 3) + n
s = s.ljust(m, "\0")
v = list(struct.unpack('<%iL' % (m >> 2), s))
if w: v.append(n)
return v

def encrypt(str, key):
if str == '': return str
v = _str2long(str, True)
k = _str2long(key.ljust(16, "\0"), False)
n = len(v) - 1
z = v[n]
y = v[0]
sum = 0
q = 6 + 52 // (n + 1)
while q > 0:
sum = (sum + _DELTA) & 0xffffffff
e = sum >> 2 & 3
for p in xrange(n):
y = v[p + 1]
v[p] = (v[p] + ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z))) & 0xffffffff
z = v[p]
y = v[0]
v[n] = (v[n] + ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[n & 3 ^ e] ^ z))) & 0xffffffff
z = v[n]
q -= 1
return _long2str(v, False)

def decrypt(str, key):
if str == '': return str
v = _str2long(str, False)
k = _str2long(key.ljust(16, "\0"), False)
n = len(v) - 1
z = v[n]
y = v[0]
q = 6 + 52 // (n + 1)
sum = (q * _DELTA) & 0xffffffff
while (sum != 0):
e = sum >> 2 & 3
for p in xrange(n, 0, -1):
z = v[p - 1]
v[p] = (v[p] - ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[p & 3 ^ e] ^ z))) & 0xffffffff
y = v[p]
z = v[n]
v[0] = (v[0] - ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[0 & 3 ^ e] ^ z))) & 0xffffffff
y = v[0]
sum = (sum - _DELTA) & 0xffffffff
return _long2str(v, True) #这上面啥高端语法啊

key = 'flag'
dec4 = decrypt(dec3, key)
print len(dec4)
print dec4

似乎python3.版本不支持这种写法,也不知道是不是我编译器没装好。

实际上python还有xxtea库,但是有巨佬写出解密过程,那我就多学习学习。(一头雾水)

得出flag

flag{CXX_and_++tea}

估计得花几个星期消化。本以为只是稍微难一点的题目(也许真的是,只是我太菜了?)

反思:尝试一次特难题,实际上有好多东西难以理解,这次做下来只是大致了解。原本以为自己基本能看懂c和python语法,结果发现自己这一方面也有很大的欠缺。这么长的伪代码分析真的是折磨。而且自己根本写不出脚本。抽空学习python、C、汇编。内存动态分配是啥意思啥作用啊😫

参考博客:

https://blog.csdn.net/Palmer9/article/details/103992451

https://impakho.com/post/redhat-2019-online-writeup