GHCTF 2025 WriteUp

9.2k words

这比赛3.2就开始了,我们当时没有听说,3.6才知道的,不过最后还是取得了不错的成绩.
在这里仅给出我个人解出的题目的WriteUp.

Reverse方向:

ASM?Signin!:

编写解密脚本:

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
data1 = [
0x26, 0x27, 0x24, 0x25, 0x2A, 0x2B, 0x28, 0x00,
0x2E, 0x2F, 0x2C, 0x2D, 0x32, 0x33, 0x30, 0x00,
0x36, 0x37, 0x34, 0x35, 0x3A, 0x3B, 0x38, 0x39,
0x3E, 0x3F, 0x3C, 0x3D, 0x3F, 0x27, 0x34, 0x11
]
blocks = [data1[i*4:(i+1)*4] for i in range(8)]
for i in range(8):
si = i * 4
di = si + 4
if di >= 28:
di -= 28
block_si = si // 4
block_di = di // 4
blocks[block_si], blocks[block_di] = blocks[block_di], blocks[block_si]
processed_data1 = []
for block in blocks:
processed_data1.extend(block)
data2 = [
0x69, 0x77, 0x77, 0x66, 0x73, 0x72, 0x4F, 0x46,
0x03, 0x47, 0x6F, 0x79, 0x07, 0x41, 0x13, 0x47,
0x5E, 0x67, 0x5F, 0x09, 0x0F, 0x58, 0x63, 0x7D,
0x5F, 0x77, 0x68, 0x35, 0x62, 0x0D, 0x0D, 0x50
]
data2_blocks = [data2[i*4:(i+1)*4] for i in range(8)]
flag = bytearray()
for i in range(8):
block = processed_data1[i*4: i*4+4]
b1, b2 = block[1], block[2]
word1 = (b2 << 8) | b1
word2 = (block[3] << 8) | b2
enc_block = data2_blocks[i]
enc_word1 = enc_block[0] | (enc_block[1] << 8)
enc_word2 = enc_block[2] | (enc_block[3] << 8)
dec_word1 = enc_word1 ^ word1
dec_word2 = enc_word2 ^ word2
flag.append(dec_word1 & 0xFF)
flag.append((dec_word1 >> 8) & 0xFF)
flag.append(dec_word2 & 0xFF)
flag.append((dec_word2 >> 8) & 0xFF)
flag_str = flag.decode('latin-1')
print("Flag:", flag_str)

运行脚本得到flag:NSSCTF{W0w_y0u’re_g00d_@t_@5M!!}.

FishingKit:

查看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
int __fastcall main(int argc, const char **argv, const char **envp)
{
memset(v4, 0, 0x14ui64);
memset(v5, 0, 0x32ui64);
memset(Str1, 0, 0x32ui64);
printf("Give me the bait:");
scanf("%s", v4);
if ( check((unsigned __int8 *)v4) ) //第一个检验
{
printf("Yes!This bait is a good one.\n\n");
printf("Give me the second thing:");
scanf("%s", v5);
printf("\nFishing...\n\n");
Sleep(0x3E8u);
RC4(v5, (__int64)Str1, v4); //输入密文并加密
if ( !strcmp(Str1, &Str2) ) //比较
printf("Did the fish take the bait?\n");
else
printf("Didn't the fish take the bait?\n");
}
else
{
printf("Oops...This bait is terrible.\n");
}
system("pause");
return 0;
}

发现首先要输入一个满足第一个校验函数的字符串bait,然后才能输入flag并用bait作为密钥将flag加密并和密文str2进行比较输出结果,先查看第一个校验函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bool __fastcall check(unsigned __int8 *x)
{
return 202 * x[8] + 216 * x[5] - 4 * x[4] - 330 * x[9] - 13 * x[4] - 268 * x[6] == -14982
&& 325 * x[8] + 195 * *x + 229 * x[1] - 121 * x[6] - 409 * x[6] - (x[1] << 7) == 22606
&& 489 * x[1] + 480 * x[6] + 105 * x[2] + 367 * x[3] - 135 * x[4] - 482 * x[9] == 63236
&& 493 * x[1] - 80 * x[4] - 253 * x[8] - 121 * x[2] - 177 * *x - 243 * x[9] == -39664
&& 275 * x[4] + 271 * x[6] + 473 * x[7] - 72 * x[5] - 260 * x[4] - 367 * x[4] == 14255
&& 286 * *x + 196 * x[7] + 483 * x[2] + 442 * x[1] - 495 * x[8] - 351 * x[4] == 41171
&& 212 * x[2] + 283 * x[7] - 329 * x[8] - 429 * x[9] - 362 * x[2] - 261 * x[6] == -90284
&& 456 * x[5] + 244 * x[7] + 92 * x[4] + 348 * x[7] - 225 * x[1] - 31 * x[2] == 88447
&& 238 * x[9] + 278 * x[7] + 216 * x[6] + 237 * *x + 8 * x[2] - 17 * x[9] == 83838
&& 323 * x[9] + 121 * x[1] + 370 * x[7] - (x[4] << 6) - 196 * x[9] - 422 * *x == 26467
&& 166 * x[9] + 90 * x[1] + 499 * x[2] + 301 * x[8] - 31 * x[2] - 206 * x[2] == 88247
&& 355 * *x + 282 * x[4] + 44 * x[9] + 359 * x[8] - 167 * x[5] - 62 * x[3] == 76658
&& 488 * x[6] + 379 * x[9] + 318 * x[2] - 85 * x[1] - 357 * x[2] - 277 * x[5] == 35398
&& 40 * *x + 281 * x[4] + 217 * x[5] - 241 * x[1] - 407 * x[7] - 309 * x[7] == -35436
&& 429 * x[3] + 441 * x[3] + 115 * x[1] + 96 * x[8] + 464 * x[1] - 133 * x[7] == 157448;
}

发现是一个矩阵,编写脚本解矩阵:

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
import numpy as np
from scipy.optimize import fsolve

def equations(vars):
x = np.array(vars)
eq1 = 202 * x[8] + 216 * x[5] - 4 * x[4] - 330 * x[9] - 13 * x[4] - 268 * x[6] + 14982
eq2 = 325 * x[8] + 195 * x[0] + 229 * x[1] - 121 * x[6] - 409 * x[6] - 128 * x[1] - 22606
eq3 = 489 * x[1] + 480 * x[6] + 105 * x[2] + 367 * x[3] - 135 * x[4] - 482 * x[9] - 63236
eq4 = 493 * x[1] - 80 * x[4] - 253 * x[8] - 121 * x[2] - 177 * x[0] - 243 * x[9] + 39664
eq5 = 275 * x[4] + 271 * x[6] + 473 * x[7] - 72 * x[5] - 260 * x[4] - 367 * x[4] - 14255
eq6 = 286 * x[0] + 196 * x[7] + 483 * x[2] + 442 * x[1] - 495 * x[8] - 351 * x[4] - 41171
eq7 = 212 * x[2] + 283 * x[7] - 329 * x[8] - 429 * x[9] - 362 * x[2] - 261 * x[6] + 90284
eq8 = 456 * x[5] + 244 * x[7] + 92 * x[4] + 348 * x[7] - 225 * x[1] - 31 * x[2] - 88447
eq9 = 238 * x[9] + 278 * x[7] + 216 * x[6] + 237 * x[0] + 8 * x[2] - 17 * x[9] - 83838
eq10 = 323 * x[9] + 121 * x[1] + 370 * x[7] - 64 * x[4] - 196 * x[9] - 422 * x[0] - 26467
eq11 = 166 * x[9] + 90 * x[1] + 499 * x[2] + 301 * x[8] - 31 * x[2] - 206 * x[2] - 88247
eq12 = 355 * x[0] + 282 * x[4] + 44 * x[9] + 359 * x[8] - 167 * x[5] - 62 * x[3] - 76658
eq13 = 488 * x[6] + 379 * x[9] + 318 * x[2] - 85 * x[1] - 357 * x[2] - 277 * x[5] - 35398
eq14 = 40 * x[0] + 281 * x[4] + 217 * x[5] - 241 * x[1] - 407 * x[7] - 309 * x[7] + 35436
eq15 = 429 * x[3] + 441 * x[3] + 115 * x[1] + 96 * x[8] + 464 * x[1] - 133 * x[7] - 157448
return [eq1, eq2, eq3, eq4, eq5, eq6, eq7, eq8, eq9, eq10, eq11, eq12, eq13, eq14, eq15]

initial_guess = np.zeros(15)

solution = fsolve(equations, initial_guess)
print(solution)
for i in range(10):
print(round(solution[i]),end=" ")
print()
for i in range(10):
print(hex(round(solution[i])), end=" ")
print()

运行脚本得到bait为DeluxeBait,接着看后面的RC4,发现多异或了0x14,提取密文:[0xE9, 0x37, 0xF8, 0xE2, 0x0C, 0x0F, 0x3D, 0xB9, 0x5C, 0xA3, 0xDE, 0x2D, 0x55, 0x96, 0xDF, 0xA2, 0x35, 0xFE, 0xB3, 0xDD, 0x7F, 0x91, 0x3C],RC4解密,发现得到的是一个fakeflag,检查发现藏了很多个版本的RC4,有异或0x33、0x66和0x14的,并且还藏了一个生成24字节密文的函数,提取这个密文:[0x21, 0x56, 0x97, 0xa6, 0x1a, 0xd5, 0xc4, 0xde, 0xa4, 0x9c, 0x82, 0x4d, 0xd1, 0x45, 0xc8, 0x56, 0xa7, 0xb4, 0x96, 0x5c, 0x4d, 0x49, 0x87, 0x20],动态调试发现在比较时跳转到了一个有多重XTEA加密的函数:

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
__int64 __fastcall dup_XTEA(_BYTE *a1, unsigned __int8 *a2)
{
v34 = a1;
memset(v32, 0, 0x14ui64);
memset(v33, 0, 0x32ui64);
for ( i = 0; i < 20; ++i )
*((_BYTE *)v32 + i) = a1[i - 80];
for ( j = 0; j < 50; ++j )
*((_BYTE *)v33 + j) = a1[j - 56];
v3 = 0;
v6 = v33[0];
v9 = v33[1];
for ( k = 0; k < 24; ++k )
{
v6 += (v32[v3 & 3] + v3) ^ (v9 + ((v9 >> 5) ^ (16 * v9)));
v3 += 0x66778899;
v9 += (v32[(v3 >> 11) & 3] + v3) ^ (v6 + ((v6 >> 5) ^ (16 * v6)));
}
v33[0] = v6;
v33[1] = v9;
v4 = 0;
v7 = v33[2];
v10 = v33[3];
for ( m = 0; m < 24; ++m )
{
v7 += (v32[v4 & 3] + v4) ^ (v10 + ((v10 >> 5) ^ (16 * v10)));
v4 += 0x66778899;
v10 += (v32[(v4 >> 11) & 3] + v4) ^ (v7 + ((v7 >> 5) ^ (16 * v7)));
}
v33[2] = v7;
v33[3] = v10;
v5 = 0;
v8 = v33[4];
v11 = v33[5];
for ( n = 0; n < 24; ++n )
{
v8 += (v32[v5 & 3] + v5) ^ (v11 + ((v11 >> 5) ^ (16 * v11)));
v5 += 0x66778899;
v11 += (v32[(v5 >> 11) & 3] + v5) ^ (v8 + ((v8 >> 5) ^ (16 * v8)));
}
v33[4] = v8;
v33[5] = v11;
v12 = 1;
for ( ii = 0; ii < 24; ++ii )
{
if ( cipher[ii] != *((unsigned __int8 *)v33 + ii) )
{
v12 = 0;
break;
}
}
if ( v12 )
{
strcpy(LibFileName, "dbtc\"#?u}}");
for ( jj = 0; jj < 10; ++jj )
LibFileName[jj] ^= 0x11u;
hModule = LoadLibraryA(LibFileName);
strcpy(ProcName, "\\tbbpvtS~iP");
for ( kk = 0; kk < 11; ++kk )
ProcName[kk] ^= 0x11u;
ProcAddress = GetProcAddress(hModule, ProcName);
strcpy(v31, "H~d6gt1rpdvye1p1sxv1wxby0");
for ( mm = 0; mm < 25; ++mm )
v31[mm] ^= 0x11u;
qmemcpy(v28, "R~", 2);
v28[2] = 127;
qmemcpy(v29, "vcped}pex~", 10);
v29[10] = 127;
strcpy(v30, "000");
for ( nn = 0; nn < 17; ++nn )
v28[nn] ^= 0x11u;
((void (__fastcall *)(_QWORD, char *, char *, _QWORD))ProcAddress)(0i64, v31, v28, 0i64);
}
while ( 1 )
{
v20 = (unsigned __int8)*v34 - *a2;
if ( v20 || !*v34 )
break;
++v34;
++a2;
}
if ( v20 > 0 )
return 1i64;
if ( v20 >= 0 )
return 0i64;
return 0xFFFFFFFFi64;
}

发现在这个函数中用DeluxeBait作为密钥,输入的字符串作为密文进行了delta为0x66778899的XTEA加密并和密文进行了比较,而这里的密文正是之前所见的第二个密文,编写解密脚本进行解密:

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
def bytes_to_dwords_little_endian(byte_array):
return [int.from_bytes(byte_array[i:i+4], byteorder='little', signed=False) for i in range(0, len(byte_array), 4)]

def dwords_to_bytes_little_endian(dword_array):
byte_list = []
for dword in dword_array:
byte_list.extend(dword.to_bytes(4, byteorder='little'))
return byte_list

delta = 0x66778899
key = [0x756C6544, 0x61426578, 0x00007469, 0x00000000]
ttl = 0

encflag = [0x21, 0x56, 0x97, 0xa6, 0x1a, 0xd5,
0xc4, 0xde, 0xa4, 0x9c, 0x82, 0x4d,
0xd1, 0x45, 0xc8, 0x56, 0xa7, 0xb4,
0x96, 0x5c, 0x4d, 0x49, 0x87, 0x20]
dwordenc = bytes_to_dwords_little_endian(encflag)
result = []
for j in range(0,len(dwordenc)//2):
m1 = dwordenc[2*j]
m2 = dwordenc[2*j+1]
ttl = (0 + 24 * delta) & 0xFFFFFFFF
for j in range(24):
m2 = (m2 - ((((m1 >> 5) ^ (m1 << 4)) + m1) ^ (ttl + key[(ttl >> 11) & 3]))) & 0xFFFFFFFF
ttl = (ttl - delta) & 0xFFFFFFFF
m1 = (m1 - ((((m2 >> 5) ^ (m2 << 4)) + m2) ^ (ttl + key[ttl & 3]))) & 0xFFFFFFFF
result.append(m1)
result.append(m2)
flag = dwords_to_bytes_little_endian(result)
for i in range(len(flag)):
print(hex(flag[i]),end=", ")
print()

运行得到flag:NSSCTF{Wh@t_@_b1g_F1sh}.

LockedSecret:

查壳发现UPX012和UPX!标识符被换成了LIVV,改回去之后脱壳,查看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
29
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [esp+0h] [ebp-108h]
char Str[256]; // [esp+4h] [ebp-104h] BYREF

memset(Str, 0, sizeof(Str));
printf("Input your flag:");
scanf("%32s", (char)Str);
if ( strlen(Str) != 32 )
{
printf("Wrong length!\n");
system("pause");
exit(0);
}
XorKeyGen();
MyTEA((int)Str);
for ( i = 0; i < 32; ++i )
{
if ( cipher[i] != Str[i] )
{
printf("Wrong!\n");
system("pause");
exit(0);
}
}
printf("Right!\n");
system("pause");
return 0;
}

显然flag长度为32,查看XorKeyGen,发现用固定的rand种子生成了一个密钥,动态调试得到值[0x64, 0x96, 0x50, 0x16, 0x69, 0xff, 0xbe, 0x60],查看加密,发现是一个8轮的TEA加密,密钥为IamTheKeyYouKnow循环异或刚才的8字节的值,delta为0x5E2377FF,编写脚本解密:

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
def dwords_to_bytes_little_endian(dword_array):
byte_list = []
for dword in dword_array:
byte_list.extend(dword.to_bytes(4, byteorder='little'))
return byte_list

def bytes_to_dwords_little_endian(byte_array):
return [int.from_bytes(byte_array[i:i+4], byteorder='little', signed=False)for i in range(0, len(byte_array), 4)]

test = [0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 0x70,
0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
0x79, 0x7a, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36]
key = [0x49, 0x61, 0x6d, 0x54, 0x68, 0x65, 0x4b, 0x65,
0x79, 0x59, 0x6f, 0x75, 0x4b, 0x6e, 0x6f, 0x77]
xor = [0x64, 0x96, 0x50, 0x16, 0x69, 0xff, 0xbe, 0x60]
delta = 0x5E2377FF
for i in range(len(key)-1):
key[i] ^= xor[i % 8]
k = bytes_to_dwords_little_endian(key)
dwordenc = bytes_to_dwords_little_endian(test)
dwordresult = []
for i in range(len(dwordenc)//2):
v0 = dwordenc[2 * i]
v1 = dwordenc[2 * i + 1]
ttl = 0
for j in range(8):
ttl = (ttl + delta) & 0xFFFFFFFF
v0 = (v0 + (((v1 << 4) + k[0]) ^ (v1 + ttl) ^ ((v1 >> 5) + k[1]))) & 0xFFFFFFFF
v1 = (v1 + (((v0 << 4) + k[2]) ^ (v0 + ttl) ^ ((v0 >> 5) + k[3]))) & 0xFFFFFFFF
dwordresult.append(v0 ^ 0xF)
dwordresult.append(v1 ^ 0xF)
result = dwords_to_bytes_little_endian(dwordresult)
for i in range(len(dwordresult)):
print(hex(dwordresult[i]),end=", ")
print()
for i in range(len(result)):
print(hex(result[i]),end=", ")
print()

encflag = [0xDC, 0x45, 0x1E, 0x03, 0x89, 0xE9, 0x76, 0x27,
0x47, 0x48, 0x23, 0x01, 0x70, 0xD2, 0xCE, 0x64,
0xDA, 0x7F, 0x46, 0x33, 0xB1, 0x03, 0x49, 0xA3,
0x27, 0x00, 0xD1, 0x2C, 0x37, 0xB3, 0xBD, 0x75]
dwordencflag = bytes_to_dwords_little_endian(encflag)
for i in range(len(dwordencflag)):
dwordencflag[i] ^= 0xF
dwordrev = []
for i in range(len(dwordencflag)//2):
v0 = dwordencflag[2 * i]
v1 = dwordencflag[2 * i + 1]
ttl = (8 * delta) & 0xFFFFFFFF
for j in range(8):
v1 = (v1 - (((v0 << 4) + k[2]) ^ (v0 + ttl) ^ ((v0 >> 5) + k[3]))) & 0xFFFFFFFF
v0 = (v0 - (((v1 << 4) + k[0]) ^ (v1 + ttl) ^ ((v1 >> 5) + k[1]))) & 0xFFFFFFFF
ttl = (ttl - delta) & 0xFFFFFFFF
dwordrev.append(v0)
dwordrev.append(v1)
rev = dwords_to_bytes_little_endian(dwordrev)
bflag = bytearray()
for i in range(len(rev)):
bflag.append(rev[i])
print(bflag)
mapflag = bflag.decode()
print("OutpFlag:", mapflag)

运行脚本得到flag:NSSCTF{!!!Y0u_g3t_th3_s3cr3t!!!}.

Mio?Ryo?Soyo?:

发现是python打包,解包后反编译program.pyc,发现程序结构很简单:

1
2
3
4
5
6
7
8
9
10
from Secret import *
if __name__ == "__main__":
print("输入:", end="")
aaaaaaaaaaaaa = input()
wwwwwwwwwww = l(aaaaaaaaaaaaa)
if sssssssssssss == wwwwwwwwwww.encode():
print("哦,对的。")
else:
print("哎,并非。")
input()

所以需要关注的是l函数和s密文,找到Secret库进行反编译:

1
2
3
4
5
6
7
8
from SecretEncrypt import *
sssssssssssss = bytes([57, 118, 33, 114, 68, 56, 117, 115, 34, 52, 52, 95, 78, 40, 49, 59,
95, 85, 63, 122, 54, 33, 77, 110, 49, 54, 34, 109, 106, 122, 60,
92, 108, 91, 61, 51, 42, 62, 35, 38, 52, 67, 62, 122, 116, 48,
76, 50, 67, 51, 59, 41, 122, 45, 45, 51, 90])

def l(_: str):
return SSSooooyyooo(MMMMiiiiiio.MMMMiiooooooo(SSSooooyyooo(RRRRyyooo.RRRRRRRyyyyooooo(_.encode()), 7).encode()), 9)

反编译SecretEncrypt:

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
import math

class MMMMiiiiiio:
MMiiiiiiooo = "".join([chr(Miiooooooooo) for Miiooooooooo in range(33, 118)])

@staticmethod
def MMMMiiooooooo(MMMMMMMMMiiiooo: bytes) -> str:
MMMMiiiiioooooooooo = ""
MMMMMMMiiiiioo = (4 - len(MMMMMMMMMiiiooo) % 4) % 4
MMMMMMMMMiiiooo += b'\x00' * MMMMMMMiiiiioo
for MMMMMMiiiiiio in range(0, len(MMMMMMMMMiiiooo), 4):
MMMMiiiiiiooooo = MMMMMMMMMiiiooo[MMMMMMiiiiiio[:MMMMMMiiiiiio + 4]]
MMMMMMiiioooooo = int.from_bytes(MMMMiiiiiiooooo, "big")
MMMMMMMiiooooooooo = ""
for _ in range(5):
MMMMMMMiiooooooooo = MMMMiiiiiio.MMiiiiiiooo[MMMMMMiiioooooo % 85] + MMMMMMMiiooooooooo
MMMMMMiiioooooo //= 85
else:
MMMMiiiiioooooooooo += MMMMMMMiiooooooooo

else:
if MMMMMMMiiiiioo:
MMMMiiiiioooooooooo = MMMMiiiiioooooooooo[None[:-MMMMMMMiiiiioo]]
return MMMMiiiiioooooooooo


class RRRRyyooo:
RRRRyooooooo = "".join([chr(RRRRRRRyyyyyoooo) for RRRRRRRyyyyyoooo in range(48, 93)])

@staticmethod
def RRRRRRRyyyyooooo(RRRRRRyyyoooooo: bytes) -> str:
RRRRyyyyyooo = []
RRyyyyyyyyyoooooo = 0
while RRyyyyyyyyyoooooo < len(RRRRRRyyyoooooo):
if RRyyyyyyyyyoooooo + 1 < len(RRRRRRyyyoooooo):
RRRRRRRRRyyo = RRRRRRyyyoooooo[RRyyyyyyyyyoooooo] << 8 | RRRRRRyyyoooooo[RRyyyyyyyyyoooooo + 1]
RRRRyyyyyooo.append(RRRRyyooo.RRRRyooooooo[RRRRRRRRRyyo % 45])
RRRRRRRRRyyo //= 45
RRRRyyyyyooo.append(RRRRyyooo.RRRRyooooooo[RRRRRRRRRyyo % 45])
RRRRRRRRRyyo //= 45
RRRRyyyyyooo.append(RRRRyyooo.RRRRyooooooo[RRRRRRRRRyyo])
RRyyyyyyyyyoooooo += 2
else:
RRRRRRRRRyyo = RRRRRRyyyoooooo[RRyyyyyyyyyoooooo]
RRRRyyyyyooo.append(RRRRyyooo.RRRRyooooooo[RRRRRRRRRyyo % 45])
RRRRRRRRRyyo //= 45
RRRRyyyyyooo.append(RRRRyyooo.RRRRyooooooo[RRRRRRRRRyyo])
RRyyyyyyyyyoooooo += 1

return "".join(RRRRyyyyyooo)


def SSSooooyyooo(SSSSooyoooooo, SSSSSoyyooooo):
SSoooooyyyyyyoo = []
for SSSSSSSSSoyooo in SSSSooyoooooo:
if "a" <= SSSSSSSSSoyooo <= "z":
SSSSoooyooooooo = (ord(SSSSSSSSSoyooo) - ord("a") + SSSSSoyyooooo) % 26
SSoooooyyyyyyoo.append(chr(ord("a") + SSSSoooyooooooo))
elif "0" <= SSSSSSSSSoyooo <= "9":
SSSSoooyooooooo = (ord(SSSSSSSSSoyooo) - ord("0") - SSSSSoyyooooo) % 10
SSoooooyyyyyyoo.append(chr(ord("0") + SSSSoooyooooooo))
else:
SSoooooyyyyyyoo.append(SSSSSSSSSoyooo)
else:
return "".join(SSoooooyyyyyyoo)

去除混淆:

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
import math

class M:
M1 = "".join([chr(idxM) for idxM in range(33, 118)])

@staticmethod
def funM(inputM: bytes) -> str:
outputM = ""
infM = (4 - len(inputM) % 4) % 4
inputM += b'\x00' * infM
for iM in range(0, len(inputM), 4):
tempM = inputM[iM[:iM + 4]]
intM = int.from_bytes(tempM, "big")
strM = ""
for _ in range(5):
strM = M.M1[intM % 85] + strM
intM //= 85
else:
outputM += strM

else:
if infM:
outputM = outputM[None[:-infM]]
return outputM

class R:
R1 = "".join([chr(idxR) for idxR in range(48, 93)])

@staticmethod
def funR(inputR: bytes) -> str:
listR = []
iR = 0
while iR < len(inputR):
if iR + 1 < len(inputR):
tempR = inputR[iR] << 8 | inputR[iR + 1]
listR.append(R.R1[tempR % 45])
tempR //= 45
listR.append(R.R1[tempR % 45])
tempR //= 45
listR.append(R.R1[tempR])
iR += 2
else:
tempR = inputR[iR]
listR.append(R.R1[tempR % 45])
tempR //= 45
listR.append(R.R1[tempR])
iR += 1

return "".join(listR)

def S(S1, S2):
listS = []
for iS in S1:
if "a" <= iS <= "z":
tempS = (ord(iS) - ord("a") + S2) % 26
listS.append(chr(ord("a") + tempS))
elif "0" <= iS <= "9":
tempS = (ord(iS) - ord("0") - S2) % 10
listS.append(chr(ord("0") + tempS))
else:
listS.append(iS)
else:
return "".join(listS)

cipher = bytes([57, 118, 33, 114, 68, 56, 117, 115, 34, 52, 52, 95, 78, 40, 49, 59,
95, 85, 63, 122, 54, 33, 77, 110, 49, 54, 34, 109, 106, 122, 60,
92, 108, 91, 61, 51, 42, 62, 35, 38, 52, 67, 62, 122, 116, 48,
76, 50, 67, 51, 59, 41, 122, 45, 45, 51, 90])

def l(_: str):
return S(M.funM(S(R.funR(_.encode()), 7).encode()), 9)

if __name__ == "__main__":
print("输入:", end="")
message = input()
enc = l(message)
if cipher == enc.encode():
print("哦,对的。")
else:
print("哎,并非。")
input()

发现S函数是一个凯撒加密,R函数是一个base45编码,M函数是一个base85编码,则加密方式为:

base45→偏移7→base85→偏移9

编写S函数的逆向脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def S_inverse(S1: str, S2: int) -> str:
listS = []
for iS in S1:
if "a" <= iS <= "z":
tempS = (ord(iS) - ord("a") - S2) % 26
listS.append(chr(ord("a") + tempS))
elif "0" <= iS <= "9":
tempS = (ord(iS) - ord("0") + S2) % 10
listS.append(chr(ord("0") + tempS))
else:
listS.append(iS)
return "".join(listS)

enc = bytearray([0x39, 0x76, 0x21, 0x72, 0x44, 0x38, 0x75, 0x73, 0x22, 0x34, 0x34, 0x5f, 0x4e, 0x28, 0x31, 0x3b, 0x5f, 0x55, 0x3f,
0x7a, 0x36, 0x21, 0x4d, 0x6e, 0x31, 0x36, 0x22, 0x6d, 0x6a, 0x7a, 0x3c, 0x5c, 0x6c, 0x5b, 0x3d, 0x33, 0x2a, 0x3e,
0x23, 0x26, 0x34, 0x43, 0x3e, 0x7a, 0x74, 0x30, 0x4c, 0x32, 0x43, 0x33, 0x3b, 0x29, 0x7a, 0x2d, 0x2d, 0x33, 0x5a])

encrypted_message = enc.decode()
shift_value = 9
decrypted_message = S_inverse(encrypted_message, shift_value)
print(f"{decrypted_message}")

输出8m!iD7lj"33_N(0;_U?q5!Me05"daq<\c[=2*>#&3C>qk9L1C2;)q--2Z,base85解码得到:JX2NG:CM:KJ?S0=:>?NC>K5<V29Z5<Y:9C=;LA1RQ9G:7,再次偏移7得到:JX9NG:CM:KJ?S7=:>?NC>K2<V96Z2<Y:6C=;LA8RQ6G:4,base45解码:

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
class R:
R1 = "".join(chr(i) for i in range(48, 93))

def decode_R(s):
result = bytearray()
i = 0
while i < len(s):
if i + 2 < len(s):
c0, c1, c2 = [R.R1.index(s[i+j]) for j in range(3)]
tempR = c0 + c1 * 45 + c2 * 45**2
result.extend([(tempR >> 8) & 0xFF, tempR & 0xFF])
i += 3
elif i + 1 < len(s):
c0, c1 = [R.R1.index(s[i+j]) for j in range(2)]
tempR = c0 + c1 * 45
result.append(tempR)
i += 2
else:
raise ValueError("Invalid length")
return bytes(result)

step4 = "JX9NG:CM:KJ?S7=:>?NC>K2<V96Z2<Y:6C=;LA8RQ6G:4"
step5 = decode_R(step4)
flag = step5.decode()
print(flag)

得到flag:NSSCTF{Th3y’r3_a11_p1aY_Ba5e!}.

TimeSpaceRescue:

查看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 __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp+0h] [ebp-11Ch]
int i; // [esp+4h] [ebp-118h]
char Str[256]; // [esp+8h] [ebp-114h] BYREF
int Src[4]; // [esp+108h] [ebp-14h] BYREF

printf("Before input the key, make sure you're on the same spacetime as Liv.\n");
printf("Try your key:");
memset(Str, 0, sizeof(Str));
scanf("%s", Str);
memset(Src, 0, sizeof(Src));
sub_401DD0((int)Src);
v4 = strlen(Str);
sub_401210(Src, 0x10u, (int)Str, v4);
for ( i = 0; i < v4; ++i )
{
if ( Str[i] != cipher[i] )
{
printf("Spacetime turbulence is detected, and rescue fails!\n");
system("pause");
exit(0);
}
}
printf("Congratulations on rescuing Liv and successfully protecting the world!\n");
system("pause");
return 0;
}

查看src的初始化函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int __cdecl sub_151DD0(int a1)
{
__time64_t v1; // rax
HANDLE CurrentProcess; // eax
int v4; // [esp+0h] [ebp-28h]
int i; // [esp+8h] [ebp-20h]
__time64_t Time; // [esp+Ch] [ebp-1Ch] BYREF
BOOL pbDebuggerPresent; // [esp+14h] [ebp-14h] BYREF
int Src[3]; // [esp+18h] [ebp-10h] BYREF

LODWORD(v1) = sub_402180(0);
Time = v1;
v4 = getlocaltime(&Time);
memset(Src, 0, sizeof(Src));
pbDebuggerPresent = 0;
CheckRemoteDebuggerPresent(CurrentProcess, &pbDebuggerPresent);
if ( !pbDebuggerPresent )
memcpy(Src, (const void *)(v4 + 12), sizeof(Src));
sub_1521A0(Src, 0xCu, a1);
for ( i = 0; i < 16; ++i )
*(_BYTE *)(i + a1) ^= 0x14u;
return 39;
}

发现有一个反调试,结尾还有一个诡异的return 39,查看汇编后发现藏了典型的call $+5花指令,抹掉后得到正确的函数:

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
unsigned int __cdecl sub_401DD0(int a1)
{
__time64_t v1; // rax
HANDLE CurrentProcess; // eax
unsigned int result; // eax
struct tm *v4; // [esp+0h] [ebp-28h]
int j; // [esp+4h] [ebp-24h]
int i; // [esp+8h] [ebp-20h]
__time64_t Time; // [esp+Ch] [ebp-1Ch] BYREF
BOOL pbDebuggerPresent; // [esp+14h] [ebp-14h] BYREF
int Src[3]; // [esp+18h] [ebp-10h] BYREF

Time = time64_(0);
v3 = localtime64_(&Time);
memset(Src, 0, sizeof(Src));
pbDebuggerPresent = 0;
CurrentProcess = GetCurrentProcess();
CheckRemoteDebuggerPresent(CurrentProcess, &pbDebuggerPresent);
if ( !pbDebuggerPresent )
memcpy(Src, &v4->tm_mday, sizeof(Src)); //时间函数,这里只有12字节(日月年),其中年份为2024,其他需要爆破
result = sub_4021A0(Src, 0xCu, a1);
for ( i = 0; i < 16; ++i )
{
result = (*(unsigned __int8 *)(i + a1) ^ 0x114) & 0x800000FF;
*(_BYTE *)(i + a1) ^= 0x14u;
}
for ( j = 0; j < 16; ++j )
{
result = j + a1;
*(_BYTE *)(j + a1) ^= 0x11u;
}
return result;
}

查看其中的sub函数,发现是16字节md5哈希,则这里的用意是选取2024年某月某日的系统时间表达中的日、月、年三个dword,对其求16字节md5哈希,并异或0x14和0x11之后返回(生成密钥),接着看加密函数,发现加密函数也有花指令,去掉之后发现是一个魔改的AES-ECB并在结尾多判定是否要额外异或一个0x11,这个值由之前对于Ntdll的是否处于调试模式的判断生成,非调试模式下为0,不会额外异或0x11,至于魔改,则是在加密前对密钥和明文进行了预处理,将密钥相邻字节交换并异或0x5(将刚才的异或0x14和0x11抵消),明文则逆序交换字节并异或0xF,加密完成后又对密文进行了相邻字节交换和异或0x5,编写同构加密脚本验证发现结果相符,编写脚本解密:

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
import hashlib
from Crypto.Cipher import AES

def reverse_operations(cipher_text):
for i in range(len(cipher_text)//2):
temp = cipher_text[2*i] ^ 0x5
cipher_text[2*i] = cipher_text[2*i+1] ^ 0x5
cipher_text[2*i+1] = temp
return cipher_text

def decrypt_and_reverse_aes_ecb(cipher_text, key):
cipher = AES.new(key, AES.MODE_ECB)
plain_text = bytearray(cipher.decrypt(cipher_text))
for i in range(8):
temp1 = plain_text[i] ^ 0xF
temp2 = plain_text[16+i] ^ 0xF
plain_text[i] = plain_text[15-i] ^ 0xF
plain_text[16+i] = plain_text[31-i] ^ 0xF
plain_text[15-i] = temp1
plain_text[31-i] = temp2

return plain_text

def find_key_and_decrypt(cipher_text, tm_year):
for day in range(1, 32): # 日范围为1到31
for tm_mon in range(1, 13): # 月范围为1到12
src = (day.to_bytes(4, 'little') + tm_mon.to_bytes(4, 'little') + tm_year.to_bytes(4, 'little'))
key = bytearray(hashlib.md5(src).digest())
for i in range(len(key)//2):
temp = key[2*i+1]
key[2*i+1] = key[2*i]
key[2*i] = temp
try:
reversed_cipher_text = reverse_operations(bytearray(cipher_text))
decrypted = decrypt_and_reverse_aes_ecb(reversed_cipher_text, key)
if decrypted.decode()[:6] == "NSSCTF":
print(f"Match time found: Day={day}, Month={tm_mon}")
print("Decrypted text:", decrypted.decode())
exit(0)
except Exception as e:
continue

cipher_text = bytearray(b'\xcd\x16\xdb\xb5\xd1\x02\xa4\x82\x8e\x59\x73\x9e\x96\x26\x56\xf2\x16\x8e\x46\xf2\x55\x7b\x92\x31\x30\xdc\xaa\x8a\xf3\x1c\xa0\xaa')
tm_year = 124

find_key_and_decrypt(cipher_text, tm_year)

运行输出flag:NSSCTF{W0w_Y0u’re_@n_AE5_M@5t3r}.

Room 0:

题目提示了除以零的异常和SMC,发现程序字节码最上端有一个.enc字段,猜测是SMC,再往下还有一个无法反编译的区域,发现有花指令,抹除后发现是SMC的解密函数,先查看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
29
30
31
32
33
34
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [esp+2Ch] [ebp-220h]
char v5[256]; // [esp+30h] [ebp-21Ch] BYREF
char Str[256]; // [esp+130h] [ebp-11Ch] BYREF
CPPEH_RECORD ms_exc; // [esp+234h] [ebp-18h]

ms_exc.registration.TryLevel = 0;
printf("Welcome to the hostel!\n");
printf("Please input your informatioin to enter your room.\n");
printf("Input your flag:");
memset(Str, 0, sizeof(Str));
scanf("%s", (char)Str);
printf("Input your Key:");
memset(v5, 0, sizeof(v5));
scanf("%s", (char)v5);
if ( KeyGen (v5) != 0x11451419 )
{
printf("Not the key.\n");
exit(0);
}
RC4encrypt (Str, 0x11451419);
for ( i = 0; i < 32; ++i )
{
if ( Str[i] != (unsigned __int8)cipher[i] )
{
printf("Your flag is incorrect.\n");
exit(0);
}
}
printf("Welcome into your room\n");
system("pause");
return 0;
}

发现要输入key和flag,查看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
int __cdecl KeyGen(char *Str)
{
int v2; // [esp+0h] [ebp-1Ch]
int i; // [esp+4h] [ebp-18h]
int v4; // [esp+8h] [ebp-14h]
int v5; // [esp+Ch] [ebp-10h]
int v6; // [esp+10h] [ebp-Ch]
int v7; // [esp+10h] [ebp-Ch]
unsigned int v8; // [esp+14h] [ebp-8h]
int v9; // [esp+18h] [ebp-4h]

v2 = toInt(Str); //这里是把输入的内容转换成一个32位int
if ( !v2 )
return 0;
v6 = 0;
v9 = v2; //整个int(比如0x12345678)
v8 = HIBYTE(v2); //最高字节(0x12)
v5 = BYTE2(v2); //第二字节(0x34)
v4 = BYTE1(v2); //第三字节(0x56)
for ( i = 0; i < 32; ++i )
{
v7 = v6 * (v8 + 0x5464A178) * (v9 - 0x57780FDF) * ((v8 - v9) ^ (v8 >> 4));
v5 = (v9 + v5) ^ (8 * v4);
v4 = (v9 + v8) ^ (8 * v5);
v8 = (v9 + v4) ^ (8 * v5);
v9 -= v4 + v5 + v8;
v6 = v7 + (v8 + 0x57780FDF) * (((v8 - v9) ^ (unsigned __int64)(v8 >> 4)) / (unsigned int)(v9 - 0x5464A178)); //这里有可能触发除零异常
}
return v9 ^ v6;
}

发现这貌似是一个单向函数,而且后文加密的时候并不需要用到key的值,所以直接在判定处进行patch,抹掉判定继续执行,发现下方的加密函数是对密文进行了密钥为0x11451419(32位int按小端序转4字节为[0x19, 0x14, 0x45, 0x11]),但对密文直接解密发现是fakeflag,说明刚才的SMC和异常处理应该也派上了用场,仔细查看main,发现有一个except分支,但是没有见到能触发的地方,调试过程中偶然发现特殊情况下跳转到了这个分支,仔细查看发现在KeyGen里面有一个div ecx,猜测若密钥正常,这一步应该是除以0 (对应上文v9 - 0x5464A178 = 0),动态调试到这一步直接修改ecx的值,触发除零异常,进入except分支,首先又调用了toint,然后进入了SMC函数,将SMC后的enc区段进行修改,观察发现enc区段末尾有大量重复的0xD3 0x75 0x5F 0xF0(真正的key是这四个字节的其中一种小端序顺序组合:755ff0d3),推测这里要循环异或这四个字节(也对应之前的32位int),编写idapython脚本让地址00401017开始的1513字节循环异或:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import ida_bytes

def cyclic_xor(start_addr, data_len, xor_key):
data = ida_bytes.get_bytes(start_addr, data_len)
if data is None:
print(f"错误:无法读取地址 0x{start_addr:X} 处的 {data_len} 字节")
return False
data_list = bytearray(data)
key_length = len(xor_key)
for i in range(data_len):
key_index = i % key_length
data_list[i] ^= xor_key[key_index]
success = ida_bytes.patch_bytes(start_addr, bytes(data_list))
if not success:
print("错误:写入数据失败")
return False
idc.analyze_area(start_addr, start_addr + data_len)
print("异或操作完成")
return True
start_addr = 0x00401017
data_len = 1513
xor_key = [0xD3, 0x75, 0x5F, 0xF0]
cyclic_xor(start_addr, data_len, xor_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
void __cdecl __noreturn sub_401000(char *Str, unsigned int a2)
{
char v2; // [esp+0h] [ebp-240h]
size_t v3; // [esp+Ch] [ebp-234h]
char v4; // [esp+10h] [ebp-230h]
int v5; // [esp+14h] [ebp-22Ch]
int ii; // [esp+18h] [ebp-228h]
size_t n; // [esp+1Ch] [ebp-224h]
int k; // [esp+20h] [ebp-220h]
int j; // [esp+24h] [ebp-21Ch]
int m; // [esp+28h] [ebp-218h]
int i; // [esp+2Ch] [ebp-214h]
char v12; // [esp+30h] [ebp-210h]
char v13; // [esp+31h] [ebp-20Fh]
unsigned __int8 v14; // [esp+32h] [ebp-20Eh]
unsigned __int8 v15; // [esp+33h] [ebp-20Dh]
char v16[256]; // [esp+34h] [ebp-20Ch] BYREF
char v17[256]; // [esp+134h] [ebp-10Ch] BYREF
int v18[2]; // [esp+234h] [ebp-Ch]

v18[0] = 0;
v18[1] = 0;
memset(v17, 0, sizeof(v17));
for ( i = 0; i < 8; ++i )
{
if ( i >= 4 )
v4 = 7 - i;
else
v4 = i;
*((_BYTE *)v18 + i) = a2 >> (8 * v4);
}
for ( j = 0; j < 256; ++j )
v17[j] = j;
memset(v16, 0, sizeof(v16));
for ( k = 0; k < 256; ++k )
v16[k] = *((_BYTE *)v18 + k % 8);
v5 = 0;
for ( m = 0; m < 256; ++m )
{
v5 = ((unsigned __int8)v16[m] + v5 + (unsigned __int8)v17[m]) % 256;
v13 = v17[m];
v17[m] = v17[v5];
v17[v5] = v13;
}
v15 = 0;
v14 = 0;
v3 = strlen(Str);
for ( n = 0; n < v3; ++n )
{
v14 += v17[++v15];
v12 = v17[v15];
v17[v15] = v17[v14];
v17[v14] = v12;
Str[n] ^= *((_BYTE *)v18 + (v15 & 7)) ^ v17[((unsigned __int8)v17[v14] + (unsigned __int8)v17[v15]) % 256];
}
for ( ii = 0; ii < 32; ++ii )
{
if ( byte_405020[ii] != Str[ii] )
{
sub_4028C0("You shouldn't come here, get out of that room!\n", v2);
exit(0);
}
}
sub_4028C0("Welcome to Room-0.\n", v2);
system("pause");
exit(0);
}

发现是一个魔改RC4,在PRGA阶段不仅使用了S盒生成的密钥流,还额外使用了 v18 中的一个字节(通过 (v15 & 7) 来索引),密钥是刚才的4字节的正反(8字节),动态调试得到密钥[0xD4, 0x35, 0x6D, 0xF8, 0xF8, 0x6D, 0x35, 0xD4],密文也变了,编写脚本解密:

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
def rc4_modified_encrypt(plaintext, key):
S = list(range(256))
K = key * (256 // len(key))
j = 0
for i in range(256):
j = (j + S[i] + K[i]) % 256
S[i], S[j] = S[j], S[i]
i = 0
j = 0
output = bytearray()

for byte in plaintext:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
t = (S[i] + S[j]) % 256
prga_output = S[t]
modified_byte = byte ^ prga_output ^ key[i % len(key)]
output.append(modified_byte)

return bytes(output)
key = [0xD4, 0x35, 0x6D, 0xF8, 0xF8, 0x6D, 0x35, 0xD4]
plaintext = b"\x22\xc4\xa0\x5a\xde\xed\x62\x5e\x25\xe2\x6d\xa6\x05\xa7\x20\x8d\x7d\x99\x52\x3e\x8c\xa7\x7f\xfa\x09\xd8\x62\xdb\x00\x80\xc2\xa9"

ciphertext = rc4_modified_encrypt(plaintext, key)
print(f"Ciphertext: {ciphertext}")

运行得到flag:NSSCTF{Int3r3st1ng_5MC_Pr0gr@m?}.

Canon:

观察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
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
int __fastcall main(int argc, const char **argv, const char **envp)
{
int j; // [rsp+20h] [rbp-1F8h]
int i; // [rsp+24h] [rbp-1F4h]
int v6; // [rsp+28h] [rbp-1F0h]
int v7[4]; // [rsp+30h] [rbp-1E8h]
__int64 v8; // [rsp+40h] [rbp-1D8h]
__int64 v9; // [rsp+48h] [rbp-1D0h]
__int64 v10; // [rsp+50h] [rbp-1C8h]
int v11[8]; // [rsp+58h] [rbp-1C0h]
int v12[4]; // [rsp+78h] [rbp-1A0h]
char Str[12]; // [rsp+88h] [rbp-190h] BYREF
char Source[12]; // [rsp+94h] [rbp-184h] BYREF
char v15[16]; // [rsp+A0h] [rbp-178h] BYREF
char Destination[112]; // [rsp+B0h] [rbp-168h] BYREF
char Str1[112]; // [rsp+120h] [rbp-F8h] BYREF
char v18[112]; // [rsp+190h] [rbp-88h] BYREF

printf("Enter the flag: ");
scanf("%36s", Str);
if ( strlen(Str) == 36 )
{
strncpy(Part1, Str, 0xCui64);
v8 = 12i64;
Part1[12] = 0;
strncpy(Part2, Source, 0xCui64);
v9 = 12i64;
Part2[12] = 0;
strncpy(Part3, v15, 0xCui64);
v10 = 12i64;
v18[12] = 0;
v11[0] = 1;
v11[1] = 5;
v11[2] = 6;
v11[3] = 3;
v11[4] = 4;
v11[5] = 1;
v11[6] = 4;
v11[7] = 5;
v12[0] = 0;
v12[1] = 1;
v12[2] = 2;
v7[0] = 0;
v7[1] = 0;
v7[2] = 0;
for ( i = 0; i < 8; ++i )
{
for ( j = 0; j < 3; ++j )
{
if ( i >= v12[j] )
{
v6 = v7[j];
if ( v6 < 8 )
{
if ( j )
{
if ( j == 1 )
{
sub_1400015D0(Str1, v18, v11[v6]);
}
else if ( j == 2 )
{
sub_1400015D0(v18, Destination, v11[v6]);
}
}
else
{
sub_1400015D0(Destination, Str1, v11[v6]);
}
++v7[j];
}
}
}
}
if ( !strcmp(Destination, "WgvDmssEvcY326bHo3nNro3vXvvfmgrz")
&& !strcmp(Str1, "gX+Ri9PG=bt5=00B6hscPQOL")
&& !strcmp(v18, "T6bHgUPL2gXUd=xT=FNHtPzV") )
{
printf("Congratulations! You have found the flag!\n");
}
else
{
printf("Invalid flag!\n");
}
return 0;
}
else
{
printf("Invalid flag!\n");
return 0;
}
}

发现这里把输入拆成了三部分,且有一个嵌套循环,把循环改成print编写C++脚本跑一遍得到加密顺序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Part1, Part2, 1
Part1, Part2, 5
Part2, Part3, 1
Part1, Part2, 6
Part2, Part3, 5
Part3, Part1, 1
Part1, Part2, 3
Part2, Part3, 6
Part3, Part1, 5
Part1, Part2, 4
Part2, Part3, 3
Part3, Part1, 6
Part1, Part2, 1
Part2, Part3, 4
Part3, Part1, 3
Part1, Part2, 4
Part2, Part3, 1
Part3, Part1, 4
Part1, Part2, 5
Part2, Part3, 4
Part3, Part1, 1

查看这个加密函数:

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
void __fastcall sub_1400015D0(char *a1, const char *a2, int a3)
{
v9 = strlen(a1);
v21 = strlen(a2);
switch ( a3 )
{
case 1: //字母数字变换
for ( i = 0; i < v9; ++i )
{
v22 = a2[i % v21];
if ( a1[i] < 65 || a1[i] > 90 )
{
if ( a1[i] < 97 || a1[i] > 122 )
{
if ( a1[i] >= 48 && a1[i] <= 57 )
a1[i] = (a1[i] + v22 - 48) % 10 + 48;
}
else
{
a1[i] = (a1[i] + v22 - 97) % 26 + 97;
}
}
else
{
a1[i] = (a1[i] + v22 - 65) % 26 + 65;
}
}
break;
case 2:
v32[0] = 1;
v32[1] = 3;
v32[2] = 5;
v32[3] = 7;
v32[4] = 9;
v32[5] = 11;
v32[6] = 15;
v32[7] = 17;
v32[8] = 19;
v32[9] = 21;
v32[10] = 23;
v32[11] = 25;
for ( j = 0; j < v9; ++j )
{
if ( a1[j] < 65 || a1[j] > 90 )
{
if ( a1[j] >= 97 && a1[j] <= 122 )
a1[j] = (a2[j % v21] + v32[j % 12] * (a1[j] - 97)) % 26 + 97;
}
else
{
a1[j] = (a2[j % v21] + v32[j % 12] * (a1[j] - 65)) % 26 + 65;
}
}
break;
case 3: //换序
v10 = *a2 % 10 + 2;
v33 = v10;
Block = malloc(saturated_mul(v10, 8ui64));
for ( k = 0; k < v10; ++k )
{
Block[k] = malloc(v9 + 1);
memset((void *)Block[k], 0, v9 + 1);
}
for ( m = 0; v10 * m < v9; ++m )
{
for ( n = 0; n < v10 && n + v10 * m < v9; ++n )
*(_BYTE *)(Block[n] + m) = a1[n + v10 * m];
}
v18 = 0;
for ( ii = 0; ii < v10; ++ii )
{
for ( jj = 0; jj < m; ++jj )
{
if ( *(_BYTE *)(Block[ii] + jj) && v18 < v9 )
a1[v18++] = *(_BYTE *)(Block[ii] + jj);
}
}
a1[v18] = 0;
for ( kk = 0; kk < v10; ++kk )
free((void *)Block[kk]);
free(Block);
break;
case 4: //也是换序
v25 = *a2 % 10 + 2;
for ( mm = 0; mm < v25; ++mm )
{
v11 = a1[v9 - 1];
for ( nn = v9 - 1; nn > 0; --nn )
a1[nn] = a1[nn - 1];
*a1 = v11;
}
break;
case 5: //异或和换了表的base64(stuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr)
v34 = v9;
v26 = malloc(saturated_mul(v9, 4ui64));
for ( i1 = 0; i1 < v9; ++i1 )
v26[i1] = (a2[i1 % v21] + 57) ^ a1[i1];
Source = (char *)sub_140001470(v26, (unsigned int)v9);
strcpy(a1, Source);
free(v26);
free(Source);
break;
case 6: //魔改RC4(额外加57)和换表base64
v3 = saturated_mul(strlen(a1), 4ui64);
v27 = malloc(v3);
RC4((__int64)a1, a2, (__int64)v27);
v4 = strlen(a1);
v30 = (char *)sub_140001470(v27, v4);
strcpy(a1, v30);
free(v27);
free(v30);
break;
case 7:
v5 = saturated_mul(strlen(a1), 4ui64);
v28 = malloc(v5);
RC4_xor39(a1, a2, v28);
v6 = strlen(a1);
v31 = (char *)sub_140001470(v28, v6);
strcpy(a1, v31);
free(v28);
free(v31);
break;
}
}

发现里面一共有7种加密逻辑,但是不是每种都用上了,编写同构加密脚本,运行发现结果相同,进行解密:

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
import base64

# case1
def case1_enc(a1: str, a2: str) -> str:
encrypted = list(a1)
len_a1 = len(a1)
len_a2 = len(a2)
for i in range(len_a1):
key_char = ord(a2[i % len_a2])
current_char = ord(encrypted[i])
if 65 <= current_char <= 90:
encrypted[i] = chr( (current_char + key_char - 65) % 26 + 65 )
elif 97 <= current_char <= 122:
encrypted[i] = chr( (current_char + key_char - 97) % 26 + 97 )
elif 48 <= current_char <= 57:
encrypted[i] = chr( (current_char + key_char - 48) % 10 + 48 )
return ''.join(encrypted)

def case1_dec(encrypted_str: str, a2: str) -> str:
decrypted = list(encrypted_str)
len_str = len(encrypted_str)
len_a2 = len(a2)
for i in range(len_str):
key_char = ord(a2[i % len_a2])
current_char = ord(decrypted[i])
if 65 <= current_char <= 90:
decrypted[i] = chr( (current_char - key_char - 65) % 26 + 65 )
elif 97 <= current_char <= 122:
decrypted[i] = chr( (current_char - key_char - 97) % 26 + 97 )
elif 48 <= current_char <= 57:
decrypted[i] = chr( (current_char - key_char - 48) % 10 + 48 )
return ''.join(decrypted)

# case3
def case3_enc(plaintext: str, key: str) -> str:
cols = (ord(key[0]) % 10) + 2
length = len(plaintext)
columns = [[] for _ in range(cols)]
for m in range((length + cols - 1) // cols):
for n in range(cols):
idx = n + cols * m
if idx < length:
columns[n].append(plaintext[idx])
return ''.join(''.join(col) for col in columns)

def case3_dec(ciphertext: str, key: str) -> str:
cols = (ord(key[0]) % 10) + 2
length = len(ciphertext)
rows_per_col = [(length - n + cols - 1) // cols for n in range(cols)]
columns = []
start = 0
for n in range(cols):
end = start + rows_per_col[n]
columns.append(list(ciphertext[start:end]))
start = end
original = []
for i in range(length):
col_idx = i % cols
row_idx = i // cols
original.append(columns[col_idx][row_idx])
return ''.join(original)

# case4
def case4_enc(input_str, a2):
v25 = (ord(a2[0]) % 10) + 2
v9 = len(input_str)
input_list = list(input_str)
for _ in range(v25):
last_char = input_list[v9 - 1]
for i in range(v9 - 1, 0, -1):
input_list[i] = input_list[i - 1]
input_list[0] = last_char
return ''.join(input_list)

def case4_dec(input_str, a2):
v25 = (ord(a2[0]) % 10) + 2
v9 = len(input_str)
input_list = list(input_str)
for _ in range(v25):
last_char = input_list[0]
for i in range(v9 - 1):
input_list[i] = input_list[i + 1]
input_list[v9 - 1] = last_char
return ''.join(input_list)

# case5
CUSTOM_BASE64_TABLE = "stuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr"
STANDARD_BASE64_TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
TRANSLATION_ENCODE = str.maketrans(STANDARD_BASE64_TABLE, CUSTOM_BASE64_TABLE)
TRANSLATION_DECODE = str.maketrans(CUSTOM_BASE64_TABLE, STANDARD_BASE64_TABLE)

def custom_base64_encode(data):
encoded = base64.b64encode(data).decode('utf-8')
return encoded.translate(TRANSLATION_ENCODE)

def custom_base64_decode(encoded_str):
standard_encoded = encoded_str.translate(TRANSLATION_DECODE)
return base64.b64decode(standard_encoded)

def case5_enc(input_str, key_str):
v9 = len(input_str)
v21 = len(key_str)
xor_result = bytearray()
for i in range(v9):
xor_char = ((ord(key_str[i % v21]) + 57) ^ ord(input_str[i]))
xor_result.append(xor_char)
encoded_result = custom_base64_encode(bytes(xor_result))
return encoded_result

def case5_dec(encoded_str, key_str):
decoded_bytes = custom_base64_decode(encoded_str)
v9 = len(decoded_bytes)
v21 = len(key_str)
original_str = ''
for i in range(v9):
original_char = chr(((ord(key_str[i % v21]) + 57) ^ decoded_bytes[i]))
original_str += original_char
return original_str

# case6
def rc4_modified(data, key, output_buffer, decrypt=False):
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + ord(key[i % len(key)])) % 256
S[i], S[j] = S[j], S[i]
i = j = 0
for idx, char in enumerate(data):
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
k = S[(S[i] + S[j]) % 256]
if decrypt:
output_buffer[idx] = ((char - 57) % 256) ^ k
else:
output_buffer[idx] = ((k ^ char) + 57) % 256

def case6_enc(input_str, key_str):
input_bytes = input_str.encode()
output_buffer = bytearray(len(input_bytes))
rc4_modified(input_bytes, key_str, output_buffer)
encoded_result = custom_base64_encode(output_buffer)
return encoded_result

def case6_dec(encoded_str, key_str):
decoded_bytes = custom_base64_decode(encoded_str)
output_buffer = bytearray(len(decoded_bytes))
rc4_modified(decoded_bytes, key_str, output_buffer, decrypt=True)
return output_buffer.decode()

# 测试加密
message = "abcdefghijklmnopqrstuvwxyz1234567890"
part1 = message[:12]
part2 = message[12:24]
part3 = message[24:]
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part1 = case1_enc(part1, part2)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part1 = case5_enc(part1, part2)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part2 = case1_enc(part2, part3)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part1 = case6_enc(part1, part2)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part2 = case5_enc(part2, part3)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part3 = case1_enc(part3, part1)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part1 = case3_enc(part1, part2)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part2 = case6_enc(part2, part3)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part3 = case5_enc(part3, part1)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part1 = case4_enc(part1, part2)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part2 = case3_enc(part2, part3)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part3 = case6_enc(part3, part1)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part1 = case1_enc(part1, part2)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part2 = case4_enc(part2, part3)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part3 = case3_enc(part3, part1)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part1 = case4_enc(part1, part2)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part2 = case1_enc(part2, part3)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part3 = case4_enc(part3, part1)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part1 = case5_enc(part1, part2)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part2 = case4_enc(part2, part3)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")
part3 = case1_enc(part3, part1)
print(f"part1: {part1}, part2: {part2}, part3: {part3}")

# 解密
part1 = "WgvDmssEvcY326bHo3nNro3vXvvfmgrz"
part2 = "gX+Ri9PG=bt5=00B6hscPQOL"
part3 = "T6bHgUPL2gXUd=xT=FNHtPzV"
part3 = case1_dec(part3, part1)
part2 = case4_dec(part2, part3)
part1 = case5_dec(part1, part2)
part3 = case4_dec(part3, part1)
part2 = case1_dec(part2, part3)
part1 = case4_dec(part1, part2)
part3 = case3_dec(part3, part1)
part2 = case4_dec(part2, part3)
part1 = case1_dec(part1, part2)
part3 = case6_dec(part3, part1)
part2 = case3_dec(part2, part3)
part1 = case4_dec(part1, part2)
part3 = case5_dec(part3, part1)
part2 = case6_dec(part2, part3)
part1 = case3_dec(part1, part2)
part3 = case1_dec(part3, part1)
part2 = case5_dec(part2, part3)
part1 = case6_dec(part1, part2)
part2 = case1_dec(part2, part3)
part1 = case5_dec(part1, part2)
part1 = case1_dec(part1, part2)
print(f"{part1}{part2}{part3}")

运行脚本得到flag:NSSCTF{P4ch3Lbel’s_C@n0n_1n_D_mAjOr}.

Crypto方向:

MIMT_RSA:

编写脚本爆破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
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
#include <gmp.h>
#include <iostream>
#include <thread>
#include <atomic>
#include <vector>
#include <chrono>
#include <mutex>
#include <iomanip>
#include <cmath>

#pragma warning(push)
#pragma warning(disable: 4146 4244)
#include <gmpxx.h>
#pragma warning(pop)

std::atomic<bool> found(false);
std::atomic<uint64_t> total_processed(0);
std::mutex cout_mutex;

const uint64_t START_KEY = 0b100000000000000000000000000000000000;
const uint64_t END_KEY = 0xFFFFFFFFF;
const uint64_t TOTAL_KEYS = END_KEY - START_KEY + 1;

mpz_class n("26563847822899403123579768059987758748518109506340688366937229057385768563897579939399589878779201509595131302887212371556759550226965583832707699167542469352676806103999861576255689028708092007726895892953065618536676788020023461249303717579266840903337614272894749021562443472322941868357046500507962652585875038973455411548683247853955371839865042918531636085668780924020410159272977805762814306445393524647460775620243065858710021030314398928537847762167177417552351157872682037902372485985979513934517709478252552309280270916202653365726591219198063597536812483568301622917160509027075508471349507817295226801011");
mpz_class target("8371316287078036479056771367631991220353236851470185127168826270131149168993253524332451231708758763231051593801540258044681874144589595532078353953294719353350061853623495168005196486200144643168051115479293775329183635187974365652867387949378467702492757863040766745765841802577850659614528558282832995416523310220159445712674390202765601817050315773584214422244200409445854102170875265289152628311393710624256106528871400593480435083264403949059237446948467480548680533474642869718029551240453665446328781616706968352290100705279838871524562305806920722372815812982124238074246044446213460443693473663239594932076");
const uint32_t e = 0x10001;

class ProgressTracker {
private:
std::chrono::steady_clock::time_point start_time;
uint64_t last_count = 0;
double avg_speed = 0.0;
const double alpha = 0.2;

public:
ProgressTracker() : start_time(std::chrono::steady_clock::now()) {}

void update(uint64_t current) {
auto now = std::chrono::steady_clock::now();
double elapsed = std::chrono::duration<double>(now - start_time).count();

if (elapsed > 0.1) {
uint64_t delta = current - last_count;
double instant_speed = delta / elapsed;
avg_speed = alpha * instant_speed + (1 - alpha) * avg_speed;

last_count = current;
start_time = now;
}
}

std::pair<double, std::string> get_progress(uint64_t done) {
double progress = std::min(100.0, done * 100.0 / TOTAL_KEYS);
double remaining = (avg_speed > 0) ? (TOTAL_KEYS - done) / avg_speed : 0;
return {progress, format_duration(remaining)};
}

private:
std::string format_duration(double seconds) {
if (seconds < 60)
return fmt_duration(seconds, "s");
if (seconds < 3600)
return fmt_duration(seconds/60, "m") + " " + fmt_duration(fmod(seconds,60), "s");
return fmt_duration(seconds/3600, "h") + " "
+ fmt_duration(fmod(seconds,3600)/60, "m");
}

std::string fmt_duration(double value, const char* unit) {
std::stringstream ss;
ss << std::fixed << std::setprecision(1) << value << unit;
return ss.str();
}
};

void check_range(uint64_t start, uint64_t end) {
mpz_class result, key;
const uint64_t batch_size = 0x1000;

for (uint64_t k = start; k <= end && !found; ) {
uint64_t batch_end = std::min(k + batch_size, end);

for (; k <= batch_end && !found; ++k) {
key = k;
mpz_powm_ui(result.get_mpz_t(), key.get_mpz_t(), e, n.get_mpz_t());

if (result == target) {
found = true;
std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << "\n\nFound key: " << k << std::endl;
break;
}
}

total_processed.fetch_add(k - start, std::memory_order_relaxed);
start = k;
}
}

void progress_monitor() {
ProgressTracker tracker;
const int bar_width = 40;

while (!found && total_processed < TOTAL_KEYS) {
uint64_t done = total_processed.load();
auto [progress, eta] = tracker.get_progress(done);

std::lock_guard<std::mutex> lock(cout_mutex);
std::cout << "\r\033[K";

int pos = bar_width * progress / 100.0;
std::cout << "[";
for (int i = 0; i < bar_width; ++i) {
if (i < pos) std::cout << "=";
else if (i == pos) std::cout << ">";
else std::cout << " ";
}
std::cout << "] ";

std::cout << std::fixed << std::setprecision(2)
<< std::setw(6) << progress << "% "
<< "ETA:" << std::setw(8) << eta
<< " Keys:" << std::setw(10) << done << "/" << TOTAL_KEYS
<< std::flush;

std::this_thread::sleep_for(std::chrono::milliseconds(200));
}

std::cout << "\r\033[K" << std::flush;
}

int main() {
const unsigned num_threads = std::thread::hardware_concurrency();
std::vector<std::thread> threads;
uint64_t range = (END_KEY - START_KEY) / num_threads;

for (unsigned i = 0; i < num_threads; ++i) {
uint64_t start = START_KEY + i * range;
uint64_t end = (i == num_threads - 1) ? END_KEY : start + range - 1;
threads.emplace_back(check_range, start, end);
}

std::thread monitor(progress_monitor);

for (auto& t : threads) {
t.join();
}
monitor.join();

if (!found) {
std::cout << "\nKey not found in the specified range!" << std::endl;
}
return 0;
}

得到key = 62495925932,flag为NSSCTF{14369380f677abec84ed8b6d0e3a0ba9}.

Misc方向:

mybrave:

发现压缩包没密码,但里面只有一个png文件,由于png文件头是89504e470d0a1a0a0000000d494844520000,并且加密算法是zipcrypto store,则可以利用明文攻击,用bkcrack破解得到密钥97d30dcc 173b15a8 6e0e7455,解密得到一张png,发现文件尾有东西:

1
2
3
4
5
6
7
8
9
10
54 00 6C 00 4E 00 54 00 51 00 31 00 52 00 47 00
65 00 30 00 6B 00 6E 00 62 00 56 00 39 00 58 00
61 00 44 00 46 00 7A 00 63 00 44 00 4E 00 79 00
61 00 55 00 35 00 6E 00 58 00 30 00 39 00 31 00
55 00 6C 00 39 00 4D 00 64 00 54 00 45 00 78 00
59 00 57 00 4A 00 5A 00 58 00 32 00 59 00 77 00
63 00 6C 00 39 00 5A 00 4D 00 48 00 56 00 66 00
64 00 47 00 39 00 66 00 51 00 32 00 39 00 4E 00
5A 00 56 00 39 00 43 00 4E 00 47 00 4E 00 72 00
58 00 30 00 68 00 76 00 62 00 57 00 56 00 39 00

去除其中的00,base64解密得到flag:NSSCTF{I’m_Wh1sp3riNg_OuR_Lu11abY_f0r_Y0u_to_CoMe_B4ck_Home}.

mywav:

发现该音频全是正弦波,0.1毫秒为一个小节,共有2种音高,一个300多Hz,一个700多Hz,当做二进制的0和1提取音频内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import numpy as np
from scipy.io import wavfile

samplerate, data = wavfile.read('E:/CTF/attachment.wav')
samples_per_bit = int(samplerate * 0.010)
fft_size = samples_per_bit
bit_sequence = []
freq_resolution = samplerate / fft_size
threshold_index = int(450 / freq_resolution)
for i in range(0, len(data), samples_per_bit):
segment = data[i:i+samples_per_bit]
windowed_segment = segment * np.hanning(len(segment))
freqs = np.fft.rfft(windowed_segment)
power_spectrum = np.abs(freqs)**2
dominant_freq_index = np.argmax(power_spectrum[1:]) + 1
if dominant_freq_index > threshold_index:
bit_sequence.append(1)
else:
bit_sequence.append(0)

for i in range(len(bit_sequence)):
print(bit_sequence[i],end="")
print()

输出的内容转换为文本为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
YbAgLvWkQbFp qk k 2021 Gfgbyue gxriobzqgx hpyau utnkxmgz Jpwx Dfcmocn ngj Bn Flvytmc. Gh cu bnlkh hg apw Mlglsmg nbokp Lxjzwdw gl hbg Lnmzmvx. Apw cipgsm ciexj sg Fhvyy XT dfio Ahzawm 10 nubav Eseimv 26, 2021.

Tux yikbla zkw qsfjcsfxj e vntcdkxgts pkejxxwabw wx 1.3 lmjjwip apkuwl ohzayyq nzuvfbksw, bgjtmnmle Aupgb MB'w whtmkdma ydjniptzmhg, FwmDyzc, ohf Tutoptgk BjeiGB.

Gspoclow

Mal awbmcq hynlf mni lmvzq yj y qijgrvhx egw bvjepw aogruf fgpx zvl Rrss Qwswe (Pakr Salgmkr) ylr u jenwyxkhuo, kdyzzclp, aaw rsoxsg Usrbcfynln zove, Wpvy Hmyl (Lo Oeazpmx). Yywe liglu gwthtrpr xekdewgts ncbyxsemxz il cgfmcf vo oxisfbuo wkgf mhbgr'f eojxevvy msknohkoal or malqj tsspbya tukuyza fwmdl. Yjhbquta zlxr jwmvhl'r gncnq xgga hapwb er dwlut, gakc vhtm ly ennfyeinmk itvo wlrip'q gnteazzll bu bzomp bos-vo-qte mgmlzsmxgmbm.

Oavg

Ilxg Gpwiyyl om Bhbn Ymrnl

I qyyle, vupdfhsi lvowdkv ufc yzuqxy eg tpz gp ejmczpefl grw bubwvpgeshee. Qxytbml pac gmjr upd qbyxtga mpdipgcl, je chywxlzmk k kclhfg aaw jimxyuaxib lonwrr mnem wyqnow fga nq phkyyx apa axrcpaiut qxymkxz. Qf dlc dowg os ygqbef kzkpjcbags, ux xiftpvk biqmzove vg nml ibzkemr mt bks qkkefl.

Ec Eorehwy cs Qbtk Qbhv

Sx yluopgrvgm egw kmlovkgbyf ybntk phtif glm lspgr gnxrl tdiq pvmk qbclyxtkxl bvlsp qfs zccrl gr bgzcjwsslhudlr hhwmtjtw. Rip jwzg uawkvpxub s nvykonkc gkgrlyvzekxgmb qjea lni kxswukxcb tlqm n lseee awox xm Qvyphnb Immr. Pvxvyclqyf bl akv rhbbzpyj gajwlfbbigxza kri qfcqeafx nik ypmjmi bchytmvggxbhu ifn yluopgrvgm tnkzcad sd fsl ioney.

Whflbzsre Nomuwbkj

WhecmA7DsE3rTf4i

发现是维尼吉亚密码,解密后得到keyword youcantgetthiskey和明文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
AnGeLiDeMiMi is a 2021 Chinese television drama starring Chen Zheyuan and Xu Mengjie. It is based on the Chinese novel Secrets in the Lattice. The series aired on Mango TV from August 10 until August 26, 2021.

The series has surpassed a cumulative viewership of 1.3 billion across various platforms, including Mango TV's domestic application, YouTube, and Thailand TrueID.

Synopsis

The series tells the story of a superior and unruly campus male god Zhou Siyue (Chen Zheyuan) and a headstrong, stubborn, and lovely Cinderella girl, Ding Xian (Xu Mengjie). From being mutually exclusive tablemates at school to becoming each other's lifelong companions in their journey through youth. Although they couldn't stand each other at first, they come to appreciate each other's strengths in their day-to-day interactions.

Main

Chen Zheyuan as Zhou Siyue

A young, handsome scholar who exudes an air of aloofness and intelligence. Despite his cold and distant exterior, he possesses a gentle and determined nature that drives him to pursue his innermost desires. In the face of family challenges, he remains resolute in his pursuit of his dreams.

Xu Mengjie as Ding Xian

An unwavering and determined young woman who never turns away from challenges until she faces an insurmountable obstacle. Her life underwent a dramatic transformation when she relocated from a small town to Shenhai City. Influenced by her youthful impulsiveness she showcase her fierce determination and unwavering pursuit of her goals.

Something Password

SolveI7ToG3tFl4g

再次观察wav文件,在010editor运行wav模板,发现尾部有一个252字节的数据块无法识别出wav特征,并且去除后不影响总时长,提取这252字节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
9E 97 BA 2A 00 80 88 C9 A3 70 97 5B A2 E4 99 B8
C1 78 72 0F 88 DD DC 34 2B 4E 7D 31 7F B5 E8 70
39 A8 B8 42 75 68 71 91 D4 EE E8 AC A8 99 2D EF
49 AC B9 DD 32 7A 2E 23 2D FE 00 96 BD B0 76 7D
BA D5 7B 14 75 C4 94 8C 0A B4 AB 3C 51 28 F9 52
58 C8 ED 7C C1 94 6F D8 A3 BA 08 6D 81 15 B2 21
DC 47 E7 DE D6 42 CE BF 82 82 18 A4 2A 2C F0 AD
62 DD 99 B5 3D 15 59 88 A8 4A EB 91 67 24 DB A1
0A 66 55 E1 B2 2E 05 C9 3B B8 55 41 BB E5 B0 40
75 C3 58 C8 58 BE E7 42 2F 06 D1 5C 2A 9E BD 50
2E 95 AF CC 40 7B AB FA F2 6F D7 67 8A 66 62 98
F3 9E 21 C2 52 2E A4 E7 30 66 BE E3 EC 83 13 4F
F7 CA 41 FA 97 79 78 26 86 AB 29 00 D9 7B 74 00
CE F8 22 0A 17 D4 0C EE 74 3E C5 3D CF F0 9F 01
48 49 00 00 E0 00 00 00 3E 6A 3A 3D 6A 3B 38 3E
69 30 39 6C 69 3D 3A 3F 00 7E 74 05

发现是OurSecret的文件头,oursecret解密提取得到flag.txt,获得flag:NSSCTF{9c99897d-5ea3-481c-b0a5-029fec9eaf42}.