Windowsに土足で乱入?！ 〜 API フックのための予備知識



┌────────────────┐

│ MS-DOS互換用ブロック │

│ ┏━━━━━━━━━━━━┓ │

│ ┃IMAGE_DOS_HEADER────┨ │

│ ┃ │e_lfanew┃e_lfanew が

│ ┣━━━━━━━┷━━━━┫ │ IMAGE_NT_HEADERS先頭アドレスを指す

│ ┃DOS用スタブコード ┃ │

│ ┗━━━━━━━━━━━━┛ │

│ イメージヘッダ (IMAGE_NT_HEADERS)

│ http://msdn2.microsoft.com/en-us/library/ms680336.aspx

│ ┏━━━━━━━━━━━━┓ │

│ ┃シグニチャ(DWORD) ┃"PE" 0x00 0x00

│ ┣━━━━━━━━━━━━┫

│ ┃IMAGE_FILE_HEADER ┃モジュールの基本情報

│ ┣━━━━━━━━━━━━┫

│ ┃IMAGE_OPTIONAL_HEADER ┃各種アドレス・サイズ・バージョン情報

│ ┃ ┌──────────┨

│ ┃ │IMAGE_DATA_DIRECTORY┃データディレクトリ

│ ┗━┷━━━━━━━━━━┛ │

│ セクションテーブル │

│ ┏━━━━━━━━━━━━┓ │

│ ┃IMAGE_SECTION_HEADER ┃ │

│ ┣━━━━━━━━━━━━┫ │

│ ┃ ： ┃ │

： ：





┏━━━━━━━━━━━━┓

┃IMAGE_OPTIONAL_HEADER ┃

┃ ┌──────────┨

┃ │IMAGE_DATA_DIRECTORY╂─┐

┗━┷━━━━━━━━━━┛ │RVA

┌─────────────┘

↓データディレクトリ配列

┏━━━━━━━━━━━━┓

┃IMAGE_DATA_DIRECTORY[0] ┃

┣━━━━━━━━━━━━┫(←#define IMAGE_DIRECTORY_ENTRY_IMPORT 1)

┃IMAGE_DATA_DIRECTORY[1] ╂─┐

┣━━━━━━━━━━━━┫ │RVA

: │

┣━━━━━━━━━━━━┫ │

┃IMAGE_DATA_DIRECTORY[15]┃ │

┗━━━━━━━━━━━━┛ │

┌────────────┘

↓インポートデスクリプタ：インポートモジュール単位の配列

┏━━━━━━━━━━━━━┓

┃IMAGE_IMPORT_DESCRIPTOR[0]┃

┃ ┃

┃ ・OriginalFirstThunk╂───────────┐RVA

┃ ・Name╂─→ "KERNEL32.dll\0" │

┃ ・FirstThunk╂┐RVA │

┣━━━━━━━━━━━━━┫│ │

┃IMAGE_IMPORT_DESCRIPTOR[1]┃RVA │

┣━━━━━━━━━━━━━┫│ │

： │ │

┃IMAGE_IMPORT_DESCRIPTOR[N]┃│ │

┗━━━━━━━━━━━━━┛│ │

│ │

┌────────────┘ ┌───────┘

↓インポートアドレステーブル ↓インポートネームテーブル

┏━━━━━━━━━━┓(IAT) ┏━━━━━━━━━━┓(INT)

┃IMAGE_THUNK_DATA[0] ┃ ┃IMAGE_THUNK_DATA[0] ┃

┃ ┃ ┃ ┃

┃ ・u1.Function╂┐ ┌╂・u1.AddressOfData ┃

┣━━━━━━━━━━┫│ │┣━━━━━━━━━━┫

┃IMAGE_THUNK_DATA[1] ┃│ RVA┃IMAGE_THUNK_DATA[1] ┃

┣━━━━━━━━━━┫│ │┣━━━━━━━━━━┫

： │ │ ：

┃IMAGE_THUNK_DATA[n] ┃│ │┃IMAGE_THUNK_DATA[n] ┃

┗━━━━━━━━━━┛│ │┗━━━━━━━━━━┛

│ ↓

↓ ┏━━━━━━━━━━━┓

┏━━━━━┓ ┃IMAGE_IMPORT_BY_NAME ┃

┃0xXXXXXXXX┃＝┃ ┃

┗━━━━━┛ ┃・Hint=49 ┃

(CloseHandle API のアドレス↑) ┃・Name="CloseHandle\0"┃

┗━━━━━━━━━━━┛



・IMAGE_IMPORT_DESCRIPTOR の Name メンバの指すモジュール名および

IMAGE_IMPORT_BY_NAME の Name メンバの指す API 名は常に ANSI で表記される



・IMAGE_IMPORT_DESCRIPTOR 配列、および IAT, INT の IMAGE_THUNK_DATA

配列は可変長であり、配列終端には構造体の全メンバが 0 にセットされた

エントリが配置される





#include <windows.h>

#include <stdio.h>

#include <imagehlp.h>



#pragma comment(lib, "Imagehlp.lib")



int main(int ac, char *av[])

{

HMODULE hMod, hLoad = NULL;

ULONG Size;

IMAGE_IMPORT_DESCRIPTOR *pImpDesc;

IMAGE_THUNK_DATA *pThunkINT, *pThunkIAT;

IMAGE_IMPORT_BY_NAME *pImpByName;



if (ac < 2) { // 引数がなければ自プロセスを対象に

hMod = GetModuleHandle(NULL);

} else { // 指定された実行形式をローダにかける

hMod = hLoad = LoadLibrary(av[1]);

}

if (!hMod) {

printf("HMODULE err

");

return -1;

}

printf("Base Address = 0x%p

", hMod);



// ベースアドレスからインポートデスクリプタのアドレスを得る

pImpDesc = (IMAGE_IMPORT_DESCRIPTOR*)

ImageDirectoryEntryToData(hMod,

TRUE,

IMAGE_DIRECTORY_ENTRY_IMPORT,

&Size);



printf("Import Desc = 0x%p, Size = %d bytes

", pImpDesc, Size);



// インポートモジュール情報のループ

while (pImpDesc->Name) {

// モジュール名

LPSTR pszModName = (LPSTR)((BYTE*)hMod + pImpDesc->Name);

printf("Module = [%s]

", pszModName);



// インポートネームテーブルのアドレスを得る

pThunkINT = (IMAGE_THUNK_DATA*)((BYTE*)hMod + pImpDesc->OriginalFirstThunk);

// インポートアドレステーブルのアドレスを得る

pThunkIAT = (IMAGE_THUNK_DATA*)((BYTE*)hMod + pImpDesc->FirstThunk);



// インポートAPIの情報

while (pThunkINT->u1.Function) {

// API アドレスを出力

printf("\tAddress = 0x%p ", (PROC)pThunkIAT->u1.Function);

if (pThunkINT->u1.AddressOfData & 0x80000000) {

// シンボルが序数情報の場合

DWORD dwOrd =pThunkINT->u1.AddressOfData ^ 0x80000000;

printf("Ordinal %d (0x%x)

", dwOrd, dwOrd);

} else {

// シンボルが名前情報の場合

pImpByName = (IMAGE_IMPORT_BY_NAME*)

((BYTE*)hMod+ pThunkINT->u1.AddressOfData);

printf("[%s]

", (LPSTR)(pImpByName->Name));

}

pThunkINT++;

pThunkIAT++;

}

pImpDesc++;

}

if (hLoad) FreeLibrary(hLoad);



return 0;

}





Base Address = 0x00400000

Import Desc = 0x00407D98

Module = [imagehlp.dll]

Address = 0x76C441B1 [ImageDirectoryEntryToData]

Module = [KERNEL32.dll]

Address = 0x7C9505D4 [HeapAlloc]

Address = 0x7C812AC6 [GetSystemInfo]

Address = 0x7C80AA66 [FreeLibrary]

Address = 0x7C801D77 [LoadLibraryA]

Address = 0x7C80B529 [GetModuleHandleA]

:

（後略）



EXE, DLL 等の Windows 実行形式(PE: Portable Executable)の先頭部分は以下の構成を持ちます。"IMAGE_xxx" はいずれも WINNT.h ヘッダファイルに定義のある構造体です。この内、API のインポート情報は IMAGE_OPTIONAL_HEADER 構造体の DataDirectory メンバの保持するアドレス情報から牽引することができます。物理ストレージ上のバイナリファイルである実行ファイルがひとたび「起動」されると、Windows のシステムローダは次の仕事を行います。1. 新しいプロセスのために仮想アドレス空間を作成する。2. 実行ファイルを仮想アドレス空間へモジュールとしてマッピングする。(実行形式のバイナリをそのままの形でマッピングするのではなくどのセクションをどのオフセットへアサインするかも併せて判断）3. モジュールに含まれるインポート情報の分析を行い、必要な DLL をプロセスのアドレス空間へモジュールとしてマッピングする。 さらに、マッピングされた DLL モジュールに含まれるインポート情報についても同様の手順を踏む。4. モジュールの必要とするインポート API が所定の DLL から実際にエクスポートされているか順次チェックを行い、モジュールのインポート情報セクションに含まれるインポートアドレステーブルへ API のアドレスを上書きする(※1)。(※1) bind.exe ユーティリティ等による「バインド」操作を経てあらかじめインポートアドレステーブルの正規化された実行形式が、実行形式自身の期待する条件を満たす状態でロードされた場合にはこの手順は省略されます一連の処理の中でエラーを検知するとローダはエラーメッセージを表示します。すべてが成功すればプログラムは正常に開始され、解決済みのインポート情報を参照することにより所定の API へアクセスできるようになります。EXE,DLL等の実行形式ファイルがプロセスのアドレス空間へマッピングされる際には「好ましいアドレス」が存在します。しかし、実際にそこへロードされるとは限りません。そのため、実行形式ファイル内での所定のセクションの指定には絶対アドレスではなく仮想アドレスが使用されます。このアドレス表記のことを RVA (Relative Virtual Addresses) といいます。これは単純にロードアドレス先頭からのオフセット情報であり、RVA 値にモジュールのロードアドレスを加算することで所定の絶対アドレスを求めることができます。ちなみに、Windows プログラミングでの「HMODULE」は、ロードされた所定のモジュールの先頭アドレスを指すものです。ロードずみモジュール内でインポート API 情報を参照する方法を以下に示します。構造体ブロックの単純なリンクをだどればよいことがわかります。構造体メンバの内特に重要なものは図中に記述していますが、詳細は WINNT.h を参照して下さい。上図の通り、IMAGE_IMPORT_DESCRIPTOR の OriginalFirstThunk メンバと FirstThunkメンバはそれぞれ INT と IAT を示します。これらはいずれも Name メンバで示されるモジュールに含まれる所定の API ごとに一要素を構成する配列情報であり、同一の要素番号を持つエントリは同一のインポートAPI についての情報を保持しています。リンクずみの実行形式ファイルの上では、FirstThunk は OriginalFirstThunk と同一の情報を保持しています(※2)が、「システムローダの役割」項で触れた通り、実行時にはローダにより API アドレス情報に書き換えられます。(※2) bind.exe ユーティリティ等でバインド操作ずみのバイナリでは異なりますなお、INT の IMAGE_THUNK_DATA エントリの最上位ビットが立っていればそのエントリは API 名情報を持つ IMAGE_IMPORT_BY_NAME への RVA ではなく、当該インポート DLL上の序数情報であることを示しており、ここから最上位ビットを落とした値が実際の序数です。名前・序数のいずれも、ローダによる API アドレス解決の際のシンボルとして有効です。最後に、ここまでの知識を元に、指定された実行ファイルのインポート API 情報の一覧を出力するサンプルコードを C 言語で書いてみることにします。なお、最も重要な IMAGE_IMPORT_DESCRIPTOR テーブルのアドレスの取得は、ImageDirectoryEntryToData() API を使うことで簡単に実現できます。＜ImageDirectoryEntryToData＞実行時の出力例 次回 は、所定の API を参照するコードが実行形式イメージにおいてどのように表現されているかを調べてみることにします。by Matt Pietrek・Advanced Windows 改訂第4版 (Microsoft Press)by Jeffrey Richter, 長尾 高弘 訳(株)アスキー刊 ISBN-13: 978-4756138057