これを実現するために、サンプルはtest.exeをデバッグ対象のプロセスとして起動している。具体的には、CreateProcess関数のfdwCreate引数にDEBUG_PROCESSフラグを指定している。test.exeが起動されたら、MessageBox関数のエントリポイントアドレスにブレークポイントを設置する。このことにより、test.exeがMessageBox関数を呼び出した際に、ブレークポイント例外がサンプルに対して通知される。サンプルはブレークポイント例外を受け取ったら、test.exeのスタックに設定されているMessageBox関数用の引数の内容を表示する。
サンプルは引数を表示後、test.exeでの処理を続行させるためにブレークポイントを外し、test.exeの実行コードを元に戻してからtest.exeに制御を戻す。したがってtest.exeはメッセージボックスの表示処理を続行することができる。ただし、このままでは以降のMessageBox関数呼び出しを検出することができなくなる。そこで、サンプルはブレークポイントを外すと同時に、test.exeをシングルステップモードに変更してから、test.exeに制御を移す。このことにより、test.exeは命令を一つ実行した後にシングルステップ例外をサンプルへ通知する。通知を受け取ったサンプルは、test.exeに対して再びブレークポイントを設定し、test.exeシングルステップモードを解除する。以上のループにより、複数回のMessageBox関数の呼び出しを監視することが可能となる。
本サンプルでは、ブレークポイントの設置にVirtualProtectEx関数を使用している。test.exeのプロセス空間内においてMessageBox関数の実行コードが存在するメモリページの保護属性を「書き込み時コピー」に変更し、「int 3h」命令を書き込んでいる。95の場合、システム領域や共有メモリ領域(2G〜4Gの領域)に対する保護属性の変更は不可能のため、本サンプルは95では動作しない。
監視対象のプロセスがMessageBox関数を、上記関数ポインタ上の実行コードを経由せずに呼び出した場合、サンプルは呼び出しを検出することはできない。
#include <windows.h>
#include <stdio.h>
#include <string.h>
/* 監視対象のアプリケーションが作成したスレッドの情報 */
typedef struct {
DWORD ThreadId;
HANDLE hThread;
} THREAD_INFO;
/* 監視対象のアプリケーションが作成したスレッドのリスト */
#define MAX_THREAD_INFO_NUM 1000
THREAD_INFO ThreadInfos[MAX_THREAD_INFO_NUM];
DWORD NumberOfThread = 0;
#define INVALID_INDEX 0xffffffff /* インデックスを返す関数が失敗した場合に返す値 */
#define OPECODE_INT_3 0xcc /* ブレークポイント命令 */
#define EFLAGS_TF 0x00000100 /* シングルステップモードフラグ */
/* スレッド情報リストに指定された情報を追加 */
BOOL AddThreadInfo(DWORD ThreadId, HANDLE hThread)
{
if(hThread != NULL) {
if(NumberOfThread -1 == MAX_THREAD_INFO_NUM) {
printf("too many thread.\n");
return FALSE;
}
ThreadInfos[NumberOfThread].ThreadId = ThreadId;
ThreadInfos[NumberOfThread].hThread = hThread;
NumberOfThread++; /* 1000個以上でアクセス違反 */
}
return TRUE;
}
/* スレッドIDを元にスレッド情報を検索 */
DWORD FindThreadInfo(DWORD ThreadId)
{
DWORD i;
for(i=0; i<NumberOfThread; i++) {
if(ThreadInfos[i].ThreadId == ThreadId) return i;
}
printf("unknown thread id.\n");
return INVALID_INDEX;
}
/* スレッド情報リストからスレッド情報を削除 */
BOOL DeleteThreadInfo(DWORD ThreadId)
{
DWORD i;
if((i = FindThreadInfo(ThreadId)) != INVALID_INDEX) {
MoveMemory(
&ThreadInfos[i],
&ThreadInfos[i+1],
sizeof(THREAD_INFO) * (NumberOfThread - i - 1));
NumberOfThread--;
return TRUE;
} else {
return FALSE;
}
}
/* hProcessで指定されたプロセス中のadrで指定されたアドレスにある文字列を表示 */
BOOL DispString(HANDLE hProcess, DWORD adr)
{
#define STRING_BUF_MAX 100
char buf[STRING_BUF_MAX];
DWORD i;
memset(buf, 0, sizeof(buf));
for(i=0; i<sizeof(buf)-1; i++) {
if(!ReadProcessMemory(hProcess, (LPCVOID)(adr + i), &buf[i], sizeof(char), NULL)) {
return FALSE;
}
if(buf[i] == '\0') break;
}
strcpy(&buf[STRING_BUF_MAX - 4], "...");
printf("%s", buf);
return TRUE;
}
/* 監視対象プロセスがMessageBox関数を呼んだことをユーザに通知 */
BOOL ProcessMessageBox(HANDLE hProcess, LPCONTEXT pct)
{
DWORD hWnd;
DWORD adrText;
DWORD adrTitle;
DWORD Style;
BOOL bResult;
/* MessageBox関数に与えられた引数を取得 */
bResult = TRUE;
bResult = bResult && ReadProcessMemory(hProcess, (LPCVOID)(pct->Esp + 4), &hWnd, sizeof(hWnd), NULL);
bResult = bResult && ReadProcessMemory(hProcess, (LPCVOID)(pct->Esp + 8), &adrText, sizeof(adrText), NULL);
bResult = bResult && ReadProcessMemory(hProcess, (LPCVOID)(pct->Esp + 12), &adrTitle, sizeof(adrTitle), NULL);
bResult = bResult && ReadProcessMemory(hProcess, (LPCVOID)(pct->Esp + 16), &Style, sizeof(Style), NULL);
if(!bResult) {
printf("memory can not read.\n");
return FALSE;
}
/* 結果を表示 */
printf("API callded: MessageBox(0x%08x, \"", hWnd);
DispString(hProcess, adrText);
printf("\", \"");
DispString(hProcess, adrTitle);
printf("\", 0x%08x);\n", Style);
return TRUE;
}
int main(void)
{
STARTUPINFO si;
PROCESS_INFORMATION pi;
SYSTEM_INFO SystemInfo;
DEBUG_EVENT de;
CONTEXT ct;
BYTE OrgOpeCode, NewOpeCode;
DWORD OrgProtect;
LPVOID hUser32;
void *pMessageBox;
DWORD ContinueStatus;
BOOL bResult;
int iResult;
DWORD i;
NewOpeCode = OPECODE_INT_3;
iResult = -1;
/* user32.dllのモジュールハンドル及びMessageBox関数ポインタを取得
* このサンプルでは取得結果が、監視対象のプロセスにおけるuser32.dllの
* ベースアドレスおよび関数ポインタとそれぞれ等しいものと仮定している */
hUser32 = LoadLibrary("user32.dll");
pMessageBox = GetProcAddress(hUser32, "MessageBoxA");
FreeLibrary(hUser32);
/* 監視対象のプロセスを作成 */
si.cb = sizeof(si);
si.lpReserved = NULL;
si.lpDesktop = NULL;
si.lpTitle = NULL;
si.dwFlags = 0;
si.cbReserved2 = 0;
si.lpReserved2 = NULL;
bResult = CreateProcess(
"test.exe",
NULL,
NULL,
NULL,
FALSE,
DEBUG_PROCESS | CREATE_NEW_CONSOLE,
NULL,
NULL,
&si,
&pi);
if(!bResult) {
printf("process can not create.\n");
return -1;
}
/* デバッグイベント取得のループ。EXIT_PROCESS_DEBUG_EVENTイベントが発生したらループを抜ける */
for(;;) {
/* デバッグイベントの待機 */
if(!WaitForDebugEvent(&de, INFINITE)) {
printf("WaitForDebugEvent failed.\n");
goto Exit;
}
/* 例外の扱い方を表すフラグの初期化。例外を破棄する場合はDBG_CONTINUEを設定する */
ContinueStatus = DBG_EXCEPTION_NOT_HANDLED;
switch(de.dwDebugEventCode) {
case CREATE_PROCESS_DEBUG_EVENT: /* プロセス作成イベントの場合 */
/* プライマリスレッドの情報をスレッド情報リストに追加 */
if(!AddThreadInfo(de.dwThreadId, de.u.CreateProcessInfo.hThread)) goto Exit;
break;
case CREATE_THREAD_DEBUG_EVENT: /* スレッド作成イベントの場合 */
/* 作成されたスレッドの情報をスレッド情報リストに追加 */
if(!AddThreadInfo(de.dwThreadId, de.u.CreateThread.hThread)) goto Exit;
break;
case EXIT_PROCESS_DEBUG_EVENT: /* プロセス終了イベントの場合 */
/* ループを抜ける */
iResult = 0;
goto Exit;
case EXIT_THREAD_DEBUG_EVENT: /* スレッド終了イベントの場合 */
/* スレッド情報リストから該当するスレッド情報を削除 */
if(!DeleteThreadInfo(de.dwThreadId)) goto Exit;
break;
case LOAD_DLL_DEBUG_EVENT: /* DLLがロードされた */
if(hUser32 == de.u.LoadDll.lpBaseOfDll) { /* "user32.dll"がロードされた場合 */
/* MessageBox関数にブレークポイントを設定するために、メモリをWRITECOPYに変更 */
GetSystemInfo(&SystemInfo);
bResult = VirtualProtectEx(
pi.hProcess,
(LPVOID)(((DWORD)pMessageBox / SystemInfo.dwPageSize) * SystemInfo.dwPageSize),
SystemInfo.dwPageSize,
PAGE_EXECUTE_WRITECOPY,
&OrgProtect);
if(!bResult) {
printf("VirtualProtextEx failed.\n");
goto Exit;
} else { /* 成功した場合、ブレークポイントを設置する */
bResult = ReadProcessMemory(pi.hProcess, pMessageBox, &OrgOpeCode, 1, NULL);
bResult = bResult && WriteProcessMemory(pi.hProcess, pMessageBox, &NewOpeCode, 1, NULL);
if(!bResult) {
printf("read or write failed.\n");
goto Exit;
}
FlushInstructionCache(pi.hProcess, NULL, 0);
}
}
break;
case EXCEPTION_DEBUG_EVENT: /* 例外発生 */
switch(de.u.Exception.ExceptionRecord.ExceptionCode) {
case EXCEPTION_BREAKPOINT:
/* ブレークポイントに遭遇した場合 */
if((i = FindThreadInfo(de.dwThreadId)) == INVALID_INDEX) goto Exit;
/* MessageBoxに設置したブレークポイントであるかどうか判定 */
ct.ContextFlags = CONTEXT_CONTROL;
if(!GetThreadContext(ThreadInfos[i].hThread, &ct)) {
printf("register can not read.\n");
goto Exit;
}
if(ct.Eip - 1 == (DWORD)pMessageBox) { /* MessageBoxのブレークポイントの場合 */
/* メッセージを表示 */
if(!ProcessMessageBox(pi.hProcess, &ct)) goto Exit;
/* ブレークポイントをいったんはずし、元の命令を実行する */
ct.Eip--;
if(!WriteProcessMemory(pi.hProcess, (void *)ct.Eip, &OrgOpeCode, 1, NULL)) {
printf("memory can not write.\n");
goto Exit;
}
FlushInstructionCache(pi.hProcess, NULL, 0);
/* 元の命令を実行したら、再びブレークポイントを設置しなければならない
* このため、シングルステップ実行モードにして、元の命令が実行されたら
* EXCEPTION_SINGLE_STEP例外を発生させる */
ct.EFlags |= EFLAGS_TF;
if(!SetThreadContext(ThreadInfos[i].hThread, &ct)) {
printf("register can not write.\n");
goto Exit;
}
}
ContinueStatus = DBG_CONTINUE;
break;
case EXCEPTION_SINGLE_STEP: /* シングルステップ実行例外 */
if((i = FindThreadInfo(de.dwThreadId)) == INVALID_INDEX) goto Exit;
/* 再びブレークポイントを設置する */
if(!WriteProcessMemory(pi.hProcess, pMessageBox, &NewOpeCode, 1, NULL)) {
printf("memory can not write.\n");
goto Exit;
}
FlushInstructionCache(pi.hProcess, NULL, 0);
/* シングルステップモードを中止 */
ct.ContextFlags = CONTEXT_CONTROL;
if(!GetThreadContext(ThreadInfos[i].hThread, &ct)) {
printf("register can not read.\n");
goto Exit;
}
ct.EFlags &= ~EFLAGS_TF;
if(!SetThreadContext(ThreadInfos[i].hThread, &ct)) {
printf("register can not write.\n");
goto Exit;
}
ContinueStatus = DBG_CONTINUE;
break;
}
break;
}
/* デバッグの継続 */
if(!ContinueDebugEvent(de.dwProcessId, de.dwThreadId, ContinueStatus)) {
printf("ContinueDebugEvent failed.\n");
goto Exit;
}
}
Exit: /* 後処理 */
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return iResult;
}