2007年02月19日

Windowsに土足で乱入?! 〜 API フックのための予備知識(続き)

はてなブックマークに登録

■ はじめに

前回の記事では Win32 実行形式に含まれるインポート API 情報のセクションへ自作のコードからアクセスするための方法を整理してみました。
では、ビルドずみの実行イメージにおいて、所定の API を呼び出すコードはどのように表現されているのでしょう?今回はそれを確かめてみることにしましょう。
■ 簡単なテストプログラムを用意する

以下の短いプログラムをコンパイル〜リンクし mb.exe を作成します。


#include <windows.h>
#include <stdio.h>

#pragma comment(lib, "User32.lib") // for MessageBox()

int main()
{
MessageBox(NULL, "1111", "AAAA", MB_OKCANCEL);
printf("MessageBox = 0x%p\n", MessageBox);
return 0;
}


Windows API の MessageBox を呼び、MessageBox のアドレスを表示するだけのプログラムですね。実行するとメッセージボックスダイアログが展開し、コンソールに次のように出力されます。


MessageBox = 0x77D304EA


この実行例では、プログラムが MessageBox() のアドレスを 0x77D304EA と認識していることがわかります。

以下、Microsoft Visual Studio に付属の dumpbin ユーティリティを使って mb.exe の分析を行ってみます。

■ 好ましいベースアドレスの確認

まず、mb.exe が仮想メモリ上にロードされる際の好ましいベースアドレスを調べてみましょう。


>dumpbin /HEADERS mb.exe


OPTIONAL HEADER VALUES

400000 image base (00400000 to 00409FFF)



好ましいアドレスは「0x00400000」ですね。

■ 実行イメージのダンプ

以下の要領で mb.exe をダンプしておきます。


>dumpbin /RAWDATA mb.exe



■ マシン語コードの確認

次に、自作の mb.exe を逆アセンブルしてみます。

注:これは Microsoft Visual C++ .NET 2003 の生成したコードの例です


>dumpbin /DISASM mb.exe
※コメントは筆者が加筆したものです

00401000: 55 push ebp
00401001: 8B EC mov ebp,esp
; MessageBox への引数をスタックへ
00401003: 6A 01 push 1 ; MB_OKCANCEL
00401005: 68 40 80 40 00 push 408040h ; "AAAA"
0040100A: 68 48 80 40 00 push 408048h ; "1111"
0040100F: 6A 00 push 0 ; NULL
; MessageBox() の呼び出し
00401011: FF 15 D4 60 40 00 call dword ptr ds:[004060D4h]

; printf への引数をスタックへ
00401017: A1 D4 60 40 00 mov eax,dword ptr ds:[004060D4h]
0040101C: 50 push eax ; MessageBox のアドレス
0040101D: 68 50 80 40 00 push 408050h ; "MessageBox = 0x%p\n"
; printf() の呼び出し
00401022: E8 07 00 00 00 call 0040102E
00401027: 83 C4 08 add esp,8
0040102A: 33 C0 xor eax,eax
0040102C: 5D pop ebp
0040102D: C3 ret


※mb.exe のダンプ結果から、上記の逆アセンブルコードより参照されている
「408040h」「408048h」「408050h」のデータを以下に引用します。


00408040: 41 41 41 41 00 00 00 00 31 31 31 31 00 00 00 00 AAAA....1111....
00408050: 4D 65 73 73 61 67 65 42 6F 78 20 3D 20 30 78 25 MessageBox = 0x%
00408060: 70 0A 00 00 BD 1D 40 00 01 00 00 00 5C 61 40 00 p....@.....\a@.



さて、逆アセンブルコードにおいて、MessageBox() の呼び出しは次のように表現されています。


call dword ptr ds:[004060D4h]


[xxxx] はポインタ経由でのアドレス参照を意味します。つまり、「004060D4h」が今回の話題のポイントですね。


■ インポート情報の確認と照合

mb.exe のインポート情報を確認します。


>dumpbin /IMPORTS mb.exe

(略)
Section contains the following imports:

USER32.dll
4060D4 Import Address Table
406EA8 Import Name Table
0 time date stamp
0 Index of first forwarder reference

1DE MessageBoxA

KERNEL32.dll
406000 Import Address Table
406DD4 Import Name Table
0 time date stamp
(略)


この結果から、0x004060D4 がまぎれもなくインポートアドレステーブル (IAT) 上のエントリーであることがわかります。
「USER32.dll」に関する IAT の開始アドレスは 0x004060D4h、インポートネームテーブル(INT) の開始アドレスは 0x00406EA8、
そして、User32.dll から実際にインポートされている API は 「1DE」の Hint 値を持つ「MessageBoxA」一件のみ、という情報が示されています。

先ほどのダンプデータから IAT/ INT に該当する箇所を覗いてみましょう。


004060D0: 00 00 00 00 B0 6E 00 00 00 00 00 00 00 00 00 00 ....°n..........


IAT 0x004060D4 の値は「B0 6E 00 00」とあります。前回の記事で触れた通り、プログラム起動時にローダは該当する API の絶対アドレス情報を IAT の各エントリへ上書きしますが、ストレージ上のファイルイメージの状態ではこれに対応する INT エントリと同じく、IMAGE_IMPORT_BY_NAME 構造体ブロックへの RVA を保持しています。

0x00406EA8 の INT 側も確認しておきましょう。同じく「B0 6E 00 00」の値が格納されています。
[ベースアドレス] 0x00400000 + [RVA 値] 0x00006EB0 = 0x00406EB0 より、絶対アドレス 0x00406EB0 のデータを確認すると、MessageBoxA についての IMAGE_IMPORT_BY_NAME 情報が正しく格納されていることがわかります。


00406EA0: 44 72 00 00 00 00 00 00 B0 6E 00 00 00 00 00 00 Dr......°n......
00406EB0: DE 01 4D 65 73 73 61 67 65 42 6F 78 41 00 55 53 T.MessageBoxA.US
00406EC0: 45 52 33 32 2E 64 6C 6C 00 00 77 01 47 65 74 4D ER32.dll..w.GetM

DE 01 --> Hint 0x01DE
4D 65 73 73 61 67 65 42 6F 78 41 00 --> "MessageBoxA\0"


これで、実行イメージ上の IAT エントリと INT エントリの内容に整合性があり、それが MessageBoxA を指していることを確認できました。

■ bind ユーティリティで IAT の正規化を試みる

上述の通り、実行時にメモリへ展開された IAT へ絶対アドレス情報を書き込むのはローダの仕事ですが、前回触れた bind.exe ユーティリティを使えばファイルイメージ上の IAT 情報をローダに代わり事前に正規化しておくことが可能です。
(※言いかえればアドレステーブルのバインディングはプログラム起動の高速化に繋がります)


>bind -u mb.exe
BIND: binding mb.exe


こうしてバインドを済ませた状態であらためて mb.exe をダンプしてみます。


>dumpbin /RAWDATA mb.exe


IAT である 0x004060D4 番地を覗いてみると・・


004060D0: 00 00 00 00 EA 04 D3 77 00 00 00 00 00 00 00 00 ....e.Ow........


「EA 04 D3 77」、すなわち 0x77D304EA ですね。

これで、冒頭の dword ptr ds:[004060D4h] が正しく MessageBox の絶対アドレスを指しており、また、mb.exe の実行時に表示された「MessageBox = 0x77D304EA」が正しい内容であることを確認できました。

■ まとめ

上記の一連の結果から、所定の API を呼び出すコードは、ビルドずみの実行イメージにおいて「IAT に格納された所定の API のアドレス情報」を「当該 IAT エントリの絶対アドレス」(*1)により間接参照する内容で表現されていることがわかりました。

したがって、IAT 上の所定のエントリに記述された API アドレス情報を代替関数のアドレスに差し替えててやれば API フックを実現できそうです。
次回は実際にこの書き換えを行ってみることにしましょう。


(*1)
お気づきのように、これはあくまでも実行形式が「好ましいベースアドレス」に
ロードされることを前提とするものです。(好ましいベースアドレスは、EXE の
場合デフォルトで、0x00400000、DLL は 0x10000000 です)

絶対アドレスをハードコードしてしまうと実際のベースアドレスが異なるときに
破綻するのではないかと心配ですが、その状況を回避するために、実行形式には
「ハードコードされたアドレス情報を書き換えるための情報」を加味することが
できるようになっています。これが「再配置セクション」と呼ばれるものです。

再配置セクションには好ましいベースアドレスを前提にイメージに埋め込まれた
絶対アドレス情報の記述箇所が羅列されており、ローダは現実のロードアドレス
に応じてこのデータを参照し再書き換えを行います。

再配置情報は dumpbin /RELOCATIONS で参照することができます。再配置につい
ての詳細は以下の記事をご参照下さい。

<MSDN: Visual C++ リンカ オプション /FIXED (固定ベース アドレス)>
http://msdn2.microsoft.com/ja-jp/library/w368ysh2(VS.80).aspx

|/FIXED オプションを指定すると、オペレーティング システムが指定されたベ
|ース アドレスにだけプログラムを読み込みます。指定したベース アドレスが
|使用できない場合は、ファイルが読み込まれません。詳細については、/BASE
| (ベース アドレス) に関するトピックを参照してください。

|既定では、DLL のビルドには /FIXED:NO が使用されます。ほかのすべての種
|類のプロジェクトには /FIXED が使用されます。



klab_gijutsu2 at 19:28│Comments(4)TrackBack(0)win 

トラックバックURL

この記事へのコメント

1. Posted by takuya   2009年10月04日 00:19
5 3連載を期待したのですが、
実際に書き換え例の記事はお書きになられているとしたら、
それはどこにあるのでしょうか?
2. Posted by tanabe   2009年10月04日 06:12
takuya さん、コメントをありがとうございます。沢山の★を頂き恐縮です。この記事はその後諸般の事情で中断しています。私自身何らかの形で書き上げたいと考えているのですが、現時点では再開時期未定です。申し訳ありませんが、宜しくご了承下さい。
3. Posted by takuya   2009年10月04日 09:18
5 2年も前の記事へのコメントに快くお答えいただきありがとうございます。
連載が中断されたと知り残念に思いますが幸いにも、第1回・第2回の知識編の解説がとても丁寧でプログラミングのイメージをつかむことができました。
まずは自分自身でテストプログラムを作ってみようと思います。

テストプログラムが成功した暁にはその旨を報告させていただきます。

ありがとうございました。
4. Posted by tanabe   2009年10月04日 10:25
takuya さん、コメントをありがとうございます。記事がお役に立てて幸いです。今後とも DSAS ブログを宜しくお願い致します。

この記事にコメントする

名前:
URL:
  情報を記憶: 評価: 顔   
 
 
 
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント