2019腾讯游戏安全竞赛PC分析

首先感谢littleNA大佬的文章https://bbs.pediy.com/thread-230312.htm,看完这篇文章后事半功倍。

第一题 EasyJob

首先为了方便动静结合调试,我先修改了PE文件头部使之跳过重定位。使用工具DIE

这样修改之后,使用ida和其他调试工具的时候地址就能对得上了,告别aslr。

用ida打开程序后,看一下main函数,确定sub_45F650sub_460050分别为检查request codeverification code的函数,做一下重命名。

值得注意的是check_request_codecheck_verification_code有同一个参数,我把它命名为req_code_stru。在后面的分析过程中,我得出该变量类型的结构体

1
2
3
4
5
6
7
8
struct req_code_stru
{
vector request_code_part;
__int64 num1;
__int64 num2;
__int64 num3;
__int64 num4;
};

其中vector结构可以参考上面littleNA大佬的文章,在这个结构体中应该是vector<string>,每个成员的作用会在后面讲到。

进入check_request_code函数后,发现字符串

很明显是一个正则表达式,于是推测request code需要符合该正则表达式。打开程序随便输入了一个符合该表达式的request code,比如1111-2222-3333-4444,输入后程序提示继续输入verification code,表明推测是正确的。通过调试后还发现该函数会把request code通过字符-分成4个string插入到req_code_stru->request_code_part中。

接下来看一下check_vertification_code。通过浏览后发现一个比较关键的函数sub_45F960,通过req_code_stru->request_code_part计算出4个num,具体代码如下

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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
int __thiscall sub_45F960(req_code_stru *req_code_stru)
{
char *v1_part1; // esi
char *v2_part1; // eax
char *v3_part4; // ebx
char *v4_part4; // ecx
int v5; // edi
char *v6_part1; // ecx
char *v7_part4; // eax
int v8; // edi
char *v9_part1; // edx
char *v10_part4; // eax
int v11; // edi
char *v12_part1; // edx
char *v13_part4; // eax
char *v14_part2; // eax
char *v15_part3; // edi
char *v16_part3; // ecx
char *v17_part2; // edx
char *v18_part3; // eax
char *v19_part2; // edx
char *v20_part3; // eax
char *v21_part1; // edx
char *v22_part3; // eax
char *v23_part1; // edx
char *v24_part1; // eax
char *v25_part2; // eax
char *v26_part2; // edx
char *v27_part3; // ecx
char *v28_part3; // eax
int v29; // ecx
char *v30_part4; // edx
char *v31_part4; // eax
char *v32_part1; // ecx
char *v33_part4; // eax
char *v34_part2; // edx
char *v35_part3; // eax
char *v36_part2; // eax
int v37; // eax
int v38; // edx
int v39; // ST24_4
int v40; // ecx
__int64 v41; // rdi
bool v42; // cf
unsigned __int64 v43; // rt0
__int64 v44; // kr38_8
__int64 v45; // rax
unsigned int v46; // ecx
unsigned int v47; // ecx
signed __int64 v48; // rax
int v49; // ecx
req_code_stru *v51_reg_code_stru; // [esp+10h] [ebp-18h]
int v52; // [esp+14h] [ebp-14h]
int v53; // [esp+14h] [ebp-14h]
int v54; // [esp+18h] [ebp-10h]
int v55; // [esp+1Ch] [ebp-Ch]
int v56; // [esp+24h] [ebp-4h]
int v57; // [esp+24h] [ebp-4h]
int v58; // [esp+24h] [ebp-4h]
int v59; // [esp+24h] [ebp-4h]
int v60; // [esp+24h] [ebp-4h]
int v61; // [esp+24h] [ebp-4h]

v1_part1 = req_code_stru->request_code_part._Myfirst;
v51_reg_code_stru = req_code_stru;
if ( *((_DWORD *)req_code_stru->request_code_part._Myfirst + 5) < 0x10u )
v2_part1 = req_code_stru->request_code_part._Myfirst;
else
v2_part1 = *(char **)v1_part1;
v3_part4 = v1_part1 + 72;
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
v4_part4 = v1_part1 + 72;
else
v4_part4 = *(char **)v3_part4;
v5 = (*v4_part4 ^ *v2_part1) << 8; // v5=(part4[0]^part1[0])<<8
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
v6_part1 = v1_part1;
else
v6_part1 = *(char **)v1_part1;
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
v7_part4 = v1_part1 + 72;
else
v7_part4 = *(char **)v3_part4;
v8 = v6_part1[1] * v7_part4[1] + v5; // v8=part1[1]*part4[1]+v5
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
v9_part1 = v1_part1;
else
v9_part1 = *(char **)v1_part1;
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
v10_part4 = v1_part1 + 72;
else
v10_part4 = *(char **)v3_part4;
v11 = v9_part1[2] / v10_part4[2] + v8; // v11=part1[2]/part4[2]+v8
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
v12_part1 = v1_part1;
else
v12_part1 = *(char **)v1_part1;
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
v13_part4 = v1_part1 + 72;
else
v13_part4 = *(char **)v3_part4;
v54 = v11 + v12_part1[2] % v13_part4[2]; // v54=part1[2]%part4[2]+v11
v14_part2 = v1_part1 + 24;
if ( *((_DWORD *)v1_part1 + 11) >= 0x10u )
v14_part2 = *(char **)v14_part2;
v15_part3 = v1_part1 + 48;
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
v16_part3 = v1_part1 + 48;
else
v16_part3 = *(char **)v15_part3;
v56 = *v16_part3 * *v14_part2 << 8; // v56=part3[0]*part2[0]<<8
if ( *((_DWORD *)v1_part1 + 11) < 0x10u )
v17_part2 = v1_part1 + 24;
else
v17_part2 = (char *)*((_DWORD *)v1_part1 + 6);
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
v18_part3 = v1_part1 + 48;
else
v18_part3 = *(char **)v15_part3;
v57 = (char)(v17_part2[1] ^ v18_part3[1]) + v56;// v57=(part2[1]^part3[1])+v56
if ( *((_DWORD *)v1_part1 + 11) < 0x10u )
v19_part2 = v1_part1 + 24;
else
v19_part2 = (char *)*((_DWORD *)v1_part1 + 6);
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
v20_part3 = v1_part1 + 48;
else
v20_part3 = *(char **)v15_part3;
v58 = v19_part2[2] % v20_part3[2] + 32 + v57; // v58=part2[2]%part3[2]+32+v57
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
v21_part1 = v1_part1;
else
v21_part1 = *(char **)v1_part1;
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
v22_part3 = v1_part1 + 48;
else
v22_part3 = *(char **)v15_part3;
v55 = v58 + v21_part1[2] / v22_part3[2]; // v55=part1[2]/part3[2]+v58
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
v23_part1 = v1_part1;
else
v23_part1 = *(char **)v1_part1;
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
v24_part1 = v1_part1;
else
v24_part1 = *(char **)v1_part1;
v59 = v23_part1[1] % v24_part1[3] << 8; // v59=part1[1]%part1[3]<<8
v25_part2 = v1_part1 + 24;
if ( *((_DWORD *)v1_part1 + 11) < 0x10u )
v26_part2 = v1_part1 + 24;
else
v26_part2 = *(char **)v25_part2;
if ( *((_DWORD *)v1_part1 + 11) >= 0x10u )
v25_part2 = *(char **)v25_part2;
v60 = v26_part2[1] / v25_part2[3] + v59; // v60=part2[1]/part2[3]+v59
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
v27_part3 = v1_part1 + 48;
else
v27_part3 = *(char **)v15_part3;
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
v28_part3 = v1_part1 + 48;
else
v28_part3 = *(char **)v15_part3;
v29 = v27_part3[1] * v28_part3[3] + 8 + v60; // v29=part3[1]*part3[3]+8+v60
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
v30_part4 = v1_part1 + 72;
else
v30_part4 = *(char **)v3_part4;
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
v31_part4 = v1_part1 + 72;
else
v31_part4 = *(char **)v3_part4;
v61 = v29 + (char)(v30_part4[1] ^ v31_part4[3]);// v61=(part4[1]^part4[3])+v29
if ( *((_DWORD *)v1_part1 + 5) < 0x10u )
v32_part1 = v1_part1;
else
v32_part1 = *(char **)v1_part1;
if ( *((_DWORD *)v1_part1 + 23) < 0x10u )
v33_part4 = v1_part1 + 72;
else
v33_part4 = *(char **)v3_part4;
v52 = (*v32_part1 ^ v33_part4[3]) << 8; // v52=(part1[0]^part4[3])<<8
if ( *((_DWORD *)v1_part1 + 11) < 0x10u )
v34_part2 = v1_part1 + 24;
else
v34_part2 = (char *)*((_DWORD *)v1_part1 + 6);
if ( *((_DWORD *)v1_part1 + 17) < 0x10u )
v35_part3 = v1_part1 + 48;
else
v35_part3 = *(char **)v15_part3;
v53 = v34_part2[1] / v35_part3[2] + v52; // v53=part2[1]/part3[2]+v52
if ( *((_DWORD *)v1_part1 + 17) >= 0x10u )
v15_part3 = *(char **)v15_part3;
v36_part2 = v1_part1 + 24;
if ( *((_DWORD *)v1_part1 + 11) >= 0x10u )
v36_part2 = *(char **)v36_part2;
v37 = v15_part3[2] * v36_part2[1]; // v37=part3[2]*part2[1]
if ( *((_DWORD *)v1_part1 + 23) >= 0x10u )
v3_part4 = *(char **)v3_part4;
if ( *((_DWORD *)v1_part1 + 5) >= 0x10u )
v1_part1 = *(char **)v1_part1;
v38 = v37 + v53 + v3_part4[3] % *v1_part1; // v38=part4[3]%part1[0]+v37+v53
v39 = v38;
v40 = (unsigned __int64)(unsigned int)v38 >> 29;
LODWORD(v41) = 20 * (v54 + 2 * v61); // v41=20*(v54+2*v61)
v42 = __CFADD__(8 * v38, v38);
v38 *= 9;
HIDWORD(v43) = v40 + v42;
LODWORD(v43) = v38;
HIDWORD(v41) = v43 >> 29;
v51_reg_code_stru->num1 = 30i64 * (unsigned int)v55
+ __PAIR__(20 * ((unsigned int)v54 + 2 * (unsigned __int64)(unsigned int)v61) >> 32, 8 * v38)
+ v41; // num1=30*v55+20*(v54+2*v61)+72*v38
v51_reg_code_stru->num2 = 23i64 * (unsigned int)v54
+ 32i64 * (unsigned int)v55
+ 42i64 * (unsigned int)v61
+ 54i64 * (unsigned int)v39;// num2=23*v54+32*v55+42*v61+54*v39
LODWORD(v41) = 25 * v55 + 38 * v61 + 67 * v39;
HIDWORD(v43) = ((unsigned __int64)(unsigned int)v54 >> 29) + __CFADD__(8 * v54, v54);
LODWORD(v43) = 9 * v54;
HIDWORD(v41) = v43 >> 31;
v44 = __PAIR__(
(25i64 * (unsigned int)v55 + 38i64 * (unsigned int)v61 + 67 * (unsigned __int64)(unsigned int)v39) >> 32,
18 * v54)
+ v41;
HIDWORD(v41) = v51_reg_code_stru;
v51_reg_code_stru->num3 = v44;
HIDWORD(v43) = ((unsigned __int64)(unsigned int)v54 >> 31) + __CFADD__(2 * v54, v54);
LODWORD(v43) = 3 * v54;
LODWORD(v41) = (__PAIR__(v43 >> 30, 12 * v54)
+ 45i64 * (unsigned int)v55
+ 33i64 * (unsigned int)v39
+ ((unsigned __int64)(unsigned int)v61 << 6)) >> 32;
v45 = SHIDWORD(v51_reg_code_stru->num1);
v46 = HIDWORD(v45) ^ LODWORD(v51_reg_code_stru->num1);
v42 = v46 < HIDWORD(v45);
LODWORD(v51_reg_code_stru->num1) = v46 - HIDWORD(v45);
v47 = v51_reg_code_stru->num2;
HIDWORD(v51_reg_code_stru->num1) = (HIDWORD(v45) ^ v45) - (v42 + HIDWORD(v45));
*(_QWORD *)(HIDWORD(v41) + 24) = __PAIR__(
(*(_DWORD *)(HIDWORD(v41) + 28) >> 31) ^ *(_DWORD *)(HIDWORD(v41) + 28),
(*(_DWORD *)(HIDWORD(v41) + 28) >> 31) ^ v47)
- __PAIR__(*(_DWORD *)(HIDWORD(v41) + 28) >> 31, *(_DWORD *)(HIDWORD(v41) + 28) >> 31);
v51_reg_code_stru->num3 = (signed __int64)(__PAIR__(
(*(_DWORD *)(HIDWORD(v41) + 36) >> 31) ^ *(_DWORD *)(HIDWORD(v41) + 36),
(*(_DWORD *)(HIDWORD(v41) + 36) >> 31) ^ *(_DWORD *)(HIDWORD(v41) + 32))
- __PAIR__(
*(_DWORD *)(HIDWORD(v41) + 36) >> 31,
*(_DWORD *)(HIDWORD(v41) + 36) >> 31))
% 20; // num3=(25*v55+38*v61+67*v39+18*v54)%20
v48 = (signed __int64)(__PAIR__(
((signed int)v41 >> 31) ^ (unsigned int)v41,
((signed int)v41 >> 31) ^ (unsigned int)(12 * v54 + 45 * v55 + 33 * v39 + (v61 << 6)))
- __PAIR__((signed int)v41 >> 31, (signed int)v41 >> 31))
% 20; // num4=(12*v54+45*v55+33*v39+v61*64)%20
v51_reg_code_stru->num4 = v48;
if ( SHIDWORD(v51_reg_code_stru->num3) >= SHIDWORD(v48) )
{
if ( SHIDWORD(v51_reg_code_stru->num3) > SHIDWORD(v48)// 如果num3>num4就互相交换
|| (LODWORD(v48) = v51_reg_code_stru->num3, (unsigned int)v48 > LODWORD(v51_reg_code_stru->num4)) )
{
v49 = v51_reg_code_stru->num3;
HIDWORD(v48) = HIDWORD(v51_reg_code_stru->num3);
LODWORD(v51_reg_code_stru->num3) = v51_reg_code_stru->num4;
LODWORD(v48) = HIDWORD(v51_reg_code_stru->num4);
HIDWORD(v51_reg_code_stru->num3) = v48;
LODWORD(v51_reg_code_stru->num4) = v49;
HIDWORD(v51_reg_code_stru->num4) = HIDWORD(v48);
}
}
return v48;
}

F5后看起来很乱,需要结合汇编一起分析。

接下来在sub_45FE00计算出num5

num5等于request_code每个数字相加,比如1111-2222-3333-4444计算出来的num5就是40

接下来回到check_request_code中把得到的4个part和5个num拼接

这里看F5可能看不太懂,可以看汇编

这里是先把4个operator<<的参数全部压栈再一个个call,应该是编译器优化的结果。得到的结果就是一个字符串s=part1+part2+part3+part4+num1+num2+num3+num4+num5

最后将得到的字符串再做一个以part1keyhmac_sha256计算

因为用ida看了字符串发现OpenSSL 1.0.2m 2 Nov 2017的字样所以事先用Rizzo打上了OpenSSL的符号。关于OpenSSL编译可以看https://cloud.tencent.com/developer/article/1343632。不过遗憾的是打上了符号也没办法识别出这个hmac使用的hash算法是什么,后来我是通过动态调试获得一组输入和输出暴力跑出了hashsha256,做完hmac_sha256后得到的结果即为verification_code

附上python3注册机代码

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

def hmac_sha256(key,s):
return hmac.new(key.encode('utf-8'), s.encode('utf-8'), 'SHA256').hexdigest()

def get_verification_code(part1,part2,part3,part4):
v5=(ord(part4[0])^ord(part1[0]))<<8
v8=ord(part1[1])*ord(part4[1])+v5
v11=ord(part1[2])//ord(part4[2])+v8
v54=ord(part1[2])%ord(part4[2])+v11
v56=ord(part3[0])*ord(part2[0])<<8
v57=(ord(part2[1])^ord(part3[1]))+v56
v58=ord(part2[2])%ord(part3[2])+32+v57
v55=ord(part1[2])//ord(part3[2])+v58
v59=ord(part1[1])%ord(part1[3])<<8
v60=ord(part2[1])//ord(part2[3])+v59
v29=ord(part3[1])*ord(part3[3])+8+v60
v61=(ord(part4[1])^ord(part4[3]))+v29
v52=(ord(part1[0])^ord(part4[3]))<<8
v53=ord(part2[1])//ord(part3[2])+v52
v37=ord(part3[2])*ord(part2[1])
v38=ord(part4[3])%ord(part1[0])+v37+v53
v39=v38
v38=v38*9
v41=12*v54+45*v55+33*v39
num1=30*v55+20*(v54+2*v61)+8*v38
num2=23*v54+32*v55+42*v61+54*v39
num3=(25*v55+38*v61+67*v39+18*v54)%20
num4=(12*v54+45*v55+33*v39+v61*64)%20
if num3>num4:
num3,num4=num4,num3
num5=0
for i in range(4):
num5+=int(part1[i])+int(part2[i])+int(part3[i])+int(part4[i])
code=part1+part2+part3+part4+str(num1)+str(num2)+str(num3)+str(num4)+str(num5)
return hmac_sha256(part1,code)

if __name__=='__main__':
request_code=input('input request code: ')
reobj=re.fullmatch('(\\d{4})-(\\d{4})-(\\d{4})-(\\d{4})',request_code)
if reobj!=None:
part1=reobj.group(1)
part2=reobj.group(2)
part3=reobj.group(3)
part4=reobj.group(4)
print(get_verification_code(part1,part2,part3,part4))
else:
print('[+] Invalid code!')

第二题 Rotate

第二题和第一题相差不大,不同的地方是多了一个num6以及hmac_sha256key不同。看一下导入表,发现一些opencv的函数

为了方便分析,去抠了一下cv::Mat的结构体。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct cvMat
{
int flags;
int dims;
int rows;
int cols;
unsigned __int8 *data;
unsigned __int8 *datastart;
unsigned __int8 *dataend;
unsigned __int8 *datalimit;
void *allocator;
void *u;
int *size_p;
unsigned __int64 *step_p;
unsigned __int64 step_buf[2];
};

其中MatStepMatSize被我拆了,影响不是很大。

首先在check_verification_code中使用cv::imread读取了题目目录下的flag.jpg

这里应该是个cv::Mat构造函数,被编译器优化成内联函数了。

接下来在sub_14007A2C0中进行图像的旋转。

通过cv::getRotationMatrix2D获得旋转的矩阵再用cv::warpAffine进行仿射变换。

sub_140079C50进行图像的处理并计算num6,首先是图像的处理

分别调用了cv::cvtColor cv::resizecv::dct

然后根据图像的数据计算num6

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
v9 = *Dst.step_p;                             // 一行元素的字节数
v10 = &v45;
v11 = (double *)(Dst.data + 24);
do
{
v12 = *(v11 - 3);
*((double *)v10 - 1) = v12;
v13 = *(v11 - 2);
*(double *)v10 = v13;
v14 = *(v11 - 1);
*((double *)v10 + 1) = v14;
v15 = *v11;
v10[2] = *(_QWORD *)v11;
v16 = v11[1];
*((double *)v10 + 3) = v16;
v17 = v11[2];
*((double *)v10 + 4) = v17;
v18 = v11[3];
*((double *)v10 + 5) = v18;
v19 = v11[4];
*((double *)v10 + 6) = v19;
v5 = v12 * 0.015625 // 图像数据的平均数
+ v5
+ v13 * 0.015625
+ v14 * 0.015625
+ v15 * 0.015625
+ v16 * 0.015625
+ v17 * 0.015625
+ v18 * 0.015625
+ v19 * 0.015625;
v10 += 8;
v11 = (double *)((char *)v11 + v9); // 下一行
--v8;
}
while ( v8 ); // 8次
v20 = 0i64;
do // 图像每一点的数据与平均数对比
// 如果数值小于平均数则为0否则为1
// 得到一个长度为64格式为二进制的字符串
{
if ( *((double *)&v44 + v20) < v5 )
{
if ( v4->_Myres < 0x10 )
v22 = v4;
else
v22 = *(string **)v4->_Buf;
v22->_Buf[v20] = '0';
}
else
{
if ( v4->_Myres < 0x10 )
v21 = v4;
else
v21 = *(string **)v4->_Buf;
v21->_Buf[v20] = '1';
}
if ( *((double *)&v45 + v20) < v5 )
{
if ( v4->_Myres < 0x10 )
v24 = v4;
else
v24 = *(string **)v4->_Buf;
v24->_Buf[v20 + 1] = '0';
}
else
{
if ( v4->_Myres < 0x10 )
v23 = v4;
else
v23 = *(string **)v4->_Buf;
v23->_Buf[v20 + 1] = '1';
}
if ( *((double *)&v46 + v20) < v5 )
{
if ( v4->_Myres < 0x10 )
v26 = v4;
else
v26 = *(string **)v4->_Buf;
v26->_Buf[v20 + 2] = '0';
}
else
{
if ( v4->_Myres < 0x10 )
v25 = v4;
else
v25 = *(string **)v4->_Buf;
v25->_Buf[v20 + 2] = '1';
}
if ( *(double *)&v47[v20] < v5 )
{
if ( v4->_Myres < 0x10 )
v28 = v4;
else
v28 = *(string **)v4->_Buf;
v28->_Buf[v20 + 3] = '0';
}
else
{
if ( v4->_Myres < 0x10 )
v27 = v4;
else
v27 = *(string **)v4->_Buf;
v27->_Buf[v20 + 3] = '1';
}
v20 += 4i64;
}
while ( v20 < 64 ); // 总共有8*8=64个数

得到的长度为64的字符串即为num6

最后拼接partnum得到字符串s=part1+part2+part3+part4+num1+num2+num3+num4+num5+num6再以num6keyhmac_sha256得到verification code,相关代码与第一题一致。

附上python3注册机

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
import re
import hmac
import cv2
import numpy as np

def hmac_sha256(key,s):
return hmac.new(key.encode('utf-8'), s.encode('utf-8'), 'SHA256').hexdigest()

def get_num6(part1,part2,part3,part4):
img=cv2.imread('.\\flag.jpg',cv2.IMREAD_COLOR)
center=(img.shape[1]*0.5,img.shape[0]*0.5)
angle=0
for i in range(4):
angle+=int(part1[i])+int(part2[i])+int(part3[i])+int(part4[i])
scale=1.0
trans=cv2.getRotationMatrix2D(center,angle,scale)
dsize=(img.shape[1],img.shape[0])
img=cv2.warpAffine(img,trans,dsize)
img=cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
img=np.float64(img)
dsize=(8,8)
img=cv2.resize(img,dsize)
img=cv2.dct(img)
total=0
for i in img:
for j in i:
total+=j
average=total/64
num6=''
for i in img:
for j in i:
if j<average:
num6+='0'
else:
num6+='1'
return num6


def get_verification_code(part1,part2,part3,part4):
v5=(ord(part4[0])^ord(part1[0]))<<8
v8=ord(part1[1])*ord(part4[1])+v5
v11=ord(part1[2])//ord(part4[2])+v8
v54=ord(part1[2])%ord(part4[2])+v11
v56=ord(part3[0])*ord(part2[0])<<8
v57=(ord(part2[1])^ord(part3[1]))+v56
v58=ord(part2[2])%ord(part3[2])+32+v57
v55=ord(part1[2])//ord(part3[2])+v58
v59=ord(part1[1])%ord(part1[3])<<8
v60=ord(part2[1])//ord(part2[3])+v59
v29=ord(part3[1])*ord(part3[3])+8+v60
v61=(ord(part4[1])^ord(part4[3]))+v29
v52=(ord(part1[0])^ord(part4[3]))<<8
v53=ord(part2[1])//ord(part3[2])+v52
v37=ord(part3[2])*ord(part2[1])
v38=ord(part4[3])%ord(part1[0])+v37+v53
v39=v38
v38=v38*9
v41=12*v54+45*v55+33*v39
num1=30*v55+20*(v54+2*v61)+8*v38
num2=23*v54+32*v55+42*v61+54*v39
num3=(25*v55+38*v61+67*v39+18*v54)%20
num4=(12*v54+45*v55+33*v39+v61*64)%20
if num3>num4:
num3,num4=num4,num3
num5=0
for i in range(4):
num5+=int(part1[i])+int(part2[i])+int(part3[i])+int(part4[i])
num6=get_num6(part1,part2,part3,part4)
code=part1+part2+part3+part4+str(num1)+str(num2)+str(num3)+str(num4)+str(num5)+num6
return hmac_sha256(num6,code)

if __name__=='__main__':
request_code=input('input request code: ')
reobj=re.fullmatch('(\\d{4})-(\\d{4})-(\\d{4})-(\\d{4})',request_code)
if reobj!=None:
part1=reobj.group(1)
part2=reobj.group(2)
part3=reobj.group(3)
part4=reobj.group(4)
print(get_verification_code(part1,part2,part3,part4))
else:
print('[+] Invalid code!')

第三题 Invisiable

该题目录下的文件

打开Invisiable.exe,首先看到有个tlscallback反调试

通过NtQueryInformationProcess查了DebugPort,如果程序被调试了这个DebugPort就不为0。检测到没有被调试后程序修改了某个函数,把这个函数填充成nop。当然这个反调试没什么影响,因为我用了SharpOD插件,所以基本不用管。

看一下main中的主要部分

验证输入的函数并不是直接调用的,ida看起来也不方便,所以我选择通过动态调试获取了3个step的验证输入的函数

first step

sub_404180中判断number-2019000是否为水仙花数。

如果是的话就就执行接下来的操作

读取题目目录下的invisiable.mp3文件数据,然后通过sub_401C20解密得到真实的文件decode.py

解密函数sub_401C20如下

将原文件数据的每个字节与字符串ReverseEngineerIsEasy的每个字符循环异或,得到真实文件数据,然后写回硬盘上。

然后对目录下的文件flag也做了同样的事情,解密后得到nice.png文件。

最后做了一个谜之操作,修改了main.dll

看了一下main.dll偏移0x46c处正是它的导出函数的位置,这里修改成了ret C

到这里first step就结束了

second step

首先判断了一下输入

如果符合条件就执行接下来的操作

读取目录下1.txt的数据

然后判断文本内容

也就是说1.txt的文本内容格式需要为xxxx-xxxxpart1长度暂时未知。

然后对这两个part进行了和first step解密文件很像的操作,buf1buf2分别为part1part2

然后将得到的结果做base64encode计算,得到的结果需要是VFxQXkljVFo=

VFxQXkljVFo=base64decode后得到T\P^IcTZ,也就是说我们的part1长度必须为8。

然后就是写脚本跑一个符合该要求的输入出来了

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
def xor_asc(l):
s='T\\P^IcTZ'
a=[ord(x) for x in s]
b=[0 for i in range(8)]
for i in range(8):
b[i]=a[i]^l[i%len(l)]
return b

def inc_asc(l):
for i in range(len(l)):
l[len(l)-i-1]+=1
if l[len(l)-i-1]==ord('-'):
l[len(l)-i-1]+=1
if l[len(l)-i-1]>126:
l[len(l)-i-1]=33
else:
break
return l

def check_asc(l):
for i in l:
if i>126 or i<33:
return False
return True

def create_txt():
part1=[]
part2=[33,33,33,33]
while(True):
part1=xor_asc(part2)
if check_asc(part1):
s1=''.join([chr(x) for x in part1])
s2=''.join([chr(x) for x in part2])
s=s1+'-'+s2
print(s)
part2=inc_asc(part2)


if __name__=='__main__':
create_txt()
#u}6uhB2q-!!f+

为了方便输入,所以只取了可视的ascii字符(不包括空格)。这个脚本不会停,所以请自行ctrl+c强制结束。随便取了一个结果u}6uhB2q-!!f+保存到1.txt后,再输入一个符合要求的number,second step就结束了。

third step

sub_404220中对输入的number进行了验证

需要输入一个长度为6的数字,然后每两个为一组拆成3个2位数,这3个数需要符合一个方程,解的结果为744110

输入正确结果后,程序加载main.dll

如果仅仅是这样,那么恭喜,程序崩溃。

还记得在first step中修改了main.dll的导出函数吗?这就是程序崩溃的原因。为了能够得到正确结果,在third step输入数字前需要把未修改的main.dll替换回去。最后符合题目要求,程序输出了flag。

但是我感觉这里过于诡异,所以不知道算不算正解。又看了一遍题目,要求输出的flag形式为flag{xxxxxxx}。。。也就是说1.txtpart1的前五位是可以确定的,但是我不想改了,就这样吧。

还有一个诡异的地方就是,first step中创建的decode.pynice.png在后面没用到,贴一下decode.py的代码

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
# coding=utf-8
import cv2
import numpy as np
import random
import os
from argparse import ArgumentParser
ALPHA = 5

def build_parser():
parser = ArgumentParser()
parser.add_argument('--original', dest='ori', required=True)
parser.add_argument('--image', dest='img', required=True)
parser.add_argument('--result', dest='res', required=True)
parser.add_argument('--alpha', dest='alpha', default=ALPHA)
return parser


def main():
parser = build_parser()
options = parser.parse_args()
ori = options.ori
img = options.img
res = options.res
alpha = float(options.alpha)
if not os.path.isfile(ori):
parser.error("original image %s does not exist." % ori)
if not os.path.isfile(img):
parser.error("image %s does not exist." % img)
decode(ori, img, res, alpha)


def decode(ori_path, img_path, res_path, alpha):
ori = cv2.imread(ori_path)
img = cv2.imread(img_path)
ori_f = np.fft.fft2(ori)
img_f = np.fft.fft2(img)
height, width = ori.shape[0], ori.shape[1]
watermark = (ori_f - img_f) / alpha
watermark = np.real(watermark)
res = np.zeros(watermark.shape)
random.seed(height + width)
x = range(height / 2)
y = range(width)
random.shuffle(x)
random.shuffle(y)
for i in range(height / 2):
for j in range(width):
res[x[i]][y[j]] = watermark[i][j]
cv2.imwrite(res_path, res, [int(cv2.IMWRITE_JPEG_QUALITY), 100])


if __name__ == '__main__':
main()

这就超出我的能力范围了(怎么还有快速傅里叶变换呢)。试着用了一下这个脚本

1
py -2 decode.py --original nice.png --image out.png --result res.png

得到图片

nice.pngout.png分别为

看了这几张图,总感觉第三题还有一点门路。然而小弟能力有限,只能到这了,希望大佬们能指导指导。