NSSCTF

[NSSRound#4 SWPU]hide_and_seek

打开调试后,发现有反调试,把一开始的exit给nop掉

然后调试跳到后面发现有flag:SCTF{wud3_0n@,34p}

但是结果不对??

再次调试,在hex视图中alt+t搜索NSSCTF,发现flag

1
NSSCTF{h1d3_0n_h34p}

[NSSRound#X Basic]ez_z3

先用die查壳,发现是upx,尝试用upx-d脱壳,然后失败,应该是改过什么东西,打开ida看看,结果发现节区名从UPX变成了XYU,所以尝试还原,还原后还是失败,在03E0处还需修改为UPX,这次UPX-d成功,参考UPX防脱壳机脱壳、去除特征码、添加花指令小探 - 『脱壳破解区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

打开ida查看main函数并尝试调试

1
2
3
4
5
Please input the flag:1212121
Can you calculated out z3?
Please input z3:121212
YOU are wrong
oh no!!!!!your flag is wrong,try again

main函数大致逻辑:

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
int __fastcall main_0(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
__int64 v4; // rax

print(std::cout, "Please input the flag:");
getin(std::cin, flag);
v3 = print(std::cout, "Can you calculated out z3?");
std::ostream::operator<<(v3, guessNoUse);
print(std::cout, "Please input z3:");
getin(std::cin, z3num);

for ( i = 0; i < j_strlen(flag); ++i )
storn[i] = change(flag[i], exenum[i]);
z3check1(z3num);
z3check2(z3num);
check = sub_7FF7CCBC10E6();

if ( check == 1 )
v4 = print(std::cout, "yeah!!!!!!you get the flag");
else
v4 = print(std::cout, "oh no!!!!!your flag is wrong,try again");


std::ostream::operator<<(v4, guessNoUse);
system("pause");
return 0;
}

根据流程大致是先对flag变换为k,然后检测输入一个z3序列是否满足条件,然后让z3序列和k变换,检查最后结果

那么先找到z3num:

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
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
from z3 import *

# 创建一个求解器实例
f = Solver()

# 创建一个整数变量的数组,用于存储z3num中的值
z3num = [Int(f'z3num[{i}]') for i in range(20)]

for num in z3num:
f.add(num >= 0)
f.add(num <= 255)

f.add(20 * z3num[19] * 19 * z3num[18]
+ 14 * z3num[13]
+ 13 * z3num[12]
+ 11 * z3num[10] * 10 * z3num[9]
+ 30 * z3num[5]
+ 5 * z3num[4]
+ z3num[0]
+ 2 * z3num[1]
- 3 * z3num[2]
- 4 * z3num[3]
- 7 * z3num[6]
+ 8 * z3num[7]
- 9 * z3num[8]
- 12 * z3num[11]
- 16 * z3num[15] * 15 * z3num[14]
- 17 * z3num[16]
- 18 * z3num[17] == 2582239)
f.add(20 * z3num[19] * 19 * z3num[18]
+ 14 * z3num[13]
+ 13 * z3num[12]
+ 11 * z3num[10] * 10 * z3num[9]
+ 30 * z3num[5]
- 7 * z3num[6]
+ 8 * z3num[7]
- 9 * z3num[8]
+ 5 * z3num[4]
+ 3 * z3num[2]
+ 2 * z3num[1] * z3num[0]
- 4 * z3num[3]
- 12 * z3num[11]
- 16 * z3num[15] * 15 * z3num[14]
- (18 * z3num[17]
+ 17 * z3num[16]) == 2602741)
f.add(19 * z3num[18]
+ 18 * z3num[17]
+ 14 * z3num[13] * 13 * z3num[12]
+ 12 * z3num[11] * 11 * z3num[10]
+ 9 * z3num[8]
+ 7 * z3num[6] * 30 * z3num[5]
+ z3num[0]
- 2 * z3num[1]
- 4 * z3num[3] * 3 * z3num[2]
- 5 * z3num[4]
+ 8 * z3num[7]
- 10 * z3num[9]
- 15 * z3num[14]
- 17 * z3num[16] * 16 * z3num[15]
- 20 * z3num[19] == 2668123)
f.add(20 * z3num[19] * 19 * z3num[18]
+ 14 * z3num[13]
+ (13 * z3num[12] + 11 * z3num[10] - 12 * z3num[11]) * 10 * z3num[9]
+ 30 * z3num[5]
+ 5 * z3num[4]
+ z3num[0]
+ 2 * z3num[1]
- 3 * z3num[2]
- 4 * z3num[3]
- 7 * z3num[6]
+ 8 * z3num[7]
- 9 * z3num[8]
- 16 * z3num[15] * 15 * z3num[14]
- 17 * z3num[16]
- 18 * z3num[17] == 2520193)
f.add(18 * z3num[17]
+ 17 * z3num[16]
+ 15 * z3num[14]
+ 13 * z3num[12] * 12 * z3num[11]
+ 10 * z3num[9]
+ 9 * z3num[8] * 8 * z3num[7]
+ 3 * z3num[2] * 2 * z3num[1] * z3num[0]
- 4 * z3num[3]
- 5 * z3num[4]
- 30 * z3num[5]
- 7 * z3num[6]
- 11 * z3num[10]
- 14 * z3num[13]
- 16 * z3num[15]
- 19 * z3num[18]
- 20 * z3num[19] == 8904587)
f.add(18 * z3num[17]
+ 7 * z3num[6] * 30 * z3num[5] * 5 * z3num[4]
+ 4 * z3num[3]
+ 8 * z3num[7]
+ z3num[0]
- 2 * z3num[1]
- 3 * z3num[2]
- 9 * z3num[8]
- 11 * z3num[10] * 10 * z3num[9]
- 16 * z3num[15] * (13 * z3num[12] + 12 * z3num[11] - 14 * z3num[13] - 15 * z3num[14])
- 17 * z3num[16]
- 19 * z3num[18]
- 20 * z3num[19] == 1227620874)
f.add(20 * z3num[19] * 19 * z3num[18]
+ 17 * z3num[16]
+ 14 * z3num[13]
+ 13 * z3num[12]
+ 12 * z3num[11] * 11 * z3num[10] * 10 * z3num[9]
+ 7 * z3num[6] * 30 * z3num[5]
+ 5 * z3num[4]
+ 3 * z3num[2]
+ z3num[0]
+ 2 * z3num[1]
+ 4 * z3num[3]
+ 8 * z3num[7]
- 9 * z3num[8]
- 16 * z3num[15] * 15 * z3num[14]
- 18 * z3num[17] == 1836606059)
f.add(20 * z3num[19] * 19 * z3num[18]
+ 16 * z3num[15] * 15 * z3num[14]
+ 14 * z3num[13]
+ 13 * z3num[12]
+ 12 * z3num[11]
+ 7 * z3num[6] * 30 * z3num[5]
+ 5 * z3num[4]
+ 2 * z3num[1] * z3num[0]
- 3 * z3num[2]
+ 4 * z3num[3]
+ 8 * z3num[7]
- 9 * z3num[8]
- 10 * z3num[9]
- 11 * z3num[10]
- 17 * z3num[16]
- 18 * z3num[17] == 8720560)
f.add(20 * z3num[19] * 19 * z3num[18]
+ 14 * z3num[13]
+ 13 * z3num[12]
+ 11
* z3num[10]
* (10 * z3num[9] + 30 * z3num[5] + 5 * z3num[4] + 4 * z3num[3] - 7 * z3num[6] + 8 * z3num[7] - 9 * z3num[8])
+ z3num[0]
+ 2 * z3num[1]
- 3 * z3num[2]
- 12 * z3num[11]
- (16 * z3num[15] - 17 * z3num[16] - 18 * z3num[17]) * 15 * z3num[14] == 11387045)
f.add(20 * z3num[19] * 19 * z3num[18]
+ 16 * z3num[15] * 15 * z3num[14]
+ 14 * z3num[13]
+ 11 * z3num[10] * 10 * z3num[9]
+ 9 * z3num[8]
+ 3 * z3num[2]
+ z3num[0]
- 2 * z3num[1]
+ 4 * z3num[3]
- 5 * z3num[4]
- 30 * z3num[5]
- 7 * z3num[6]
+ 8 * z3num[7]
- 12 * z3num[11]
- 13 * z3num[12]
- 17 * z3num[16]
- 18 * z3num[17] == 7660269)
f.add(20 * z3num[19] * 19 * z3num[18]
+ 14 * z3num[13]
+ 13 * z3num[12]
+ 11 * z3num[10] * 10 * z3num[9]
- 12 * z3num[11]
+ z3num[0]
+ 2 * z3num[1]
- (4 * z3num[3] * 3 * z3num[2]
- 5 * z3num[4]
- 30 * z3num[5])
- 7 * z3num[6]
+ 8 * z3num[7]
- 9 * z3num[8]
- 16 * z3num[15] * 15 * z3num[14]
- 17 * z3num[16]
- 18 * z3num[17] == 2461883)
f.add(14 * z3num[13]
+ 11 * z3num[10] * 10 * z3num[9]
+ 9 * z3num[8] * 8 * z3num[7]
+ 7 * z3num[6]
+ 2 * z3num[1] * z3num[0]
- 4 * z3num[3] * 3 * z3num[2]
- 5 * z3num[4]
- 30 * z3num[5]
- 12 * z3num[11]
- 13 * z3num[12]
- 15 * z3num[14]
- 17 * z3num[16] * 16 * z3num[15]
- 18 * z3num[17]
- 19 * z3num[18]
- 20 * z3num[19] == -966296)
f.add(14 * z3num[13]
+ 13 * z3num[12]
+ (11 * z3num[10] * 10 * z3num[9]
+ 30 * z3num[5]
+ 5 * z3num[4]
+ 3 * z3num[2]
+ 4 * z3num[3]
- 7 * z3num[6]
+ 8 * z3num[7]
- 9 * z3num[8])
* 2
* z3num[1]
+ z3num[0]
- 12 * z3num[11]
- 15 * z3num[14]
- 16 * z3num[15]
- 17 * z3num[16]
- 18 * z3num[17]
- 20 * z3num[19] * 19 * z3num[18] == 254500223)
f.add(16 * z3num[15] * 15 * z3num[14]
+ 14 * z3num[13]
+ 11 * z3num[10] * 10 * z3num[9]
+ 7 * z3num[6] * 30 * z3num[5]
+ z3num[0]
- 2 * z3num[1]
- 3 * z3num[2]
- 5 * z3num[4] * 4 * z3num[3]
+ 8 * z3num[7]
- 9 * z3num[8]
- 12 * z3num[11]
- 13 * z3num[12]
- 17 * z3num[16]
- 18 * z3num[17]
- 19 * z3num[18]
- 20 * z3num[19] == 6022286)
f.add(18 * z3num[17]
+ 16 * z3num[15]
- 17 * z3num[16]
+ 14 * z3num[13]
+ 12 * z3num[11]
+ 11 * z3num[10] * 10 * z3num[9]
+ 30 * z3num[5]
+ 5 * z3num[4]
+ 4 * z3num[3] * 3 * z3num[2]
+ 2 * z3num[1] * z3num[0]
- 9 * z3num[8] * 8 * z3num[7] * 7 * z3num[6]
- 13 * z3num[12]
- 15 * z3num[14]
- 19 * z3num[18]
- 20 * z3num[19] == -636956022)
f.add(20 * z3num[19] * 19 * z3num[18]
+ 13 * z3num[12]
+ 12 * z3num[11]
+ 11 * z3num[10] * 10 * z3num[9]
+ 7 * z3num[6]
+ 30 * z3num[5]
+ 5 * z3num[4]
+ 3 * z3num[2] * 2 * z3num[1] * z3num[0]
- 4 * z3num[3]
- 9 * z3num[8] * 8 * z3num[7]
- 14 * z3num[13]
- 15 * z3num[14]
- 16 * z3num[15]
- 17 * z3num[16]
- 18 * z3num[17] == 10631829)
f.add(20 * z3num[19] * 19 * z3num[18]
+ 16 * z3num[15]
- 17 * z3num[16]
- 18 * z3num[17]
+ 15 * z3num[14] * 14 * z3num[13]
+ 13 * z3num[12]
+ 11 * z3num[10] * 10 * z3num[9]
- 12 * z3num[11]
+ 7 * z3num[6]
+ (4 * z3num[3] - 5 * z3num[4] - 30 * z3num[5]) * 3 * z3num[2]
+ z3num[0]
+ 2 * z3num[1]
+ 8 * z3num[7]
- 9 * z3num[8] == 6191333)
f.add(14 * z3num[13]
+ 10 * z3num[9] * 9 * z3num[8] * 8 * z3num[7]
+ 5 * z3num[4]
+ 4 * z3num[3] * 3 * z3num[2]
+ 2 * z3num[1] * z3num[0]
- 7 * z3num[6] * 30 * z3num[5]
- 11 * z3num[10]
- 13 * z3num[12] * 12 * z3num[11]
- 16 * z3num[15] * 15 * z3num[14]
- 18 * z3num[17] * 17 * z3num[16]
- 20 * z3num[19] * 19 * z3num[18] == 890415359)
f.add(20 * z3num[19]
+ 19 * z3num[18]
+ 18 * z3num[17]
+ 16 * z3num[15]
- 17 * z3num[16]
+ 12 * z3num[11]
+ 11 * z3num[10]
+ 10 * z3num[9]
+ 9 * z3num[8]
+ 30 * z3num[5]
+ z3num[0]
+ 4 * z3num[3] * 3 * z3num[2] * 2 * z3num[1]
- 5 * z3num[4]
- 7 * z3num[6]
+ 8 * z3num[7]
- 13 * z3num[12]
- 14 * z3num[13]
- 15 * z3num[14] == 23493664)
f.add(20 * z3num[19] * 19 * z3num[18]
+ 13 * z3num[12]
+ 12 * z3num[11]
+ 10 * z3num[9]
+ 3 * z3num[2] * 2 * z3num[1]
+ z3num[0]
- 4 * z3num[3]
- 5 * z3num[4]
+ 8 * z3num[7] * 7 * z3num[6] * 30 * z3num[5]
- 9 * z3num[8]
- 11 * z3num[10]
- 14 * z3num[13]
- 16 * z3num[15] * 15 * z3num[14]
- 17 * z3num[16]
- 18 * z3num[17] == 1967260144)

print(f.check())
while (f.check()==sat):
condition = []
m = f.model()
p =""
for i in range (20):
p += chr(int("%s" % (m[z3num[i]])))
condition.append(z3num[i]!=int("%s" % (m[z3num[i]])))
print(p)
f.add(Or(condition))
#hahahathisisfackflag

这里奇怪的是一开始用或逻辑解不出来正确值,但是把所有都与之后却可以得到正确的结果,同时这个不是flag是中间用做加密的值

又因为后来的算法是

1
2
3
4
5
6
7
8
void __fastcall sub_7FF7CCBC4830(char *z3num)
{
__int64 v1; // rdx

j___CheckForDebuggerJustMyCode(&unk_7FF7CCBDB069, v1);
for ( i = 0; i < j_strlen(flag); ++i )
last[i] = z3num[j_strlen(flag) - i - 1] ^ storn[i];
}

最后的检查是

1
2
3
4
5
6
7
8
9
10
11
12
size_t sub_7FF7CCBC4770(__int64 a1)
{
__int64 v1; // rdx
size_t len; // rax

j___CheckForDebuggerJustMyCode(&unk_7FF7CCBDB069, v1);
zer0 = 0;
len = j_strlen(flag);
if ( len )
return last[zer0] == strange_num[zer0]; // 前两位相等,因为是DWORD
return len;
}

这个可能错了什么,可能前两位相等相当于全部相等。

1
last = [0x00001207, 0x00004CA0, 0x00004F21, 0x00000039, 0x0001A523, 0x0000023A, 0x00000926, 0x00004CA7, 0x00006560, 0x00000036, 0x0001A99B, 0x00004CA8, 0x0001BBE0, 0x00003705, 0x00000926, 0x000077D3, 0x00009A98, 0x0000657B, 0x00000018, 0x00000B11, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000]

前面的变换是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__int64 __fastcall change2(int flag, __int64 exenum)
{
unsigned int a; // [rsp+24h] [rbp+4h]
int exei; // [rsp+148h] [rbp+128h]

exei = exenum;
j___CheckForDebuggerJustMyCode(&unk_7FF7CCBDB069, exenum);
a = 1;
while ( exei )
{
if ( (exei & 1) != 0 ) // exenum这一位是奇数
a *= flag;
flag = flag * flag % 1000;
exei >>= 2;
}
return a;
}
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
exe = [0x7, 0x7, 0x7, 0x9, 0x5, 0x6, 0x7, 0x7, 0x7, 0x9, 0x7, 0x7, 0x5, 0x7, 0x7, 0x7, 0x5, 0x7, 0x9, 0x7]

last = [0x1207, 0x4CA0, 0x4F21, 0x0039, 0x1A523, 0x023A, 0x0926, 0x4CA7, 0x6560, 0x0036, 0x1A99B, 0x4CA8, 0x1BBE0, 0x3705, 0x0926, 0x77D3, 0x9A98, 0x657B, 0x0018, 0x0B11]

z3num = list('hahahathisisfackflag')

stron = [0]*20

for i in range (20):
stron[i] = ord(z3num[19 - i]) ^ last[i]

flag = [0] * 20
def po(flag,b):
a = 1
while (b):
if (b & 1 != 0):
a *= flag
flag = flag * flag % 1000
b >>= 2
return a
for i in range(20):
for chrr in range(32,127):
if (po(chrr,exe[i]) == stron[i]):
flag[i] = chr(chrr)

print(''.join(flag))
#T1e_z3_1s_v1r9_3asy!

爆破得出答案

总结:

z3遇到或时计算非常慢,用bitvec型或把或逻辑改成与加快速度

最后一步遇到不好逆向的也不好z3的,可以用爆破求解因为字符一定在32到127

一个疑问是:最后一步的b都是一位数,说明a一定是1或者flag的值,stron里面就应该是这个数为什么stron里不是呢

[NSSCTF 2nd]MyBase

打开ida后发现有tls,需要注意一下

ida打开之后

main函数有两个子函数

1
2
3
4
5
6
int __fastcall main(int argc, const char **argv, const char **envp)
{
_main();
test();
return 0;
}

先调试看看有没有反调试,然后发现并没有

第一个_main看不懂,直接进test,里面有一个base64

打开后根据码表直接解密,但是解不出来。。

去data里面看看,发现还有码表,进交叉引用的函数发现有一个打乱的代码

是个c语言函数,有srand函数,所以调试把他找出来

真:

1
YsVO0tvT2o4puZ38j1dwf7MArGPNeQLDRHUK+SChbFanmklWEcgixXJIq6y5B/9z

一开始加密的:

1
+86420ywusqomkigecaYWUSQOMKIGECABDFHJLNPRTVXZbdfhjlnprtvxz13579/

然后发现这个打乱代码执行了很多次

感觉哪里漏了,最后查找交叉引用发现打乱代码在加密里面有一行:

1
2
3
  if ( !setjmp(env) )
exception_handler();
}

经过查阅:

setjmp和longjmp成对使用实现函数间跳转

setjmp将函数在此处的上下文保存在jmp_buf结构体中,以供longjmp从此结构体中恢复

1
int setjmp(jmp_buf env);

env为保存的结构体变量的位置,若直接调用返回0,否则返回非0(主要指通过longjmp跳转)

1
void longjmp(jmp_buf env, int val);

env为由setjmp保存的上下文,val表示longjmp传给setjmp的返回值,如果val=0,则setjmp返回1,否则返回val

longjmp 不直接返回,而是从 setjmp 函数中返回,longjmp 执行完之后,程序就像刚从 setjmp 函数返回一样。

所以;

1
2
3
if ( !setjmp(env) )
exception_handler();
}

这段代码先执行setjmp(env),返回0

进入exception_handler();

1
2
3
4
5
void __noreturn exception_handler()
{
generate_base64_table(base64_table);
longjmp_0(env, 1);
}

然后更新base64码表

所以逻辑是:

1
2
3
4
5
6
7
对第一组加密
更新
对第二组加密
更新
对第三组加密
更新
...

所以密文

1
2
3
4
5
6
7
8
9
YkLY
v1Xj
23X7
N0E5
eoFg
UveK
eos1
XS8K
9r4g

码表

1
2
3
4
5
6
7
8
9
+86420ywusqomkigecaYWUSQOMKIGECABDFHJLNPRTVXZbdfhjlnprtvxz13579/
YsVO0tvT2o4puZ38j1dwf7MArGPNeQLDRHUK+SChbFanmklWEcgixXJIq6y5B/9z
xDfpNE4LYH5Tk+MRtrlv1oFbQm0gP37eqIajh2syUnZcSV8iBK6O/XWuzdCwA9GJ
YvHeOZECmTyg0Mw2i7PIGKblsfF59rzUk6p3hVdW1qaQ+xRANnXLj48BcJDotS/u
xDfpNE4LYH5Tk+MRtrlv1oFbQm0gP37eqIajh2syUnZcSV8iBK6O/XWuzdCwA9GJ
YvHeOZECmTyg0Mw2i7PIGKblsfF59rzUk6p3hVdW1qaQ+xRANnXLj48BcJDotS/u
xDfpNE4LYH5Tk+MRtrlv1oFbQm0gP37eqIajh2syUnZcSV8iBK6O/XWuzdCwA9GJ
YvHeOZECmTyg0Mw2i7PIGKblsfF59rzUk6p3hVdW1qaQ+xRANnXLj48BcJDotS/u
xDfpNE4LYH5Tk+MRtrlv1oFbQm0gP37eqIajh2syUnZcSV8iBK6O/XWuzdCwA9GJ

然后看了半天

1
2
3
4
v10 = (bbb << 8) + (aa << 16) + cc;
basenum[v14] = base64_table[cc & 0x3F];
basenum[v14 + 1] = base64_table[(v10 >> 6) & 0x3F];
basenum[v14 + 2] = base64_table[(v10 >> 12) & 0x3F];

发现是倒序输出

那么就直接倒序解码:

1
2
3
4
5
6
7
8
9
YLkY
jX1v
7X32
5E0N
gFoe
KevU T0_
1soe Re_
K8SX
g4r9 ld}
1
NSSCTF{Welc0me_T0_Re_World}

[NSSRound#3 Team]jump_by_jump_revenge

打开后发现main函数不能反编译,感觉有花指令

然后nop掉两行代码

获得正确的main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
int i; // [esp+D0h] [ebp-40h]
char Str1[36]; // [esp+E8h] [ebp-28h] BYREF

sub_411037("%s", (char)Str1);
for ( i = 0; i < 29; ++i )
Str1[i] = (Str1[i] + Str1[(i * i + 123) % 21]) % 96 + 32;
if ( !j_strcmp(Str1, "~4G~M:=WV7iX,zlViGmu4?hJ0H-Q*") )
puts("right!");
else
puts("nope!");
return 0;
}

这个算法要注意要从后面往前,因为前面的已经被覆盖了

非常抽象(TAT)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
last = "~4G~M:=WV7iX,zlViGmu4?hJ0H-Q*"
flag = [0]*29
c = [0]*29
for i in range(29):
c[i] = (i * i + 123) % 21

for i in range(28,-1,-1):
#flag[i] = (flag[i] + flag[c[i]]) % 96 + 32
if (i > c[i]):
for j in range(2):
uuk = (ord(last[i])-32)+ j * 96 -ord(last[c[i]])
if (31< uuk < 127):
flag[i]=uuk
else:
for j in range(2):
uuk = (ord(last[i])-32)+ j * 96 -flag[c[i]]
if (31< uuk < 127):
flag[i]=uuk
u = ''
for i in range(29):
u += chr(flag[i])
print(u)
#NSSCTF{Jump_b9_jump!_r3V3n9e}

总结:逆向加密算法时要注意是递推式的还是映射式的

看了看大佬的wp,好简洁:

1
2
3
4
5
6
7
8
9
ciper='~4G~M:=WV7iX,zlViGmu4?hJ0H-Q*'

for i in range(len(ciper)-1,-1,-1):
tmp=ord(ciper[i])-32-ord(ciper[(i*i+123)%21])
while(tmp<33):
tmp+=96
ciper=ciper[:i]+chr(tmp)+ciper[i+1:]

print(ciper)

[NSSRound#2 Able]findxenny

打开main函数看看,对输入的flag的检查的函数返回值是本身??没理解暂时跳过,看到最后的检查有三个check

1
2
3
4
5
6
7
8
9
10
if ( (unsigned int)check1(*v14) || (unsigned int)check2(*v15) || (unsigned int)check3(*v16) )
{
v10 = sub_7FF72A641262(std::cout, "Try harder");
std::ostream::operator<<(v10, sub_7FF72A6411FE);
}
else
{
v9 = sub_7FF72A641262(std::cout, "I can't believe my golden doge eye! we are comarde!");
std::ostream::operator<<(v9, sub_7FF72A6411FE);
}

打开之后是一组数,通过交叉引用查看这个数据是通过函数生成的,但是没看懂。。。

看了wp发现这个是SMC,三个check是函数,双击进入对应地址,发现都是数据,这时要c加p组合再f5进入反编译结果检查的值就在这里

image-20231227230725460

1
2
3
4
5
6
7
__int64 __fastcall sub_278FBE97370(__int64 a1)
{
if ( a1 == 0x665F756F795F686Fi64 )
return 0i64;
else
return -1i64;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__int64 __fastcall sub_278FBEA01C0(__int64 a1)
{
__int64 v1; // rcx
__int64 v3[2]; // [rsp+0h] [rbp-40h]
int v4[12]; // [rsp+10h] [rbp-30h] BYREF

v3[0] = a1;
qmemcpy(v4, "ound_our", 8);
v1 = 0i64;
while ( *((_BYTE *)v3 + v1) == *((_BYTE *)v4 + v1) )
{
if ( ++v1 >= 8 )
return 0i64;
}
return -1i64;
}
1
2
3
4
5
6
7
__int64 __fastcall sub_278FBE999B4(__int64 a1)
{
if ( a1 == 0x796E6E33785Fi64 )
return 0i64;
else
return -1i64;
}

直接得出结果

1
oh_you_found_our_x3nny

总结,如果看到virtulprotect和数据当作地址,尝试把数据改成代码

[NSSCTF 2nd]Bytecode

打开后,是py字节码

反编译一下

安洵杯

mobilego

jadx打开mainactivity

模拟器中尝试输入,返回nonono

发现判断函数:

1
2
3
4
5
6
7
8
    public /* synthetic */ void m141lambda$onCreate$0$comexamplemobilegoMainActivity(View v) {
if (Game.checkflag(this.editText.getText().toString()).equals(getResources().getString(C0569R.string.cmp))) {
Toast.makeText(this, "yes your flag is right", 0).show();
} else {
Toast.makeText(this, "No No No", 0).show();
}
}
}

在C0569R中有

1
2
3
4
public static final class string {
public static int app_name = 2131689500;
public static int cmp = 2131689512;
}

这里的cmp是它的id值而不是它的具体值

所以动态调试找到终值

1
49021}5f919038b440139g74b7Dc88330e5d{6

这里使用了Game.checkflag来加密

所以先看这个

根据game

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package game;

import p004go.Seq;

/* loaded from: classes.dex */
public abstract class Game {
private static native void _init();

public static native String checkflag(String str);

static {
Seq.touch();
_init();
}

private Game() {
}

public static void touch() {
}
}

怀疑checkflag是通过lib加载的

打开ida,反汇编so文件

搜索checkflag

找到5个函数

image-20231228150946781

函数是这几个

好复杂,看不懂。。

看了看wp,发现一个一直没注意的东西,如果输入

1
2
1234567890变为6143580927
{qwertyuiopasdfghjklzxcvbnm}变为bhgsruc{iojqkdpwnezyvxa}lfmt

说明这只是字符交换,交换之后是

1
49021}5f919038b440139g74b7Dc88330e5d{6

一共38个字符

那么尝试输入

1
t2F7GTglISYLnMzc6CqhDN5OdX8wPjsKufVbE}

变为

1
NVLjz}ShscYPndXT62qlDFO5I8tgEKw7ufbMGC

根据这个对应关系,逆向解出原来的关系

1
2
3
4
5
6
7
8
9
10
11
12
d1 = 't2F7GTglISYLnMzc6CqhDN5OdX8wPjsKufVbE}'
d2 = 'NVLjz}ShscYPndXT62qlDFO5I8tgEKw7ufbMGC'

f2 = '49021}5f919038b440139g74b7Dc88330e5d{6'
f1 = [0]*38

for i in range(38):
for j in range(38):
if (d2[j] == d1[i]):
f1[i] = f2[j]
print(''.join(f1))
#D0g3{4c3b5903d11461f94478b7302980e958}

总结:

猜测输入时尽量使用有规律的字符,更容易看清

如果不容易逆向可以尝试看或者猜

你见过蓝色的小鲸鱼吗

打开文件后,发现是win程序,说明进入主窗口程序和判断函数不一定在一个位置

所以打开后需要找到判断代码,搜索string无效

根据弹窗函数是MessageBox,尝试搜索

然后找到判断函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __thiscall lastcall(void *this)
{
char wrong[28]; // [esp+D0h] [ebp-54h] BYREF
CHAR yes[20]; // [esp+ECh] [ebp-38h] BYREF
CHAR Caption[24]; // [esp+100h] [ebp-24h] BYREF
void *v5; // [esp+118h] [ebp-Ch]

v5 = this;
__CheckForDebuggerJustMyCode(&unk_52102F);
strcpy(Caption, "tip");
strcpy(yes, "You Get It!");
strcpy(wrong, "Wrong user/passwd");
if ( *((_DWORD *)v5 + 2) != *((_DWORD *)v5 + 3)
|| j__memcmp(*(const void **)v5, *((const void **)v5 + 1), *((_DWORD *)v5 + 3)) )
{
return MessageBoxA(0, wrong, Caption, 0);
}
else
{
return MessageBoxA(0, yes, Caption, 0);
}
}

这里的v5+2和v5+3一定指向一个是输入(可能加密了),另一个说不准,因为有一个密码一个用户名

由于需要进入else分支,所以条件必须为假

即:

1
2
*((_DWORD *)v5 + 2) == *((_DWORD *)v5 + 3)
&& !j__memcmp(*(const void **)v5, *((const void **)v5 + 1), *((_DWORD *)v5 + 3))

既然如此,需要找出能改变v5+2和+3的地址的函数(修改了this),向上找:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
void *v4; // [esp+10h] [ebp-154h]
void *v5; // [esp+24h] [ebp-140h]
CHAR *v6; // [esp+114h] [ebp-50h]
CHAR *lpString; // [esp+120h] [ebp-44h]
HWND DlgItem; // [esp+12Ch] [ebp-38h]
HWND hWnd; // [esp+138h] [ebp-2Ch]
int v10; // [esp+144h] [ebp-20h]
int WindowTextLengthA; // [esp+150h] [ebp-14h]

__CheckForDebuggerJustMyCode(&unk_52105E);
hWnd = GetDlgItem((HWND)argc, 1003);
DlgItem = GetDlgItem((HWND)argc, 1004);
WindowTextLengthA = GetWindowTextLengthA(hWnd);
v10 = GetWindowTextLengthA(DlgItem);
lpString = (CHAR *)j__malloc(__CFADD__(WindowTextLengthA, 16) ? -1 : WindowTextLengthA + 16);
result = (int)j__malloc(__CFADD__(v10, 16) ? -1 : v10 + 16);
v6 = (CHAR *)result;
if ( lpString && result )
{
GetWindowTextA(hWnd, lpString, WindowTextLengthA + 16);
GetWindowTextA(DlgItem, v6, v10 + 16);
v5 = operator new(0x10u);
if ( v5 )
{
sub_451B43(0x10u);
v4 = (void *)sub_450CE3(v5);
}
else
{
v4 = 0;
}
sub_44FC2B(&unk_51D38C, 0x10u);
sub_45126F(lpString, WindowTextLengthA, (int)v6, v10);
j_lastcall(v4);
j__free(lpString);
j__free(v6);
result = (int)v4;
if ( v4 )
return sub_44F77B(1);
}
return result;
}

动调可知:第一个是用户名,第二个是密码

化简:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int result; // eax
void *v4; // [esp+10h] [ebp-154h]
void *v5; // [esp+24h] [ebp-140h]
CHAR *password; // [esp+114h] [ebp-50h]
CHAR *username; // [esp+120h] [ebp-44h]
HWND DlgItem; // [esp+12Ch] [ebp-38h]
HWND hWnd; // [esp+138h] [ebp-2Ch]
int lenOFpass; // [esp+144h] [ebp-20h]
int lenOFname; // [esp+150h] [ebp-14h]

__CheckForDebuggerJustMyCode(&unk_52105E);
hWnd = GetDlgItem((HWND)argc, 1003);
DlgItem = GetDlgItem((HWND)argc, 1004);
lenOFname = GetWindowTextLengthA(hWnd);
lenOFpass = GetWindowTextLengthA(DlgItem);
username = (CHAR *)j__malloc(__CFADD__(lenOFname, 16) ? -1 : lenOFname + 16);
result = (int)j__malloc(__CFADD__(lenOFpass, 16) ? -1 : lenOFpass + 16);
password = (CHAR *)result;
if ( username && result ) // 检查空指针
{
GetWindowTextA(hWnd, username, lenOFname + 16);// 用户名
GetWindowTextA(DlgItem, password, lenOFpass + 16);// 密码
v5 = operator new(0x10u);
if ( v5 )
{
sub_451B43(0x10u); // 分配内存
v4 = (void *)sub_450CE3(v5); // 初始化
}
else
{
v4 = 0;
}
sub_44FC2B(&specialloc, 0x10u); // 初始化对象
encode(username, lenOFname, (int)password, lenOFpass);
j_lastcall(v4); // 判断
j__free(username);
j__free(password);
result = (int)v4;
if ( v4 )
return sub_44F77B(1);
}
return result;
}

后面实在分析不出来了,里面有一些涉及c++的理解的

在后面有加密函数是blowfish

在cmp时下断点buf1就是最后的值

1
11A51F049550E2508F17E16CF1632B47

写blowfish的解密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Crypto.Cipher import Blowfish
import binascii

# 密文(以十六进制表示)
ciphertext_hex = "11A51F049550E2508F17E16CF1632B47"
ciphertext = binascii.unhexlify(ciphertext_hex)

# 密钥(简单字符串)
key = "UzBtZTBuZV9EMGcz"

# 创建Blowfish ECB模式的解密器
cipher = Blowfish.new(key.encode('utf-8'), Blowfish.MODE_ECB)

plaintext_bytes = cipher.decrypt(ciphertext)
plaintext = plaintext_bytes.decode('utf-8')
print(plaintext)
#QHRoZWJsdWVmMXNo

这个答案是个base64,解密后是@thebluef1sh

(和蓝鲸的唯一关系吗。。。)

所以

D0g3{UzBtZTBuZV9EMGczQHRoZWJsdWVmMXNo}