RCTF把web狗打疯了,不想学web了.jpg

好吧来学学逆向算了

open-source

直接给了源码

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
#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
if (argc != 4) {
printf("what?\n");
exit(1);
}

unsigned int first = atoi(argv[1]);
//51966
if (first != 0xcafe) {
printf("you are wrong, sorry.\n");
exit(2);
}

unsigned int second = atoi(argv[2]);
// 25
if (second % 5 == 3 || second % 17 != 8) {
printf("ha, you won't get it!\n");
exit(3);
}

//h4cky0u
if (strcmp("h4cky0u", argv[3])) {
printf("so close, dude!\n");
exit(4);
}

printf("Brr wrrr grr\n");

unsigned int hash = first * 31337 + (second % 17) * 11 + strlen(argv[3]) - 1615810207;

printf("Get your key: ");
printf("%x\n", hash);
return 0;
}

.\8b6405c25fe447fa804c6833a0d72808.exe 51966 25 h4cky0u即可

simple-unpack

ExeinfoPe看到是ELF文件,upx的壳,这几天有空的话可能会写一篇水文分析下upx

这里直接upx -d脱掉,ida打开在字符串里就能看到flag

logmein

ida 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
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
size_t v3; // rsi
int i; // [rsp+3Ch] [rbp-54h]
char s[36]; // [rsp+40h] [rbp-50h]
int v6; // [rsp+64h] [rbp-2Ch]
__int64 v7; // [rsp+68h] [rbp-28h]
char v8[8]; // [rsp+70h] [rbp-20h]
int v9; // [rsp+8Ch] [rbp-4h]

v9 = 0;
strcpy(v8, ":\"AL_RT^L*.?+6/46");
v7 = 28537194573619560LL;
v6 = 7;
printf("Welcome to the RC3 secure password guesser.\n", a2, a3);
printf("To continue, you must enter the correct password.\n");
printf("Enter your guess: ");
__isoc99_scanf("%32s", s);
v3 = strlen(s);
if ( v3 < strlen(v8) )
sub_4007C0();
for ( i = 0; i < strlen(s); ++i )
{
if ( i >= strlen(v8) )
sub_4007C0();
if ( s[i] != (char)(*((_BYTE *)&v7 + i % v6) ^ v8[i]) )
sub_4007C0();
}
sub_4007F0();
}
1
2
3
4
5
void __noreturn sub_4007C0()
{
printf("Incorrect password!\n");
exit(0);
}
1
2
3
4
5
void __noreturn sub_4007F0()
{
printf("You entered the correct password!\nGreat job!\n");
exit(0);
}
  • 直接修改c代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    #include<stdio.h>
    #include<string.h>
    #define _BYTE char

    int main(__int64 a1, char **a2, char **a3)
    {
    size_t v3; // rsi
    int i; // [rsp+3Ch] [rbp-54h]
    char s[36]; // [rsp+40h] [rbp-50h]
    int v6; // [rsp+64h] [rbp-2Ch]
    __int64 v7; // [rsp+68h] [rbp-28h]
    char v8[18]; // [rsp+70h] [rbp-20h]
    int v9; // [rsp+8Ch] [rbp-4h]

    v9 = 0;
    strcpy(v8, ":\"AL_RT^L*.?+6/46");
    v7 = 28537194573619560LL;
    v6 = 7;
    for ( i = 0; i < 17; ++i )
    {
    printf("%c",(char)(*((_BYTE *)&v7 + i % v6) ^ v8[i]));
    }
    return 0;
    }
  • 用python写
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    key1 = ":\"AL_RT^L*.?+6/46"
    key2="72616865626d6172616865626d61726168"
    # 28537194573619560的十六进制是0x65626d61726168,循环了两组+3个字节,所以是72616865626d6172616865626d61726168

    j = -2
    for i in range(0,17):
    temp = int('0x'+key2[j:][:2],16)
    j = j-2
    result = chr(temp ^ ord(key1[i]))
    print(result,end='')

insanity

32位的ELF文件,字符串里就有flag

python-trade

python字节码逆向

1
uncompyle6 f417c0d03b0344eb9969ed0e1f772091.pyc > source.py

得到源码

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
# uncompyle6 version 3.6.4
# Python bytecode 2.7 (62211)
# Decompiled from: Python 3.7.4 (default, Jul 11 2019, 10:43:21)
# [GCC 8.3.0]
# Embedded file name: 1.py
# Compiled at: 2017-06-03 10:20:43
import base64

def encode(message):
s = ''
for i in message:
x = ord(i) ^ 32
x = x + 16
s += chr(x)

return base64.b64encode(s)


correct = 'XlNkVmtUI1MgXWBZXCFeKY+AaXNt'
flag = ''
print 'Input flag:'
flag = raw_input()
if encode(flag) == correct:
print 'correct'
else:
print 'wrong'
# okay decompiling f417c0d03b0344eb9969ed0e1f772091.pyc

按程序逻辑解码即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import base64

def decode(message):
message = base64.b64decode(message)
flag=''
for i in message:
x = ord(i)
x -= 16
result = x^32
flag += chr(result)
return flag

message = 'XlNkVmtUI1MgXWBZXCFeKY+AaXNt'
print(decode(message))

game

一个小游戏,需要把所有的灯都打开

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
void main_0()
{
signed int i; // [esp+DCh] [ebp-20h]
int v1; // [esp+F4h] [ebp-8h]

sub_45A7BE(&unk_50B110);
sub_45A7BE(&unk_50B158);
sub_45A7BE(&unk_50B1A0);
sub_45A7BE(&unk_50B1E8);
sub_45A7BE(&unk_50B230);
sub_45A7BE(&unk_50B278);
sub_45A7BE(&unk_50B2C0);
sub_45A7BE(&unk_50B308);
sub_45A7BE(&unk_50AFD0);
sub_45A7BE("| by 0x61 |\n");
sub_45A7BE("| |\n");
sub_45A7BE("|------------------------------------------------------|\n");
sub_45A7BE(
"Play a game\n"
"The n is the serial number of the lamp,and m is the state of the lamp\n"
"If m of the Nth lamp is 1,it's on ,if not it's off\n"
"At first all the lights were closed\n");
sub_45A7BE("Now you can input n to change its state\n");
sub_45A7BE(
"But you should pay attention to one thing,if you change the state of the Nth lamp,the state of (N-1)th and (N+1)th w"
"ill be changed too\n");
sub_45A7BE("When all lamps are on,flag will appear\n");
sub_45A7BE("Now,input n \n");
while ( 1 )
{
while ( 1 )
{
sub_45A7BE("input n,n(1-8)\n");
sub_459418();
sub_45A7BE("n=");
sub_4596D4("%d", &v1);
sub_45A7BE("\n");
if ( v1 >= 0 && v1 <= 8 )
break;
sub_45A7BE("sorry,n error,try again\n");
}
if ( v1 )
{
sub_4576D6(v1 - 1);
}
else
{
for ( i = 0; i < 8; ++i )
{
if ( (unsigned int)i >= 9 )
j____report_rangecheckfailure();
byte_532E28[i] = 0;
}
}
j__system("CLS");
sub_458054();
if ( byte_532E28[0] == 1
&& byte_532E28[1] == 1
&& byte_532E28[2] == 1
&& byte_532E28[3] == 1
&& byte_532E28[4] == 1
&& byte_532E28[5] == 1
&& byte_532E28[6] == 1
&& byte_532E28[7] == 1 )
{
sub_457AB4();
}
}
}
  • 其中这一段是在判断所有的灯是否打开
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    if ( byte_532E28[0] == 1
    && byte_532E28[1] == 1
    && byte_532E28[2] == 1
    && byte_532E28[3] == 1
    && byte_532E28[4] == 1
    && byte_532E28[5] == 1
    && byte_532E28[6] == 1
    && byte_532E28[7] == 1 )
    {
    sub_457AB4();
    }
  • sub_457AB4会调用sub_45E940计算并输出flag

接下来方法有很多:

  • 修改汇编,运行至sub_457AB4();
  • 根据游戏规则,暴力穷举可行解
  • 根据sub_45E940的逻辑算出flag

这里用最快的第一种方法

找到对应汇编

找到对应下面这段代码的汇编

1
2
3
4
5
6
7
8
9
10
11
if ( byte_532E28[0] == 1
&& byte_532E28[1] == 1
&& byte_532E28[2] == 1
&& byte_532E28[3] == 1
&& byte_532E28[4] == 1
&& byte_532E28[5] == 1
&& byte_532E28[6] == 1
&& byte_532E28[7] == 1 )
{
sub_457AB4();
}
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
text:0045F5D1                 movzx   edx, byte_532E28[ecx]
.text:0045F5D8 cmp edx, 1
.text:0045F5DB jnz loc_45F671
.text:0045F5E1 mov eax, 1
.text:0045F5E6 shl eax, 0
.text:0045F5E9 movzx ecx, byte_532E28[eax]
.text:0045F5F0 cmp ecx, 1
.text:0045F5F3 jnz short loc_45F671
.text:0045F5F5 mov eax, 1
.text:0045F5FA shl eax, 1
.text:0045F5FC movzx ecx, byte_532E28[eax]
.text:0045F603 cmp ecx, 1
.text:0045F606 jnz short loc_45F671
.text:0045F608 mov eax, 1
.text:0045F60D imul ecx, eax, 3
.text:0045F610 movzx edx, byte_532E28[ecx]
.text:0045F617 cmp edx, 1
.text:0045F61A jnz short loc_45F671
.text:0045F61C mov eax, 1
.text:0045F621 shl eax, 2
.text:0045F624 movzx ecx, byte_532E28[eax]
.text:0045F62B cmp ecx, 1
.text:0045F62E jnz short loc_45F671
.text:0045F630 mov eax, 1
.text:0045F635 imul ecx, eax, 5
.text:0045F638 movzx edx, byte_532E28[ecx]
.text:0045F63F cmp edx, 1
.text:0045F642 jnz short loc_45F671
.text:0045F644 mov eax, 1
.text:0045F649 imul ecx, eax, 6
.text:0045F64C movzx edx, byte_532E28[ecx]
.text:0045F653 cmp edx, 1
.text:0045F656 jnz short loc_45F671
.text:0045F658 mov eax, 1
.text:0045F65D imul ecx, eax, 7
.text:0045F660 movzx edx, byte_532E28[ecx]
.text:0045F667 cmp edx, 1
.text:0045F66A jnz short loc_45F671
.text:0045F66C call sub_457AB4

可以看到这里进行了多次cmp比较,不符合要求就jnz跳走,全部符合就执行call sub_457AB4

那么直接在x64dbg或者ollydbg里把jnz改成nop就好

关闭aslr

由于aslr的存在,程序运行时会发生基址重定位。因此x64dbg和ollydbg里程序的基址和ida里是不同的,可以使用loadpe关掉exe的aslr

选择Characteristics,勾选第一项Relocation stripped然后save即可

修改汇编

把8个jnz都改成nop,运行到最后的call即可

Hello, CTF

反汇编

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
signed int v3; // ebx
char v4; // al
int result; // eax
int v6; // [esp+0h] [ebp-70h]
int v7; // [esp+0h] [ebp-70h]
char v8; // [esp+12h] [ebp-5Eh]
char v9[20]; // [esp+14h] [ebp-5Ch]
char v10; // [esp+28h] [ebp-48h]
__int16 v11; // [esp+48h] [ebp-28h]
char v12; // [esp+4Ah] [ebp-26h]
char v13; // [esp+4Ch] [ebp-24h]

strcpy(&v13, "437261636b4d654a757374466f7246756e");
while ( 1 )
{
memset(&v10, 0, 0x20u);
v11 = 0;
v12 = 0;
sub_40134B(aPleaseInputYou, v6);
scanf(aS, v9);
if ( strlen(v9) > 0x11 )
break;
v3 = 0;
do
{
v4 = v9[v3];
if ( !v4 )
break;
sprintf(&v8, asc_408044, v4);
strcat(&v10, &v8);
++v3;
}
while ( v3 < 17 );
if ( !strcmp(&v10, &v13) )
sub_40134B(aSuccess, v7);
else
sub_40134B(aWrong, v7);
}
sub_40134B(aWrong, v7);
result = stru_408090._cnt-- - 1;
if ( stru_408090._cnt < 0 )
return _filbuf(&stru_408090);
++stru_408090._ptr;
return result;
}

将用户输入转成16进制与437261636b4d654a757374466f7246756e对比,把437261636b4d654a757374466f7246756e转成字符串即可

getit

64位的ELF文件

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)
{
char v3; // al
__int64 v5; // [rsp+0h] [rbp-40h]
int i; // [rsp+4h] [rbp-3Ch]
FILE *stream; // [rsp+8h] [rbp-38h]
char filename[8]; // [rsp+10h] [rbp-30h]
unsigned __int64 v9; // [rsp+28h] [rbp-18h]

v9 = __readfsqword(0x28u);
LODWORD(v5) = 0;
while ( (signed int)v5 < strlen(s) ) // s = db 'c61b68366edeb7bdce3c6820314b7498',0
{
if ( v5 & 1 )
v3 = 1;
else
v3 = -1;
*(&t + (signed int)v5 + 10) = s[(signed int)v5] + v3;
LODWORD(v5) = v5 + 1;
}
strcpy(filename, "/tmp/flag.txt");
stream = fopen(filename, "w");
fprintf(stream, "%s\n", u, v5);
for ( i = 0; i < strlen(&t); ++i )
{
fseek(stream, p[i], 0);
fputc(*(&t + p[i]), stream);
fseek(stream, 0LL, 0);
fprintf(stream, "%s\n", u);
}
fclose(stream);
remove(filename);
return 0;
}

gdb调试下发现经过第一个whilet指向的字符串的就是flag….

程序的逻辑有空再写…(咕了)

re1

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 __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
__int128 v5; // [esp+0h] [ebp-44h]
__int64 v6; // [esp+10h] [ebp-34h]
int v7; // [esp+18h] [ebp-2Ch]
__int16 v8; // [esp+1Ch] [ebp-28h]
char v9; // [esp+20h] [ebp-24h]

_mm_storeu_si128((__m128i *)&v5, _mm_loadu_si128((const __m128i *)&xmmword_413E34));
v7 = 0;
v6 = qword_413E44;
v8 = 0;
printf(&byte_413E4C);
printf(&byte_413E60);
printf(&byte_413E80);
scanf("%s", &v9);
v3 = strcmp((const char *)&v5, &v9);
if ( v3 )
v3 = -(v3 < 0) | 1;
if ( v3 )
printf(aFlag);
else
printf((const char *)&unk_413E90);
system("pause");
return 0;
}

逻辑很明显,将用户输入v9v5对比,正确则输出aFlag

_mm_storeu_si128((__m128i *)&v5, _mm_loadu_si128((const __m128i *)&xmmword_413E34));类似于一个memset,将xmmword_413E34的值赋给v5。实际上xmmword_413E34就是flag,不过ida会将其识别为16进制数

用R指令转为字符串

因为是小端存储所以要倒过来

用x64dbg的话直接就会识别出字符串…

no-strings-attached

不知道为啥运行会报错..

1
2
3
4
5
6
7
8
int __cdecl main(int argc, const char **argv, const char **envp)
{
setlocale(6, &locale);
banner();
prompt_authentication();
authenticate();
return 0;
}
  • authenticate()是核心
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void authenticate()
{
int ws[8192]; // [esp+1Ch] [ebp-800Ch]
wchar_t *s2; // [esp+801Ch] [ebp-Ch]

s2 = decrypt(&s, &dword_8048A90);
if ( fgetws(ws, 0x2000, stdin) )
{
ws[wcslen(ws) - 1] = 0;
if ( !wcscmp(ws, s2) )
wprintf(&unk_8048B44);
else
wprintf(&unk_8048BA4);
}
free(s2);
}
  • decrypt()中会计算flag
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    wchar_t *__cdecl decrypt(wchar_t *s, wchar_t *a2)
    {
    size_t v2; // eax
    signed int v4; // [esp+1Ch] [ebp-1Ch]
    signed int i; // [esp+20h] [ebp-18h]
    signed int v6; // [esp+24h] [ebp-14h]
    signed int v7; // [esp+28h] [ebp-10h]
    wchar_t *dest; // [esp+2Ch] [ebp-Ch]

    v6 = wcslen(s);
    v7 = wcslen(a2);
    v2 = wcslen(s);
    dest = (wchar_t *)malloc(v2 + 1);
    wcscpy(dest, s);
    while ( v4 < v6 )
    {
    for ( i = 0; i < v7 && v4 < v6; ++i )
    dest[v4++] -= a2[i];
    }
    return dest;
    }
  • s2 = decrypt(&s, &dword_8048A90);对应的汇编是
    1
    2
    .text:08048720                 call    decrypt
    .text:08048725 mov [ebp+s2], eax

gdb 断在0x8048725

可以看到eax存储的是一个地址,该地址存储了一个以9开头的字符串,查看该地址即可得到flag

csaw2013reversing2

运行会出现乱码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // ecx
CHAR *lpMem; // [esp+8h] [ebp-Ch]
HANDLE hHeap; // [esp+10h] [ebp-4h]

hHeap = HeapCreate(0x40000u, 0, 0);
lpMem = (CHAR *)HeapAlloc(hHeap, 8u, MaxCount + 1);
memcpy_s(lpMem, MaxCount, &unk_409B10, MaxCount);
if ( sub_40102A() || IsDebuggerPresent() )
{
__debugbreak();
sub_401000(v3 + 4, (int)lpMem);
ExitProcess(0xFFFFFFFF);
}
MessageBoxA(0, lpMem + 1, "Flag", 2u);
HeapFree(hHeap, 0, lpMem);
HeapDestroy(hHeap);
ExitProcess(0);
}
  • 这里会通过IsDebuggerPresent()判断是否为调试器运行,正常运行就显示上面乱码的窗口
  • sub_401000(v3 + 4, (int)lpMem)将乱码转换为flag
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    unsigned int __fastcall sub_401000(int a1, int a2)
    {
    int v2; // esi
    unsigned int v3; // eax
    unsigned int v4; // ecx
    unsigned int result; // eax

    v2 = dword_409B38;
    v3 = a2 + 1 + strlen((const char *)(a2 + 1)) + 1;
    v4 = 0;
    result = ((v3 - (a2 + 2)) >> 2) + 1;
    if ( result )
    {
    do
    *(_DWORD *)(a2 + 4 * v4++) ^= v2;
    while ( v4 < result );
    }
    return result;
    }
    那么尝试x64dbg跟进去

这里的int3应该对应于ida反汇编出的__debugbreak();,会使程序在这里死循环,改成nop即可

运行完sub_401000得到flag

实际上一进到sub_401000 x64dbg就在第三行的注释那里显示了flag…但是跟一下程序逻辑会发现flag是在后面0x0040101F的循环中计算并写入0x23d05c9的(这个地址是堆的地址,每次都不同),看起来是x64dbg提前计算了出来?

maze

迷宫问题

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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
signed __int64 i; // rbx
signed int v4; // eax
bool status; // bp
bool result; // al
const char *v7; // rdi
__int64 v9; // [rsp+0h] [rbp-28h]

v9 = 0LL;
puts("Input flag:");
scanf("%s", &input, 0LL);
if ( strlen(&input) != 24 || strncmp(&input, "nctf{", 5uLL) || *(&byte_6010BF + 24) != '}' )
{
WRONG_FLAG:
puts("Wrong flag!");
exit(-1);
}
i = 5LL;
if ( strlen(&input) - 1 > 5 )
{
while ( 1 )
{
v4 = *(&input + i);
status = 0;
if ( v4 > 'N' )
{
v4 = (unsigned __int8)v4;
if ( (unsigned __int8)v4 == 'O' ) // 左
{
result = sub_400650((_DWORD *)&v9 + 1);
goto LABEL_14;
}
if ( v4 == 'o' ) // 右
{
result = sub_400660((int *)&v9 + 1);
goto LABEL_14;
}
}
else
{
v4 = (unsigned __int8)v4;
if ( (unsigned __int8)v4 == '.' ) // 上
{
result = sub_400670(&v9);
goto LABEL_14;
}
if ( v4 == '0' ) // 下
{
result = sub_400680((int *)&v9);
LABEL_14:
status = result;
goto LABEL_15;
}
}
LABEL_15:
if ( !(unsigned __int8)check((__int64)maze, SHIDWORD(v9), v9) )// 检查是否走到了*上
goto WRONG_FLAG;
if ( ++i >= strlen(&input) - 1 )
{
if ( status ) // 检查是否越界
break;
WRONG_FLAG2:
v7 = "Wrong flag!";
goto OUTPUT;
}
}
}
if ( maze[8 * (signed int)v9 + SHIDWORD(v9)] != '#' )// 检查是否到达终点
goto WRONG_FLAG2;
v7 = "Congratulations!";
OUTPUT:
puts(v7);
return 0LL;
}
  • asc_601060用字符串表示了一个8x8迷宫
    1
    db '  *******   *  **** * ****  * ***  *#  *** *** ***     *********',0
    转换成8x8迷宫
    1
    2
    3
    4
    5
    6
    7
    8
    9
    maze = "  *******   *  **** * ****  * ***  *#  *** *** ***     *********"

    for i in range(64):
    if i%8==0:
    print("\n")
    if maze[i]==' ':
    print('0',end='')
    else:
    print(maze[i],end='')
    1
    2
    3
    4
    5
    6
    7
    8
    00******
    *000*00*
    ***0*0**
    **00*0**
    *00*#00*
    **0***0*
    **00000*
    ********
  • v9表示当前所处位置,(int *)&v9是行,(int *)&v9 + 1HIDWORD(v9)是列

其中SHIDWORD是ida逆向常用的宏,定义为

1
#define SHIDWORD(x)  (*((int32*)&(x)+1))
  • 最后把迷宫的走法转成对应的字符就可以得到flag
    1
    2
    3
    4
    5
    6
    7
    8
    9
    str = "右下右右下下左下下下右右右右上上左左"
    str = str.replace('上', '.')
    str = str.replace('下', '0')
    str = str.replace('左', 'O')
    str = str.replace('右', 'o')

    str = 'nctf{' + str + '}'

    print(str)