Win32APIで書かれた古いゲームとかでちょっと外部からコード書き換えたいな、ってときに使ってたコードを発掘してきたので、要点を忘れないように覚書。
#####ターゲットの設定
char targetfilename="c:\\hogehoge.exe";
LPVOID HookAddress=(LPVOID)0xXXXXXXXX
OllyDbgとかで、ブレイクポイントを探しておきます。
#####ターゲットプロセスの起動
STARTUPINFO si;
memset(&si, 0, sizeof(si));
si.cb=sizeof(si);
PROCESS_INFORMATION pii;
memset(&pii, 0, sizeof(pii));
PROCESS_INFORMATION* pi=&pii;
char targetworkfolder[MAX_PATH];
strcpy(targetworkfolder, targetfilename); PathRemoveFileSpec(targetworkfolder);
BOOL res=CreateProcess(NULL, targetfilename, NULL, NULL, TRUE,
NORMAL_PRIORITY_CLASS|CREATE_NEW_CONSOLE|CREATE_DEFAULT_ERROR_MODE|CREATE_SUSPENDED,
NULL, targetworkfolder, &si, pi);
if(res==FALSE) goto DebugExit;
/*
起動前にやっておきたい処理
*/
ResumeThread(pi->hThread);
WaitForInputIdle(pi->hProcess, 5000);
アドレスサーチでブレイクポイントを探したい場合は、サスペンド状態でプロセスを起動してサーチします。
#####ブレイクポイントのセット
BYTE OrgOpeCode;
BYTE NewOpeCode=0xcc; // INT3
ReadProcessMemory(pi->hProcess, HookAddress, &OrgOpeCode, 1, NULL);
WriteProcessMemory(pi->hProcess, HookAddress, &NewOpeCode, 1, NULL);
FlushInstructionCache(pi->hProcess, NULL, 0);
ブレイクしたい位置にINT3を書き込みます。
#####ターゲットのデバッグ開始
if(DebugActiveProcess(pi->dwProcessId)==FALSE) goto DebugExit;
すでに他のデバッガーなどでデバッグ状態になっていると失敗します。
###デバッグループ
DEBUG_EVENT de;
DWORD DStatus;
DWORD ThreadId=0;
HANDLE hThread;
for(;;){
if(!WaitForDebugEvent(&de, INFINITE)) goto DebugExit;
DStatus=DBG_EXCEPTION_NOT_HANDLED;
switch(de.dwDebugEventCode){
case LOAD_DLL_DEBUG_EVENT:
{
/*
DLLのアドレスの場合、ここでブレイクポイントをセット
*/
}
break;
case CREATE_PROCESS_DEBUG_EVENT:
{
ThreadId=de.dwThreadId;
hThread=de.u.CreateProcessInfo.hThread;
}
break;
case CREATE_THREAD_DEBUG_EVENT:
{
ThreadId=de.dwThreadId;
hThread=de.u.CreateThread.hThread;
}
break;
case EXIT_PROCESS_DEBUG_EVENT:
{
goto DebugExit;
}
break;
case EXIT_THREAD_DEBUG_EVENT:
{
if(ThreadId==de.dwThreadId){
ThreadId=0;
hThread=NULL;
}
}
break;
case EXCEPTION_DEBUG_EVENT:
{
CONTEXT ct;
switch(de.u.Exception.ExceptionRecord.ExceptionCode){
case EXCEPTION_BREAKPOINT:
{
if(ThreadId==de.dwThreadId){
ct.ContextFlags=CONTEXT_FULL;
GetThreadContext(hThread, &ct);
/*
ブレイク中にしたい処理
*/
WriteProcessMemory(pi->hProcess, (LPVOID)HookAddress, &OrgOpeCode, 1, NULL);
FlushInstructionCache(pi->hProcess, NULL, 0);
ct.Eip--;
ct.EFlags|=0x00000100; // シングルステップモード
SetThreadContext(hThread, &ct);
}
DStatus=DBG_CONTINUE;
}
break;
case EXCEPTION_SINGLE_STEP:
{
if(ThreadId==de.dwThreadId){
ct.ContextFlags=CONTEXT_CONTROL;
GetThreadContext(ThreadInfos[i].hThread, &ct);
WriteProcessMemory(pi->hProcess, HookAddress, &NewOpeCode, 1, NULL);
FlushInstructionCache(pi->hProcess, NULL, 0);
ct.EFlags&=~0x00000100;
SetThreadContext(ThreadInfos[i].hThread, &ct);
DStatus=DBG_CONTINUE;
}
break;
}
}
break;
}
if(!ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DStatus)) goto DebugExit;
}
DebugExit:
CloseHandle(pi->hThread);
CloseHandle(pi->hProcess);
ブレイクポイントがDLLの関数である場合には、LOAD_DLL_DEBUG_EVENTのところでブレイクポイントをセットします。
端折っていますが、スレッドID/スレッドハンドルは複数保持できるようにしておきます。
ターゲットがブレイクポイント(INT3)で停止するとEXCEPTION_BREAKPOINTに処理が行きますので、お好きな処理を実行します。
オリジナルの処理をさせたい場合にはオリジナルのコードに書き換えて1つ戻り、シングルステップモードにします。シングルステップモードで1つ進むとEXCEPTION_SINGLE_STEPに処理が行きますので、またINT3に書き換えて再開させます。
この繰り返し。