跳转至

2019 CISCN 初赛

标签:VM, Go语言, DOS/MBR, wasm

国赛初赛总算打完了,成绩还算可以吧,就是时间有点不够,做题慢了,不然还能出一道的。第一天1道easyGo,第二天四道题,这谁顶得住啊。安卓题还是不会做,要赶紧学安卓咯!

where_u_are拿了个三血还是有丶开心

题目下载地址

easyGo

这题是由Go语言编译成的二进制文件。之前没做过go的题目,有点无从下手。参考了一下夜影师傅的一篇文章,用文章中类似的方法能在00000000004CEFB8找到提示语字符串”Please input you flag like flag{123} to judge:“,通过查找引用能在00000000004E1130附近找到其他字符串的引用。再次查找引用定位到sub_495150,这里是主要加密逻辑。不过单纯静态做也有点乱,动调之后看到在0000000000495318对比长度0x2A,在

.text:00000000004953A1 call sub_4023F0

下断点,RDX直接指向了flag

1
2
3
000000C0000181B0  66 6C 61 67 7B 39 32 30  39 34 64 61 66 2D 33 33  flag{92094daf-33
000000C0000181C0  63 39 2D 34 33 31 65 2D  61 38 35 61 2D 38 62 66  c9-431e-a85a-8bf
000000C0000181D0  62 64 35 64 66 39 38 61  64 7D 00 00 00 00 00 00  bd5df98ad}......

之后仔细看看也能看到base64密文在00000000004CF8B6,加密表在000000C00006C580

1
2
3
4
5
6
import base64
a = "6789_-abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012345"
b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
base_fix = "tGRBtXMZgD6ZhalBtCUTgWgZfnkTgqoNsnAVsmUYsGtCt9pEtDEYsql3"
table = base_fix.maketrans(a, b)
print(base64.b64decode(base_fix.translate(table)))

bbvvmm

拿flag需要解出username和password,nc上去拿flag。

username

username用国密SM4加密,其中

key = 'DA98F1DA312AB753A5703A0BFD290DD6'.decode('hex')

cipher = 'RVYtG85NQ9OPHU4uQ8AuFM+MHVVrFMJMR8FuF8WJQ8Y='

很明显是base64,但其实加密表也被替换了,在0000000000406C20

username的加密就是标准的国密加密。踩坑警告:不要用gmssl,用pysm4

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import base64
import string
from pysm4 import *
a = "IJLMNOPKABDEFGHCQRTUVWXSYZbcdefa45789+/6ghjklmnioprstuvqwxz0123y"
b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
base_fix = "RVYtG85NQ9OPHU4uQ8AuFM+MHVVrFMJMR8FuF8WJQ8Y="
key = int('DA98F1DA312AB753A5703A0BFD290DD6',16)
table = string.maketrans(a, b)
cipher = int(base64.b64decode(base_fix.translate(table)),16)
plain = decrypt(cipher,key)
print(hex(plain)[2:-1].decode('hex').decode('hex'))

password

虚拟机题

vm结构体:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
00000000 vm              struc ; (sizeof=0x4D0, mappedto_10)
00000000 reg             dd 12 dup(?)
00000030 bytecode_base   dd ?
00000034 zero            dd ?
00000038 _ip             dq ?                    ; offset
00000040 func            handle 73 dup(?)
000004D0 vm              ends
000004D0
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 handle          struc ; (sizeof=0x10, mappedto_11)
00000000                                         ; XREF: vm/r
00000000 num             dd ?
00000004 zero            dd ?
00000008 func            dq ?                    ; offset
00000010 handle          ends

opcode反编译:

 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
B0 19 00 00 00          push 0x19
B5 0A                   pop rA
B2 0B                   push rB
B4 09                   pop r9[rA]
B0 1A 00 00 00          push 0x1A
B5 0A                   pop rA
04 0B 09                mov r9[rA], rB
B0 1A 00 00 00          push 0x1A
B5 0A                   pop rA
B2 0B                   push rB
B4 09                   pop r9[rA]
90 C2 00 00 00          jmp short 0xC2
91                      nop
loc_00:---------------------------------
01 1A 00 00 00 0A       mov r5, 0x1A
02 09 00                mov r0, r9[rA]
10 09 30 00 00 00 01    lea r1, r9[0x30]
B2 01                   push r1
B2 00                   push r0
C0                      mov arg0, arg1[arg0]
B5 00                   pop r0
B0 F4 FF FF FF          push 0xfffffff4         ;-12
B5 0A                   pop rA
B1 00                   push r0[rA]
B5 01                   pop r1
01 1A 00 00 00 0A       mov rA, 0x1A
B1 09                   push r9[rA]
B5 00                   pop r0
10 00 78 00 00 00 00    lea r0, r0[0x78]
70 00 FF 00 00 00 00    and r0, 0xff
50 00 18 00 00 00 00    shl r0, r0, 0x18
B2 00                   push r0
B0 18 00 00 00          push 18
C8                      shr arg1,arg0
B5 00                   pop r0
B2 01                   push r1
B2 00                   push r0
C3                      xor arg0,arg1
B5 00                   pop r0
50 00 18 00 00 00 00    shl r0, r0, 0x18
B2 00                   push 0x18
B0 18 00 00 00          push 0x18
C8                      shr arg1,arg0
B5 00                   pop r0
70 00 FF 00 00 00 01    and r0, r1, 0xff
01 19 00 00 00 0A       mov rA, 0x19
02 09 00                mov r0, r9[rA]
11 01 00 00             lea r0, r1[r0]
loc_01:----------------------------------
B0 19 00 00 00          push 0x19
B5 0A                   pop rA
B2 00                   push r0
B4 09                   pop rA[r9]
01 1A 00 00 00 0A       mov rA, 0x1A
B1 09                   push r9
B5 00                   pop r0
10 00 01 00 00 00 00    lea r0, r0[1]
01 1A 00 00 00 0A       mov rA, 0x1A
04 00 09                mov r9[rA], r0
B0 1A 00 00 00          push 0x1A
B5 0A                   pop rA
02 09 00                mov r0, r9[rA]
86 00 06 00 00 00 00    cmp r0, r0, 0x06
88 00 26 00 00 00       jb loc_00
91                      nop
FF                      exit

这部分算法很简单,不过直接看opcode会有点晕,建议直接调试,对着主要结构体看不难发现直接异或了0x78开始的6个字节,最后相加要为0。

远程打不通,可能是read读了回车。

1
2
3
4
5
6
7
from pwn import *
p = remote('39.106.224.151','10001')
print(p.recv())
p.sendline('badrer12')
print(p.recv())
p.send('xyz{|}')
print(p.recv())

strang_int

一开始不知道时啥架构的,后面放到linux里面file一下可以看到是DOS/MBR程序,直接用默认x86打开就行了。这题如果对操作系统比较熟悉的话做起来会上手一点。

刚开始的一段代码是十六进制的,第一行的jmp其实跳的就是下一行,因为MBR加载的位置在0x7C00,之后初始化了一些东西,不用太关心。

接下来0x01FE的代码是32进制的。注意到刚开始将ds赋值成了10h,所以接下来的寻址都要加0x100。

000001FE开始的一部分初始化了一部分寄存器,也不用太关心。接下来的逻辑是关键:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
seg000:0000022C                 xor     ebx, ebx
seg000:0000022E loc_22E:                                ; CODE XREF: seg000:0000025Dj
seg000:0000022E                 nop
seg000:0000022F                 cmp     ebx, 10h
seg000:00000232                 jge     short loc_25F
seg000:00000234                 mov     eax, 80000h
seg000:00000239                 lea     edx, ds:0D08h[ebx*4]
seg000:00000240                 mov     edx, [edx]
seg000:00000242                 mov     ax, dx
seg000:00000245                 mov     dx, 8E00h
seg000:00000249                 mov     ecx, 21h ; '!'
seg000:0000024E                 add     ecx, ebx
seg000:00000250                 lea     esi, ds:128h[ecx*8]
seg000:00000257                 mov     [esi], eax
seg000:00000259                 mov     [esi+4], edx
seg000:0000025C                 inc     ebx
seg000:0000025D                 jmp     short loc_22E

在第一个0x10的循环内,修改了ds:128h开始的一些数据,把ds:0D08h和8E00复制过去。去道0xE08并不能看出什么,但是如果再往下看0x100个字节,到了0xF08能看到刚好连续16个word指向了16个地址。顺着这些地址也发现不了什么,但是把这些地址都加0x100后,刚刚好能对应16个类似函数的代码。这时大概能猜到一点东西:程序里的偏移不太对。

最终0x228+0x21*8这里存放了连续16个地址,映射到0xF08也就是`00000D7C开始的16个函数。

再来看这一段代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
seg000:0000025F ; --------------------------------------------------------------------
seg000:0000025F
seg000:0000025F loc_25F:                                ; CODE XREF: seg000:00000232j
seg000:0000025F                                         ; seg000:00000266j
seg000:0000025F                 call    sub_268
seg000:00000264                 int     21h             ; DOS -
seg000:00000266                 jmp     short loc_25F
seg000:00000268
seg000:00000268 ; =============== S U B R O U T I N E ================================
seg000:00000268
seg000:00000268
seg000:00000268 sub_268         proc near               ; CODE XREF: seg000:loc_25Fp
seg000:00000268                 mov     edi, large ds:0B78h
seg000:0000026E                 lea     edi, ds:0D48h[edi*4]
seg000:00000275                 mov     eax, [edi]
seg000:00000277                 mov     large ds:65h, al
seg000:0000027C                 mov     ecx, [edi+4]
seg000:0000027F                 mov     eax, [edi+8]
seg000:00000282                 retn
seg000:00000282 sub_268         endp

同样的,这里的0B78h和0D48h和65h应当对应地址0x0D78和0xF48和265h。在F48看到一连串相似的结构体,每个的第一个字节都是0x21到0x30中的某一个。

继续看代码,从0xF48+[0xD78]*4读取一个字节,覆盖到0x265上,刚好覆盖了int 指令的操作数那一字节,联系之前的修改函数地址,不难得出,这道题通过修改中断向量表,用中断服务程序实现了一个虚拟机功能,用ecx和eax实现操作数的传递。其中00000D64开始的5个dword是寄存器,00000D78是ip。接下来就分析00000D7C开始的每个handler了。

用下面的代码可以得到opcode对应的汇编代码:

 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
opcode = [[0x21, 0x0, 0x81], [0x27, 0x1, 0x1], [0x24, 0x1, 0x1], [0x23, 0x2, 0x0], [0x22, 0x3, 0x2], [0x21, 0x4, 0x8], [0x28, 0x3, 0x4], [0x27, 0x2, 0x3], [0x28, 0x3, 0x4], [0x27, 0x2, 0x3], [0x28, 0x3, 0x4], [0x27, 0x2, 0x3], [0x27, 0x3, 0x3], [0x23, 0x4, 0x3], [0x24, 0x3, 0x2], [0x27, 0x2, 0x4], [0x24, 0x0, 0x2], [0x21, 0x1, 0x1], [0x25, 0x0, 0x1], [0x22, 0x1, 0x0], [0x21, 0x2, 0x81], [0x26, 0x1, 0x2], [0x21, 0x2, 0x9], [0x26, 0x1, 0x2], [0x21, 0x2, 0x9], [0x2D, 0x2, 0x1], [0x21, 0x0, 0x81], [0x22, 0x1, 0x0], [0x21, 0x2, 0x9], [0x25, 0x1, 0x2], [0x23, 0x3, 0x0], [0x23, 0x4, 0x1], [0x26, 0x3, 0x4], [0x21, 0x4, 0x7E], [0x2D, 0x4, 0x3], [0x21, 0x3, 0x1], [0x25, 0x0, 0x3], [0x25, 0x1, 0x3], [0x26, 0x2, 0x3], [0x21, 0x4, 0x5A], [0x2D, 0x4, 0x2], [0x2F, 0x0, 0x0], [0x30, 0x0, 0x0]]

def ana(a):
    if a[0] == 0x21:
        return ("mov r%d, 0x%x"%(a[1],a[2]))
    elif a[0] == 0x22:
        return ("mov r%d, r%d"%(a[1],a[2]))
    elif a[0] == 0x23:
        return ("mov r%d, [r%d*4 +0E48h]"%(a[1],a[2]))
    elif a[0] == 0x24:
        return ("mov [r%d*4 + 0E48h], r%d"%(a[1],a[2]))
    elif a[0] == 0x25:
        return ("add r%d, r%d"%(a[1],a[2]))
    elif a[0] == 0x26:
        return ("sub r%d, r%d"%(a[1],a[2]))
    elif a[0] == 0x27:
        return ("xor r%d, r%d"%(a[1],a[2]))
    elif a[0] == 0x28:
        return ("shl r%d, r%d"%(a[1],a[2]))
    elif a[0] == 0x29:
        return ("shr r%d, r%d"%(a[1],a[2]))
    elif a[0] == 0x2a:
        return ("and r%d, r%d"%(a[1],a[2]))
    elif a[0] == 0x2b:
        return ("jmp r%d"%(a[1]))
    elif a[0] == 0x2c:
        return ("jz r%d, r%d"%(a[2],a[1]))
    elif a[0] == 0x2d:
        return ("jnz r%d, r%d"%(a[1],a[2]))
    elif a[0] == 0x2e:
        return ("pause")
    elif a[0] == 0x2f:
        return ("correct!")
    elif a[0] == 0x30:
        return ("wrong!")
    else :
        return ("error!")

for i in range(len(opcode)):
    print('%02d'%i, ana(opcode[i]))
 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
00            mov r0, 0x81
01            xor r1, r1
02            mov [r1*4 + 0E48h], r1
02 loc_03:     
03            mov r2, [r0*4 +0E48h]      ;plain
04            mov r3, r2
05            mov r4, 0x8
06            shl r3, r4
07            xor r2, r3
08            shl r3, r4
09            xor r2, r3
10            shl r3, r4
11            xor r2, r3
11            
12            xor r3, r3
13            mov r4, [r3*4 +0E48h]
14            mov [r3*4 + 0E48h], r2
15            xor r2, r4
16            mov [r0*4 + 0E48h], r2
16            
17            mov r1, 0x1
18            add r0, r1
19            mov r1, r0
20            mov r2, 0x81
21            sub r1, r2
22            mov r2, 0x9
23            sub r1, r2
24            mov r2, 0x9
25            jnz r2, r1                ;loc_03
25            
26            mov r0, 0x81              ;memcmp
27            mov r1, r0
28            mov r2, 0x9
29            add r1, r2
30 loc_30:    mov r3, [r0*4 +0E48h]
31            mov r4, [r1*4 +0E48h]
32            sub r3, r4
33            mov r4, 0x7e
34            jnz r4, r3                ;loc_42
35            mov r3, 0x1
36            add r0, r3
37            add r1, r3
38            sub r2, r3
39            mov r4, 0x5a
40            jnz r4, r2                ;loc_30
41            correct!
42 loc_42:    wrong!

加密算法并不难,注意一开头将opcode的前4个字节清零,然后这里放每次循环得到的结果,下一个循环继续异或。

写出解密算法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
cipher = [0x65, 0x55, 0x63, 0x57, 0x01, 0x04, 0x53, 0x06, 0x49, 0x49, 0x49, 0x1F, 0x1F, 0x07, 0x57, 0x51, 0x57, 0x43, 0x5F, 0x57, 0x57, 0x5E, 0x43, 0x57, 0x0A, 0x02, 0x57, 0x43, 0x5E, 0x03, 0x5E, 0x57, 0x00, 0x00, 0x59, 0x0F]
key = [0 for i in range(4)]
plain = ''
for i in range(0,36,4):
    temp = cipher[i:i+4]
    for j in range(4):
        temp[j] ^= key[j]
    key = temp[:]
    for j in range(1,4)[::-1]:
        temp[j]^=temp[j-1]
    for j in range(4):
        plain+=chr(temp[j])
print(plain)

where_u_are

环境配置

一道wasm逆向题。wasm的汇编代码比x86的恶心多了。。。

用python本地可以开个web服务:

1
python -m SimpleHTTPServer 8080

chrome中输入localhost:8080,点击html进入web服务。按f12,在sources界面可以调试。第一次打开并没有加载好wasm,刷新一次可以看到wasm,可以看到左侧各种函数,中间是主要的汇编代码,右侧有调用栈,断点,变量监视等。断点设好后可以在右侧Scope里看到各种全局变量和局部变量。之后静态分析不动了可以来这里动调确认猜想。

另外跑web的服务的时候不清楚为啥点了确认之后会反复弹窗,点了取消猜出结果,而且只会输出两个浮点数,结果的提示语并不会输出。

刚刚说了,wasm的汇编及其恶心。先用wabt的wasm2c将wasm转成C代码。这里的C代码仍然究极恶心。我们可以用gcc+IDA帮我们优化,然后阅读IDA反编译出来的代码。

首先是wasm2c。编译好wabt后,在bin文件夹内能找到wasm2c,使用指令

1
wasm2c where_u_are.wasm -o where_u_are.c

得到wabt反编译的C代码,然后把wasm2c文件夹内的wasm-rt.h复制到同目录下,使用指令

1
gcc -c where_u_are.c

得到只编译不连接的.o文件,这时就可以拖进ida分析了。

算法分析

经过gcc+IDA的优化,代码总算多少能看了。main函数对应wasm的第25个函数即f25。

分析算法之前,先明白wasm的字符串常量全都保存在文件末尾,我们不用太关心它是怎样使用的,只要知道哪里用到了这串字符串即可。用010editor在文件末尾我们看到了几个字符串:"%s" "%lf %lf" "Correct!!" "Wrong!"

回到main函数,可以看到几个类似偏移的数据:0x1170u 0x1173u 0x1186u 0x117Cu。如果我们稍作计算,可以发现这些偏移和这些字符串是一一对应的。可以断定这里就是对应这些字符串,也就不难猜出f98和f97是类似printf和scanf的函数。那么主要逻辑应该在f23和f24里面。分析f23中的循环,先猜测f43是strlen,在循环内的f22中,看到另一个可疑的偏移0x400,在010editor中看到是一个长度32位的字符串,对应0-9a-z,字母部分空缺了几个。回到f23的循环,每个循环内部还有大小为5的循环,结合最后一行(>>j)&1,以及刚刚的字符串,应该能想到,这里将输入的字符按照字符串映射到0到31,然后把每一个bit取出来,这样的到了n*5个bit

来到f24,稍作分析这里的代码。

 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
  v9 = 0.0;
  v10 = 0.0;
  if ( ++wasm_rt_call_stack_depth > 0x1F4u )
    wasm_rt_trap(7LL, a2, a3);
  v8 = g10;
  g10 += 272;
  if ( g10 >= g11 )
    Z_envZ_abortStackOverflowZ_vi(272LL, a2);
  v7 = v8 + 112;
  v11 = -180.0;
  v12 = 180.0;
  v13 = -90.0;
  v14 = 90.0;
  for ( i = 0; i < 50; ++i )
  {
    v6 = i32_load(Z_envZ_memory, (unsigned int)(4 * i + (_DWORD)a1));
    if ( (i + 1) % 2 == 1 )
      i32_store(Z_envZ_memory, (unsigned int)(4 * ((i + 1) / 2) + v7), v6);
    else
      i32_store(Z_envZ_memory, (unsigned int)(4 * (i / 2) + v8), v6);
  }
  for ( j = 0; j < 20; ++j )
  {
    if ( (unsigned int)i32_load(Z_envZ_memory, (unsigned int)(4 * j + v7)) == 1 )
    {
      v11 = v10;
      v10 = (v10 + v12) / 2.0;
    }
    else if ( (unsigned int)i32_load(Z_envZ_memory, (unsigned int)(4 * j + v7)) == 0 )
    {
      v12 = v10;
      v10 = (v11 + v10) / 2.0;
    }
    if ( (unsigned int)i32_load(Z_envZ_memory, (unsigned int)(4 * j + v8)) == 1 )
    {
      v13 = v9;
      v9 = (v9 + v14) / 2.0;
    }
    else if ( (unsigned int)i32_load(Z_envZ_memory, (unsigned int)(4 * j + v8)) == 0 )
    {
      v14 = v9;
      v9 = (v13 + v9) / 2.0;
    }
  }

大概就是将刚刚的n*5个bit按奇偶分成两组,然后进行这里的“算法”。最终校验的代码IDA分析的不是很好,不过大概也能看出结果要为25和175。

比赛时没看出是啥算法,就爆破了:

 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
for i in range(1048576):
    a = "{0:020b}".format(i)
    v9 = 0.0
    v10 = 0.0
    v11 = -180.0
    v12 = 180.0
    # v11 = -90.0
    # v12 = 90.0
    for i in range(20):
        if a[i]=='0':
            v12 = v10
            v10 = (v11 + v10)/2.0
        else:
            v11 = v10
            v10 = (v10 + v12) / 2.0
    if (v10-175)>=-0.0001 and (v10-175)<=0.0001:
        print(a)
        break
for i in range(1048576):
    b = "{0:020b}".format(i)
    v9 = 0.0
    v10 = 0.0
    v11 = -90.0
    v12 = 90.0
    for i in range(20):
        if a[i]=='0':
            v12 = v10
            v10 = (v11 + v10)/2.0
        else:
            v11 = v10
            v10 = (v10 + v12) / 2.0
    if (v10-25)>=-0.0001 and (v10-25)<=0.0001:
        print(b)
        break
# a = '11111100011100011100'
# b = '10100011100011100011'
s = ''
for i in range(len(a)):
    s += a[i]
    s += b[i]
print(s)
flag = ''
table =  '0123456789bcdefghjkmnpqrstuvwxyz'
for i in range(0,len(s),5):
    flag += table[eval('0b' + s[i:i+5])]
flag = 'flag{' + flag + '}'
print(flag)

赛后发现这其实是二分查找,奇偶两组是两个区间[-180, 180]和[-90, 90],每一个bit的0或1决定二分的方向,那只要求出查找175和25过程中的方向即可。

 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
def getbit(start, result, min_, max_):
    res = []
    current = start
    for i in range(20):
        if current<result:
            min_ = current
            res.append(1)
        else:
            max_ = current
            res.append(0)
        current = (min_+max_)/2.0
    print(current)
    assert abs(current-result)<=0.0001
    return res
evenbit = getbit(0.0,175.0,-180.0,180.0)
oddbit = getbit(0.0,25.0,-90.0,90.0)
table =  '0123456789bcdefghjkmnpqrstuvwxyz'
flag = ''
s = ''
for i in range(20):
    s+=str(evenbit[i])
    s+=str(oddbit[i])
print(s)
for i in range(0,len(s),5):
    flag += table[eval('0b' + s[i:i+5])]
flag = 'flag{' + flag + '}'
print(flag)

评论