【CTF对抗-2023 研究生国赛 re wp】此文章归类为:CTF对抗。
4道题做了3道,都不太方便纯静态,动调倒是都挺简单的. 最后一题unity的游戏没什么经验,不知道怎么下手,CE也没下就放弃了
打开ida发现看不全代码
1
2
3
4
5
6
7
8
9
|
int
__cdecl main(
int
argc,
const
char
**argv,
const
char
**envp)
{
int
result;
// eax
char
v4;
// [esp+0h] [ebp-1CCh]
sub_401020(
"please input your flag:"
, v4);
__asm { retn }
return
result;
}
|
汇编层面发现是利用call $+5
,内联了一个没有用的函数,同时加了垃圾指令
.text:00401560 83 C4 04 add esp, 4 .text:00401563 55 push ebp .text:00401564 E8 00 00 00 00 call $+5 .text:00401564 .text:00401569 .text:00401569 loc_401569: ; DATA XREF: _main+2B↓o .text:00401569 5D pop ebp .text:0040156A 48 dec eax .text:0040156B 83 C5 08 add ebp, (offset loc_401570+1 - offset loc_401569) .text:0040156E 55 push ebp .text:0040156F C3 retn .text:0040156F .text:0040156F _main endp ; sp-analysis failed .text:0040156F .text:00401570 ; --------------------------------------------------------------------------- .text:00401570 .text:00401570 loc_401570: ; DATA XREF: _main+2B↑o .text:00401570 08 5D 8D or [ebp-73h], bl
还有一些动态调试的api,我直接从0x401560 nop 到0x4015B8,把无关逻辑的混淆和动态都去掉了。得到的伪代码如下
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
|
int
__cdecl main(
int
argc,
const
char
**argv,
const
char
**envp)
{
int
i;
// ecx
int
v4;
// ecx
char
v6;
// [esp-8h] [ebp-1D4h]
char
v7;
// [esp-4h] [ebp-1D0h]
char
v8;
// [esp+0h] [ebp-1CCh]
char
flag[264];
// [esp+54h] [ebp-178h] BYREF
__int128 v10[4];
// [esp+15Ch] [ebp-70h]
int
v11;
// [esp+19Ch] [ebp-30h]
int
v12;
// [esp+1A0h] [ebp-2Ch]
int
v13;
// [esp+1A4h] [ebp-28h]
int
v14;
// [esp+1A8h] [ebp-24h]
int
v15;
// [esp+1ACh] [ebp-20h]
int
v16;
// [esp+1B0h] [ebp-1Ch]
int
v17;
// [esp+1B4h] [ebp-18h]
int
v18;
// [esp+1B8h] [ebp-14h]
int
v19[3];
// [esp+1BCh] [ebp-10h] BYREF
sub_401020(
"please input your flag:"
, v8);
v11 = 50462976;
v12 = 117835012;
v13 = 185207048;
v14 = 252579084;
v15 = 319951120;
v16 = 387323156;
v17 = 454695192;
v18 = 522067228;
v19[0] = 0;
v19[1] = 1241513984;
v19[2] = 0;
memset
(flag, 0, 0xC8u);
printf
(
"%s"
, (
char
)flag);
if
(
strlen
(flag) == 46 )
{
v10[0] = *(_OWORD *)flag;
v10[1] = *(_OWORD *)&flag[16];
v10[2] = *(_OWORD *)&flag[32];
sub_401370(46);
sub_401080(v19);
for
( i = 0; i < 64; ++i )
{
if
( i >= 46 )
break
;
*((_BYTE *)&v10[3] + i) = *((_BYTE *)v10 + i) ^ flag[i + 200];
}
v4 = 0;
while
( *((_BYTE *)&v10[3] + v4) == byte_403114[v4] )
{
if
( ++v4 >= 46 )
{
sub_401020(
"you get your flag,the flag is your input!"
, v7);
sub_401020(
"\n"
, v6);
getchar
();
return
0;
}
}
sub_401020(
"error\n"
, v7);
}
else
{
sub_401020(
"length error!"
, v7);
}
return
0;
}
|
发现奇怪的地方,*((_BYTE *)&v10[3] + i) = *((_BYTE *)v10 + i) ^ flag[i + 200];
flag[i+200]
是个很怪的地方,问题只能出在sub_401370
和sub_401080
上
1
2
3
4
|
void
sub_401370()
{
__asm { retn }
}
|
这是也是混淆,nop一下看看
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
|
int
__usercall sub_401370@<eax>(unsigned
__int8
*a1@<edx>, _DWORD *a2@<ecx>,
int
a3, unsigned
__int8
*a4)
{
int
v5;
// ecx
int
v6;
// esi
int
v7;
// ecx
int
v8;
// eax
int
v9;
// ecx
int
v10;
// eax
int
v11;
// ecx
int
v12;
// eax
int
v13;
// ecx
int
v14;
// eax
int
v15;
// ecx
int
v16;
// eax
int
v17;
// ecx
int
v18;
// eax
int
v19;
// ecx
int
v20;
// eax
int
v22;
// ecx
int
v23;
// eax
int
v24;
// ecx
int
v25;
// eax
int
v26;
// ecx
int
result;
// eax
v5 = a1[7] << 8;
v6 = *((unsigned
__int16
*)a1 + 1);
qmemcpy(a2,
"expand 32-byte k"
, 16);
v7 = a1[5] | ((a1[6] | v5) << 8);
a2[4] = *a1 | ((a1[1] | (v6 << 8)) << 8);
v8 = a1[10];
a2[5] = a1[4] | (v7 << 8);
v9 = a1[8] | ((a1[9] | ((v8 | (a1[11] << 8)) << 8)) << 8);
v10 = a1[14];
a2[6] = v9;
v11 = a1[12] | ((a1[13] | ((v10 | (a1[15] << 8)) << 8)) << 8);
v12 = a1[18];
a2[7] = v11;
v13 = a1[16] | ((a1[17] | ((v12 | (a1[19] << 8)) << 8)) << 8);
v14 = a1[22];
a2[8] = v13;
v15 = a1[20] | ((a1[21] | ((v14 | (a1[23] << 8)) << 8)) << 8);
v16 = a1[26];
a2[9] = v15;
v17 = a1[24] | ((a1[25] | ((v16 | (a1[27] << 8)) << 8)) << 8);
v18 = a1[30];
a2[10] = v17;
v19 = a1[29] | ((v18 | (a1[31] << 8)) << 8);
v20 = a1[28];
a2[11] = v20 | (v19 << 8);
v22 = *((unsigned
__int16
*)a4 + 1);
a2[12] = 1111;
v23 = a4[6];
a2[13] = *a4 | ((a4[1] | (v22 << 8)) << 8);
v24 = a4[4] | ((a4[5] | ((v23 | (a4[7] << 8)) << 8)) << 8);
v25 = a4[10];
a2[14] = v24;
v26 = a4[9] | ((v25 | (a4[11] << 8)) << 8);
result = a4[8];
a2[15] = result | (v26 << 8);
return
result;
}
|
很复杂的一个加密函数,但是从调用看和我们的输入无关,可以动调的情况下,这个函数可以不用看了
另一个函数也是很复杂的,但是去完上一个函数混淆后,回到main函数,重新f5,然后设置类型调整一下就可以看懂了
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
|
int
__cdecl main(
int
argc,
const
char
**argv,
const
char
**envp)
{
int
i;
// ecx
int
v4;
// ecx
char
v6;
// [esp-8h] [ebp-1D4h]
char
v7;
// [esp-4h] [ebp-1D0h]
char
v8;
// [esp+0h] [ebp-1CCh]
__m128i v9[4];
// [esp+14h] [ebp-1B8h] BYREF
char
flag[200];
// [esp+54h] [ebp-178h] BYREF
char
xor_array[64];
// [esp+11Ch] [ebp-B0h] BYREF
char
flag2[64];
// [esp+15Ch] [ebp-70h]
int
v13[8];
// [esp+19Ch] [ebp-30h] BYREF
int
v14[3];
// [esp+1BCh] [ebp-10h] BYREF
sub_401020(
"please input your flag:"
, v8);
v13[0] = 50462976;
v13[1] = 117835012;
v13[2] = 185207048;
v13[3] = 252579084;
v13[4] = 319951120;
v13[5] = 387323156;
v13[6] = 454695192;
v13[7] = 522067228;
v14[0] = 0;
v14[1] = 1241513984;
v14[2] = 0;
memset
(flag, 0,
sizeof
(flag));
printf
(
"%s"
, (
char
)flag);
if
(
strlen
(flag) == 46 )
{
*(_OWORD *)flag2 = *(_OWORD *)flag;
*(_OWORD *)&flag2[16] = *(_OWORD *)&flag[16];
*(_OWORD *)&flag2[32] = *(_OWORD *)&flag[32];
init((unsigned
__int8
*)v13, v9, 46, (unsigned
__int8
*)v14);
enc(v9, (
int
)xor_array);
for
( i = 0; i < 64; ++i )
{
if
( i >= 46 )
break
;
flag2[i + 48] = flag2[i] ^ xor_array[i];
}
v4 = 0;
while
( flag2[v4 + 48] == cipher[v4] )
{
if
( ++v4 >= 46 )
{
sub_401020(
"you get your flag,the flag is your input!"
, v7);
sub_401020(
"\n"
, v6);
getchar
();
return
0;
}
}
sub_401020(
"error\n"
, v7);
}
else
{
sub_401020(
"length error!"
, v7);
}
return
0;
}
|
这就可以看到,init函数和enc函数也就是上面分析的函数,主要是生成xor_array
数组,也与输入无关,那么动调可以直接得到。
主要的加密也就是flag和xor_array异或,直接动调就可以
exp:
1
2
3
4
5
6
7
8
9
10
|
xor_array
=
[
0xFF
,
0x24
,
0x3F
,
0xDA
,
0xBE
,
0xA9
,
0xB6
,
0xF7
,
0x12
,
0x8F
,
0x29
,
0xD0
,
0x73
,
0xF7
,
0xF7
,
0xA2
,
0x83
,
0xAD
,
0x5F
,
0xB0
,
0x51
,
0x90
,
0x3F
,
0x68
,
0xF6
,
0x8C
,
0xC1
,
0x0A
,
0xB7
,
0xB5
,
0xBC
,
0x82
,
0xCC
,
0xFC
,
0x67
,
0xDE
,
0xE9
,
0xFF
,
0x5B
,
0xCB
,
0xC9
,
0x67
,
0xEA
,
0xF6
,
0xA6
,
0x1A
,
0x39
,
0x56
,
0xCA
,
0x23
,
0x46
,
0xE3
,
0xC8
,
0x71
,
0x43
,
0x53
,
0xFF
,
0x72
,
0x2F
,
0xC3
,
0x5C
,
0x1C
,
0x5B
,
0x94
]
cipher
=
[
0x99
,
0x48
,
0x5E
,
0xBD
,
0xC5
,
0x9B
,
0x85
,
0x96
,
0x20
,
0xFC
,
0x18
,
0xB2
,
0x00
,
0xC5
,
0xDA
,
0xC0
,
0xB1
,
0xC8
,
0x6C
,
0x81
,
0x63
,
0xBD
,
0x09
,
0x50
,
0xC2
,
0xBB
,
0xEC
,
0x33
,
0xD6
,
0xD7
,
0x8F
,
0xAF
,
0xAD
,
0xCE
,
0x14
,
0xED
,
0x8C
,
0xCE
,
0x6F
,
0xA9
,
0xA8
,
0x02
,
0x8C
,
0x90
,
0x94
,
0x67
]
for
i
in
range
(
len
(cipher)):
cipher[i] ^
=
xor_array[i]
print
(bytes(cipher))
|
flag: flag{23a2s1bs2-b2e312-6847-9ab3-a2s3e14baeff2}
ida打开后分析,发现是将真实的逻辑分割成6个部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
int
__cdecl main_0(
int
argc,
const
char
**argv,
const
char
**envp)
{
__CheckForDebuggerJustMyCode(&unk_D8E0A7);
val1[0] = ret_address((
int
)j_part_1);
// 将函数J_program_start地址传递到val1中
// 读取flag
j_add_address(val1[0], (
int
)part_2, val2);
// 添加part_2地址到val1+4位置
// flag length
j_add_address(val1[0], (
int
)j_part_3, val3);
// 添加part_3地址到val1+8位置
// arr1 ^= arr2
j_add_address(*(_DWORD *)(val1[0] + 4), (
int
)j_part_4, val2);
// 添加part_4地址到val1+8的位置
// arr1等于打乱后的flag
j_add_address(*(_DWORD *)(val1[0] + 4), (
int
)j_part_5, val3);
// 添加part_4地址到val1+12的位置
// rc4 key=GoodLuck
j_add_address(*(_DWORD *)(val1[0] + 8), (
int
)part_6, val2);
// 添加part_4地址到val1+12的位置
// strcmp(arr1, cipher)
run(val1[0]);
return
0;
}
|
将6个部分插入到二叉树中,最后run函数是前序遍历,实际上6个部分可以通过各下一个断点,然后运行来调试出执行顺序。但是顺序是val2和val3来控制,所以需要注意这两个变量,通过交叉引用可以查到到这两个变量果然有一个反调试
1
2
3
4
5
6
7
8
9
10
|
void
*__thiscall check_debug(
void
*
this
)
{
__CheckForDebuggerJustMyCode(&unk_D8E0A7);
if
( sub_D8110E() )
// 判断是否调试
{
val2 = 1;
val3 = 0;
}
return
this
;
}
|
虽然我不知道这个函数到底是哪里调用的,很怪,但是就是会偷偷改调用顺序
调试可以发现调用顺序是
1
2
3
4
5
6
|
part1
part2
part4
part5
part3
part6
|
1,2是输入,检查输入长度,6是对比密文,关键就是4,5,3,具体的功能来分析一下
将flag打乱后放到arr1数组中
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
|
int
part_4()
{
int
result;
// eax
int
i;
// [esp+D0h] [ebp-A0h]
int
v2[37];
// [esp+DCh] [ebp-94h]
result = __CheckForDebuggerJustMyCode(&unk_D8E0A7);
v2[0] = 4;
v2[1] = 19;
v2[2] = 9;
v2[3] = 35;
v2[4] = 34;
v2[5] = 1;
v2[6] = 24;
v2[7] = 14;
v2[8] = 5;
v2[9] = 0;
v2[10] = 18;
v2[11] = 31;
v2[12] = 21;
v2[13] = 16;
v2[14] = 11;
v2[15] = 29;
v2[16] = 12;
v2[17] = 2;
v2[18] = 30;
v2[19] = 13;
v2[20] = 3;
v2[21] = 15;
v2[22] = 8;
v2[23] = 7;
v2[24] = 17;
v2[25] = 32;
v2[26] = 33;
v2[27] = 6;
v2[28] = 25;
v2[29] = 20;
v2[30] = 26;
v2[31] = 10;
v2[32] = 23;
v2[33] = 22;
v2[34] = 27;
v2[35] = 28;
for
( i = 0; i < 34; ++i )
{
*(&arr1 + i) = flag[v2[i]];
result = i + 1;
}
return
result;
}
|
代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
int
part_5()
{
size_t
v0;
// eax
char
Str[128];
// [esp+190h] [ebp-29Ch] BYREF
int
v3;
// [esp+210h] [ebp-21Ch]
char
v4[264];
// [esp+21Ch] [ebp-210h] BYREF
int
key_stream[65];
// [esp+324h] [ebp-108h] BYREF
__CheckForDebuggerJustMyCode(&unk_D8E0A7);
j_memset(key_stream, 0, 0x100u);
j_memset(v4, 0, 0x100u);
v3 = 36;
j_memset(&Str[20], 0, 0x64u);
strcpy
(Str,
"GoodLuck"
);
v0 = j_strlen(Str);
j_rc4_init(key_stream, v4, Str, v0);
return
rc4_crypt((
int
)key_stream, (
int
)&arr1, &arr1);
}
|
分析后可以知道是rc4加密,密钥是GoodLuck
1
2
3
4
5
6
7
8
9
10
11
12
13
|
int
part_3()
{
int
result;
// eax
int
i;
// [esp+D0h] [ebp-8h]
result = __CheckForDebuggerJustMyCode(&unk_D8E0A7);
for
( i = 0; i < 33; ++i )
{
*(&arr1 + i) ^= arr2[i];
result = i + 1;
}
return
result;
}
|
这里的arr2其实就是arr1的下一位
所以其实这个加密可以写成
1
2
|
for
i
in
range
(
len
(arr1)
-
1
):
arr1[i] ^
=
arr1[i
+
1
]
|
所以解密就先解密3,再5,再4就可以
exp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
from
Crypto.Cipher
import
ARC4
c
=
[
0x2C
,
0x40
,
0xCE
,
0x88
,
0xEA
,
0xB3
,
0xA7
,
0xFA
,
0xBE
,
0xE3
,
0x32
,
0xD9
,
0x8B
,
0xE4
,
0x1C
,
0x77
,
0xFC
,
0xD4
,
0x76
,
0xAB
,
0x87
,
0x41
,
0xB0
,
0xCE
,
0xF5
,
0x5E
,
0x61
,
0x86
,
0xA8
,
0xCF
,
0x71
,
0x99
,
0x5C
,
0xB1
]
# part3
for
i
in
range
(
len
(c)
-
1
,
0
,
-
1
):
c[i
-
1
] ^
=
c[i]
# part5
key
=
b
'GoodLuck'
rc4
=
ARC4.new(key
=
key)
c
=
rc4.decrypt(bytes(c))
# part4
box
=
[
4
,
19
,
9
,
1
,
24
,
14
,
5
,
0
,
18
,
31
,
21
,
16
,
11
,
29
,
12
,
2
,
30
,
13
,
3
,
15
,
8
,
7
,
17
,
32
,
33
,
6
,
25
,
20
,
26
,
10
,
23
,
22
,
27
,
28
]
flag
=
[
0
]
*
34
for
i
in
range
(
len
(c)):
flag[box[i]]
=
c[i]
print
(bytes(flag))
|
这是一道golang语言编程的题目
搜索字符串,发现base64字符表,然后在程序中查找base64相关函数,发现在main_thirdChall中有一个base64字符串
1
2
3
4
5
6
7
8
9
10
11
12
|
.text:00000000003FFAE0 E8 FB 06 FE FF call encoding_base64___Encoding__EncodeToString
.text:00000000003FFAE0
.text:00000000003FFAE5 48 83 FB 28 cmp rbx, 28h ;
'('
.text:00000000003FFAE9 75 19 jnz
short
loc_3FFB04
.text:00000000003FFAE9
.text:00000000003FFAEB 48 8D 1D 30 49 02 00 lea rbx, aReftq1rge2hhc2 ;
"REFTQ1RGe2hhc2FraS1wZHR6cHR6LXZ4bmZudX0"
...
.text:00000000003FFAF2 B9 28 00 00 00 mov ecx, 28h ;
'('
.text:00000000003FFAF7 E8 44 2D F6 FF call runtime_memequal
.text:00000000003FFAF7
.text:00000000003FFAFC 0F 1F 40 00 nop dword ptr [rax+00h]
.text:00000000003FFB00 84 C0 test al, al
.text:00000000003FFB02 75 61 jnz
short
loc_3FFB65
|
直接解码上图base64的密文(REFTQ1RGe2hhc2FraS1wZHR6cHR6LXZ4bmZudX0=
)得到一个flagDASCTF{hasaki-pdtzptz-vxnfnu}
但是这个flag不对,其实只有中间那部分不对。
这是main_thirdChall前面还有main_firstChall和main_secondChall
查看前面的代码,main_firstChall有检查长度的部分
1
2
3
4
5
6
|
if
( v0 != 6 )
{
fmt_Fprintln();
main_menu();
return
0LL;
}
|
运行程序后,测试多次发现输入DASCTF{hasaki-pdtzptz-vxnfnu}
中的第一个部分hasaki
可以通过
实际上看汇编或者动态调试分析main_firstChall是输入6个字符,rot13后和密文对比
1
2
3
4
5
6
|
if
( v4 != 6 || *(_DWORD *)v3 !=
'nfnu'
|| *(_WORD *)(v3 + 4) !=
'vx'
)
// unfnxv 调试发现是rot13后的结果
{
fmt_Fprintln();
main_menu();
return
v9;
}
|
unfnxv
rot13加密后得到的就是hasaki
,所以第一关的输入就是hasaki
然后第二部分测试后发现输入DASCTF{hasaki-pdtzptz-vxnfnu}
中的第三个部分vxnfnu
可以通过。
一定要分析的话,下断点到对比密文的地方就可以了,因为不加密输入,而是加密其他数据,然后和输入对比,所以动调就可以得到正确的输入是vxnfnu
1
2
|
if
( v8 == a1 && (unsigned
__int8
)runtime_memequal() )
// vxnfnu
return
runtime_slicerunetostring();
|
然后第三个部分要求输入是29个字符,然后处理输入后base64加密对比密文,可以想到输入就是类似DASCTF{hasaki-pdtzptz-vxnfnu}
的形式,只是中间那一段被处理了,我们调试这一部分
然后下断点到base64加密前,然后开始调试
前两关就还是输入hasaki
,vxnfnu
,第三个直接输入错误的flag,通过调试看看差别在哪里
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
欢迎来到小林的世界
请选择您要进行的操作:
1. 自我介绍
2. 开始闯关
3. 关闭程序
请输入操作选项:2
有一天小林发现了一张古老地图,上面标记着一个传说中的宝藏。然而,为了找到宝藏,需要先找到一把铜钥匙。
:: hasaki
恭喜你找到了铜钥匙!
于是他开始沿着地图指示的路径进行探索。经过长时间的跋涉和寻找,他最终来到了一个神秘的洞穴。在洞穴中,他看到了一扇大门,门上有一个锁。他观察了一下锁孔,发现需要一把银钥匙才能打开。
:: vxnfnu
恭喜你找到了银钥匙!
于是他开始四处搜索,但是任何线索都没有找到金钥匙。这时候,他想起了地图上的一些细节,破解了一些谜题,得到了一些提示。这些提示指向了一个古老的祭坛,据说这里曾经有传说中的金钥匙。
:: DASCTF{hasaki-pdtzptz-vxnfnu}
|
然后查看内存,可以发现flag中间那段变了
1
2
3
|
000000C0000ABD20
07
00
00
00
00
00
00
00
44
41
53
43
54
46
7B
68
........DASCTF{h
000000C0000ABD30
61
73
61
6B
69
2D
75
69
79
65
75
79
65
2D
76
78
asaki
-
uiyeuye
-
vx
000000C0000ABD40
6E
66
6E
75
7D
00
00
00
AE
81
01
00
C0
00
00
00
nfnu}...........
|
我们输入的是pdtzptz
,变成了uiyeuye
像misc一样测试一下偏移,发现每个字符差5,就是凯撒密码,所以解一下cyberchef解一下凯撒可以得到flag
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
欢迎来到小林的世界
请选择您要进行的操作:
1.
自我介绍
2.
开始闯关
3.
关闭程序
请输入操作选项:
2
有一天小林发现了一张古老地图,上面标记着一个传说中的宝藏。然而,为了找到宝藏,需要先找到一把铜钥匙。
:: hasaki
恭喜你找到了铜钥匙!
于是他开始沿着地图指示的路径进行探索。经过长时间的跋涉和寻找,他最终来到了一个神秘的洞穴。在洞穴中,他看到了一扇大门,门上有一个锁。他观察了一下锁孔,发现需要一把银钥匙才能打开。
:: vxnfnu
恭喜你找到了银钥匙!
于是他开始四处搜索,但是任何线索都没有找到金钥匙。这时候,他想起了地图上的一些细节,破解了一些谜题,得到了一些提示。这些提示指向了一个古老的祭坛,据说这里曾经有传说中的金钥匙。
:: DASCTF{hasaki
-
kyoukou
-
vxnfnu}
恭喜你获取到了金钥匙,这就是最终的宝藏!
|
flag: DASCTF{hasaki-kyoukou-vxnfnu}
更多【CTF对抗-2023 研究生国赛 re wp】相关视频教程:www.yxfzedu.com