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コール状況の監視ですが、動的解析中にクラッシュしてしまうことが何度かあったため、解析の手始めにちょっとだけ使ってみるくらいの利用頻度です。
基本的な使い方
簡単なプログラムを例に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; }
API Monitorのフックを調べてみる
プログラムごとに3パートに分けてAPI Monitorの動作を説明します。
Part1
まず、先ほどの「Hello world」を出力するプログラムを用いて動作を調べてみます。API Monitorによる監視前後におけるプロセスのメモリマップの変化を確認します。監視を開始すると「apimonitor-drv-x86.sys」をはじめ、いくつかのコードが注入されていることがわかります。
特筆すべきは、下図のコードです。命令列らしきコードですね。
ここで、デバッガを使って当該プロセスにアタッチし、このコードを逆アセンブルしてみます(下図)。GetProcAddress関数やMessageBoxA関数などのアドレスを引数として「apimonitor-drv-x86.sys」内をコールしていることがわかります。
これがAPI Monitorによる関数の検知の入り口です。実際にMessageBoxA関数に関してステップインで追っていくと、User32.dllがロードされているメモリではなく下図の箇所が最初に呼び出されます。
さて、当該プログラムのメモリロード後のIAT(Import Address Table)を見てみます。下図のメモリダンプ(Dump1)では、MessageBoxA関数のアドレスが「1C 00 DD 02」となっており、先のコード列を指していることがわかります。このようなAPIのフックは、IATフックと呼ばれます。
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関数を検出することはできませんでした。