第5回 PEフォーマットを解釈せよ!
岩村 誠
日本電信電話株式会社
情報流通プラットフォーム研究所 研究主任
2012/2/17
コンピュータウイルスの解析などに欠かせないリバースエンジニアリング技術ですが、何だか難しそうだな、という印象を抱いている人も多いのではないでしょうか。この連載では、「シェルコード」を例に、実践形式でその基礎を紹介していきます。(編集部)
Windows APIの呼び出し方法に迫れ!
第4回「Undocumentedなデータ構造体を知る」に引き続き、今回もシェルコードがWindowsのAPIを呼び出す方法について迫っていきたいと思います。
シェルコードでは、自由にAPIを呼び出すために以下の3ステップの処理を実行します。
- kernel32.dllのベースアドレスを取得する
- (kernel32.dllがエクスポートしている)LoadLibrary関数とGetProcAddress関数のアドレスを取得する
- LoadLibrary関数とGetProcAddress関数を利用して任意のAPIを呼び出す
前回は、download_exec.binを逆アセンブルしながら「kernel32.dllのベースアドレスを取得する」方法について解説しました。今回はさらに、LoadLibrary関数とGetProcAddress関数のアドレスの取得方法と、それらを用いた任意のAPIの呼び出し方法について見ていきましょう。今回は日本語の分量に負けないくらいのアセンブリが出てきますので、覚悟して読み進めてくださいね(笑)。
PEフォーマットとエクスポートテーブル
シェルコードの解析に入る前に、まずPEフォーマットについて簡単に触れたいと思います。kernel32.dllは、PE(Portable Executable)フォーマットと呼ばれる形式のファイルです。kernel32.dllが持つLoadLibrary関数やGetProcAddress関数のアドレスを探すには、このPEフォーマットを解釈していくことになります。
PEフォーマットについては、「Microsoft Portable Executable and Common Object File Format Specification」に詳しい情報がありますので、興味のある方はそちらも併せてご覧ください。
PEフォーマットの構成要素は、Microsoft Platform SDKに含まれるヘッダファイル、winnt.hで定義されています。PEフォーマットの全体像を把握したい方には、各構成要素の関連を図示した文書(http://www.openrce.org/reference_library/files/reference/PE%20Format.pdf)がお勧めです。
kernel32.dllはまず、winnt.hで定義されているIMAGE_DOS_HEADERから始まります。
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
……
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
この構造体の最後のメンバであるe_lfanew(IMAGE_DOS_HEADERの先頭から3Chバイト目)には、ファイル先頭からPEヘッダまでのオフセットが格納されています。つまりkernel32.dllのベースアドレスに、このe_lfanewの値を足すことで、PEヘッダのアドレスを取得できます。PEフォーマットの説明では、このようなモジュールのベースアドレスからのオフセットのことを、RVA(Relative Virtual Address)と呼ぶことが多いようです。
次に、PEヘッダを見てみましょう。winnt.hではIMAGE_NT_HEADERS32構造体として定義されています。
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
Signatureには、winnt.hで定義されているIMAGE_NT_SIGNATURE(0x00004550)が格納されています。さらにIMAGE_OPTIONAL_HEADER32構造体のメンバを見てみましょう。
typedef struct _IMAGE_OPTIONAL_HEADER {
……
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
IMAGE_OPTIONAL_HEADER32構造体の最後のメンバは、DataDirectoryという構造体の配列になります。この配列の一番目の要素、DataDirectory[0]に、エクスポート関数に関わる情報が格納されています。
では、DataDirectory[0]の型であるIMAGE_DATA_DIRECTORYを見てみましょう。
typedef struct _IMAGE_DATA_DIRECTORY {
+00 DWORD VirtualAddress;
+04 DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
DataDirectory[0].VirtualAddressが、以下のIMAGE_EXPORT_DIRECTORY構造体へのRVAとなっています。ちなみに、DataDirectory[0].VirtualAddressはPEヘッダ(IMAGE_NT_HEADER32)の先頭から78hバイト目です。
typedef struct _IMAGE_EXPORT_DIRECTORY {
+00 DWORD Characteristics;
+04 DWORD TimeDateStamp;
+08 WORD MajorVersion;
+0A WORD MinorVersion;
+0C DWORD Name;
+10 DWORD Base;
+14 DWORD NumberOfFunctions;
+18 DWORD NumberOfNames;
+1C DWORD AddressOfFunctions; // Export Address Table RVA
+20 DWORD AddressOfNames; // Name Pointer Table RVA
+24 DWORD AddressOfNameOrdinals; // Ordinal Table RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
このIMAGE_EXPORT_DIRECTORY構造体には、図1に示すようにエクスポート関数に関連する3つのテーブルのRVAが含まれています。
| 図1 エクスポート関数に関するテーブル(クリックすると拡大します) |
これらのテーブルを参照することで、kernel32.dllがエクスポートしているGetProcAddress関数やLoadLibrary関数のアドレスを取得できます。各テーブルの概要は以下のとおりです。
■ネームポインタテーブル
当該モジュールがエクスポートしている関数(変数)名のRVAが格納されています。IMAGE_EXPORT_DIRECTORY構造体のAddressOfNamesにこのテーブルのRVA、NumberOfNamesに要素数が格納されています。
■序数テーブル
このテーブルの要素は、「エクスポートアドレステーブル内で何番目の要素か」を意味する序数となっています。IMAGE_EXPORT_DIRECTORY構造体のAddressOfNameOrdinalsにこのテーブルのRVAが格納されています。また、序数テーブルとネームポインタテーブルの各要素は1対1で対応しているため、その要素数は、ネームポインタテーブルの要素数と同じくNumberOfNamesとなります。
■エクスポートアドレステーブル
当該モジュールがエクスポートしている関数(変数)のRVAの配列です。IMAGE_EXPORT_DIRECTORY構造体のAddressOfFunctionsにこのテーブルのRVA、NumberOfFunctionsに要素数が格納されています。
では、これら3つのテーブルを参照し、“LoadLibrary”という文字列からLoadLibrary関数のアドレスを取得する方法について見てみましょう。具体的には以下のような処理になります。
- ネームポインタテーブルから文字列“LoadLibrary”を探し、該当した要素がネームポインタテーブル内で何番目か(ここでは【インデックス】と呼びます)という情報を取得。
- 序数テーブル内で【インデックス】番目の値を取得。これがLoadLibrary関数の【序数】になります。
- エクスポートアドレステーブル内で【序数】番目の要素を取得。これがLoadLibrary関数のRVAとなります。
- 3で得られたRVAにkernel32.dllのベースアドレスを加算し、LoadLibrary関数のアドレスを算出。
それでは、以上の流れを念頭に置いて、download_exec.binを読んでいきましょう。
1/3 |
| Index | |
| PEフォーマットを解釈せよ! | |
| Page1 Windows APIの呼び出し方法に迫れ! PEフォーマットとエクスポートテーブル |
|
| Page2 逆アセンブル、スタート! |
|
| Page3 レジスタの値を頭に入れながら 方法はこれだけじゃないけれど |
|
リバースエンジニアリング入門 バックナンバー
| リバースエンジニアリング入門 連載インデックス |
TechTargetジャパン
- 実録、「Hardening Zero」の舞台裏 (2012/5/25)
コラムの更新頻度を落として何をやっていたかって? 「守る技術」に焦点を当てたこんなイベントを開催しました - 複雑化、巧妙化する脅威への対策は? (2012/5/23)
データ保護や標的型攻撃対策、クラウドセキュリティ……「第9回 情報セキュリティEXPO」の会場で見つけた製品を一挙に紹介 - 仮想化がはらむ新たなリスク (2012/5/17)
仮想化に伴って生じるセキュリティやパフォーマンスへの影響を慎重に考慮し、うまく制御していく方法を紹介します - 新入生も新入社員も勉強会に寄っといで! (2012/5/14)
週末ともなれば至るところでセキュリティ系勉強会やCTFなどのイベントがあり、ツイートも盛り上がりました
|
|
キャリアアップ
スポンサーからのお知らせ
- - PR -
イベントカレンダー
- - PR -

