Win32APIでデバッグフック

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に書き換えて再開させます。
この繰り返し。