當讀者需要獲取到特定程式內的暫存器資訊時,則需要在上述程式碼中進行完善,首先需要編寫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 許可協議。轉載請註明出處!