$ cat ~ / posts /reverse /ReverseCTF
reverse

Reverse题记

Reverse题记

有段时间没有写 CTF Reverse 题目了,在 AI 盛行的时代,手搓题目只能说是自己的兴趣了,在此记录一下以后手搓的 Reverse 题目,作为纪念吧

Attachments_505

找到 main 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
scanf("%s", Str);
if ( strlen(Str) != 49 )
{
puts_0("It's not enough.");
system_0("pause");
exit(0);
}
v10 = 0;
for ( i = 0; i <= 8; ++i )
{
for ( j = 0; j <= 8; ++j )
{
if ( !box[9 * i + j] )
{
v3 = v10++;
box[9 * i + j] = Str[v3] - 48;
}
}
}
check1();
check2();
check3();

判断输入字符串是否为 49

此处 v3v10 其实是一个变量,将对应的 Str[i] 减去 48 储存到 box 中,0 ASCII 码值为 48,实则为字符转数字,49长度的字符串转为每9个一行

check1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
for ( i = 0; i <= 8; ++i )
{
for ( j = 1; j <= 9; ++j )
{
for ( k = 0; ; ++k )
{
result = (unsigned int)(char)box[9 * i + k];
if ( j == (_DWORD)result )
break;
if ( k == 8 )
{
printf_0("Wrong!!!Try again!!!");
system_0("pause");
exit(0);
}
}
}
}

每一行,需要满足 1-9 的数字都出现一次

check2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
for ( i = 0; i <= 8; ++i )
{
for ( j = 1; j <= 9; ++j )
{
for ( k = 0; ; ++k )
{
result = (unsigned int)(char)box[9 * k + i];
if ( j == (_DWORD)result )
break;
if ( k == 8 )
{
printf_0("Wrong!!!Try again!!!");
system_0("pause");
exit(0);
}
}
}
}

同样的,每一列,需要满足 1-9 的数字都出现一次

check3

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
for ( i = 0; i <= 8; i += 3 )
{
for ( j = 0; j <= 8; j += 3 )
{
for ( k = 1; k <= 9; ++k )
{
v5 = 0;
v4 = 0;
while ( 1 )
{
result = (unsigned int)(char)box[9 * i + 9 * v5 + j + v4];
if ( k == (_DWORD)result )
break;
if ( v5 == 2 && v4 == 2 )
{
printf_0("Wrong!!!Try again!!!");
system_0("pause");
exit(0);
}
if ( ++v4 == 3 )
{
++v5;
v4 = 0;
}
}
}
}
}

为了检查每个 3x3 的小方格是否包含 1-9 的数字

那么 box 实际为一个满足数独要求的 9x9 的二维数组,输入的字符串长度为 49

实际上可以从 puts_0("Enjoy the beauty of reverse and sudoku!"); 看出这里是数独

最后输出:

1
2
3
for ( k = 0; k < strlen(Str); ++k )
putchar_0(Str[k] ^ magic[k]);
putchar_0(125)

magic 数组:

1
2
3
4
5
6
 .data:0000000000404080 magic           db 6Bh, 2, 66h, 70h, 44h, 69h, 7Eh, 6Eh, 43h, 4Ah, 78h
.data:0000000000404080 ; DATA XREF: main+196↑o
.data:000000000040408B db 4Ah, 6Dh, 60h, 56h, 0, 51h, 59h, 50h, 43h, 50h, 51h
.data:0000000000404096 db 6Dh, 74h, 2, 55h, 50h, 52h, 6Eh, 6Fh, 79h, 40h, 5Dh
.data:00000000004040A1 db 4Bh, 1Eh, 19h, 1Ch, 74h, 3, 54h, 7, 4Ch, 52h, 6Ah, 60h
.data:00000000004040AD db 50h, 58h, 40h, 58h, 0Fh dup(0)

64 位,即

1
6B 02 66 70 44 69 7E 6E 43 4A 78 4A 6D 60 56 00 51 59 50 43 50 51 6D 74 02 55 50 52 6E 6F 79 40 5D 4B 1E 19 1C 74 03 54 07 4C 52 6A 60 50 58 40 58 0F 00

同时可查询到 box 数组默认值

1
2
3
4
5
6
7
8
.data:0000000000404020 box             db 2 dup(0), 5, 2 dup(0), 4, 3, 6, 5 dup(0), 5, 2 dup(0)
.data:0000000000404020 ; DATA XREF: check1+69↑o
.data:0000000000404020 ; check2+69↑o ...
.data:0000000000404030 db 2, 4, 0, 4, 9, 6, 7, 4 dup(0), 1, 0, 6, 0, 2, 2 dup(0)
.data:0000000000404042 db 3, 0, 9, 2 dup(0), 7, 2 dup(0), 1, 0, 8, 0, 3, 3 dup(0)
.data:0000000000404052 db 5, 0, 9, 0, 2, 2 dup(0), 5, 0, 7, 2 dup(0), 9, 7, 0
.data:0000000000404061 db 4, 3 dup(0), 8, 3 dup(0), 9, 2 dup(0), 4, 3 dup(0)
.data:0000000000404070 db 6, 0Fh dup(0)

1
2
3
4
5
6
7
8
9
10
11
0 0 5 | 0 0 4 | 3 6 0
0 0 0 | 0 5 0 | 0 2 4
0 4 9 | 6 7 0 | 0 0 0
------+-------+------
1 0 6 | 0 2 0 | 0 3 0
9 0 0 | 7 0 0 | 1 0 8
0 3 0 | 0 0 5 | 0 9 0
------+-------+------
2 0 0 | 5 0 7 | 0 0 9
7 0 4 | 0 0 0 | 8 0 0
0 9 0 | 0 4 0 | 0 0 6

解数独

1
2
3
4
5
6
7
8
9
10
11
8 2 5 | 9 1 4 | 3 6 7
6 7 1 | 3 5 8 | 9 2 4
3 4 9 | 6 7 2 | 5 8 1
------+-------+------
1 8 6 | 4 2 9 | 7 3 5
9 5 2 | 7 6 3 | 1 4 8
4 3 7 | 1 8 5 | 6 9 2
------+-------+------
2 6 8 | 5 3 7 | 4 1 9
7 1 4 | 2 9 6 | 8 5 3
5 9 3 | 8 4 1 | 2 7 6

对应应该输入的字符串为:

1
8291767138932581849755263447186268341129653538127
1
2
3
4
5
6
7
8
9
# ./chall.exe
<--- moectf2021 --->
[A_game] Welcome to moectf2021.
Let's play a game!
Now input your answer, and if you are right, I will give you flag
input : 8291767138932581849755263447186268341129653538127
Congratulations!!!!
Enjoy the beauty of reverse and sudoku!
And here is your flag : moectf{S0_As_I_prAy_Un1imited_B1ade_WOrks---E1m1ya_Shiro}
1
moectf{S0_As_I_prAy_Un1imited_B1ade_WOrks---E1m1ya_Shiro}

Attachments_493

静态链接,C++ 代码编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
std::operator<<<std::char_traits<char>>(&_TMC_END__, "Welcome to MoeCTF 2020!\nPlease input your flag here >>", envp);
__isoc99_scanf("%22s", flag);
sub_0();
for ( i = 0; i <= 21; ++i )
{
v3 = (unsigned __int8)flag[i];
if ( (_BYTE)v3 != key[i] )
{
v4 = std::operator<<<std::char_traits<char>>(&_TMC_END__, "Ruaaaaa~Wrong!", v3);
std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
return 0;
}
}
v6 = std::operator<<<std::char_traits<char>>(
&_TMC_END__,
"Congratulations!!!\nWhat you input here is the true flag!!",
v3);
std::ostream::operator<<(v6, &std::endl<char,std::char_traits<char>>);

输入了一个长度为 22 的字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__int64 __fastcall sub_0()
{
x = (byte_741B1 | y) ^ x & byte_741B1 ^ (2 * x);
y = (byte_741B1 | x) ^ y & byte_741B1 ^ (2 * y);
byte_741A9 = ~(6 - byte_741A9);
sub_1();
sub_1998();
return sub_1999();
}

__int64 __fastcall sub_1()
{
x = (byte_741A9 | y) ^ x & byte_741A9 ^ (2 * x);
y = (byte_741A9 | x) ^ y & byte_741A9 ^ (2 * y);
byte_741A9 = ~(-8 - byte_741A9);
sub_2();
if ( 2 * y + ((((_BYTE)x - 1) * (_BYTE)x) & 1) == (((((((_BYTE)x - 1) * (_BYTE)x) & 1) + 2 * y) | 1) == 1) )
exit(0);
sub_1996();
return sub_1997();
}

....

进行了多次加密,顺序为

1
sub_0 -> sub_1 -> sub_2 -> ... ->sub_1996 -> sub_1997 -> sub_1998 -> sub_1999

主函数主要是比较输入的字符串的和 key 字符串的每个字符是否相同,key 字符串为:

1
2
3
.data:0000000000074060 key             db 2, 0Ah, 6, 16h, 13h, 1Ch, 2, 4Dh, 41h, 28h, 3Dh, 56h
.data:0000000000074060 ; DATA XREF: main+5A↑o
.data:000000000007406C db 52h, 39h, 19h, 70h, 51h, 1Bh, 5Ah, 59h, 7Ah, 22h, 2 dup(0)

1
02 0A 06 16 13 1C 02 4D 41 28 3D 56 52 39 19 70 51 1B 5A 59 7A 22

通过动态调试知道第 k 个输入只影响输出 kk-1

我们知道 flag 字符串前面是固定的 meoctf{,所以可以尝试写 gdb_python 脚本爆破

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import gdb, string

flag_addr = int(gdb.parse_and_eval('(char*)&flag'))
key_addr = int(gdb.parse_and_eval('(char*)&key'))
x_addr = int(gdb.parse_and_eval('(int*)&x'))
y_addr = int(gdb.parse_and_eval('(int*)&y'))
inf = gdb.selected_inferior()
key = bytes(inf.read_memory(key_addr, 22))

charset = string.ascii_letters + string.digits + '_{}-!@#$%^&*()+='
charset += ''.join(chr(i) for i in range(33, 127) if chr(i) not in charset)

def transform(s):
s = s.ljust(22, '\x00')[:22]
inf.write_memory(x_addr, (0xdeadbeef).to_bytes(4, 'little'))
inf.write_memory(y_addr, (0xdeadbeef).to_bytes(4, 'little'))
inf.write_memory(flag_addr, s.encode('latin1'))
gdb.execute('call (void) sub_0()', to_string=True)
return bytes(inf.read_memory(flag_addr, 22))

paths = ['m']
for j in range(21):
new = []
for p in paths:
if j + 1 < len('moectf{'):
candidates = 'moectf{'[j + 1]
elif j + 1 == 21:
candidates = '\x00'
else:
candidates = charset
for c in candidates:
s = p + c
if transform(s)[j] == key[j]:
new.append(s)
paths = new
print(j, paths[:5])

print(paths)

上面这个代码的逻辑就是不断尝试调用 sub0 来爆破 flag,如果下一位算出来的答案是 key[j] 那么就说明这个字符是正确的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
pwndbg> source solution.py
0 ['mo']
1 ['moe']
2 ['moec']
3 ['moect']
4 ['moectf']
5 ['moectf{']
6 ['moectf{y']
7 ['moectf{y0']
8 ['moectf{y0u']
9 ['moectf{y0u_']
10 ['moectf{y0u_a']
11 ['moectf{y0u_a2']
12 ['moectf{y0u_a2e']
13 ['moectf{y0u_a2e_']
14 ['moectf{y0u_a2e_G']
15 ['moectf{y0u_a2e_G0']
16 ['moectf{y0u_a2e_G0d']
17 ['moectf{y0u_a2e_G0d~']
18 ['moectf{y0u_a2e_G0d~!']
19 ['moectf{y0u_a2e_G0d~!}']
20 ['moectf{y0u_a2e_G0d~!}\x00']
['moectf{y0u_a2e_G0d~!}\x00']

得到 flag

1
moectf{y0u_a2e_G0d~!}

Attachments_494

搜索字符串发现可疑字符串

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
.rdata:0000000140005470 unk_140005470   db  6Dh ; m             ; DATA XREF: main+163↑o
.rdata:0000000140005471 db 6Fh ; o
.rdata:0000000140005472 db 65h ; e
.rdata:0000000140005473 db 63h ; c
.rdata:0000000140005474 db 74h ; t
.rdata:0000000140005475 db 66h ; f
.rdata:0000000140005476 db 7Bh ; {
.rdata:0000000140005477 db 0E4h
.rdata:0000000140005478 db 0B8h
.rdata:0000000140005479 db 96h
.rdata:000000014000547A db 0E7h
.rdata:000000014000547B db 95h
.rdata:000000014000547C db 8Ch
.rdata:000000014000547D db 0E3h
.rdata:000000014000547E db 81h
.rdata:000000014000547F db 0A7h
.rdata:0000000140005480 db 0E4h
.rdata:0000000140005481 db 0B8h
.rdata:0000000140005482 db 80h
.rdata:0000000140005483 db 0E7h
.rdata:0000000140005484 db 95h
.rdata:0000000140005485 db 0AAh
.rdata:0000000140005486 db 0E5h
.rdata:0000000140005487 db 0B9h
.rdata:0000000140005488 db 0B8h
.rdata:0000000140005489 db 0E3h
.rdata:000000014000548A db 81h
.rdata:000000014000548B db 9Bh
.rdata:000000014000548C db 0E3h
.rdata:000000014000548D db 81h
.rdata:000000014000548E db 0AAh
.rdata:000000014000548F db 0E5h
.rdata:0000000140005490 db 0A5h
.rdata:0000000140005491 db 0B3h
.rdata:0000000140005492 db 0E3h
.rdata:0000000140005493 db 81h
.rdata:0000000140005494 db 0AEh
.rdata:0000000140005495 db 0E5h
.rdata:0000000140005496 db 0ADh
.rdata:0000000140005497 db 90h
.rdata:0000000140005498 db 0E2h
.rdata:0000000140005499 db 80h
.rdata:000000014000549A db 94h
.rdata:000000014000549B db 0EFh
.rdata:000000014000549C db 0BCh
.rdata:000000014000549D db 0A3h
.rdata:000000014000549E db 0EFh
.rdata:000000014000549F db 0BCh
.rdata:00000001400054A0 db 0A8h
.rdata:00000001400054A1 db 0EFh
.rdata:00000001400054A2 db 0BCh
.rdata:00000001400054A3 db 0B4h
.rdata:00000001400054A4 db 0EFh
.rdata:00000001400054A5 db 0BCh
.rdata:00000001400054A6 db 0A8h
.rdata:00000001400054A7 db 0EFh
.rdata:00000001400054A8 db 0BCh
.rdata:00000001400054A9 db 0AFh
.rdata:00000001400054AA db 0EFh
.rdata:00000001400054AB db 0BCh
.rdata:00000001400054AC db 0ACh
.rdata:00000001400054AD db 0EFh
.rdata:00000001400054AE db 0BCh
.rdata:00000001400054AF db 0ACh
.rdata:00000001400054B0 db 0EFh
.rdata:00000001400054B1 db 0BCh
.rdata:00000001400054B2 db 0B9h
.rdata:00000001400054B3 db 0E2h
.rdata:00000001400054B4 db 80h
.rdata:00000001400054B5 db 94h
.rdata:00000001400054B6 db 7Dh ; }

main+163 处有交叉引用,这边直接 UTF8 解码得到

1
moectf{世界で一番幸せな女の子—CHTHOLLY—}

main 函数有点复杂

先根据程序的输出对应一下

1
2
3
4
5
6
7
8
9
# ./CxxIntro.exe

「だからきっと
今の私は、誰が何と言おうと
世界一幸せな女の子だ」
——クトリ・ノタ・セニオリス

Input your hex-encoded magic spell to help her romance.
>

找到 cout 对象

1
2
sub_1400026E0(std::cout, &unk_1400054E0);
sub_1400026E0(std::cout, "\nInput your hex-encoded magic spell to help her romance.\n> ");

&unk_1400054E0 就是那一串日文

最后 v32 = sub_140002EA0(std::cout, v31, v30[2]);

预估是将输入进行一系列变换之后的结果

根据汇编代码,缺失的 v21cin 输入的字符串,v22 为字符串转为字节的字节串

修改相关变量:

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
sub_1400026E0(
std::cout,
"\n"
"「だからきっと\n"
" 今の私は、誰が何と言おうと\n"
" 世界一幸せな女の子だ」\n"
"\t\t——クトリ・ノタ・セニオリス\n");
sub_1400026E0(std::cout, "\nInput your hex-encoded magic spell to help her romance.\n> ");
v62[2] = 0;
v63 = 15;
LOBYTE(v62[0]) = 0;
sub_140002CB0(std::cin, v62);
cin1 = sub_140001C40(v21, &v57, v62);
i1 = 0;
i2 = 0;
C15_ = 15;
C15 = 15;
LOBYTE(Src[0]) = 0;
cincopy1 = (_BYTE *)cin1;
if ( *(_QWORD *)(cin1 + 24) >= 0x10u )
cincopy1 = *(_BYTE **)cin1;
cincopy2 = (_BYTE *)cin1;
if ( *(_QWORD *)(cin1 + 24) >= 0x10u )
cincopy2 = *(_BYTE **)cin1;
v27 = &cincopy2[*(_QWORD *)(cin1 + 16)];
if ( cincopy1 != v27 )
{
while ( 1 )
{
xor = *cincopy1 ^ random(&v67);
if ( i1 >= C15_ )
{
push_back(Src, v29, v30, xor);
}
else
{
i2 = i1 + 1;
Srcptr = Src;
if ( C15_ >= 0x10 )
Srcptr = (void **)Src[0];
*((_BYTE *)Srcptr + i1) = xor;
*((_BYTE *)Srcptr + i1 + 1) = 0;
}
if ( ++cincopy1 == v27 )
break;
C15_ = C15;
i1 = i2;
}
}
v32 = sub_140001380((__int64)Block, v54, Src);
v33 = v32;
if ( v32[3] >= 0x10u )
v33 = (_QWORD *)*v32;
v34 = sub_140002EA0(std::cout, v33, v32[2]);

random 函数查看:

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
__int64 __fastcall random(unsigned int *a1)
{
__int64 v2; // r9
unsigned int v3; // ecx
_DWORD *v4; // r8
int v5; // edx
_DWORD *v6; // r11
unsigned int v7; // eax
__int64 v8; // rbx
unsigned int v9; // edx
_DWORD *v10; // r11
__int64 v11; // rbx
unsigned int v12; // eax
unsigned int v13; // edx
unsigned int v14; // edx
unsigned int v15; // edx

v2 = 624;
v3 = *a1;
if ( v3 == 624 )
{
v4 = a1 + 2;
do
{
v5 = *v4 ^ *(v4 - 1);
++v4;
v4[622] = ((*(v4 - 2) ^ v5 & 0x7FFFFFFFu) >> 1)
^ v4[395]
^ (((*((_BYTE *)v4 - 8) ^ (unsigned __int8)v5) & 1) != 0 ? 0x9908B0DF : 0);
--v2;
}
while ( v2 );
v3 = *a1;
}
else if ( v3 >= 0x4E0 )
{
v6 = a1 + 625;
v7 = a1[625];
v8 = 227;
do
{
v9 = v7 ^ (v6[1] ^ v7) & 0x7FFFFFFF;
v7 = v6[1];
*(v6 - 624) = (v9 >> 1) ^ v6[397] ^ ((v6[1] & 1) != 0 ? 0x9908B0DF : 0);
++v6;
--v8;
}
while ( v8 );
v10 = a1 + 852;
v11 = 396;
v12 = a1[852];
do
{
v13 = v12 ^ (v10[1] ^ v12) & 0x7FFFFFFF;
v12 = v10[1];
*(v10 - 624) = (v13 >> 1) ^ *(v10 - 851) ^ ((v10[1] & 1) != 0 ? 0x9908B0DF : 0);
++v10;
--v11;
}
while ( v11 );
a1[624] = ((a1[1248] ^ (a1[1] ^ a1[1248]) & 0x7FFFFFFF) >> 1) ^ a1[397] ^ ((a1[1] & 1) != 0 ? 0x9908B0DF : 0);
v3 = 0;
*a1 = 0;
}
v14 = a1[v3 + 1];
*a1 = v3 + 1;
v15 = ((((v14 >> 11) & a1[1249] ^ v14) & 0xFF3A58AD) << 7) ^ (v14 >> 11) & a1[1249] ^ v14;
return ((v15 & 0xFFFFDF8C) << 15) ^ v15 ^ ((((v15 & 0xFFFFDF8C) << 15) ^ v15) >> 18);
}

这个为 MT19937 实现的梅森旋转算法(随机数生成函数)

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
_QWORD *__fastcall push_back(_QWORD *Src, __int64 a2, __int64 a3, char a4)
{
size_t v4; // rbp
__int64 v5; // rbx
unsigned __int64 v8; // r14
unsigned __int64 v9; // rcx
unsigned __int64 v10; // rdx
size_t v11; // rcx
void *v12; // rax
_QWORD *v13; // rdi
_QWORD *v14; // rbx

v4 = Src[2];
v5 = 0x7FFFFFFFFFFFFFFFLL;
if ( v4 == 0x7FFFFFFFFFFFFFFFLL )
sub_1400011B0();
v8 = Src[3];
v9 = (v4 + 1) | 0xF;
if ( v9 <= 0x7FFFFFFFFFFFFFFFLL )
{
v10 = v8 >> 1;
if ( v8 <= 0x7FFFFFFFFFFFFFFFLL - (v8 >> 1) )
{
v5 = (v4 + 1) | 0xF;
if ( v9 < v10 + v8 )
v5 = v10 + v8;
}
}
v11 = v5 + 1;
if ( v5 == -1 )
v11 = -1;
if ( v11 < 0x1000 )
{
if ( v11 )
{
_mm_lfence();
v13 = operator new(v11);
}
else
{
v13 = 0;
}
}
else
{
if ( v11 + 39 < v11 )
sub_140001110();
_mm_lfence();
v12 = operator new(v11 + 39);
if ( !v12 )
goto LABEL_20;
v13 = (_QWORD *)(((unsigned __int64)v12 + 39) & 0xFFFFFFFFFFFFFFE0uLL);
*(v13 - 1) = v12;
}
Src[2] = v4 + 1;
Src[3] = v5;
if ( v8 < 0x10 )
{
memcpy(v13, Src, v4);
*((_BYTE *)v13 + v4) = a4;
*((_BYTE *)v13 + v4 + 1) = 0;
goto LABEL_22;
}
v14 = (_QWORD *)*Src;
memcpy(v13, (const void *)*Src, v4);
*((_BYTE *)v13 + v4) = a4;
*((_BYTE *)v13 + v4 + 1) = 0;
if ( v8 + 1 >= 0x1000 )
{
if ( (unsigned __int64)v14 - *(v14 - 1) - 8 <= 0x1F )
{
v14 = (_QWORD *)*(v14 - 1);
goto LABEL_19;
}
LABEL_20:
invalid_parameter_noinfo_noreturn();
}
LABEL_19:
_mm_lfence();
j_j_free(v14);
LABEL_22:
*Src = v13;
return Src;
}

v9 = (v4 + 1) | 0xF; 为新容量计算,v10 + v8 即容量+容量/2 为典型的动态数组扩容策略,则该函数为 std::vectorpush_back 实现

那么上述流程即位将输入的字符串转为字节串,然后每个字节与 random 函数生成的随机数进行异或,暂存到 Src 中,调用 sub_140001380 进行处理,最后输出到 cout

这里的 Block 为上述构建的,sub_140001380 为自定义算法

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
// Hidden C++ exception states: #wind=1
_QWORD *__fastcall sub_140001380(__int64 Block, _QWORD *a2, _QWORD *a3)
{
char i; // r15
unsigned __int8 j; // r13
_QWORD *v7; // rcx
_QWORD *v8; // rdx
_BYTE *num2_a3; // rbp
_BYTE *len; // r12
unsigned __int64 row1; // r9
__int64 v12; // rdx
_BYTE *SBoxChar1; // rax
unsigned __int64 row2; // r10
char *SBoxChar2; // r11
char v16; // al
char *S1Other; // rdx
char v18; // cl
__int64 v19; // r14
__int64 v20; // rcx
char temp; // dl
_BYTE *S2Other; // r8
__int64 col3; // rcx
unsigned __int64 row3; // rax
char out; // r9
unsigned __int64 v26; // rcx
unsigned __int64 v27; // rdx
_QWORD *v28; // rax

a2[2] = 0;
a2[3] = 15;
*(_BYTE *)a2 = 0;
i = 0;
j = 0;
v7 = a3;
if ( a3[3] < 0x10u )
{
v8 = a3;
}
else
{
v7 = (_QWORD *)*a3;
v8 = (_QWORD *)*a3;
}
num2_a3 = v7;
len = (char *)v8 + a3[2];
if ( v7 != (_QWORD *)len )
{
do
{
row1 = (unsigned __int64)(unsigned __int8)++i >> 4;
v12 = row1 + Block;
if ( (i & 0xF) != 0 )
SBoxChar1 = (_BYTE *)((i & 0xF) + *(_QWORD *)(Block + 8 * row1));
else
SBoxChar1 = (_BYTE *)(v12 + 128);
j += *SBoxChar1;
row2 = (unsigned __int64)j >> 4;
if ( (j & 0xF) != 0 )
SBoxChar2 = (char *)((j & 0xF) + *(_QWORD *)(Block + 8 * row2));
else
SBoxChar2 = (char *)(row2 + Block + 128);
v16 = *SBoxChar2;
if ( (i & 0xF) != 0 )
{
v19 = i & 0xF;
v20 = *(_QWORD *)(Block + 8 * row1);
temp = *(_BYTE *)(v20 + v19);
*(_BYTE *)(v20 + v19) = v16;
*SBoxChar2 = temp; // 交换 SBoxChar1 和 SBoxChar2
S1Other = (char *)(v19 + *(_QWORD *)(Block + 8 * row1));
}
else
{
S1Other = (char *)(v12 + 128);
v18 = *S1Other;
*S1Other = v16;
*SBoxChar2 = v18;
}
if ( (j & 0xF) != 0 )
S2Other = (_BYTE *)(*(_QWORD *)(Block + 8 * row2) + (j & 0xF));
else
S2Other = (_BYTE *)(row2 + Block + 128);
col3 = (*S2Other + *S1Other) & 0xF;
row3 = (unsigned __int64)(unsigned __int8)(*S2Other + *S1Other) >> 4;
if ( ((*S2Other + *S1Other) & 0xF) != 0 )
row3 = *(_QWORD *)(Block + 8 * row3);
else
col3 = Block + 128;
out = *num2_a3 ^ *(_BYTE *)(row3 + col3);
v26 = a2[2];
v27 = a2[3];
if ( v26 >= v27 )
{
push_back(a2, v27, (__int64)S2Other, out);
}
else
{
a2[2] = v26 + 1;
v28 = a2;
if ( v27 >= 0x10 )
v28 = (_QWORD *)*a2;
*((_BYTE *)v28 + v26) = out;
*((_BYTE *)v28 + v26 + 1) = 0;
}
++num2_a3;
}
while ( num2_a3 != len );
}
return a2;
}

i 为字节索引,j 为累加器,每次加上上次取出的值 SBoxChar1

这个函数为取出之前生成的 Block 里面的内容,取出两个值 SBoxChar1=Block[i >> 4][j & 0xF]SBoxChar2[i >> 4][(j + SBoxChar1) & 0xF],接着交换 SBoxChar1SBoxChar2,然后计算key = Block[(SBoxChar1 + SBoxChar2) >> 4][(SBoxChar1 + SBoxChar2) & 0xF] ,与函数传入的 a3 进行逐字节异或,放到输出字符串中

接下来查看 Block 怎么初始化的

1
2
3
4
5
6
7
8
9
10
11
12
13
for ( i = 0; i < 0x10; ++i )
{
v4 = operator new(0x10u);
v5 = Block[i];
Block[i] = v4;
if ( v5 )
{
j_j_free(v5);
v4 = Block[i];
}
*v4 = 0;
*(_BYTE *)Block[i] = 73 * (i + 1);
}

每一行的第一个字符为 73 * (i + 1),即 49 92 135 178 221 8 51 94 137 180 223 66 109 152 195 238,然后经过了一系列变化,发现这个变化是固定的,动态拿到 Block 的值

编写 IDA-python 脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import idc

def dump_sbox():
rsp = idc.get_reg_value("RSP")
if rsp is None:
print("Cannot read RSP.")
return

v65_offset = 0x1A0
block_offset = 0x120

v65_addr = rsp + v65_offset
block_addr = rsp + block_offset

v65_data = idc.get_bytes(v65_addr, 16)
if not v65_data:
print("Failed to read v65 at", hex(v65_addr))
return

sbox = []
for row in range(16):
ptr_addr = block_addr + row * 8
row_ptr = idc.get_qword(ptr_addr)
if row_ptr == 0:
print("Row", row, "pointer is NULL")
continue
row_data = idc.get_bytes(row_ptr, 16)
if not row_data:
print("Failed to read row", row)
continue
correct_row = bytearray(16)
correct_row[0] = v65_data[row]
correct_row[1:] = row_data[1:16]
sbox.append(list(correct_row))

if len(sbox) != 16:
print("Incomplete S-Box, only got", len(sbox), "rows")
return

print("const unsigned char SBOX[16][16] = {")
for row in sbox:
hex_vals = ", ".join("0x{:02X}".format(x) for x in row)
print(" {" + hex_vals + "},")
print("};")

dump_sbox()

得到 SBOX

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const unsigned char SBOX[16][16] = {
{0x97, 0xE6, 0x29, 0x74, 0xD2, 0x11, 0x73, 0x29, 0x4A, 0x75, 0x13, 0x0E, 0x52, 0xF6, 0xE1, 0x54},
{0xDE, 0xFF, 0x14, 0xC8, 0xC3, 0x25, 0xAD, 0xCE, 0x1A, 0x92, 0x7D, 0xE4, 0x79, 0x64, 0xDA, 0x62},
{0x6F, 0xCA, 0x47, 0x32, 0xAC, 0x30, 0x45, 0x75, 0x14, 0xFF, 0x60, 0x08, 0x22, 0x56, 0xEF, 0x09},
{0x42, 0xD6, 0xF0, 0x35, 0xBD, 0xD7, 0x10, 0xA4, 0xBE, 0xFE, 0x8B, 0xA5, 0xE2, 0x72, 0x8C, 0xC9},
{0x59, 0xE6, 0x30, 0xA6, 0x91, 0xF2, 0x28, 0x65, 0xB4, 0xF7, 0x42, 0xA0, 0xDF, 0x41, 0xF7, 0x18},
{0x43, 0xE1, 0xDC, 0x20, 0xC4, 0xAF, 0x22, 0xAC, 0xCD, 0xE2, 0x96, 0x91, 0xF3, 0x7B, 0x9C, 0xE8},
{0x60, 0x4B, 0xB2, 0x47, 0x32, 0xA8, 0x30, 0x3D, 0x98, 0x15, 0x00, 0x7A, 0xFE, 0x13, 0x43, 0xE2},
{0xCD, 0x2E, 0xD6, 0xF0, 0x24, 0xBD, 0xD7, 0x10, 0xA4, 0xBE, 0x03, 0x8B, 0xA5, 0xDE, 0x72, 0x8C},
{0xCC, 0x59, 0xE6, 0x23, 0xB3, 0xCD, 0x0A, 0x9A, 0xB4, 0xFE, 0x74, 0x5F, 0xC0, 0xF6, 0x33, 0x82},
{0xC5, 0x10, 0x6E, 0xAD, 0x0F, 0xC5, 0xE6, 0x11, 0xAF, 0xAA, 0xEE, 0x92, 0x7D, 0xF0, 0x7A, 0x9B},
{0xB0, 0x64, 0x5F, 0xC1, 0x49, 0x6A, 0xB6, 0x2E, 0x19, 0x80, 0x15, 0x00, 0x76, 0xFE, 0x0B, 0x66},
{0xE3, 0xCE, 0x48, 0xCC, 0xE1, 0x11, 0xB0, 0x9B, 0xFC, 0xA4, 0xBE, 0xF2, 0x8B, 0xA5, 0xDE, 0x72},
{0x8C, 0xD1, 0x59, 0xE6, 0x1F, 0xB3, 0xCD, 0x0D, 0x9A, 0xB4, 0xF1, 0x81, 0x9B, 0xD8, 0x68, 0x82},
{0xCC, 0x42, 0x2D, 0x8E, 0xC4, 0x01, 0x50, 0x93, 0xDE, 0x3C, 0x7B, 0xDD, 0x93, 0xB4, 0xDF, 0x7D},
{0x78, 0xBC, 0x60, 0x4B, 0xBE, 0x48, 0x69, 0x7E, 0x32, 0x2D, 0x8F, 0x17, 0x38, 0x84, 0xFC, 0xE7},
{0x4E, 0xE3, 0xCE, 0x44, 0xCC, 0xD9, 0x34, 0xB1, 0x9C, 0x16, 0x9A, 0xAF, 0xDF, 0x7E, 0x69, 0xCA},
};

main 函数来看,输入 -> random 异或 -> sub_140001380 处理 -> 输出,这个序列是确定的,但是和 flag 似乎没有关系

但注意到在 .rodata 段下面有一个字符串:

1
magic{75b1743a9200a5bc60c70b666eaad2911ddb2292eed4d3d2b7ebc7867b2aa5499949810031756dcdc1dc6500}

这个字符串比较奇怪,通过尝试可以知道:

75b1743a9200a5bc60c70b666eaad2911ddb2292eed4d3d2b7ebc7867b2aa5499949810031756dcdc1dc6500 通过逆sub_140001380 处理后得到结果

编写脚本

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
from pathlib import Path

data = Path("CxxIntro.exe").read_bytes()

flag_key = 'moectf{世界で一番幸せな女の子—CHTHOLLY—}'.encode('utf-8')

magic = bytes.fromhex(
'75b1743a9200a5bc60c70b666eaad2911ddb2292eed4d3d2b7ebc7867b2aa5499949810031756dcdc1dc6500'
)

period = bytes((0x2a + i * data[0]) & 0xff for i in range(0x41))
sbox = [(period[i % len(period)] + flag_key[i % len(flag_key)]) & 0xff for i in range(256)]

class MT19937:
def __init__(self, seed):
self.mt = [seed & 0xffffffff]
for i in range(1, 624):
x = self.mt[-1]
self.mt.append((0x6c078965 * (x ^ (x >> 30)) + i) & 0xffffffff)
self.index = 624

def rand(self):
if self.index >= 624:
for i in range(624):
y = (self.mt[i] & 0x80000000) | (self.mt[(i + 1) % 624] & 0x7fffffff)
self.mt[i] = self.mt[(i + 397) % 624] ^ (y >> 1)
if y & 1:
self.mt[i] ^= 0x9908b0df
self.index = 0
y = self.mt[self.index]
self.index += 1
y ^= y >> 11
y ^= (y << 7) & 0x9d2c5680
y ^= (y << 15) & 0xefc60000
y ^= y >> 18
return y & 0xffffffff

def crypt(buf):
s = sbox[:]
i = j = 0
out = bytearray()
for b in buf:
i = (i + 1) & 0xff
j = (j + s[i]) & 0xff
s[i], s[j] = s[j], s[i]
out.append(b ^ s[(s[i] + s[j]) & 0xff])
return bytes(out)

flag = crypt(magic)
print(flag.decode())

# 让原程序打印 flag,输入的是 middle 再异或 MT19937 低字节后的 hex
mt = MT19937(0x44)
spell = bytes(b ^ (mt.rand() & 0xff) for b in magic)
print(spell.hex())

或者:

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
magic = bytes.fromhex(
'75b1743a9200a5bc60c70b666eaad2911ddb2292eed4d3d2b7ebc7867b2aa5499949810031756dcdc1dc6500'
)

sbox = [
0x97, 0xE6, 0x29, 0x74, 0xD2, 0x11, 0x73, 0x29, 0x4A, 0x75, 0x13, 0x0E, 0x52, 0xF6, 0xE1, 0x54,
0xDE, 0xFF, 0x14, 0xC8, 0xC3, 0x25, 0xAD, 0xCE, 0x1A, 0x92, 0x7D, 0xE4, 0x79, 0x64, 0xDA, 0x62,
0x6F, 0xCA, 0x47, 0x32, 0xAC, 0x30, 0x45, 0x75, 0x14, 0xFF, 0x60, 0x08, 0x22, 0x56, 0xEF, 0x09,
0x42, 0xD6, 0xF0, 0x35, 0xBD, 0xD7, 0x10, 0xA4, 0xBE, 0xFE, 0x8B, 0xA5, 0xE2, 0x72, 0x8C, 0xC9,
0x59, 0xE6, 0x30, 0xA6, 0x91, 0xF2, 0x28, 0x65, 0xB4, 0xF7, 0x42, 0xA0, 0xDF, 0x41, 0xF7, 0x18,
0x43, 0xE1, 0xDC, 0x20, 0xC4, 0xAF, 0x22, 0xAC, 0xCD, 0xE2, 0x96, 0x91, 0xF3, 0x7B, 0x9C, 0xE8,
0x60, 0x4B, 0xB2, 0x47, 0x32, 0xA8, 0x30, 0x3D, 0x98, 0x15, 0x00, 0x7A, 0xFE, 0x13, 0x43, 0xE2,
0xCD, 0x2E, 0xD6, 0xF0, 0x24, 0xBD, 0xD7, 0x10, 0xA4, 0xBE, 0x03, 0x8B, 0xA5, 0xDE, 0x72, 0x8C,
0xCC, 0x59, 0xE6, 0x23, 0xB3, 0xCD, 0x0A, 0x9A, 0xB4, 0xFE, 0x74, 0x5F, 0xC0, 0xF6, 0x33, 0x82,
0xC5, 0x10, 0x6E, 0xAD, 0x0F, 0xC5, 0xE6, 0x11, 0xAF, 0xAA, 0xEE, 0x92, 0x7D, 0xF0, 0x7A, 0x9B,
0xB0, 0x64, 0x5F, 0xC1, 0x49, 0x6A, 0xB6, 0x2E, 0x19, 0x80, 0x15, 0x00, 0x76, 0xFE, 0x0B, 0x66,
0xE3, 0xCE, 0x48, 0xCC, 0xE1, 0x11, 0xB0, 0x9B, 0xFC, 0xA4, 0xBE, 0xF2, 0x8B, 0xA5, 0xDE, 0x72,
0x8C, 0xD1, 0x59, 0xE6, 0x1F, 0xB3, 0xCD, 0x0D, 0x9A, 0xB4, 0xF1, 0x81, 0x9B, 0xD8, 0x68, 0x82,
0xCC, 0x42, 0x2D, 0x8E, 0xC4, 0x01, 0x50, 0x93, 0xDE, 0x3C, 0x7B, 0xDD, 0x93, 0xB4, 0xDF, 0x7D,
0x78, 0xBC, 0x60, 0x4B, 0xBE, 0x48, 0x69, 0x7E, 0x32, 0x2D, 0x8F, 0x17, 0x38, 0x84, 0xFC, 0xE7,
0x4E, 0xE3, 0xCE, 0x44, 0xCC, 0xD9, 0x34, 0xB1, 0x9C, 0x16, 0x9A, 0xAF, 0xDF, 0x7E, 0x69, 0xCA
]

class MT19937:
def __init__(self, seed):
self.mt = [seed & 0xffffffff]
for i in range(1, 624):
x = self.mt[-1]
self.mt.append((0x6c078965 * (x ^ (x >> 30)) + i) & 0xffffffff)
self.index = 624

def rand(self):
if self.index >= 624:
for i in range(624):
y = (self.mt[i] & 0x80000000) | (self.mt[(i + 1) % 624] & 0x7fffffff)
self.mt[i] = self.mt[(i + 397) % 624] ^ (y >> 1)
if y & 1:
self.mt[i] ^= 0x9908b0df
self.index = 0
y = self.mt[self.index]
self.index += 1
y ^= y >> 11
y ^= (y << 7) & 0x9d2c5680
y ^= (y << 15) & 0xefc60000
y ^= y >> 18
return y & 0xffffffff

def crypt(buf):
s = sbox[:]
i = j = 0
out = bytearray()
for b in buf:
i = (i + 1) & 0xff
j = (j + s[i]) & 0xff
s[i], s[j] = s[j], s[i]
out.append(b ^ s[(s[i] + s[j]) & 0xff])
return bytes(out)

flag = crypt(magic)
print(flag.decode())

# 让原程序打印 flag,输入的是 middle 再异或 MT19937 低字节后的 hex
mt = MT19937(0x44)
spell = bytes(b ^ (mt.rand() & 0xff) for b in magic)
print(spell.hex())

输出:

1
2
moectf{CHTHOLLY_the_best__sukasuka_the_best}
4a43d39e58ec55207850df2918a6fe023248395a656f66933ee37187217e2e1cd943ddd75f397e2885e0f2b3

flag 为:

1
moectf{CHTHOLLY_the_best__sukasuka_the_best}

Attachments_495

神秘 .lisp 文件,居然还有 .lisp 玩的

最后给了注释:

1
;;;;flagis"&Dx16Y!x3xYDlShWbQ5hmzWf3EZly6h8UwD#d-1-&#WlDHJaxM5qAzlPP"

大致浏览一下,发现这是一个简单的 base57 编码器

定义 alphabetAB#DEd@f&hi!klmnLMw3^5678N}PF|HIxyz012JKYZab%Q{SUVWX-pqrs

len 为长度,即 57

定义函数 divmod 返回 values 包括商和余数

定义加密函数 encode:字符串倒序转化为 256 进制大整数,再转为 57 进制数,最后输出密文

编写解密函数

1
2
3
4
5
6
7
8
9
alphabet = "AB#DEd@f&hi!klmnLMw3^5678N}PF|HIxyz012JKYZab%Q{SUVWX-pqrs"
enc = "&Dx16Y!x3xYDlShWbQ5hmzWf3EZly6h8UwD#d-1-&#WlDHJaxM5qAzlPP"

value = 0
for ch in enc:
value = value * len(alphabet) + alphabet.index(ch)

raw = value.to_bytes((value.bit_length() + 7) // 8, "big")
print(raw)

不对!

查看官方仓库发现题出错了

实际上注释应该是

1
;;;;flagis"6dsJ6lbHyhBXHfKnYdEddMU@0JKA^hlW0MMyx^3rpMM{{&Q-lnLrZBbbKJy"

无敌了

flag

1
moectf{woO0Oow_Y0u-ar3_th3_g0D_0f_LIIIISP!}

Attachments_512

查壳

810X345/1.png

UPX 壳,去掉

1
2
3
4
5
6
7
8
9
10
# ./upx.exe -d Art.exe
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2026
UPX 5.1.1 Markus Oberhumer, Laszlo Molnar & John Reiser Mar 5th 2026

File size Ratio Format Name
-------------------- ------ ----------- -----------
18944 <- 11264 59.46% win64/pe Art.exe

Unpacked 1 file
1
2
3
4
5
6
7
scanf("%s", Str1);
for ( i = 0; i <= 27; ++i )
v4[i] = Str1[i];
for ( i = 1; i <= 27; ++i )
Str1[i - 1] ^= (Str1[i - 1] % 17 + Str1[i]) ^ 0x19;
if ( !strcmp(Str1, &Str2) && (unsigned int)sub_401550(v4) )
puts("\nGood job!!! You know UPX and hash!!!");

Str1 为输入,然后诸位运算得到结果

1
Str1[i - 1] ^= (Str1[i - 1] % 17 + Str1[i]) ^ 0x19;

那么

1
Str1[i] = (Str1[i - 1] ^ 0x19) - (Str1[i - 1] % 17)
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
_BOOL8 __fastcall sub_401550(const char *a1)
{
size_t v1; // rax
_BYTE v3[112]; // [rsp+20h] [rbp-A0h] BYREF
_BYTE Buf2[44]; // [rsp+90h] [rbp-30h] BYREF
int v5; // [rsp+BCh] [rbp-4h]

v5 = 1;
sub_401B66(v3);
v1 = strlen(a1);
sub_401BE0(v3, a1, v1);
sub_401C7D(v3, Buf2);
return memcmp(&unk_404040, Buf2, 0x20u) == 0;
}

// 初始化
__int64 __fastcall sub_401B66(__int64 a1)
{
*(_DWORD *)(a1 + 64) = 0;
*(_QWORD *)(a1 + 72) = 0;
*(_DWORD *)(a1 + 80) = 1779033703;
*(_DWORD *)(a1 + 84) = -1150833019;
*(_DWORD *)(a1 + 88) = 1013904242;
*(_DWORD *)(a1 + 92) = -1521486534;
*(_DWORD *)(a1 + 96) = 1359893119;
*(_DWORD *)(a1 + 100) = -1694144372;
*(_DWORD *)(a1 + 104) = 528734635;
*(_DWORD *)(a1 + 108) = 1541459225;
return a1;
}

__int64 __fastcall sub_401BE0(__int64 init_hash, __int64 str, unsigned __int64 len)
{
__int64 result; // rax
unsigned int i; // [rsp+2Ch] [rbp-4h]

for ( i = 0; ; ++i )
{
result = i;
if ( len <= i )
break;
*(_BYTE *)(init_hash + (unsigned int)(*(_DWORD *)(init_hash + 64))++) = *(_BYTE *)(str + i);
if ( *(_DWORD *)(init_hash + 64) == 64 )
{
sub_401710(init_hash, init_hash);
*(_QWORD *)(init_hash + 72) += 512LL;
*(_DWORD *)(init_hash + 64) = 0;
}
}
return result;
}


_BYTE *__fastcall sub_401C7D(__int64 a1, __int64 a2)
{
unsigned int v2; // eax
unsigned int v3; // eax
_BYTE *result; // rax
unsigned int v5; // [rsp+2Ch] [rbp-4h]
unsigned int v6; // [rsp+2Ch] [rbp-4h]
unsigned int v7; // [rsp+2Ch] [rbp-4h]
unsigned int i; // [rsp+2Ch] [rbp-4h]

v5 = *(_DWORD *)(a1 + 64);
if ( v5 > 0x37 )
{
v7 = v5 + 1;
*(_BYTE *)(a1 + *(unsigned int *)(a1 + 64)) = 0x80;
while ( v7 <= 0x3F )
{
v3 = v7++;
*(_BYTE *)(a1 + v3) = 0;
}
sub_401710(a1, a1);
memset((void *)a1, 0, 0x38u);
}
else
{
v6 = v5 + 1;
*(_BYTE *)(a1 + *(unsigned int *)(a1 + 64)) = 0x80;
while ( v6 <= 0x37 )
{
v2 = v6++;
*(_BYTE *)(a1 + v2) = 0;
}
}
*(_QWORD *)(a1 + 72) += (unsigned int)(8 * *(_DWORD *)(a1 + 64));
*(_BYTE *)(a1 + 63) = *(_QWORD *)(a1 + 72);
*(_BYTE *)(a1 + 62) = BYTE1(*(_QWORD *)(a1 + 72));
*(_BYTE *)(a1 + 61) = BYTE2(*(_QWORD *)(a1 + 72));
*(_BYTE *)(a1 + 60) = BYTE3(*(_QWORD *)(a1 + 72));
*(_BYTE *)(a1 + 59) = BYTE4(*(_QWORD *)(a1 + 72));
*(_BYTE *)(a1 + 58) = (unsigned __int16)WORD2(*(_QWORD *)(a1 + 72)) >> 8;
*(_BYTE *)(a1 + 57) = BYTE6(*(_QWORD *)(a1 + 72));
*(_BYTE *)(a1 + 56) = HIBYTE(*(_QWORD *)(a1 + 72));
result = (_BYTE *)sub_401710(a1, a1);
for ( i = 0; i <= 3; ++i )
{
*(_BYTE *)(a2 + i) = *(_DWORD *)(a1 + 80) >> (-8 * i + 24);
*(_BYTE *)(i + 4 + a2) = *(_DWORD *)(a1 + 84) >> (-8 * i + 24);
*(_BYTE *)(i + 8 + a2) = *(_DWORD *)(a1 + 88) >> (-8 * i + 24);
*(_BYTE *)(i + 12 + a2) = *(_DWORD *)(a1 + 92) >> (-8 * i + 24);
*(_BYTE *)(i + 16 + a2) = *(_DWORD *)(a1 + 96) >> (-8 * i + 24);
*(_BYTE *)(i + 20 + a2) = *(_DWORD *)(a1 + 100) >> (-8 * i + 24);
*(_BYTE *)(i + 24 + a2) = *(_DWORD *)(a1 + 104) >> (-8 * i + 24);
result = (_BYTE *)(i + 28 + a2);
*result = *(_DWORD *)(a1 + 108) >> (-8 * i + 24);
}
return result;
}

这个为计算 SHA-256 哈希

具体过程为:sub_401B66 初始化,sub_401BE0 处理字符串每 64 个就执行 sub_401710 压缩,具体压缩过程略过,最后调用 sub_401C7D

和最终储存的 &unk_404040 比较

储存为

1
40f53ff2b934da894ed3ff1f6e76c68a30ed92bcf764e839d5b31196d0899287

那这个主函数主要是进行了一个逐字节的异或运算,然后计算 SHA-256 哈希,分别和预设值比较,反过来只需要通过异或值反推答案再计算 SHA-256 查看是否正确即可

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

target = bytes.fromhex("02180ff8190427d8eb0035484d2a456b592e4301185c09090909b57d")
sha = bytes.fromhex("40f53ff2b934da894ed3ff1f6e76c68a30ed92bcf764e839d5b31196d0899287")

cands = [bytearray(target)]
for i in range(27, 0, -1):
new = []
for buf in cands:
nxt = buf[i]
want = buf[i - 1]
for x in range(256):
if (x ^ ((nxt + (x % 0x11)) & 0xff) ^ 0x19) == want:
nb = bytearray(buf)
nb[i - 1] = x
new.append(nb)
cands = new

for buf in cands:
flag = bytes(buf)
if hashlib.sha256(flag).digest() == sha:
print(flag.decode())

得到 flag

1
moectf{Art_i5_b14s7ing!!!!!}

Attachments_511

apk 文件,解压看看,存在 classes*.dex,这是 Android 的 Dalvik 字节码文件,使用 jadx 反编译工具可以将其反编译为 Java 源代码。

直接搜索发现存在 flag

1551X737/2.png

flag 为

1
moectf{Andr01d_1s_so00oo_e@sy_t0_cr4ck!!!}

Attachments_513

自定义 base64

1
2
3
4
5
6
7
8
9
strcpy(base64, "1wX/yRrA4RfR2wj72Qv52x3L5qa=");
printf("Welcome to moectf,plz input your flag!\n");
gets_0(inp);
base64_decode(base64, de64);
if ( !strcmp(de64, inp) )
printf("great!");
else
printf("wrong!");
gets_0(a);
1
.rdata:0000000140009000 aAbcdefghijklmn db 'abcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZ',0

编写脚本

1
2
3
4
5
6
7
8
import base64

custom = "abcdefghijklmnopqrstuvwxyz0123456789+/ABCDEFGHIJKLMNOPQRSTUVWXYZ"
standard = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
cipher = "1wX/yRrA4RfR2wj72Qv52x3L5qa="

translated = cipher.translate(str.maketrans(custom, standard))
print(base64.b64decode(translated).rstrip(b"\x00").decode())

得到 flag

1
moectf{qwqbase_qwq}

Attachments_515

和上面那题 hash 差不多,不过这个是 SHA-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
from pathlib import Path
import struct

MASK = 0xffffffff

def rol(x, n):
return ((x << n) & MASK) | (x >> (32 - n))

def broken_digest(msg):
h = [0x39063906, 0xefcda233, 0x74c74c74, 0x55555555, 0xd33b4700]
k = [0xb1acb1dd, 0x23333333, 0x66666666, 0xca6272d6]
data = bytearray(msg) + b"\x80"
while len(data) % 64 != 56:
data.append(0)
data += (len(msg) * 8).to_bytes(8, "big")

for off in range(0, len(data), 64):
block = data[off:off + 64]
w = [int.from_bytes(block[i:i + 4], "big") for i in range(0, 64, 4)]
for i in range(16, 80):
w.append(rol(w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16], 2))

a, b, c, d, e = h
for i in range(80):
if i < 20:
f, kk = (b & c) ^ ((~b) & d), k[0]
elif i < 40:
f, kk = b ^ c ^ d, k[1]
elif i < 60:
f, kk = (b & c) ^ (b & d) ^ (c & d), k[2]
else:
f, kk = b ^ c ^ d, k[3]
a, b, c, d, e = (f + e + rol(a, 5) + kk + w[i]) & MASK, a, rol(b, 30), c, d
h = [(x + y) & MASK for x, y in zip(h, [a, b, c, d, e])]

return b"".join(x.to_bytes(4, "big") for x in h)

data = Path(__file__).with_name("Broken_hash.exe").read_bytes()
targets = [struct.unpack_from("<I", data, 0x3400 + 4 * i)[0] for i in range(0x58)]
charset = "".join(chr(i) for i in range(0x21, 0x7f))

flag = ""
for target in targets:
hits = [
ch for ch in charset
if int.from_bytes(broken_digest(ch.encode())[:4], "little") == target
]
assert len(hits) == 1
flag += hits[0]

print(flag)

得到 flag

1
moectf{F1nd_th3_SEH_7hen_B1a5t_My_Fla9_and_Y0u_Can_Get_A_Cup_Of_Milk_Tea_From_YunZh1Jun}

Attachments_518

NET 6,反编译得到:

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
internal class D_flate
{
// Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250
private static void Main()
{
int f = 0;
int[] flag = new int[]
{
109, 111, 101, 99, 116, 102, 123, 68, 95, 102,
108, 97, 116, 101, 95, 105, 115, 95, 67, 95,
115, 104, 97, 114, 112, 33, 125
};
Console.WriteLine("In music theory, there is a note that has the same pitch as D flat.");
Console.WriteLine("Do you know it?\nNow plz input your flag:");
string input = Console.ReadLine();
byte[] byteArray = Encoding.ASCII.GetBytes(input);
for (int i = 0; i < input.Length; i++)
{
if (flag[i] == (int)byteArray[i])
{
f++;
}
}
if (f == flag.Length)
{
Console.WriteLine("TTTTTQQQQQQLLLLLLL!!! This is your flag!");
return;
}
Console.WriteLine("QwQ, plz try again.");
}
}

这个 ASCII 储存 flag,编写脚本

1
2
3
4
5
6
arr = [
109, 111, 101, 99, 116, 102, 123, 68, 95, 102,
108, 97, 116, 101, 95, 105, 115, 95, 67, 95,
115, 104, 97, 114, 112, 33, 125,
]
print("".join(map(chr, arr)))

得到 flag

1
moectf{D_flate_is_C_sharp!}