wivern ロゴ

サイバーセキュリティ研究所

サイバーセキュリティを中心に、軍事、防犯、サーバーの管理と監視、その他、最新技術を研究しています。


「リバースエンジニアリングバイブル」勉強メモ#3

C++ のクラスとリバースエンジニアリング

「リバースエンジニアリングバイブル」#3

前回に続き、「リバースエンジニアリングバイブル」の勉強メモ第3弾です。 実行環境は、Windows7(64bit版)です。実行環境は、Windows7(64bit版)です。

3章 C++ のクラスとリバースエンジニアリング
構造的な骨組みが C++ とアセンブラでは1対1で対応しないため、直感的に把握することが容易ではない。

C++ のコードを解析するためには、「豊富なコーディング経験」と「センス」が必要。
「コーディング経験」は誰かに教えてもらえるものではなく、自分で努力を積み重ねて作るものだが、「センス」は既に形になっている知識を学習することで、いくらでも磨くことができる。

IDA は、逆アセンブルされたコードを具体的に詳細化して表現することで有名なツールである。

メモリ上でスタックサイズを増やして変数が使用する領域を確保する点は、リバースエンジニアリングするうえで、構造体とクラスが持つ共通点である。

クラスのメンバー変数は、ECX にオブジェクトのポインターを入れて関数を呼び出す形が一般的である。
この日本語(クラスのメンバー変数)、主語がおかしくないか?
「クラスのメンバー関数は、ECX にオブジェクトのポインターを入れて呼び出される形が一般的である。」
が正しいのではないか?
[誤植]
p.50 5行目後半
誤「EAX を 84 バイト分加算し」
正「EAX を 0x84 バイト分加算し」
ECX を使用する this ポインターは、バイナリコードと確保されたスタックがクラスなのか構造体なのかを把握するのに大きな役割を果たす。

ecx にオブジェクトのポインターを入れる場合に、
lea ecx,[ebp+var_88]
のように lea を使う場合と、
mov ecx,[ebp+var_4]
のように mov を使う場合の、2パターンがある。
直後に、call が続く点は共通している。
クラスをグローバルにすると、「public:」で宣言されたさまざまなメンバー変数はすべてグローバル変数になる。

↑ここまでは、静的なオブジェクトの話で、↓ここからは、オブジェクトの動的な割り当てと開放の話。

静的な場合と動的な場合の2つの違い
①最初からスタックをクラスのサイズ分に確保するのではなく、ポインターを1つ宣言して使用する。
②メモリ割り当てのコードが必要

メンバー変数に値を入れる場合、
静的なときは、スタックに直接値を入れたが、

 mov [ebp+var_4],100h

動的に割り当てた後は、スタックに直接値を入れることはなく、オブジェクトが指すアドレスにオフセットを加算する方法で、クラスのメンバー変数を探し出して(算出して)値を入れる処理が行われる。

 mov eax,[ebp+var_4]
 mov dword ptr [eax+84h],100h

[コンストラクター]
コンストラクターが追加されると、コンパイラが内部的に( FS レジスタを利用した)例外処理コードを生成する。
コンストラクターは戻り値を持たない関数だが、(逆アセンブル結果からは)明らかに戻り値を持っており、その戻り値を利用してクラスオブジェクトを使用している。
(動的メモリの)割り当てコードの直後に関数が登場し、その関数が返す値を処理し、複数の変数を制御する部分が見えたら、その変数はクラスのオブジェクトであり、クラスのコンストラクターだと判断できる。

コンストラクターを探すときには複数の変数をまとめて0に初期化するコードを探してみる。
そして、その辺に memset() や malloc() などが見えたら、その関数は9割以上の確率でコンストラクターと考えてよい。

[デストラクター]
デストラクターは、通常コードの最後に位置する。
関数の中ではさまざまな変数のメモリを開放したり、Close 関連の API を呼び出している部分が見つかることが多い。

[カプセル化( encapsulation )の解析]
private 関数は、コンパイラによってバイナリに変換されると普通の関数と同じになる。
バイナリ上ではこれが private 関数なのか、一般的なメンバー関数なのかを把握する方法はなく、分析には限界がある。
カプセル化された部分をリバースエンジニアリングで100%見つけ出すことは不可能に近い。

[多様性の把握( polymorphism : ポリモーフィズム)]
仮想関数の場合は、一般的な関数が作成される方法とは異なり、別途テーブルが生成される。
これらの仮想関数は継承と関連があり、テーブルの情報をコンストラクターとデストラクターでも管理する。
仮想関数は継承問題があって、別にテーブルで管理されており、そのテーブルは .rdata セクションに配置されている。
したがって、.rdata セクションに保存された関数ポインターを発見したときは、仮想関数だと判断できる。


記述に際しては、細心の注意をしたつもりですが、間違いやご指摘がありましたら、こちらからお知らせいただけると幸いです。


→「リバースエンジニアリングバイブル」勉強メモ#4
←「リバースエンジニアリングバイブル」勉強メモ#2


« 戻る