NPC²CTF 2025 决赛 WriteUp (Reverse & Misc 方向)

2.3k 词

无意间发现还有决赛,积极参加了,题目依然很有意思

Misc方向:

final_happy:

jpg图片,查看作者名发现是一个base64:aV9sb3ZlX3Jvc2U=,得到i_love_rose,用steghide输入密钥提取得到flag.txt,发现是一个wav文件,文件头的RIFF少了前三个字母,补上之后发现是摩斯电码:– ….- –. .—- -.-. -… -.– - …– … .—- … - …. . .– ….- -.–,得到m4g1cbyt3s1sthew4y,则flag为flag{m4g1c_byt3s_1s_the_w4y}.

情书:

先看flag.txt:****-/*—-/—-*/****-/****-/*—-/—**/*—-/****-/*—-/-****/***–/****-/*—-/—-*/**—/-****/**—/**—/***–/–***/****-/,是著名的2009贴吧情书,直接得到解密结果iloveyoutoo,瞪眼法发现给的情书文件大小是10240KB,说明是veracrypt硬盘镜像,用解密结果挂载,发现里面有一个flag.txt,内容是where is flag,但是仔细查看发现回收站里还有个小鹤.txt,还原得到:

1
2
3
4
5
ulpb vfde hfyz yisi buuima

key jqui xxmm vedrhx de qrpb xnxp

ulpb ui veyh dazide

查询得知这是小鹤双拼,还原得到:

双拼真的很有意思不是吗

key 就是下面这段话的全拼小写

双拼是这样打字的

flag{shuangpinshizheyangdazide}.

Reverse方向:

ezenc:

和XYCTF的题目一样,只是少了vm部分,提取arm部分字节码,单独dump后进行反编译:

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
__int64 __fastcall sub_0(__int64 a1)
{
v6 = 0x11451419;
v3[0] = MEMORY[0xBA0];
v3[1] = MEMORY[0xBA8];
v5 = v3;
for ( i = 0; ; i += 8 )
{
result = (unsigned int)i;
if ( i > 47 )
break;
v4 = (unsigned int *)(a1 + i);
v11 = *v4;
v10 = v4[1];
v8 = 0;
for ( j = 0; j <= 63; ++j )
{
v8 += v6;
v11 += ((v10 << 7) + *v5) ^ (v8 + 0x11223344) ^ v10 ^ ((v10 >> 4) + v5[1]);
v10 += (32 * v11 + v5[2]) ^ (v8 + 0x55667788) ^ v11 ^ ((v11 >> 6) + v5[3]);
}
*v4 = v11;
v4[1] = v10;
}
return result;
}

魔改tea,直接套脚本秒了:

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

encflag = bytearray.fromhex("636080EC4E731CB5E1098EB329AA7BE7B95C9DD242B58AF4F1248D5D5B4041712B473F0B17251FE8795D3EB5367E7AA1")
dwordenc = bytes_to_dwords_little_endian(encflag)
key = bytearray.fromhex("564E40696E6B65795F76735F6A6A6B6B")
dwordkey = bytes_to_dwords_little_endian(key)

delta = 0x11451419
result = []
for j in range(0, len(dwordenc)//2):
m1 = dwordenc[2*j]
m2 = dwordenc[2*j+1]
ttl = (0x40 * delta) & 0xFFFFFFFF
for j in range(0x40):
m2 = (m2 - (((m1 << 5) + dwordkey[2]) ^ (ttl + 0x55667788) ^ m1 ^ ((m1 >> 6) + dwordkey[3]))) & 0xFFFFFFFF
m1 = (m1 - (((m2 << 7) + dwordkey[0]) ^ (ttl + 0x11223344) ^ m2 ^ ((m2 >> 4) + dwordkey[1]))) & 0xFFFFFFFF
ttl = (ttl - delta) & 0xFFFFFFFF
result.append(m1)
result.append(m2)
flag = dwords_to_bytes_little_endian(result)
flagbytes = bytearray(flag)
print(flagbytes.decode())

flag{D0_you_l1ke_th3_m4gic_uN1c0rn_with_A4rch64}.

right_or_wrong:

有UPX,脱壳,发现main函数有很多跳转自身+1的花,去花,查看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
93
94
95
96
97
98
99
100
101
102
103
104
105
__int64 __fastcall main(int a1, char **a2, char **a3)
{
v42 = __readfsqword(0x28u);
std::operator<<<std::char_traits<char>>(&std::cout, "plz input your flag:", a3);
std::string::basic_string(v40);
std::operator>><char>(&std::cin, v40);
AppendChar((__int64)v41, (__int64)v40, 0xA);
std::string::operator=();
std::string::~string(v41);
InitVFTable((__int64)vftable);
std::allocator<char>::allocator(&v34);
LoadString((__int64)v41, (__int64)"right", (__int64)&v34); //创建了三个子程序
v3 = operator new(0x60uLL);
sub_403D58(v3, (__int64)v41, 1248728, &unk_419AE0); //管道通信
v35 = v3;
UpdVFTable((__int64)vftable, (__int64)&v35);
std::string::~string(v41);
std::allocator<char>::~allocator(&v34);
std::allocator<char>::allocator(&v34);
LoadString((__int64)v41, (__int64)"or", (__int64)&v34);
v4 = operator new(0x60uLL);
sub_403D58(v4, (__int64)v41, 14472, &unk_412360);
v35 = v4;
UpdVFTable((__int64)vftable, (__int64)&v35);
std::string::~string(v41);
std::allocator<char>::~allocator(&v34);
std::allocator<char>::allocator(&v34);
LoadString((__int64)v41, (__int64)"wrong", (__int64)&v34);
v5 = operator new(0x60uLL);
sub_403D58(v5, (__int64)v41, 16528, &unk_40E2C0);
v35 = v5;
UpdVFTable((__int64)vftable, (__int64)&v35);
std::string::~string(v41);
std::allocator<char>::~allocator(&v34);
for ( i = 0; i <= 2; ++i )
{
v6 = (_QWORD *)offset128(vftable, i);
Base32(*v6 + 8LL, &unk_409136); //这里是一个base32函数
v7 = (_QWORD *)offset128(vftable, i);
(*(void (__fastcall **)(_QWORD, const char *))(*(_QWORD *)*v7 + 8LL))(*v7, "1234567890123456"); //貌似是某种16字节密钥
v8 = (_QWORD *)offset128(vftable, i);
sub_4044E4(*v8); //这个函数和下面的404914、403EF8都有特殊的操作,此函数写入文件
v9 = (_QWORD *)offset128(vftable, i);
sub_404914(*v9); //此函数执行文件
}
v10 = *(_QWORD *)offset128(vftable, 0LL);
v11 = std::string::size(v40);
v12 = (const void *)std::string::c_str(v40);
sub_4046B0(v10, v12, v11);
v36 = (const void *)operator new[](0x400uLL);
v13 = (__int64 *)offset128(vftable, 0LL);
v32 = sub_4046E4(*v13, (__int64)v36, 0x400uLL);
if ( !v32 )
{
v15 = std::operator<<<std::char_traits<char>>(&std::cout, "failed", v14);
std::ostream::operator<<(v15, &std::endl<char,std::char_traits<char>>);
}
v16 = (__int64 *)offset128(vftable, 1LL);
sub_4046B0(*v16, v36, v32 - 1);
v17 = (__int64 *)offset128(vftable, 1LL);
v33 = sub_4046E4(*v17, (__int64)v36, 0x400uLL);
if ( !v33 )
{
v19 = std::operator<<<std::char_traits<char>>(&std::cout, "failed", v18);
std::ostream::operator<<(v19, &std::endl<char,std::char_traits<char>>);
}
v20 = (__int64 *)offset128(vftable, 2LL);
sub_4046B0(*v20, v36, v33);
v21 = (__int64 *)offset128(vftable, 2LL);
if ( !(unsigned int)sub_4046E4(*v21, (__int64)v36, 0x400uLL) )
{
v23 = std::operator<<<std::char_traits<char>>(&std::cout, "failed", v22);
std::ostream::operator<<(v23, &std::endl<char,std::char_traits<char>>);
}
for ( j = 0; j < (unsigned __int64)(std::string::size(v40) - 1); ++j )
{
if ( *((_BYTE *)v36 + j) != cipher[j] ) //比较
{
v24 = std::operator<<<std::char_traits<char>>(&std::cout, "wrong", cipher);
std::ostream::operator<<(v24, &std::endl<char,std::char_traits<char>>);
goto LABEL_20;
}
}
v26 = std::operator<<<std::char_traits<char>>(&std::cout, "right", v25);
std::ostream::operator<<(v26, &std::endl<char,std::char_traits<char>>);
v37 = vftable;
v34 = sub_405BB8(vftable);
v35 = sub_405C04((__int64)v37);
while ( (unsigned __int8)sub_405C54(&v34, &v35) )
{
v27 = *(void **)sub_405CB8((__int64)&v34);
v38 = v27;
v28 = v27;
if ( v27 )
{
sub_403EF8(v27);
operator delete(v28, 0x60uLL);
}
sub_405C94(&v34);
}
LABEL_20:
sub_405B16(vftable);
std::string::~string(v40);
return 0LL;
}

其中标红的三个函数都调用了sub_40437A:

1
2
3
4
5
6
7
8
9
10
11
__int64 __fastcall sub_40437A(__int64 a1, __int64 a2)
{
char v3[40]; // [rsp+10h] [rbp-40h] BYREF
unsigned __int64 v4; // [rsp+38h] [rbp-18h]

v4 = __readfsqword(0x28u);
sub_405544(v3, a2 + 8);
sub_405780(a1, "/tmp/", v3);
std::string::~string(v3);
return a1;
}

则执行的暂存文件被保存在/tmp/,访问该目录,提取这三个文件,分别为N5ZA0000(or)O5ZG63TH(wrong)OJUWO2DU(right),在加密过程中,这三个子程序按right or wrong的顺序对输入值进行了加密,先反编译right,发现是用go语言写的,找到了一个TEA加密流程,main函数:

1
2
3
4
5
p_main_tea = (main_tea *)runtime_newobject(&RTYPE_main_tea);
v76 = p_main_tea;
p_main_tea->rounds = 64LL;
if ( p_main_tea != (main_tea *)"satzuWDHqsQ9TtT9" )
qmemcpy(p_main_tea, "satzuWDHqsQ9TtT9", 16);

得到轮数64,密钥satzuWDHqsQ9TtT9,再看TEA函数:

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
// main.(*tea).Encrypt
void __golang main__ptr_tea_Encrypt(_ptr_main_tea a1, _slice_uint8 a2, _slice_uint8 a3)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

if ( a3.len <= 3 )
runtime_panicIndex(3LL, a2.ptr, a3.len, a2.cap);
if ( a3.len - 4 <= 3 )
runtime_panicIndex(3LL, a2.ptr, a3.len - 4, a2.cap);
cap = a2.cap;
v3 = _byteswap_ulong(*(_DWORD *)a3.ptr);
v4 = _byteswap_ulong(*(_DWORD *)&a3.ptr[((signed __int64)(4 - a3.cap) >> 63) & 4]);
v5 = _byteswap_ulong(*(_DWORD *)a1->key);
v6 = _byteswap_ulong(*(_DWORD *)&a1->key[4]);
v7 = _byteswap_ulong(*(_DWORD *)&a1->key[8]);
v8 = _byteswap_ulong(*(_DWORD *)&a1->key[12]);
v9 = 0LL;
for ( i = 0; ; i -= 0x61C88647 )
{
v12 = a1->rounds / 2;
if ( v9 >= v12 )
break;
v3 += (v5 + 16 * v4) ^ (i + v4 - 0x61C88647) ^ (v6 + (v4 >> 5));
v11 = (v7 + 16 * v3) ^ (i + v3 - 0x61C88647) ^ (v8 + (v3 >> 5));
++v9;
v4 += v11;
}
if ( a2.len <= 3 )
runtime_panicIndex(3LL, a2.ptr, a2.len, v12);
v13 = a2.len - 4;
*(_DWORD *)a2.ptr = _byteswap_ulong(v3);
if ( v13 <= 3 )
runtime_panicIndex(3LL, a2.ptr, v13, v12);
*(_DWORD *)&a2.ptr[((__int64)(4 - cap) >> 63) & 4] = _byteswap_ulong(v4);
}

是一个魔改TEA,继续查看or:

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 main(int a1, char **a2, char **a3)
{
v8 = __readfsqword(0x28u);
v5[0] = 1;
v5[1] = 2;
v5[2] = 3;
v5[3] = 4;
v5[4] = 5;
v5[5] = 6;
v5[6] = 7;
v5[7] = 8;
v5[8] = 9;
v5[9] = 10;
KSA((__int64)v6, (__int64)v5, 10);
v4 = read(0, buf, 0x3CuLL);
PRGA((__int64)v6, (__int64)buf, v4);
write(1, buf, v4);
return 0LL;
}
__int32 *__fastcall KSA(__int32 *sbox, __int32 *key, int len)
{
__int32 *result; // rax
unsigned __int32 v4; // edx
int v6; // [rsp+24h] [rbp-Ch]
int i; // [rsp+28h] [rbp-8h]
int j; // [rsp+2Ch] [rbp-4h]

for ( i = 0; i <= 255; ++i )
sbox[i] = i;
sbox[256] = 0;
result = sbox;
sbox[257] = 0;
v6 = 0;
for ( j = 0; j <= 255; ++j )
{
v4 = (sbox[257] + sbox[j] + key[v6]) >> 31;
sbox[257] = (unsigned __int8)(HIBYTE(v4) + *((_BYTE *)sbox + 1028) + LOBYTE(sbox[j]) + LOBYTE(key[v6])) - HIBYTE(v4);
swap(&sbox[j], &sbox[sbox[257]]);
result = (__int32 *)(unsigned int)((v6 + 1) / len);
v6 = (v6 + 1) % len;
}
return result;
}
__int32 *__fastcall PRGA(__int32 *sbox, __int8 *m, int len)
{
__int64 *v3; // kr00_8
__int64 v4; // kr08_8
__int32 *result; // rax
int i; // [rsp+30h] [rbp-10h]
int j; // [rsp+34h] [rbp-Ch]
int index; // [rsp+38h] [rbp-8h]

i = sbox[256];
j = sbox[257];
for ( index = 0; index < len; ++index )
{
i = (i + 1) % 256;
v3 = (__int64 *)(sbox[i] + j);
j = (unsigned __int8)(HIBYTE(v3) + LOBYTE(sbox[i]) + j) - HIBYTE(HIDWORD(v3));
swap(&sbox[i], &sbox[j]);
v4 = sbox[i] + sbox[j];
m[index] ^= sbox[(unsigned __int8)(HIBYTE(v4) + LOBYTE(sbox[i]) + LOBYTE(sbox[j])) - HIBYTE(HIDWORD(v4))];
m[index] ^= 0x5Cu;
}
sbox[256] = i;
result = sbox;
sbox[257] = j;
return result;
}

发现是一个额外异或0x5C的魔改RC4,继续查看wrong,发现有很多花,去花,反编译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __fastcall main(int argc, const char **argv, const char **envp)
{
int i; // [rsp+8h] [rbp-98h]
int v5; // [rsp+Ch] [rbp-94h]
char buf[136]; // [rsp+10h] [rbp-90h] BYREF
unsigned __int64 v7; // [rsp+98h] [rbp-8h]

v7 = __readfsqword(0x28u);
v5 = read(0, buf, 0x80uLL);
for ( i = 1; i < v5; ++i )
buf[i] ^= buf[i - 1];
buf[0] ^= buf[v5 - 1];
write(1, buf, v5);
return 0;
}

发现是一个简单异或,重构以上三种加密:

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
enc = bytearray.fromhex("DCB8877743ACAC9D2C94A4E2CFFCBA60FCE920134424A9D520362B42B9ED2944186757CD3747F9D8")

def wrong_enc(buf):
length = len(buf)
for i in range(1, length):
buf[i] ^= buf[i - 1]
buf[0] ^= buf[length - 1]
return bytearray(buf)

def wrong_dec(buf):
length = len(buf)
buf[0] ^= buf[length - 1]
for i in range(length - 1, 0, -1):
buf[i] ^= buf[i - 1]
return bytearray(buf)

def ksa(sbox, key):
for i in range(256):
sbox[i] = i
sbox[256] = 0
sbox[257] = 0
v6 = 0
for j in range(256):
sbox[257] = (sbox[257] + sbox[j] + key[v6]) & 0xFF
sbox[j], sbox[sbox[257]] = sbox[sbox[257]], sbox[j]
v6 = (v6 + 1) % len(key)
return sbox

def prga(sbox, m, a3):
i = sbox[256]
j = sbox[257]
for index in range(a3):
i = (i + 1) % 256
j = (sbox[i] + j) % 256
sbox[i], sbox[j] = sbox[j], sbox[i]
m[index] ^= sbox[(sbox[i] + sbox[j]) % 256]
m[index] ^= 0x5C
sbox[256] = i
sbox[257] = j
return bytes(m)

def or_rc4(key, data):
sbox = [0] * 258
sbox = ksa(sbox, key)
data = bytearray(data)
return prga(sbox, data, len(data))

def right_tea_enc(dwordenc, dwordkey):
delta = 0x9E3779B9
rounds = 32
result = []
for j in range(0, len(dwordenc) // 2):
m1 = dwordenc[2 * j]
m2 = dwordenc[2 * j + 1]
ttl = 0
for _ in range(rounds):
ttl = (ttl + delta) & 0xFFFFFFFF
print(hex(m1), hex(m2))
m1 = (m1 + ((dwordkey[0] + (m2 << 4)) ^ (ttl + m2) ^ (dwordkey[1] + (m2 >> 5)))) & 0xFFFFFFFF
m2 = (m2 + ((dwordkey[2] + (m1 << 4)) ^ (ttl + m1) ^ (dwordkey[3] + (m1 >> 5)))) & 0xFFFFFFFF

result.append(m1)
result.append(m2)
return result

def right_tea_dec(dwordenc, dwordkey):
delta = 0x9E3779B9
rounds = 32
result = []
for j in range(0, len(dwordenc) // 2):
m1 = dwordenc[2 * j]
m2 = dwordenc[2 * j + 1]
ttl = (delta * rounds) & 0xFFFFFFFF
for _ in range(rounds):
m2 = (m2 - (((m1 << 4) + dwordkey[2]) ^ (m1 + ttl) ^ ((m1 >> 5) + dwordkey[3]))) & 0xFFFFFFFF
m1 = (m1 - (((m2 << 4) + dwordkey[0]) ^ (m2 + ttl) ^ ((m2 >> 5) + dwordkey[1]))) & 0xFFFFFFFF
ttl = (ttl - delta) & 0xFFFFFFFF
result.append(m1)
result.append(m2)
return result

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

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

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

or_rc4key = [1,2,3,4,5,6,7,8,9,10]
right_teakey = b"satzuWDHqsQ9TtT9"
dwordkey = b2dbe(right_teakey)

print(enc)
step_wrong = wrong_dec(enc)
print(step_wrong)
step_or = or_rc4(or_rc4key, step_wrong)
print(step_or)
dwordenc = b2dbe(step_or)
step_right = right_tea_dec(dwordenc, dwordkey)
flag = d2bbe(step_right)
flagbytes = bytearray(flag)
print(flagbytes.decode())

flag{kDwydRjSsfBIc_NULDqluFic?wQfbpJan6}.