トップレベルインターフェイス

そういうわけで,angrのプロジェクトをロードできたぞ.さあどうしよう?

このドキュメントでは,angr.Projectのインスタンスから参照できる全属性を解説します.

基本的なプロパティ

>>> import angr, monkeyhex, claripy
>>> b = angr.Project('/bin/true')

>>> b.arch
<Arch AMD64 (LE)>
>>> b.entry
0x401410
>>> b.filename
'/bin/true'
>>> b.loader
<Loaded true, maps [0x400000:0x4004000]>
  • archはどのアーキテクチャ向けにプログラムがコンパイルされたかを示す,archinfo.Archオブジェクトのインスタンスです.たくさんの楽しい情報が詰まっています! 注意を払うべき共通の要素はarch.bit, arch.bytes(これはメインArchクラス@property宣言です),arch.name, そしてarch.memory_endnessです.
  • entryはバイナリのエントリポイントです!
  • filenameはファイルの絶対パス名です.うわーすげーかっこいい.
  • loaderは各プロジェクトにおけるcle.Loaderのインスタンスです.使用法の詳細はこちら

AnalysesとSurveyors

>>> b.analyses
<angr.analysis.Analyses object at 0x7f5220d6a890>
>>> b.surveyors
<angr.surveyor.Surveyors object at 0x7f52191b9dd0>

>>> filter(lambda x: '_' not in x, dir(b.analyses))
['BackwardSlice',
 'BinDiff',
 'BoyScout',
 'BufferOverflowDetection',
 'CDG',
 'CFG',
 'DDG',
 'GirlScout',
 'SleakMeta',
 'Sleakslice',
 'VFG',
 'Veritesting',
 'XSleak']
>>> filter(lambda x: '_' not in x, dir(b.surveyors))
['Caller', 'Escaper', 'Executor', 'Explorer', 'Slicecutor', 'started']

analysessurveyorsはどちらもそれぞれAnalysesとSurveyorsのコンテナオブジェクトです.

Analysesはカスタマイズ可能な分析ルーチンで,プログラムからなんらかの情報を抽出できます. 最も一般的なのは,制御フローグラフを構成するCFGと,値セットの解析を実行するVFGです. それらの使用法と,独自のAnalysesを作成する方法はこちらに文書化されています.

Surveyorsはある共通の目的に向けてシンボリック実行を実施するための基本ツールです. 最も一般的なのは,あるアドレスを回避しつつあるアドレスに辿り着くためのExplorerです. Surveyorsについてはこちらを呼んでください. これはクールですが,しかし将来はPath Groups(下記)に置き換えられる予定です.

Factory

b.factoryは,b.analysesb.surveyorsと同様にクールな情報を持っているコンテナオブジェクトです. これはJavaにおけるfactoryのようなものではありません.単にangrの重要なクラスの新しいインスタンスを生みだす全関数の元で,Projectごとに設定されます.

>>> import claripy # あとで使うため

>>> block = b.factory.block(addr=b.entry)
>>> block = b.factory.block(addr=b.entry, insn_bytes='\xc3')
>>> block = b.factory.block(addr=b.entry, num_inst=1)

>>> state = b.factory.blank_state(addr=b.entry)
>>> state = b.factory.entry_state(args=['./program', claripy.BVS('arg1', 20*8)])
>>> state = b.factory.call_state(0x1000, "hello", "world")
>>> state = b.factory.full_init_state(args=['./program', claripy.BVS('arg1', 20*8)])

>>> path = b.factory.path()
>>> path = b.factory.path(state)

>>> group = b.factory.path_group()
>>> group = b.factory.path_group(path)
>>> group = b.factory.path_group([path, state])

>>> strlen_addr = b.loader.main_bin.plt['strlen']
>>> strlen = b.factory.callable(strlen_addr)
>>> assert claripy.is_true(strlen("hello") == 5)

>>> cc = b.factory.cc()
  • factory.blockはangrのリフターです.渡されたアドレスを起点としてバイナリを基本ブロックに区切り,ブロックのさまざまな表現を含むBlockオブジェクトを返します.詳細はのちほど示します.
  • factory.blank_stateは,渡されたパラメータに応じて初期化したSimStateオブジェクトを返します.Statesの全貌はこちらで論じられています.
  • factory.entry_stateは,バイナリのエントリポイントに相当するプログラムの状態に初期化したSimStateを返します.
  • factory.call_stateは,渡されたアドレスの関数を引数とともに呼び出したかのように初期化したSimStateを返します.
  • factory.full_init_stateentry_stateに似ていますが,エントリポイントではなく,SimProcedureを示すプログラムカウンタのSimStateを返します.SimProcedureは動的ローダーの提供を目的としていて,エントリポイントにジャンプする前に各共有ライブラリを初期化します.
  • factory.pathはPathオブジェクトを返します.PathsはSimStatesまわりのちょっとしたラッパーで,stateを引数としてpathを呼び出すと,そのstateをラップしたpathを取得できます.単純な例を挙げると,pathに渡されたキーワード引数はentry_stateを経由し,結果としてラップ対象のstateが作成されます.その詳細はこちらで論じられています.
  • factory.path_groupはpath groupを作成します! Path groupsは未来をもたらします.これはpathsのかしこいリストで,pathやstate(pathにラップされます),あるいはpathsやstatesのリストを渡すことができます.その詳細はこちらで論じられています.
  • factory.callableマジでクールですよ.Callablesは任意のバイナリコードへのFFI(外部関数インターフェイス)で,その詳細はこちらで論じられています.
  • factory.ccは呼び出し規約 (calling convention) オブジェクトを初期化します.この初期化に際して,関数呼び出しの引数や,関数プロトタイプを設定できます.そして,どのように引数や戻り値,リターンアドレスがメモリにレイアウトされるかfactory.callableあるいはfactory.call_stateを通じて設定できます.その詳細はこちらで論じられています.

リフター

factory.blockを介してリフターにアクセスできます. このメソッドは,こちらにあるように,いくつかの省略可能なパラメータをとります! つまるところ,block()はコードの基本ブロックへの汎用的なインターフェイスを提供するということです. .size(バイト単位)のようにブロックからプロパティを取得できますが,おもしろいことをしたければ,基本ブロックのより具体的な表現形が必要になります. .vexにアクセスしてPyVEX IRSBを取得するか,.capstoneにアクセスしてCapstone blockを取得してください.

ファイルシステムオプション

stateの初期化ルーチンには,ファイルシステムの利用方法に影響を与えるオプションを渡すことができます.これには,fs, concrete_fs, およびchrootのオプションが含まれます.

fsオプションを使用すると,事前に設定されたSimFileオブジェクトに対して辞書またはファイル名を渡せるようになります. このオプションによって,ファイルの内容についてサイズの制限を設けるといったことができるようになります.

concrete_fsオプションにTrueを設定すれば,angrはディスク上の実ファイルを重視するようになります.たとえば,シミュレーション中にプログラムがbanner.txtを開こうとしたとしましょう.ここでconcrete_fsFalse(デフォルト)に設定されている場合,メモリを記号値として扱うようにSimFileが作成され,ファイルが存在するかのようにシミュレートが行われます.concrete_fsTrueに設定されている場合,メモリを具体値として扱うようにSimFileが作成され,記号値だけでファイルを扱うことで生じるであろう状態爆発を抑制できます. さらにconcrete_fsモードでは,シミュレーション中にbanner.txtが存在しないと場合はSimFileオブジェクトを作成せず,エラーコードを返します. 加えて,パスが'/dev/'から始まるファイルはconcrete_fsTrueに設定されていた場合開かれないことに注意してください.

chrootオプションでは,concrete_fsオプションを用いるときのルートを設定できます.解析対象のプログラムが絶対パスを用いてファイルを参照しようとする場合に便利なオプションです.たとえば,解析対象のプログラムが'etc/passwd'を開こうとするとき,chrootをカレントワーキングディレクトリに設定しておけば,'/etc/passwd'へのアクセス試行は'$CWD/etc/passwd'にリダイレクトされます.

>>> import simuvex
>>> files = {'/dev/stdin': simuvex.storage.file.SimFile("/dev/stdin", "r", size=30)}
>>> s = b.factory.entry_state(fs=files, concrete_fs=True, chroot="angr-chroot/")

この例では,標準入力から30バイトまでの記号値を受け取ったとしてstateを作成します.そして,新しいルートディレクトリangr-chroot内でファイルの参照を具体的に解決することになります.

残りの要素について書く前に,この初版で注意しておかなければならないこと: argsenvキーワード引数はentry_stateおよびfull_init_stateで動作します.これらはそれぞれ,文字列またはclaripy BVオブジェクトのリストまたは辞書で,いろいろな文字列の具体値と記号値を表現することができます.もっと知りたければソースコードを読んでください!

フッキング

>>> def set_rax(state):
...    state.regs.rax = 10

>>> b.hook(0x10000, set_rax, length=5)
>>> b.is_hooked(0x10000)
True
>>> b.unhook(0x10000)
>>> b.hook_symbol('strlen', simuvex.SimProcedures['stubs']['ReturnUnconstrained'])

フックはプログラムのあるべき動作を変化させる手法です. 特定のアドレスにフックを仕掛ければ,プログラムの実行がそこに到達するたびに,フック内のpythonコードを実行できます. 実行はフックされたアドレスからlengthバイトぶんスキップしたアドレスからレジュームされます.length引数を省略した場合,実行は0バイトぶんスキップされ,フックされたアドレスからレジュームされます.

基本的な関数に加えて,SimProcedureを用いてアドレスをフックすることもできます.これは,プログラムの実行をきめ細かく制御するためのより複雑なシステムです.SimProcedureはまったく同じhook関数から利用できますが,simuvex.SimProcedureの(インスタンスではなく!)サブクラスを提供しています.

is_hookedunhookメソッドはまあ自明でしょう.

hook_symbolはこれらと異なる目的を果たす別の関数です.アドレスではなくバイナリのインポート関数名を渡します.関数呼び出しにおいて解決されるコードへの内部 (GOT) ポインタは,第3引数で指定したSimProcedureまたはフック関数へのポインタに置き換えられます.同様に,通常の整数を渡し,その値のシンボルに置き換えることもできます.

results matching ""

    No results matching ""