10.1 除錯事件讀取暫存器

lyshark發表於2023-10-04

當讀者需要獲取到特定程式內的暫存器資訊時,則需要在上述程式碼中進行完善,首先需要編寫CREATE_PROCESS_DEBUG_EVENT事件,程式被首次載入進入記憶體時會被觸發此事件,在該事件內首先我們透過lpStartAddress屬性獲取到當前程式的入口地址,並透過SuspendThread暫停程式的執行,當被暫停後則我沒就可以透過ReadProcessMemory讀取當前位置的一個位元組機器碼,目的是儲存以便於後期的恢復,接著透過WriteProcessMemory向對端(void*)dwAddr地址寫出一個0xCC斷點,該斷點則是int3停機指令,最後ResumeThread恢復這個執行緒的執行,此時程式中因存在斷點,則會觸發一個EXCEPTION_DEBUG_EVENT異常事件。

case CREATE_PROCESS_DEBUG_EVENT:
{
    // 獲取入口地址 0x0 可以增加偏移到入口後任意位置
    DWORD dwAddr = 0x0 + (DWORD)de.u.CreateProcessInfo.lpStartAddress;

    // 暫停執行緒
    SuspendThread(de.u.CreateProcessInfo.hThread);

    // 讀取入口地址處的位元組碼
    ReadProcessMemory(de.u.CreateProcessInfo.hProcess, (const void*)dwAddr, &bCode, sizeof(BYTE), &dwNum);

    // 在入口地址處寫入 0xCC 即寫入 INT 3 暫停程式執行
    WriteProcessMemory(de.u.CreateProcessInfo.hProcess, (void*)dwAddr, &bCC, sizeof(BYTE), &dwNum);

    // 恢復執行緒
    ResumeThread(de.u.CreateProcessInfo.hThread);
    break;
}

當異常斷點被觸發後,則下一步就會觸發兩次異常,第一次異常我們可以使用break直接跳過,因為此斷點通常為系統斷點,而第二次斷點則是我們自己設定的int3斷點,此時需要將該請求傳送至OnException異常處理函式對其進行處理,在傳遞時需要給與&de除錯事件,以及&bCode原始的機器碼;

case EXCEPTION_DEBUG_EVENT:
{
    switch (dwCC_Count)
    {
        // 第0次是系統斷點,這裡我們直接跳過
    case 0:
        dwCC_Count++; break;

        // 第1次斷點,我們讓他執行下面的函式
    case 1:
        OnException(&de, &bCode); dwCC_Count++; break;
    }
}

異常事件會被流轉到OnException(DEBUG_EVENT* pDebug, BYTE* bCode)函式內,在本函式內我們首先透過使用OpenProcess/OpenThread兩個函式得到當前程式的控制程式碼資訊,接著使用SuspendThread(hThread)暫時暫停程式內執行緒的執行,透過呼叫ReadProcessMemory得到執行緒上下文異常產生的首地址,當得到首地址後,則可以呼叫GetThreadContext(hThread, &context)得到當前執行緒的上下文,一旦上下文被獲取到則讀者即可透過context.的方式得到當前程式的所有暫存器資訊,為了讓程式正常執行當讀取結束後,透過WriteProcessMemory我們將原始機器碼寫回到記憶體中,並SetThreadContext設定當前上下文,最後使用ResumeThread執行該執行緒;

void OnException(DEBUG_EVENT* pDebug, BYTE* bCode)
{
    CONTEXT context;
    DWORD dwNum;
    BYTE bTmp;

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pDebug->dwProcessId);
    HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, pDebug->dwThreadId);

    // 暫停指定的執行緒
    SuspendThread(hThread);

    // 讀取出異常首地址
    ReadProcessMemory(hProcess, pDebug->u.Exception.ExceptionRecord.ExceptionAddress, &bTmp, sizeof(BYTE), &dwNum);

    context.ContextFlags = CONTEXT_FULL;
    GetThreadContext(hThread, &context);

    printf("\n");

    printf("EAX = 0x%08X  |  EBX = 0x%08X  |  ECX = 0x%08X  |  EDX = 0x%08X \n",
        context.Eax, context.Ebx, context.Ecx, context.Edx);
    printf("EBP = 0x%08X  |  ESP = 0x%08X  |  ESI = 0x%08X  |  EDI = 0x%08X \n\n",
        context.Ebp, context.Esp, context.Esi, context.Edi);

    printf("EIP = 0x%08X  |  EFLAGS = 0x%08X\n\n", context.Eip, context.EFlags);

    // 將剛才的CC斷點取消,也就是回寫原始指令集
    WriteProcessMemory(hProcess, pDebug->u.Exception.ExceptionRecord.ExceptionAddress, bCode, sizeof(BYTE), &dwNum);
    context.Eip--;

    // 設定執行緒上下文
    SetThreadContext(hThread, &context);

    // printf("程式控制程式碼:   0x%08X \n", pDebug->u.CreateProcessInfo.hProcess);
    // printf("主執行緒控制程式碼: 0x%08X \n", pDebug->u.CreateProcessInfo.hThread);
    printf("虛擬入口點: 0x%08X \n", pDebug->u.CreateProcessInfo.lpBaseOfImage);

    // 恢復執行緒執行
    ResumeThread(hThread);
    CloseHandle(hThread);
    CloseHandle(hProcess);
}

當這段程式被執行後,讀者可看到如下圖所示的輸出資訊,該程式中當前暫存器的狀態基本上都可以被獲取到;

本文作者: 王瑞
本文連結: https://www.lyshark.com/post/94ad4ba.html
版權宣告: 本部落格所有文章除特別宣告外,均採用 BY-NC-SA 許可協議。轉載請註明出處!

相關文章