Pwntoolsの使い方 (初級編)

PwntoolsというのはGallopsledというチームが公開しているCTFのためのPython2系ライブラリです.

このライブラリを使いこなせればPwn問題が格段に楽に解ける(はず)なので,今回はksnctfの村人Bを倒すのに役立つ程度の機能を初級編としてまとめて行こうと思います.


もっと詳しく知りたい人は

あたりを読むといいと思います.

インストール方法

pipで一発です.以下Ubuntuでのインストール方法です.(Ubuntu以外のLinux,FreeBSD,MAC OS等でも動きます)

$ apt-get update
$ apt-get install python2.7 python-pip python-dev git libssl-dev libffi-dev build-essential
$ pip install pwntools

Ubuntu以外の環境へのインストール方法はこちらを参考にしてください.

あとはソースコードの先頭,またはインタプリタで

from pwn import *

と宣言すれば使えるようになります.

使い方

上述のとおり,基本的な使い方に絞って紹介します.

標準機能

プロセス起動

Pwntools上で与えられたプログラムを動かしたり,TCP接続するためには以下のようにします.

# ローカルでプログラムを実行する場合
p = process('program')

# コマンドライン引数や環境変数を指定すること可能
p = process(['program', 'hogehoge', 'fugafuga'], env={'MYENV': 'MYVAL'})

# リモートにTCP接続する場合
p = remote('example.com', 12345) # ホスト名,ポート番号

# SSHで接続した先にあるプログラムを実行する場合
session = ssh('username', 'example.com', port=22, password='password')
p = session.process('sh', env={"PS1":""})

標準入出力

起動したプロセスとデータをやり取りする方法です.

# データの受信
ret = p.recv(n) # nバイト読み出し
ret = p.recvline() # 一行読み出し
ret = p.recvuntil(delim) # 文字列delimが来るまで読み出し

# データの送信
p.send(data) # 文字列dataを送信
p.sendline(data) # 最後に'¥n'を付けて文字列dataを送信

# インタラクティブモードに移行
p.interactive()

整数操作

整数型 <=> byte型の変換に関する機能.整数型を送信データに変換する場合などに使います.

# 整数型からbyte型
>>> p32(0xdeadbeef)
'\xef\xbe\xad\xde'
>>> p64(0xdeadbeefcafebabe)
'\xbe\xba\xfe\xca\xef\xbe\xad\xde'

# byte型から整数型
>>> hex(u32('\xef\xbe\xad\xde'))
'0xdeadbeef'
>>> hex(u64('\xbe\xba\xfe\xca\xef\xbe\xad\xde'))
'0xdeadbeefcafebabe'

# 桁数が足りない場合はljustを使う
>>> hex(u32('\xef\xbe'))
struct.error: unpack requires a string argument of length 4
>>> hex(u32('\xef\xbe'.ljust(4,'\0')))

この他,u8u16p8p16もあります.
エンディアンや符号を指定できるpackunpackもありますが,私があまり使ったことがないので割愛します.

ELF解析

基本機能

Pwntoolを使えば実行ファイルのpltやgotを調べることもできます.

# ファイルの取り込み
>>> elf = ELF('program')
[*] '/home/user/program'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

>>> hex(elf.plt['puts'])
'0x80484c4'
>>> hex(elf.got['puts'])
'0x80499f4'
>>> hex(elf.symbols['main'])
'0x80485b4'

これはobjdumpでは以下のように表される値です.

$ objdump -d -M intel program
(...)
Disassembly of section .plt:
080484c4 <puts@plt>:
 80484c4: ff 25 f4 99 04 08 jmp DWORD PTR ds:0x80499f4
 80484ca: 68 30 00 00 00 push 0x30
 80484cf: e9 80 ff ff ff jmp 8048454 <.plt>
(...)
Disassembly of section .text:
080485b4 <main>:
 80485b4: 55 push ebp
 80485b5: 89 e5 mov ebp,esp
 80485b7: 83 e4 f0 and esp,0xfffffff0
 80485ba: 81 ec 20 04 00 00 sub esp,0x420
(...)

Base Addressの変更

リークしたアドレスを用いてBase Addressを変更し,ASLR回避などに役立てることもできます.

>>> elf = ELF('libc.so.6')
>>> hex(elf.symbols['system'])
'0x3af60'
>>> elf.address = 0xf7a23000
>>> hex(elf.symbols['system'])
'0xf7a5df60'

Fmtstr

Pwntoolsには書式文字列攻撃で送信するペイロードを自動的に生成する機能も存在します.
fmtstr_payloadで指定できる引数は以下の4種類です.

  • offset(int): printfの引数があるオフセット
  • writes(dict): {書き込み先アドレス:書き込む値}形式の辞書(複数指定可)
  • numbwritten(int): printfが既に出力したバイト数
  • write_size(str): 何バイトずつ書き込むか
    • ‘byte’: 1バイトずつ(printfの%hhnに対応)
    • ‘short’: 2バイトずつ(printfの%hnに対応)
    • ‘int’: 4バイトずつ(printfの%nに対応)
>>> elf = ELF('program')
>>> hex(elf.got['strcmp'])
'0x80499fc'
>>> got_strcmp = elf.got['strcmp']
>>> addr_target = 0x8048691
>>> offset = 6
>>> fmtstr_payload(offset, {got_strcmp: addr_target})
'\xfc\x99\x04\x08\xfd\x99\x04\x08\xfe\x99\x04\x08\xff\x99\x04\x08%129c%6$hhn%245c%7$hhn%126c%8$hhn%4c%9$hhn'

この辺は実際の使用例がないと分かりづらいので,Pwntoolsを使ったwriteupを見てみることをおすすめします.

書式文字列攻撃にはlibformatstrのFormatStrを使うこともありますが,できることはPwntoolsと大体同じなので割愛します.

おわりに

Pwntoolsの機能を紹介しましたが,便利すぎてこれ無しではいられない身体になっちゃいそうです.Pwnの理論を忘れないようにするために,ときどきハリネズミ本を読み返す必要がありそうですね.
Pwntoolsには他にもアセンブラ関係の機能や,GDBでデバッグする機能,ROP用機能もあるので,そのうち使用例と一緒にまとめたいと思います.

シェアする

  • このエントリーをはてなブックマークに追加

フォローする