30C3 CTFに参加しました

30C3 CTF(https://30c3ctf.aachen.ccc.de/)にチーム0x0で参加しました。

個人的にはELFバイナリが全部64bitだったのがつらかったです。急遽VirtualboxUbuntu Server(64bit)を突っ込みました。
以下Write upっぽいものです。

fourier(NUMBERS 200)

fourier.tar.gzを解凍するとfourierというELFと、flag.fourというテキストファイルがでてきます。flag.fourはフラグをfourierで暗号化したものです。ということでfourierの逆アセンブルを読んだところ、どうやらGMP(The GNU Multiple Precision Arithmetic Library)という多倍長演算のライブラリを使って書いてあるようでした。関数の内容はhttps://gmplib.org/manual/Function-Index.htmlとかを見ればOKです。アセンブリを読むとだいたい次のような内容でした。

v10 = 0;
while(まだファイルが残っている) {
	v10 << 8;//多倍長演算
	v10 += <ファイルから1バイト読む>;
}
while(24 <= v10) {
	v30 = 0;
	v40 = 0;
	v40 = v10 - 4;
	v40 *= 4;
	v40 -= 5;
	if(0x444 < v40) v40 = 0x444;
	v30 = <0〜v40までの乱数>;
	v30 += 5;
	//v30と同じ数だけ4を出力する
	v20 = 0;
	while(v20 != v30) {
		++v20;
		<'4'をファイルに出力>;
	}
	<' 'をファイルに出力>;
	v50 = 0;
	v20 = 4;
	while(v20 <= v10) {
		<'4'をファイルに出力>;
		v20 <-> v50;//スワップ
		v20 = v50 * v30;
		v20 += 4;
	}
	v10 -= v50;
	<' 'をファイルに出力>;
}
//v10と同じ数だけ4を出力
while(v10 != 0) {
	--v10;
	<'4'をファイルに出力>;
}

最初にv10にファイルの内容を読み込んでいます。つまり、最初のv10がわかればファイルが復元できるわけです。最後のv10の値は暗号文の最後の4の数を見ればわかります。メインループの中でv10が変更されているのはv50を引いているところだけなので、メインループの繰り返しごとのv50の値がわかればv10が復元できます。v50の値はv10とv30の値にのみ依存していますが、v30は上の方のループでの4の数と同じです。v30の値と、下の方のループの繰り返し回数(これは4の数と同じです)がわかれば、v50の値がわかるのでv10の値はわからなくてもよいです。
これでflagが復元できます。
以下rubyで書いたやつ。各行に4の数を書いたテキストを食わせるとフラグが出ます。

#!/usr/bin/ruby
list = []
while line = STDIN.gets
	list.push(line.to_i)
end
list = list.reverse

v10 = list.shift

while list.length != 0
	l = list.shift
	r = list.shift
	v50 = 0
	l.times do
		v50 = 4 + v50 * r
	end
	v10 += v50
end

res = []
while v10 != 0
	res.push(v10 % 256)
	v10 /= 256
end
res = res.reverse
flag = []
res.length.times do |i|
	flag.push(res[i].chr)
end
puts flag.join

DOGE1(PWN 100)

犬に名前をつけて餌を与えたりするサーバです。
途中、同じディレクトリにあるascii_art_doge_color.txtを読むなどの動作をします。
手元で動かして犬の名前に長い文字列を突っ込んだら
No such file or directoryってなったので、

echo -en '12345678901234567890123456789012/etc/passwd\x00'

をncに渡して終了。ユーザーdogeのパスワードが答えです。
zshだとechoに-eオプション付けなくても動きます。

angler(NUMBERS 300)

これも暗号化プログラムと暗号文(バイナリ)がセットになってるやつでした。
暗号化プログラムはErlangで書かれていたのでソースコードを読むだけでOKでしたが、
Erlangは今まで触ったことがなかったのでそのへんの入門記事を読みながらやりました。
Erlangはバイナリパターンマッチングができるのが面白いです。
剰余のあたりが偶奇反転(バグ?)してるような気がしましたがよくわかりませんでした。
とりあえず動いたのでよしとします。

xxd -p sniffed_message | nkf -f2-0 | head -n -1 | xargs -I{} printf "%d\n" 0x{}

してから、以下のrubyで書いたやつに突っ込みました。

#!/usr/bin/ruby

$prod = []
256.times do |i|
	$prod.push((i * 0x41 + 7) % 256)
end

def prod2index(v)
	index = 0
	for element in $prod
		if element == v
			return index
		end
		index += 1
	end
	return -1
end

def rotate(i)
	i.times do
		t = $prod.shift
		$prod.push(t)
	end
end

list = []
while line = STDIN.gets
	list.push(line.to_i)
end

output = []
(list.length / 2).times do
	i1 = list.shift;
	i2 = list.shift;
	output.unshift(i2);
	output.unshift(i1);
end

for o in output
	i = prod2index(o)
	print i.chr
	if i % 2 == 0
		rotate(256 - i)
	elsif
		rotate(i)
	end
end

所感

  • x64の呼び出し規約がWindowsgccで違うのを初めて知りました(Windowsはrcx, rdx, r8, r9, スタック、gccはrdi, rsi, rdx, rcx, r8, r9, レジスタの順)。
  • バイナリ読めてもExploit書けないとだめだめだというのがわかりました。