LoginSignup
4
2

More than 5 years have passed since last update.

Win32APIでデバッグフック

Last updated at Posted at 2017-03-07

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

4
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
2