Ayumu's I/O

ねだるな勝ち取れ、さすれば与えられん

API Monitorのフックについて調べてみる

あゆむです。今回はAPI Monitorというツールのフックの動作を簡単に調べてみました。

API Monitorとは

プロセス(32bit/64bit)によるAPIコールを監視・制御できるツールです。監視対象とするAPIは、一覧から選択することで簡単に追加することができます。
配布元のサイトをみると、Version 2.0(Alpha-r13)が最新版で、2013/03/14以降はバージョンアップされていないようです。とはいえ、Version 1.5と2.0(Alpha)まで9年の開きがあることから、今後のサポートもありうると考えられます。
個人的な使用用途はマルウェア解析中のAPIコール状況の監視ですが、動的解析中にクラッシュしてしまうことが何度かあったため、解析の手始めにちょっとだけ使ってみるくらいの利用頻度です。

www.rohitab.com

基本的な使い方

簡単なプログラムを例にAPI Monitorの使い方を説明します。

環境

本記事における動作環境は次の通りです。

OS Windows 7 64bit
コンパイラ gcc version 6.3.0 (MinGW.org GCC-6.3.0-1)
その他 API Monitor v2 Alpha-r13

プログラムの準備

MessageBoxA関数でダイアログを表示するだけのプログラムを準備します。すぐにダイアログが表示されてしまうと扱いづらいので、getchar関数でユーザ入力されるまで待機するようにします。

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

int main(void) {
	printf("Press \"Enter\" key to continue: ");
	getchar();
	MessageBoxA(NULL, "Hello world", "Message", MB_OK);
	return 0;
}

監視の手順

1. プログラムを実行します。
f:id:aes256:20180125145425p:plain
2. API Monitorの左上のパネル「API Filter」から監視対象とするAPIを選択します。ここでは、User32.dll!MessageBoxAを監視項目にいれます。
f:id:aes256:20180125145340p:plain
3. API Monitorの左下のパネル「Running Processes」で当該プロセスを選択し、モニタリングを開始します。
f:id:aes256:20180125145541p:plain
4. Enterキーを押下し、ダイアログを表示させます。
f:id:aes256:20180125145648p:plain
5. API Monitorを確認します。右上のパネル「Summary」にて引数の情報がモニタリングできていることが確認できます。
f:id:aes256:20180125145706p:plain

API Monitorのフックを調べてみる

プログラムごとに3パートに分けてAPI Monitorの動作を説明します。

Part1

まず、先ほどの「Hello world」を出力するプログラムを用いて動作を調べてみます。API Monitorによる監視前後におけるプロセスのメモリマップの変化を確認します。監視を開始すると「apimonitor-drv-x86.sys」をはじめ、いくつかのコードが注入されていることがわかります。
f:id:aes256:20180125154744p:plain
特筆すべきは、下図のコードです。命令列らしきコードですね。
f:id:aes256:20180125154645p:plain
ここで、デバッガを使って当該プロセスにアタッチし、このコードを逆アセンブルしてみます(下図)。GetProcAddress関数やMessageBoxA関数などのアドレスを引数として「apimonitor-drv-x86.sys」内をコールしていることがわかります。
これがAPI Monitorによる関数の検知の入り口です。実際にMessageBoxA関数に関してステップインで追っていくと、User32.dllがロードされているメモリではなく下図の箇所が最初に呼び出されます。
f:id:aes256:20180125155212p:plain

さて、当該プログラムのメモリロード後のIAT(Import Address Table)を見てみます。下図のメモリダンプ(Dump1)では、MessageBoxA関数のアドレスが「1C 00 DD 02」となっており、先のコード列を指していることがわかります。このようなAPIのフックは、IATフックと呼ばれます。
f:id:aes256:20180125161117p:plain

Part2

Part1で使用したプログラムでは、User32.dllを静的リンクで呼び出していましたが、Part2では動的リンクで呼び出すように書き換えてみます(次のコード参照)。これによって、MessageBoxA関数のアドレスがIATに含まれなくなります。つまり、Part2では、IATに監視対象のAPIがない場合にAPI Monitorで監視対象のAPIコールを検出できるかを確認するということです。マルウェアによっては元のIATが破壊されていることがあるため、これができるか否かで使い物になるかどうかが変わってきます。

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

int main(void) {
	printf("Press \"Enter\" key to continue: ");
	getchar();

	FARPROC mba = GetProcAddress(LoadLibraryA("user32.dll"), "MessageBoxA");
	mba(NULL, "Hello world", "Message", MB_OK);
	return 0;
}

図は省略しますが、動的リンクの場合でも監視対象を検出することができます。調べたところ、GetProcAddress関数が呼び出された場合にMessageBoxA関数検知のためのルーチン(Part1において”API Monitorによる関数の検知の入り口”と呼んだ箇所)を追加して、そのアドレスを返すようにしているようです。

Part3

最後はGetProcAddress関数を使用せずに、User32.dllのEAT(Export Address Table)からMessageBoxA関数のアドレスを直接取得するプログラムで試してみます。コードは次の通りです。

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

int main(void) {
	printf("Press \"Enter\" key to continue: ");
	getchar();

	HMODULE hModule = LoadLibraryA("user32.dll");
	PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER) hModule;
	PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS) ((LPBYTE)hModule + pDosHeader->e_lfanew);
	PIMAGE_OPTIONAL_HEADER pOptionalHeader = &pNtHeaders->OptionalHeader;
	PIMAGE_DATA_DIRECTORY pDirectory   = &pOptionalHeader->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
	PIMAGE_EXPORT_DIRECTORY pImgExport = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)hModule + pDirectory->VirtualAddress);

	DWORD* functions = (DWORD*)((void*)hModule + pImgExport->AddressOfFunctions);
	DWORD* names     = (DWORD*)((void*)hModule + pImgExport->AddressOfNames);
	WORD*  ordinals  = (WORD*) ((void*)hModule + pImgExport->AddressOfNameOrdinals);

	for (int i = 0; i < pImgExport->NumberOfNames; ++i) {
		int ord = ordinals[i];
		//printf("%08X (%08x), %s\n", functions[ord], (void*)hModule+functions[ord], (void*)hModule+names[i]);

		if (strcmp((char*)hModule+names[i], "MessageBoxA") == 0) {
			FARPROC mba = ((void*)hModule + functions[ord]);
			mba(NULL, "Hello world", "Message", MB_OK);
			break;
		}
	}
	return 0;
}

結果は下図の通りです。APIMonitorではMessageBoxA関数を検出することはできませんでした。
f:id:aes256:20180126194616p:plain