Cisco Japan Blog
Share

IDA-minsc が Hex-Rays 社のプラグイン コンテストで第 2 位を獲得


2018年10月11日


はじめに

Cisco Talos の Ali Rizvi-Santiago が開発したプラグイン「IDA-minscpopup_icon」が最近、IDA プラグイン コンテストで、他の開発者と同順位で第 2 位popup_iconを獲得しました。IDA は、Hex-Rays 社が開発したマルチプロセッサの逆アセンブラで、デバッガとしても機能します。今年は合計 9 点の出品があり、4 点が入賞しました。同社は毎年、自社の製品を向上させるプラグインの出品を研究者から募っています。Talos では、IDA-minsc はユーザ エクスペリエンスを十分に向上させるため、今年の受賞に値するだろうと判断しました。

このプラグインは、バイナリの逆アセンブルや注釈を簡単に行えるようにすることを目指しており、これによって、注釈プロセスの迅速化と作業の効率化を実現できると考えています。そのために、Python の開発方法を変えるいくつかの概念を導入し、逆アセンブルの対象を、思い通りのクエリや注釈のために使用するデータセットとして扱えるようにします。そうしたことを、ユーザの現在の選択に基づいて関数のパラメータを自動判断する、プラグインのさまざまなコンポーネントと結びつけます。これにより、ユーザはデータベースの異なる部分にマークと注釈を行うコードをすばやく書けるようになります。

プラグイン自体は、ここpopup_iconでホスティングされており、詳細なドキュメンテーションはこちらpopup_iconにあります。この記事では、Borland Delphi で書かれた文書作成ソフトウェア、Atlantis Word Processor の逆アセンブルによって、このプラグインの機能をデモンストレーションします。具体的には、クエリ用に構成した任意のオブジェクトにすばやくタグ付けする方法、RTF パーサーとその属性に属するトークンを特定する方法、その他の関数で定義した変数を参照するクロージャの操作方法について概説します。以下で述べる機能の詳細はすべて、前述のリンク先のドキュメントに記載されています。または、Python の「help()」関数を名前空間またはモジュールから直接呼び出すことで参照できます。

背景

IDAPython は、本質的に、IDA のさまざまなコンポーネントの実装に直接対応した個別のモジュールである IDA SDK を中心としたラッパーです。IDA 6.95 で使用されるモジュールは複雑で、理解するのは簡単ではありません。IDAPython では、多くの高度な関数を実装することでその問題が解消されていますが、新しいモジュールには一般的な名前が付いているため、IDC スクリプト言語の予備知識が必要です。Talos は今回の新しいプラグインの開発中に、使用されるさまざまなコンポーネントと関数を個別のモジュールにグループ化することで、それらの再呼び出しと参照をすぐに行えることに気が付きました。このプラグインには、ほかにも便利なモジュールがありますが、その主な特長は IDA-minsc で最初に着目した 2 つの「状況」に関連しています。これらの各モジュールには、「名前空間」として使用される静的メソッドによるクラス定義があり、同様のデータまたは同様のセマンティクスに従って関数がグループ化されています。

最初のデータベースの作成

Atlantis Word Processor で「awp.exe」のファイルを最初に開くと、IDA が、そのファイルの処理を開始します。処理が完了すると、プラグインが起動し、そのタグ キャッシュを構築します。タグ キャッシュは特に、タグ付けとタグのクエリに利用されます。このプロセスでは、プラグインが、データベースで使用可能なコメント全体を反復処理するほか、内部キャッシュを更新し、その進捗状況を表示します。こうした処理が完了すると、逆アセンブルのプロセスを開始できます。

すべてのクラスとそのサイズのタグ付け

すべての Delphi アプリケーションには通常、「TObject」というクラスが含まれています。このクラスが多数のクラスで継承できることを利用して、一般にコンストラクタとして使用される「System.New」を検索できます。では最初に、IDA-minsc を使用して、「TObject」を参照するすべてのシンボル名を表示してみます。これは、IDA の「Names」ウィンドウ(Shift + F4)を使用する場合と同じですが、IDA-minsc のコンポーネントの一致を使用すると、キーワードを指定して、IDA の異なるウィンドウをフィルタリングできます。

Python>db.names.list(like='*TObject')
[    0] 0x4010b0 _cls_System_TObject
[  382] 0x4058e4 GetCurrentObject
[  408] 0x4059b4 SelectObject
[11340] 0x67d658 __imp_SelectObject
[11366] 0x67d6c0 __imp_GetCurrentObject

「_cls_System_TObject」のアドレスまたはシンボルをダブルクリックすると、IDA は、次の図 1 で示すように、指定したアドレスに移動し、目的の「TObject」を表示します。これを相互参照(Ctrl + X)すると、この文字列がそのすぐ右横にあるアドレス、0x401070 から参照されていることを確認できます。これは、Delphi の実際のクラス定義を表しています。このアドレスが、「TObject」を継承するクラスから参照されているアドレスです。

図 1

このアドレスがあれば、その参照を取得して、「TObject」を継承するクラスをすべて確認できます。ここには、約 122 個のクラスがあるようです。ランダムに 1 つを選択すると、構造のようなものがわかります。自己参照で始まるこの構造には、呼び出せる多数の関数が含まれています。Delphi では、こうした自己参照によって、オブジェクトの範囲と関数を制御するデフォルトの関数を区別します。この参照を追うと、その上にある関数は標準ライブラリに関連している一方で、それに続く関数はカスタムの実装メソッドであることがわかります。

図 2

図 2 の 3 つの関数は、標準ライブラリに関連しているため、1 つずつ見ていくと、それらがいくつかのタスクを実行することがわかります。アドレス 0x406dec の 3 つ目の関数は、「CloseHandle」を呼び出すので、これはおそらく、デストラクタです。アドレス 0x406De4 の最初の関数は典型的なコンストラクタです。この関数を選択して、それ(X)を使用する参照を一覧にすると、473 の参照があることを確認できます。これらの参照を使用して、各クラスを検索し、ラベルを付けます。その前に、この構造を詳しく見てみましょう。

CODE:00406DB0 F0 6D 40 00           off_406DB0      dd offset off_406DF0    ; [1]
CODE:00406DB0
CODE:00406DB4 00 00 00 00 00 00+                    dd 7 dup(0)
CODE:00406DD0 F8 6D 40 00                           dd offset str.TThread   ; [3] "TThread"
CODE:00406DD4 28 00 00 00                           dd 28h                  ; [5]
CODE:00406DD8 70 10 40 00                           dd offset off_401070
CODE:00406DDC 38 2A 40 00                           dd offset sub_402A38
CODE:00406DE0 40 2A 40 00                           dd offset nullsub_9
CODE:00406DE4 98 28 40 00                           dd offset sub_402898    ; Constructor
CODE:00406DE8 AC 28 40 00                           dd offset sub_4028AC    ; Finalizer
CODE:00406DEC E8 A9 40 00                           dd offset sub_40A9E8    ; Destructor
CODE:00406DF0 38 AA 40 00           off_406DF0      dd offset loc_40AA38    ; [2]
CODE:00406DF4 C4 25 40 00                           dd offset nullsub_11
CODE:00406DF8 07 54 54 68 72 65+    str.TThread     db 7,'TThread'          ; [4]
CODE:00406E00 04 6E 40 00           off_406E00      dd offset byte_406E04

すでに述べたように、この構造には自己参照が含まれています [1]。この参照は、IDA によってラベル付けされます。データベース内で関数がそれを使用するためです。この参照により [2] が指定され、クラスの「コンストラクタ」、「ファイナライザ」、「デストラクタ」が特定されます。また、文字列へのポインタが、最初の方にあります [3]。この文字列は、[4] にあるクラス名とその内容を表します。その形式は、Pascal と同じで、1 バイトの長さで始まり、文字列を表すバイト数がその後に続きます。最後に、クラスの長さが表示されています [5]。これは、そのメンバーを格納するために割り当てる必要のあるサイズを表します。まず、IDAPython のコマンド ラインで、Pascal 形式の文字列を設定する関数を簡単に定義してみましょう。

これを行うには、「database.set.integer」の名前空間を使用して、先頭のバイトの長さに「uint8_t」を設定します。文字列の残りの部分には、長さを設定した「database.set.string」を使用して、アドレスを指定した長さの文字列に変換します。

Python>def set_pascal_string(ea):
Python>    ch = db.set.i.uint8_t(ea)
Python>    return db.set.string(ea + 1, ch)

これを完了したら、必要に応じて「database.get.string」を使用して、それを逆に読むことができます。
作成した値を返す「database.set」の名前空間であるにもかかわらず、次のように定義すると、上記で指定したコードを逆アセンブルできます。

Python>def get_pascal_string(ea):
Python>    ch = db.get.i.uint8_t(ea)
Python>    return db.get.string(ea + 1, length=ch)

これで、次のように入力して、そのアドレスの文字列を読めるようになります。

Python>print get_pascal_string(0x406df8)
Tthread

これで、名前を得られる文字列に抽出と適用の両方を行い、その名前をクラスのラベル付けに使用できるようになりました。実際にそうするには、コンストラクタの参照をすべて使用し、各参照を基に、すべてのクラスの異なるフィールドを計算します。その後、タグを使用して、異なるオブジェクトをすべてマークし、必要に応じてそれらにクエリを実行できるようにします。開始するには、最初にアドレス 0x406de4 の「コンストラクタ」をダブルクリックします。これにより、関数「sub_402898」に移動できます。この関数が何かはわかっているので、次の手順でこれに名前を付けます。

Python>func.name('System.New')
sub_402898

お気付きのように、アドレスを指定しませんでした。そのため、現在の関数が指定されたはずです。これは、IDA-minsc の「multicased」関数のコンポーネントです。「function.name」に「help()」を実行すると、その他のバリエーションを表示できます。

Python>help(function.name)
Help on function name in module function:
name(*arguments, **keywords)
name() -> Return the name of the current function.
name(string=basestring, *suffix) -> Set the name of the current function to ``string``.
name(none=NoneType) -> Remove the custom-name from the current function.
name(func) -> Return the name of the function ``func``.
name(func, none=NoneType) -> Remove the custom-name from the function ``func``.
name(func, string=basestring, *suffix) -> Set the name of the function ``func`` to ``string``.

関数に名前を付けたところで、その参照をすべて反復処理し、その異なるフィールドを取得します。前述したフィールドの参考として、次のレイアウトがあります。

CODE:00406DAE 8B C0                                 align 10h
CODE:00406DB0 F0 6D 40 00           off_406DB0      dd offset off_406DF0    ; [6] Top of class or Info (Reference - 16*4)
CODE:00406DB0
CODE:00406DB4 00 00 00 00 00 00+                    dd 7 dup(0)
CODE:00406DD0 F8 6D 40 00                           dd offset str.TThread   ; [7] Class name (Reference - 8*4)
CODE:00406DD4 28 00 00 00                           dd 28h                  ; [8] Class size (Reference - 7*4)
CODE:00406DD8 70 10 40 00                           dd offset off_401070    ; [9] Parent class (Reference - 6*4)
CODE:00406DDC 38 2A 40 00                           dd offset sub_402A38
CODE:00406DE0 40 2A 40 00                           dd offset nullsub_9
CODE:00406DE4 98 28 40 00                           dd offset sub_402898    ; [10] Constructor (Reference - 3*4)
CODE:00406DE8 AC 28 40 00                           dd offset sub_4028AC    ; [11] Finalizer (Reference - 2*4)
CODE:00406DEC E8 A9 40 00                           dd offset sub_40A9E8    ; [12] Destructor (Reference - 1*4)
CODE:00406DF0 38 AA 40 00           off_406DF0      dd offset loc_40AA38    ; [13] * Reference
CODE:00406DF4 C4 25 40 00                           dd offset nullsub_11
CODE:00406DF8 07 54 54 68 72 65+    str.TThread     db 7,'TThread'
CODE:00406E00 04 6E 40 00           off_406E00      dd offset byte_406E04

このレイアウトでは、コンストラクタを参照するすべてのクラスの異なるコンポーネントを抽出できます。また、それらにタグ付けしてクエリを実行できます。先ほど、そのコンストラクタをダブルクリックして名前を付けたので、現在、「System.New」関数を操作していることになります。すべての参照を取得するために、「function.up()」を使用できます。その後、そのすべての参照を反復処理し、0xc (3 * 4 == 12) の追加によって [13] の参照に到達します。さらに、その参照を基に、残りのフィールドを検索します。クラス名 [7] に対しては、「set_pascal_string」関数と「get_pascal_string」関数の両方を使用し、標準の範囲指定の構造 [10]、[11]、[12] に対しては、下方に処理を進め、「type」によってタグ付けします。こうした処理を実行するコードは次のようになります。これよりもはるかに短くできますが、読みやすさのために略さずに記述しています。

Python>for ea in func.up():
Python>    ref = ea + 3*4  # [13] calculate address to reference
Python>
Python>    # read our fields
Python>    lookup = {}
Python>    lookup['info'] = ref - 16*4         # [6]
Python>    lookup['name'] = ref - 8*4          # [7]
Python>    lookup['size'] = ref - 7*4          # [8]
Python>    lookup['parent'] = ref - 6*4        # [9]
Python>    lookup['constructor'] = ref - 3*4   # [10]
Python>    lookup['finalizer'] = ref - 2*4     # [11]
Python>    lookup['destructor'] = ref - 1*4    # [12]
Python>    lookup['object'] = ref              # [13]
Python>
Python>    # dereference any fields that need it
Python>    name_ea = db.get.i.uint32_t(lookup['name'])
Python>    parent_ea = db.get.i.uint32_t(lookup['parent'])
Python>    size = db.get.i.uint32_t(lookup['size'])
Python>
Python>    # set our name (just in case IDA has it defined as something else)
Python>    set_pascal_string(name_ea)
Python>
Python>    # decode our name
Python>    name = get_pascal_string(name_ea)
Python>
Python>    # name our addresses
Python>    db.name(lookup['info'], 'gv', "Info({:s})".format(name))
Python>    db.name(lookup['object'], 'gv', "Object({:s})".format(name))
Python>
Python>    # tag our methods
Python>    m_constructor = db.get.i.uint32_t(lookup['constructor'])
Python>    func.tag(m_constructor, 'function-type', 'constructor')
Python>    m_finalizer = db.get.i.uint32_t(lookup['finalizer'])
Python>    func.tag(m_finalizer, 'function-type', 'finalizer')
Python>    m_destructor = db.get.i.uint32_t(lookup['destructor'])
Python>    func.tag(m_destructor, 'function-type', 'destructor')
Python>
Python>    # tag our class structure
Python>    db.tag(lookup['info'], 'object.name', name)
Python>    db.tag(lookup['info'], 'object.methods', lookup['object'])
Python>    db.tag(lookup['info'], 'object.size', size)
Python>    if parent_ea:
Python>        db.tag(lookup['info'], 'object.parent', parent_ea)
Python>    continue

このコードを実行すると、データベース内のすべての Delphi オブジェクトがタグ付けされます。タグにより、データベースでコメントが活用でき、指定したアドレスに関連付けられたタグがどのようになるかを、逆アセンブル中にすぐに確認できます。IDAPython のコマンド ラインで上記のコードを実行すると、「TThread」オブジェクトは、図 3 のように表示されます。

図 3

この大きめのコード ブロックを実行すると、データベース内のすべてのオブジェクトがタグ付けされます。これにより、「database.select()」を使用してデータベースにクエリを実行し、特定のサイズのクラスを検索できるようになります。「database.select」の詳細については、「help()」を確認してください。この方法で、サイズが 0x38 のオブジェクトを検索する例を以下に示します。

Python>for ea, tags in db.select(Or=('object.name', 'object.size')):
Python>    if tags['object.size'] == 0x38:
Python>        print hex(ea), tags
Python>    continue

 

RTF トークンの列挙

Atlantis Word Processor には、RTF パーサーが含まれています。このファイル形式はよく知られているため、その形式でサポートされるトークンを特定しやすく、うまくいけば各トークンのパラメータを解析する関数を見つけられます。そのためには、まず、配列内で定義されていると思われる、「objemb」文字列を検索します。IDA のテキスト検索(Alt + T)を使用できますが、ここでは、「go()」関数と「database.search」の名前空間の組み合わせによる IDA-minsc の機能を使用して、その文字列にすばやく移動してみます。

Python>go(db.search.by_text(‘objemb’))

これにより、「objemb」文字列の最初のインスタンスに直接移動できます。これを相互参照(Ctrl + X)すると、その文字列を使用する参照が 1 つしかないことがわかります。図 4 のような、文字列の参照を示す一覧が表示されます。

図 4

これには参照がないため、IDA により作成された、前に定義済みのラベルにすばやく移動してみましょう。このラベルは、IDA にそうした特定のアドレスを参照する逆アセンブルされたコードがあるために存在します。これを行うには、「database.address」の名前空間の機能である、「nextlabel()」関数と「prevlabel()」関数を、IDAPython のコマンド ラインで、次のように入力して使用します。

Python>go(db.a.prevlabel())

 

このデータは配列のように思われますが、IDA ではそれを作成していません。「*」を入力して IDA の [配列に変換(Convert to array)] ダイアログを起動できますが、その代わりに IDA-minsc を使用します。「database.address.nextlabel()」と「database.here()」(「h()」のエイリアス)によって、配列の要素数を計算し、それを「databaset.set.array()」を使用するデータベースに割り当てます。「database.set.array」関数は、パラメータの 1 つとして「pythonic」型を使用します。これについては、IDA-minsc のドキュメンテーションに解説があります。この型により、正確なフラグや typeid を理解しなくても IDA で型を記述できます。この場合、「(int, 4)」で 4 バイト整数(dword)を指定できますが、これは 32 ビットのデータベースなので、「int」によってデフォルトの整数サイズを使用します。

Python>size = (db.a.nextlabel() - h())
Python>db.set.array(int, size / 4)

現在のアドレスの配列にも「database.name()」を使用して名前を付けましょう。

Python>db.name('gv', "rtfTokenArray({:d})".format(db.t.array.length()))

しかし、図 5 で示すように、この配列の要素の一部には、IDA が実際の文字列としてマークしていないものがあるようです。

図 5

配列内のすべてのアドレスを反復処理することで、この問題をすぐに解消できます。具体的には、アドレスの定義を解除した後に、手動の操作と同様に、文字列として再定義します。この処理は、IDAPython のコマンド ラインで次のように実行できます。

Python>for ea in db.get.array():
Python>    db.set.undefined(ea)
Python>    db.set.string(ea)

配列を修正できました。最初に戻ると、この配列が多数の配列の連続であることがわかります。では、この配列の 1 つ上の配列を最初に修正し、現在の位置を「position」変数に保存しましょう。「database.address.prevlabel()」を使用してその位置に移動すると、そこでも最初の配列で行った同じ操作を行えます。

Python>position = h()
Python>
Python>go(db.a.prevlabel())
Python>
Python>db.set.array(int, (db.a.nextlabel() - h()) / 4)
Python>db.name('gv', "rtfTokenArray({:d})".format(db.t.array.length()))
Python>for ea in db.get.array():
Python>    db.set.undefined(ea)
Python>    db.set.string(ea)

これで、以前に保存した位置に戻り、次の 2 つの配列に対してこのプロセスを繰り返し実行できます。

Python>go(position)
Python>
Python>go(db.a.nextlabel())
Python>
Python>db.set.array(int, (db.a.nextlabel() - h()) / 4)
Python>db.name('gv', "rtfTokenArray({:d})".format(db.t.array.length()))
Python>for ea in db.get.array():
Python>    db.set.undefined(ea)
Python>    db.set.string(ea)
Python>
Python>go(db.a.nextlabel())
Python>
Python>db.set.array(int, (db.a.nextlabel() - h()) / 4)
Python>db.name('gv', "rtfTokenArray({:d})".format(db.t.array.length()))
Python>for ea in db.get.array():
Python>    db.set.undefined(ea)
Python>    db.set.string(ea)

完了しました。これで、「database.names」の名前空間を使用して、処理後の配列をすべて表示できます。「gv_rtfToken」で始まるシンボルをすべて表示してみましょう。この一覧で、定義した最初の配列(「gv_rtfTokenArray(213)」)を探し、そのアドレスをダブルクリックします。

Python>db.names.list('gv_rtfToken*')
[11612] 0x668ba8 gv_rtfTokenArray(64)
[11613] 0x668ca8 gv_rtfTokenArray(213)
[11614] 0x668ffc gv_rtfTokenArray(46)
[11615] 0x6690b4 gv_rtfTokenArray(135)

「gv_rtfTokenArray(213)」の定義に移動しました。これを相互参照(Ctrl + X)すると、それに対するコード参照がアドレス 0x431DD7 に 1 つだけあることがわかります [14]。

CODE:00431DD7 000 A1 A8 8C 66 00                    mov     eax, ds:gv_rtfTokenArray(213)   ; [14]
CODE:00431DDC 000 A3 EC 85 67 00                    mov     ds:dword_6785EC, eax            ; [15]
CODE:00431DE1 000 C7 05 F0 85 67 00+                mov     ds:dword_6785F0, 4
CODE:00431DEB 000 C7 05 F4 85 67 00+                mov     ds:dword_6785F4, 4
CODE:00431DF5
CODE:00431DF5                       locret_431DF5:
CODE:00431DF5 000 C3                                retn
CODE:00431DF5                       sub_431D38      endp

この命令では、トークン配列のアドレスを読み取り、[15] で別のグローバル配列に書き込みます。これは、独自の配列のポインタにすぎないので、このアドレスに名前も付けましょう。IDA の [アドレスの名称変更(Rename address)] ダイアログを操作したり、「dword_6785EC」をダブルクリックした後、現在のアドレスを指定して「database.name」を使用したりするのではなく、命令のオペランドからアドレスを直接抽出します。この処理は「instruction.op」関数によって実行できます。アドレス 0x431ddc を選択すると、グローバル トークンの配列が現在の命令における最初のオペランドに配置されます。そのオペランドを、IDAPython コマンド ラインで、名前付きタプルとして抽出できます。

Python>ins.op(0)
OffsetBaseIndexScale(offset=6784492L, base=None, index=None, scale=1)
「Instruction.op」では、最初のパラメータにアドレスを指定していないため、この関数は、現在の命令が参照されていると仮定します。名前付きタプルの「offset」フィールドには、dword のアドレスが含まれているので、次の操作により、同じアドレスを選択してこのポインタに名前を付けることができます。このアドレスには「dword_6785EC」という名前がすでにあるため、「database.name」関数は元の名前を返します。

Python>ea = ins.op(0).offset
Python>db.name(ea, 'gp','rtfTokenArray(213)')
dword_6785EC

この同じ関数を使用して、以前定義したすべての配列のグローバル ポインタに同じ割り当てを行えます。このプロセスを繰り返して、それらすべてに名前を付けた後に相互参照することで、RTF トークナイザを検索できます。そうした操作の前に準備することがあります。トークンの配列に戻り、それらから文字列を抽出しておきます。名前付けが完了した文字列を一覧表示すると、次のようになります。

Python>db.names.list('gv_rtfToken*')
[11612] 0x668ba8 gv_rtfTokenArray(64)
[11613] 0x668ca8 gv_rtfTokenArray(213)
[11614] 0x668ffc gv_rtfTokenArray(46)
[11615] 0x6690b4 gv_rtfTokenArray(135)

引き続き、このリスト全体を反復処理します。特にその処理を目的として、「database.names」の名前空間には「iterate()」関数が含まれています。その関数を「database.get.array()」と組み合わせることで配列を 1 つのリストとして保存できます。IDAPython のコマンド ラインで、次を実行します。

Python>tokens = []
Python>for ea, name in db.names.iterate('gv_rtftoken*'):
Python>    rtfTokenArray = db.get.array(ea)
Python>    tokens.extend(rtfTokenArray)
Python>
Python>len(tokens)
458

実際の RTF トークンをポイントするアドレスを 458 個含む 1 つのリストを取得できました。これを、簡単なリスト内包表記を使用して文字列のリストに変換し、アドレスを文字列にマッピングします。そうすることで、トークンの識別子を、その実際のトークン文字列に変換できるようになります。

Python>tokens = [db.get.string(ea) for ea in tokens]

RTF トークン パラメータ パーサーの検索

この時点では、まだ「sub_431D38」を操作しています。ここで割り当てたトークン配列のポインタを相互参照(Ctrl + X)して、最終的に、トークンとその引数を処理する関数に移動することもできますが、さらに簡単な方法がありそうです。データベースで、各関数のすべての switch にタグ付けできます。その際に、各 switch の case の数をカウントしましょう。そうすることで、case 数の最も多い関数についてクエリをすばやく実行できるようになります。また、「function.switches()」を使用して関数内の switch をすべて列挙できます。これにより、関数内で定義されているすべての switch を反復処理できるほか、デフォルト以外の case の合計を計算する IDA-minsc の「switch_t」インスタンスを返すことができます。こうした処理を行うために、まず関数の switch の数と case の合計数にタグ付けする関数を定義しましょう。

Python>def tag_switches(ea):
Python>    count, total = 0, 0
Python>    for sw in func.switches(ea):
Python>        count += 1
Python>        total += len(sw.cases)
Python>    if count > 0:
Python>        func.tag(ea, 'switch.count', count)
Python>        func.tag(ea, 'switch.cases', total)
Python>    return

これで、switch の数に「switch.count」タグを、case の合計に「switch.cases」タグを使用して、IDA データベース内の関数にタグ付けする関数を定義できました。その定義をデータベース内の各関数に適用すると、クエリを実行できるようになります。以下のように「database.functions()」を使用して、すべての関数に反復処理を実行しましょう。

Python>for ea in db.functions():
Python>    tag_switches(ea)

時間がかかる場合があるため、現在の進行状況を確認できると便利です。IDA-minsc では、「ツール」モジュール内で利用できる「tools.map」を提供しています。このツールはパラメータに、呼び出し可能なオブジェクトを取ります。必要に応じて、次のように使用できます。

Python>_ = tools.map(tag_switches)

各関数のすべての switch にタグを付けたので、データベース全体でクエリを実行してソートできます。Python の「sorted」関数の「キー」キーワードを使用できますが、クエリの結果を整理して、最初のエントリが、各関数に属する switch の合計になるようにします。その後、「sorted」関数を使用してソートし、最後の要素を確認します。その要素に最も多くの case が含まれるはずです。

Python>results = []
Python>for ea, tags in db.select('switch.cases'):
Python>    results.append( (tags['switch.cases'], ea) )
Python>
Python>results = sorted(results)
Python>len(results)
162

ほとんどの switch を持つ関数をすべて示すソートされたリストが取得できたので、最後の要素でその内容を詳しく確認できます。最終結果からアドレスを抽出して、そのアドレスに移動しましょう(図 6)。

Python>results[-1]
(294, 5797552)
Python>_, ea = results[-1]
Python>go(ea)


図 6

幸運にも、パーサーのようなものを発見したようです。または、それはまさに、case が多数含まれる switch を持つトークナイザかもしれません。switch の数を「function.tag()」を呼び出して再確認してみましょう。

ほとんどの switch を持つ関数をすべて示すソートされたリストが取得できたので、最後の要素でその内容を詳しく確認できます。最終結果からアドレスを抽出して、そのアドレスに移動しましょう(図 6)。

Python>print func.tag('switch.count')
1

switch は 1 つしかないようです。switch の情報を取得して、どのような case が含まれるかを確認しましょう。これを行うには、主要な分岐のアドレス 0x5876d1 をクリックします。これで、アドレスが選択済みとなります。アドレスを「database.get.switch()」に渡す必要はなく、現在のアドレスを使用するよう指定できます。結果は「sw」変数に格納します。

Python>sw = db.get.switch()
Python>sw
<type 'switch_t{456}' at 0x5876c5> default:*0x58af5c branch[456]:*0x5876d8 register:edx

 

この switch に含まれる case の数と、デフォルトの case の数を確認してみます。デフォルトの case の数は、そのトータル長から、有効な case の長さを引くことによって計算します。

Python>print 'number of cases:', len(sw)
number of cases: 456
Python>print 'number of default cases:', len(sw) - len(sw.cases)
number of default cases: 162

 

特定の case に関連するトークンを識別しやすくするために、「tokens」リストに保存したトークンを各 case にタグ付けできます。利用可能な case のリストは「sw」変数の「cases」属性にあります。その「.case()」メソッドを呼び出すと特定の case のハンドラを取得できます。こうした属性を使用して、各 case ハンドラに「token」タグを付けます。このタグには、上記で割り当てた「tokens」リストのトークンが含まれています。

Python>for case in sw.cases:
Python>    handler = sw.case(case)
Python>    db.tag(handler, 'token', tokens[case])

ただ、ここでひとつ問題なのは、各 case で複数のトークンを処理できることです。このために、各ハンドラによって処理される case を取得する必要があります。これを行うには、割り当てた「sw」変数の「ハンドラ」メソッドを使用します。「function.select」を使用して、タグ付けしたハンドラをすべて選択し、各ハンドラの case を取得します。その後、適切なトークンをハンドラに再度タグ付けします。「function.select()」は関数のアドレスをパラメータに取りますが、私たちは現在の関数を検索するクエリを実行したいため、アドレスの指定は不要です。

Python>for ea, tags in func.select('token'):
Python>    toks = []
Python>    for case in sw.handler(ea):
Python>        toks.append( tokens[case] )
Python>    db.tag(ea, 'token', toks)

これにより、「token」の名前でタグ付けした各 case によって処理されるトークンのリストを取得できます。ハンドラを再度特定する、または各 case で処理されるトークンを特定する必要がある場合には、「function.select」を使用するだけでそれらを取得できます。図 7 は、case 66、68、69、183、427 のハンドラがそれに関連するトークンを多数含むことを示しています。

図 7

次に、最初の呼び出し命令を検索するために「token」タグを付けた各アドレスを反復処理します。また、無条件分岐が見つかった場合、ハンドラが処理を終了し、switch の外に分岐するため、無条件分岐も探します。呼び出し命令または無条件分岐を特定するには、「命令」モジュールを使用します。このモジュール内には関数「instruction.is_jmp」と関数「instruction.is_call」があります。これらのいずれかに一致する「next」命令を検索するために、述語を取る「database.address.next」のバリエーションを使用します。この処理では完全な精度が得られませんが、後で手動で処理できます。したがって、この状況では、精度はさほど重要ではありません。では、「function.select」を使用してハンドラのクエリを実行しましょう。その後、そのハンドラのアドレスに、最初の呼び出し命令のターゲットを「rtf-parameter」の名前でタグ付けします。

Python>for ea, tags in func.select('token'):
Python>    next_call = db.a.next(ea, lambda ea: ins.is_jmp(ea) or ins.is_call(ea))
Python>    if ins.is_call(next_call):
Python>        db.tag(ea, 'rtf-parameter', ins.op(next_call, 0))
Python>    elif ins.is_jmp(next_call):
Python>        print "found an unconditional branch with the target: {:x}".format(ins.op(next_call, 0))
Python>    continue

まったく重要ではない無条件分岐が多数表示されました。では、クエリを実行して最初の呼び出し命令としてタグ付けされた結果を見てみましょう。

Python>found = set()
Python>for ea, tags in func.select('rtf-parameter'):
Python>    found.add(tags['rtf-parameter'])
Python>
Python>found
set([4397600, 6556480, 4226632, 5797484, 5797008, 4226316, 4226640, 4397688, 5797216, 4226452, 5796288, 5797400, 5767988, 6487132])

残念ながら、これらの数値は利用できません。クリックできるようにするために、この数値を名前にマッピングしてみましょう。

Python>map(func.name, found)
['sub_431A20', 'sub_640B40', 'sub_407E48', 'sub_58766C', 'sub_587490', 'sub_407D0C', 'sub_407E50', 
'sub_431A78', 'sub_587560', 'sub_407D94', 'sub_5871C0', 'sub_587618', 'sub_580334', 'sub_62FC5C']

それぞれの数値を手動で処理し、目立った点をすばやく探して結果を簡単にまとめると、次のようになります。

  • sub_431A20:数字とハイフンに関係している様子。おそらく範囲の指定か?
  • sub_640B40:これが何かは不明。
  • sub_407E48:オブジェクトから、何かのプロパティを取得している。呼び出しているものは何もない。
  • sub_58766C:対象にするには複雑すぎる。
  • sub_587490:対象にするには複雑すぎる。
  • sub_407D0C:インデックスを使用して、配列から何かのプロパティを取得している。
  • sub_407E50:インデックスを使用して、配列から何かのプロパティを取得している。
  • sub_431A78:2 つの関数を呼び出している。1 つ目は空間を使用して何かを処理し、2 つ目はこのリストの最初の関数で、数字とハイフンを使用して何かを処理している。
  • sub_587560:複雑に見える。文字列に関連するようなものを呼び出している。
  • sub_407D94:何かの割り当て、おそらくリストへの割り当てを行う関数を呼び出している。
  • sub_5871C0:「{」、バックスラッシュ「\」、引用符のような、rtf 固有の文字をいくつか参照している。
  • sub_587618:現在このリスト内にあるいくつかの関数を呼び出している。
  • sub_580334:現在このリスト内にあるいくつかの関数を呼び出している。
  • sub_62FC5C:何らかのオブジェクトのサイズを変更している様子。

このリストのコメント全体を振り返ると、sub_431A20、sub_431A78、sub_5871C0、sub_587618 が解析に関連していると考えられます。この関数のリストを元に、関数をアドレスに逆変換してみます。こうすることで、それらをセットに格納して、switch に属するどの case がそうした関数を使用しているかを確認できます。ではまず、「function.address()」によって、関数の名前をアドレスに逆変換します。

Python>res = map(func.address, ['sub_431A20', 'sub_431A78', 'sub_5871c0', 'sub_587618'])
Python>match = set(res)

一致させるセットを取得できたところで、すべての case を、それが呼び出す「rtf-parameter」とともに収集しましょう。また、個別に確認する場合に備えて、それが処理する case も出力します。

Python>cases = []
Python>for ea, tags in func.select('rtf-parameter'):
Python>    if tags['rtf-parameter'] in match:
Python>        print hex(ea), sw.handler(ea)
Python>        for i in sw.handler(ea):
Python>            cases.append((i, func.name(tags['rtf-parameter'])))
Python>        continue
Python>    continue
Python>
Python>len(cases)
116

このリストからランダムにサンプルを取得して、これらがどのトークンをポイントしているかを確認してみます。また、各トークンを処理する、関数の最初の呼び出しを特定します。取得したランダムなサンプルを見ると、そのうちの多数が、関数「sub_431A20」を呼び出しているようです。私たちは、この関数が、ハイフンを含めることができる数字を処理していると判断しました。IDAPython のコマンド ラインから次のように実行すると、case 246、100、183 がこの関数を呼び出しており、それぞれが次のトークンを表していることがわかります。

Python>cases[246]
(246, 'sub_431A20')
Python>cases[100]
(100, 'sub_431A20')
Python>cases[183]
(183, 'sub_431A20')
Python>
Python>tokens[246], tokens[100], tokens[183]
('margt', 'colsx', 'highlight')

RTF ファイル形式のドキュメンテーションによると、これらのトークン(「\margt」、「\colsx」、「\highlight」)は、パラメータに数字を取るようです。これで問題なく、「sub_431A20」は RTF トークンに従う数字のパラメータを処理する役割を果たすと想定できます。では、これにタグ付けして、別の関数を確認し、それがどのような種類のパラメータを取るかを判断してみましょう。

Python>func.tag(0x431a20, 'synopsis', 'parses a numerical parameter belonging to an rtf token')

上記で作成した「cases」リストに何が残っているかをダンプに取り、まだ把握していない関数を簡単に確認してみます。またトークンの名前も含めるようにします。これにより、ファイル形式の仕様をすぐに参照して、その関数が取るパラメータの種類を判断できます。

Python>for i, name in cases:
Python>    if name != 'sub_431A20':
Python>        print i, tokens[i], name
Python>    continue
27 b sub_587618
63 caps sub_587618
105 contextualspace sub_431A78
114 deleted sub_431A78
123 embo sub_587618
186 hyphauto sub_431A78
187 hyphcaps sub_431A78
190 hyphpar sub_431A78
191 i sub_587618
193 impr sub_587618
232 listsimple sub_431A78
270 outl sub_587618
346 scaps sub_587618
363 shad sub_587618
373 strike sub_587618
423 u sub_5871C0
426 ul sub_431A78

「\b」、「\caps」、「\i」のタグについてドキュメンテーションを調べたところ、これらのタグではその機能をオフにするために、パラメータにオプションの「0」を指定できることがわかりました。つまり、「sub_587618」は、こうしたトークンを切り替えるパラメータを処理する関数であると想定できます。この関数にもタグ付けしましょう。

Python>func.tag(0x587618, 'synopsis', 'parses an rtf parameter that can be toggled with "0"')

出力したリストの中で目立ったその他のトークンは、「\hyphauto」、「\hyphcaps」、「\hyphpar」です。これらのトークンはパラメータに 1 つの数字、「1」または「0」のいずれかを取り、機能を切り替えます。これもトグルですが、パラメータは必須です。新しく得た知識も含めてこれにタグ付けしましょう。

Python>func.tag(0x431a78, 'synopsis','parses an rtf parameter that can be toggled with "0" or "1"')

これで、こうした多くの関数の意味を確認できました。また、Atlantis でサポートされているトークンをすぐに特定できました。この情報を利用して、この特定のターゲットをあいまいにするために使用できる文法を導き出せます。

クロージャの操作

Delphi 2009 で、「匿名メソッド」と呼ばれる新しい機能が導入されました。この機能により、Delphi プログラミング言語でクロージャが可能になります。クロージャは、一連のローカル変数をキャプチャして囲い込みます。これにより、完全に異なる関数に属している変数を、クロージャ内のコードで変更できるようになります。

これは、Delphi によって生成されたアセンブリの場合、図 8 のようになり、「%ebp」レジスタのフレーム ポインタが引数として関数に渡されます。これにより、関数はポインタを逆参照でき、それを使用して参照先フレームのローカル変数のアドレスを計算します。


図 8

こうした処理では、リバース エンジニアが多忙になりかねません。通常、まったく異なる関数内ではローカル変数が初期化されることがあるからです。それをデバッガで追跡するために、リバース エンジニアは変数の範囲を判定し、ハードウェアの分岐点を使用して、それに書き込みを行う最初の関数を特定しようとするでしょう。しかし、これを静的に行えばたちまち、克服するのが難しい大規模な問題が発生する可能性があります。

こうしたローカル変数の使用状況は、次のようになっています。[16] で、フレームが引数から抽出され、「%eax」レジスタに格納されます。これを、[17] と [18] で何度も繰り返すことで、スタックに積み、各呼び出し元のフレーム ポインタを逆参照します。最後に、[19] で決定したフレームのローカル変数が取得されます。Atlantis では、この種類の構造が非常に一般的で、管理が困難になることがあります。

CODE:0058BED8 018 8B 55 FC                          mov     edx, [ebp+var_4]
CODE:0058BEDB 018 8B 45 08                          mov     eax, [ebp+arg_0]    ; [16]
CODE:0058BEDE 018 8B 40 08                          mov     eax, [eax+8]        ; [17]
CODE:0058BEE1 018 8B 40 08                          mov     eax, [eax+8]        ; [18]
CODE:0058BEE4 018 8B 40 E8                          mov     eax, [eax-18h]      ; [19]
CODE:0058BEE7 018 8B 88 64 05 00 00                 mov     ecx, [eax+564h]
CODE:0058BEED 018 8B 45 08                          mov     eax, [ebp+arg_0]
CODE:0058BEF0 018 8B 40 08                          mov     eax, [eax+8]
CODE:0058BEF3 018 8B 40 08                          mov     eax, [eax+8]
CODE:0058BEF6 018 8B 40 E8                          mov     eax, [eax-18h]
CODE:0058BEF9 018 E8 12 39 07 00                    call    sub_5FF810
CODE:0058BEFE 018 84 C0                             test    al, al
CODE:0058BF00 018 75 0C                             jnz     short loc_58BF0E

しかし、IDA-minsc では、呼び出し元のフレームを引数に格納する関数と、各フレームが属している関数のアドレスにタグ付けできます。そうして、[19] に似た命令が参照するフレーム メンバーを特定できるようになります。これを行うには、2 つのタグ名を使用します。ひとつは、呼び出し元のフレームが含まれる引数の名前を格納する「frame-avar」で、もうひとつは参照先フレームが属する関数のアドレスを格納する「frame-lvars」です。図 8 を見ると、アドレス 0x590a32 で、関数「sub_590728」が、自身のフレームを、0x590a33 の呼び出し命令に引数として渡していることがわかります。この関数をダブルクリックしてその呼び出しをたどると、IDA の機能で、関数「sub_58BE98」の最上位に移動できます。この関数には呼び出し元が 1 つしかありません。さらに、その参照を詳細表示(Ctrl + X)すると、移動元のアドレスを確認できます。これがわかったので、呼び出し元のアドレスを使用して、この関数にタグ付けできます。

Python>callers = func.up()
Python>caller = callers[0]
Python>func.tag('frame-lvars', caller)

引数を識別しやすくするために、その引数に [スタック変数名の変更(Stack Variable Rename)] ダイアログを使用して「ap_frame_0」という名前を付けます。これを行うには「arg_0」を選択して N キーを押します。変数名を「arg_0」に変更したら、再びタグ付けを使用して、引数名を「frame-avar」として保存します。こうすることで、フレームを含む引数を識別したい場合に、「frame-avar」タグを使用してそれを抽出できます。

Python>func.tag('frame-avar', 'ap_frame_0')

これを行うと、関数は次のようなコードになります。これで、「ap_frame_0」引数の変数への参照すべてに反復処理を実行して、それらに「frame-lvars」タグで見つかった値を使用してタグ付けできます。

CODE:0058BE98                       ; [frame-avar] ap_frame_0
CODE:0058BE98                       ; [frame-lvars] 0x590a33
CODE:0058BE98                       ; Attributes: bp-based frame
CODE:0058BE98
CODE:0058BE98                       sub_58BE98      proc near
CODE:0058BE98
CODE:0058BE98                       var_4           = dword ptr -4
CODE:0058BE98                       ap_frame_0      = dword ptr  8
CODE:0058BE98

これを行うには、「function.frame()」関数を使用して、フレームを構造として把握します。それが完了すると、「ap_frame_0」変数を表すメンバーを取得できます。その後、この構造のメンバーを使用して、現在の関数における、メンバーへの参照をすべて列挙できます。

Python>f = func.frame()
Python>f.members
<type 'structure' name='$ F58BE98' size=+0x10>
[0] -4:+0x4 'var_4'      (<type 'int'>, 4) 
[1]  0:+0x4 ' s'         [(<type 'int'>, 1), 4] 
[2]  4:+0x4 ' r'         [(<type 'int'>, 1), 4] 
[3]  8:+0x4 'ap_frame_0' (<type 'int'>, 4) 

メンバーの取得には、インデックスまたは名前のどちらかを使用できます。今回は、名前で参照を行います。メンバーを取得したら、続いて、その「refs()」メソッドを呼び出すことで、関数内のメンバーへの参照すべてに反復処理を行えます。

Python>m = f.by('ap_frame_0')
Python>len(m.refs())
8
Python>for r in m.refs():
Python>    print r
Python>
AddressOpnumReftype(address=5815998L, opnum=1, reftype=ref_t(r))
AddressOpnumReftype(address=5816027L, opnum=1, reftype=ref_t(r))
AddressOpnumReftype(address=5816045L, opnum=1, reftype=ref_t(r))
AddressOpnumReftype(address=5816066L, opnum=1, reftype=ref_t(r))
AddressOpnumReftype(address=5816087L, opnum=1, reftype=ref_t(r))
AddressOpnumReftype(address=5816112L, opnum=1, reftype=ref_t(r))
AddressOpnumReftype(address=5816134L, opnum=1, reftype=ref_t(r))
AddressOpnumReftype(address=5816175L, opnum=1, reftype=ref_t(r))

参照は、名前付きタプルで返り、アドレスとそのオペランド番号のほか、参照が変数の読み取りであるか、変数への書き込みであるかを示す情報を含んでいます。操作中の命令を表示するには、タプルから取り出したアドレスを「database.disassemble」関数で使用します。

Python>for ea, _, _ in m.refs():
Python>    print db.disasm(ea)
Python>
58bebe: mov eax, [ebp+ap_frame_0]
58bedb: mov eax, [ebp+ap_frame_0]
58beed: mov eax, [ebp+ap_frame_0]
58bf02: mov eax, [ebp+ap_frame_0]
58bf17: mov eax, [ebp+ap_frame_0]
58bf30: mov eax, [ebp+ap_frame_0]
58bf46: mov eax, [ebp+ap_frame_0]
58bf6f: mov eax, [ebp+ap_frame_0]

これらの命令がすべて、呼び出し元のフレームを含む引数を参照していることが確実になったところで、一時的に、「frame-operand」という名前と、オペランドのインデックスを使用して、命令にタグ付けしてみます。

Python>for ea, idx, _ in m.refs():
Python>    db.tag(ea, 'frame-operand', idx)
Python>

次の目的は、フレームの変数が割り当てられているレジスタを使用するこうした参照ごとに、次の命令を特定することです。最初のオペランドに属するレジスタを特定するために、「instruction.op」を使用できます。また、レジスタの読み取り命令を見つけるには、「database.address.nextreg」関数を使用します。ただし、結果にタグ付けする前に、まず「frame-operand」タグを選択しましょう。さらに、「instruction.op」と「database.address.nextreg」を組み合わせて使用し、結果がどのようになるかを確認します。

Python>for ea, tags in func.select('frame-operand'):
Python>    reg = ins.op(ea, 0)
Python>    next_ref = db.a.nextreg(ea, reg, read=True)
Python>    print hex(ea), '->', db.disasm(next_ref)
Python>
58bebe -> 58bec1: push eax
58bedb -> 58bede: mov eax, [eax+8]
58beed -> 58bef0: mov eax, [eax+8]
58bf02 -> 58bf05: mov eax, [eax+8]
58bf17 -> 58bf1a: mov eax, [eax+8]
58bf30 -> 58bf33: mov eax, [eax+8]
58bf46 -> 58bf49: mov eax, [eax+8]
58bf6f -> 58bf72: mov eax, [eax+8]

アドレス 0x58bebe を見ると、「%eax」レジスタは次に、「push」命令によって、0x58bec1 で使用されるようです。これは、呼び出し元のフレームを関数の呼び出しに渡すのが目的と思われます。現在の関数で使用されるフレームの変数のみを対象にしていたので、現時点ではこのアドレスからタグを削除します。

Python>db.tag(0x58bebe, 'frame-operand', None)
1

このアドレスからタグを削除すると、フレームから読み取る割り当て命令のみが存在するはずです。前に、呼び出し元のアドレスを関数のタグ「frame-lvars」に格納したので、そのタグを使用して、「ap_frame_0」変数への参照に続く各割り当て命令に「frame」タグを付けられます。

Python>for ea, tags in func.select('frame-operand'):
Python>    reg = ins.op(ea, 0)
Python>    next_ref = db.a.nextreg(ea, reg, read=True)
Python>    lvars = func.tag('frame-lvars')
Python>    db.tag(next_ref, 'frame', lvars)
Python>

これで、フレームを使用する命令をポイントする「frame」タグを新しく作成できました。「frame-operand」タグはもう必要ないため、IDAPython のコマンド プロンプトで次のコードを実行することで、このタグを削除できます。

Python>for ea, _ in func.select('frame-operand'):
Python>    db.tag(ea, 'frame-operand', None)
Python>

では、関数を検索するクエリを、「frame」でタグ付けした命令すべてを対象に実行して、結果を確認しましょう。ここでも、「database.disassemble」を使用しますが、今回は、パラメータのひとつに「comment」キーワードを使用することで、指定したすべてのコメントを含めます。

Python>for ea, tags in func.select('frame'):
Python>    print db.disasm(ea, comment=True)
Python>
58bede: mov eax, [eax+8]; [frame] 0x590a33
58bef0: mov eax, [eax+8]; [frame] 0x590a33
58bf05: mov eax, [eax+8]; [frame] 0x590a33
58bf1a: mov eax, [eax+8]; [frame] 0x590a33
58bf33: mov eax, [eax+8]; [frame] 0x590a33
58bf49: mov eax, [eax+8]; [frame] 0x590a33
58bf72: mov eax, [eax+8]; [frame] 0x590a33

これらの各命令に「frame」でタグ付けしたので、オフセットが実際に参照しているフレームがどれであるかを確認できるようになりました。これにより、逆アセンブル中にダブルクリックして、特定の変数を持つフレームをすぐに表示できます。ただし、もう少しよい方法があります。出力した命令のひとつをダブルクリックすると、「instruction.op」関数を使用して、フレームの変数を参照するオペランドを抽出できます。それを、命令のひとつに移動して試してみましょう。

Python>ins.op(1)
OffsetBaseIndexScale(offset=8L, base=<internal.interface.register.eax(0,dt_dword) 'eax' 0:+32>, index=None, scale=1)

 

現在の命令における最初のオペランドを「instruction.op」によって出力するとすぐに、名前付きタプルが返ります。このタプルには、表示したいフレームの変数を参照するオフセットが含まれます。このオフセットと「function.frame」を使用すると、フレームのメンバーを特定して、その名前を取得できます。このメソッドでメンバー名を取得し、それを「frame-member」として命令にタグ付けしてみます。

Python>for ea, tags in func.select('frame'):
Python>    frame = func.frame(tags['frame'])
Python>    offset = ins.op(ea, 1).offset
Python>    member = frame.by(offset)
Python>    db.tag(ea, 'frame-member', member.name)
Python>

これで、フレームの変数名を含む「frame-member」タグが、現在の関数内で、「frame」とマークされたすべての命令に含まれていることを確認できます。「frame」タグの付いた命令があると、前述のコードが、「frame」の値で特定される関数が所有するフレームを調べて、その名前への参照をタグ付けします。このように、複数の命令が、正しいフレームをタグに含む場合、前述のコードが、参照先の変数名を格納します。0x590a33 の 関数の呼び出し元を判断できれば、0x58bf36 の命令にも同じ操作を行えます。これにより、タグ名「frame」と、関数のアドレスを 0x58bf36 にタグ付けできます。

CODE:0058BF33 018 8B 40 08                          mov     eax, [eax+8]    ; [frame] 0x590a33
CODE:0058BF33                                                               ; [frame-member] arg_0
CODE:0058BF36 018 8B 40 08                          mov     eax, [eax+8]
CODE:0058BF39 018 8B 80 54 F9 FF FF                 mov     eax, [eax-6ACh]
CODE:0058BF3F 018 8B D3                             mov     edx, ebx

しかし、これを参照するために、タグを使用しない、さらによい方法があります。IDA-minsc では、「instruction.op_structure」関数によって、フレームの構造そのものをオペランドに適用できます。この操作を行うには、「frame」タグを同様に選択します。また、オフセットを取得してフレーム メンバーの名前を判断するのではなく、フレーム構造そのものを指定して「instruction.op_structure」を使用するだけです。

Python>for ea, tags in func.select('frame'):
Python>    frame = func.frame(tags['frame'])
Python>    ins.op_struct(ea, 1, frame)
Python>

これにより、タグ付きの各命令を取得できます。この命令には、ポイント先のフレーム メンバーを参照する 2 番目のオペランドがあります。0x58bf49 の「frame」タグの場合、次のように表示されます。

CODE:0058BF36 018 8B 40 08                          mov     eax, [eax+8]
CODE:0058BF39 018 8B 80 54 F9 FF FF                 mov     eax, [eax-6ACh]
CODE:0058BF3F 018 8B D3                             mov     edx, ebx
CODE:0058BF41 018 E8 EA BE E7 FF                    call    sub_407E30
CODE:0058BF46 018 8B 45 08                          mov     eax, [ebp+ap_frame_0]
CODE:0058BF49 018 8B 40 08                          mov     eax, [eax+($ F590728.arg_0-0Ch)] ; [frame] 0x590a33

まとめ

IDA-minsc には、IDA がリバース エンジニアに公開しているさまざまなバイナリとプログラミングを通じて対話できる多数の機能があります。これまでに述べたこのプラグインの機能には、「構造」モジュールで提供される多彩なツールも含まれています。また、「function.frame()」を使用すると、特定の関数のスタック フレームに属している変数について、クエリを実行できます。こうしたモジュールで Python の「help()」キーワードを実行して、利用できる機能を把握することをお勧めします。または、こちらpopup_iconのドキュメンテーションをぜひご覧ください。

こうした処理は、多くのリバース エンジニアリングのタスクと同じく、デバッガや効果的なブレークポイントがあれば実行できます。しかし、逆アセンブルしているコードに注釈を付けるスクリプトを書けることは間違いなく大きなメリットと言えます。このプラグインは、一貫性があり、かつ冗長ではない API を提供し、関数のパラメータのほとんどを自動的に判断します。これにより、逆アセンブルの自動化ソリューションの開発に必要な時間を削減します。リバース エンジニアは、こうした点を、大規模かつ処理対象が複雑なプロジェクトに取りかかる際に活用できます。膨大な時間をつぎ込むことで悩む必要もありません。

GitHubpopup_icon のリポジトリでプラグインをダウンロードして機能をご確認ください。「Star」による評価もお待ちしています。報告済みの問題や寄与については、リポジトリ内の CONTRIBUTING.md ファイルを参照してください。

本稿は 2018年9月25日に Talos Grouppopup_icon のブログに投稿された「IDA-minsc Wins Second Place in Hex-Rays Plugins Contestpopup_icon」の抄訳です。

Tags:
コメントを書く