Windows 提供一組原生 API,讓使用者模式程式能扮演偵錯器角色,控制另一個進程(被偵錯程式,debuggee)。核心能力包括:
CreateProcess(..., DEBUG_PROCESS, ...) 或 DebugActiveProcess(pid)WaitForDebugEvent(&DEBUG_EVENT, INFINITE)DEBUG_EVENT.dwDebugEventCode 分派處理ContinueDebugEvent(pid, tid, DBG_CONTINUE | DBG_EXCEPTION_NOT_HANDLED)循環執行 2–4 步,直到收到 EXIT_PROCESS_DEBUG_EVENT。
DEBUG_EVENT STRUCT
dwDebugEventCode DWORD ? ; 事件類型
dwProcessId DWORD ? ; 進程 ID
dwThreadId DWORD ? ; 執行緒 ID
u DEBUGSTRUCT <> ; 聯合體,依事件類型解釋
DEBUG_EVENT ENDS
主要事件代碼與對應聯合體成員:
| 事件代碼 | 意義 | 聯合體成員 |
|---|---|---|
| CREATE_PROCESS_DEBUG_EVENT | 進程建立/附著 | u.CreateProcessInfo (CREATE_PROCESS_DEBUG_INFO) |
| EXIT_PROCESS_DEBUG_EVENT | 進程結束 | u.ExitProcess (EXIT_PROCESS_DEBUG_INFO) |
| CREATE_THREAD_DEBUG_EVENT | 執行緒建立 | u.CreateThread (CREATE_THREAD_DEBUG_INFO) |
| EXIT_THREAD_DEBUG_EVENT | 執行緒結束 | u.ExitThread (EXIT_THREAD_DEBUG_INFO) |
| LOAD_DLL_DEBUG_EVENT | DLL 載入 | u.LoadDll (LOAD_DLL_DEBUG_INFO) |
| UNLOAD_DLL_DEBUG_EVENT | DLL 卸載 | u.UnloadDll (UNLOAD_DLL_DEBUG_INFO) |
| EXCEPTION_DEBUG_EVENT | 例外發生 | u.Exception (EXCEPTION_DEBUG_INFO) |
| OUTPUT_DEBUG_STRING_EVENT | OutputDebugString | u.DebugString (OUTPUT_DEBUG_STRING_INFO) |
| RIP_EVENT | 系統偵錯錯誤 | u.RipInfo (RIP_INFO) |
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
.data
AppName db "Win32 Debug Example",0
ofn OPENFILENAME <>
FilterStr db "Executable Files",0,"*.exe",0,"All Files",0,"*.*",0,0
ExitMsg db "The debuggee exits",0
ProcInfoFmt db "File Handle: %lx",0dh,0ah
db "Process Handle: %lx",0dh,0ah
db "Thread Handle: %lx",0dh,0ah
db "Image Base: %lx",0dh,0ah
db "Start Address: %lx",0
.data?
buffer db 512 dup(?)
startInfo STARTUPINFO <>
procInfo PROCESS_INFORMATION <>
dbgEvent DEBUG_EVENT <>
.code
start:
; 選擇要偵錯的執行檔
mov ofn.lStructSize, SIZEOF ofn
mov ofn.lpstrFilter, offset FilterStr
mov ofn.lpstrFile, offset buffer
mov ofn.nMaxFile, 512
mov ofn.Flags, OFN_FILEMUSTEXIST or OFN_PATHMUSTEXIST or OFN_LONGNAMES or OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, addr ofn
.if eax == TRUE
invoke GetStartupInfo, addr startInfo
invoke CreateProcess, addr buffer, NULL, NULL, NULL, FALSE, \
DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS, NULL, NULL, \
addr startInfo, addr procInfo
.while TRUE
invoke WaitForDebugEvent, addr dbgEvent, INFINITE
.if dbgEvent.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT
invoke MessageBox, 0, addr ExitMsg, addr AppName, MB_OK+MB_ICONINFORMATION
.break
.elseif dbgEvent.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT
; 顯示進程核心資訊
invoke wsprintf, addr buffer, addr ProcInfoFmt, \
dbgEvent.u.CreateProcessInfo.hFile, \
dbgEvent.u.CreateProcessInfo.hProcess, \
dbgEvent.u.CreateProcessInfo.hThread, \
dbgEvent.u.CreateProcessInfo.lpBaseOfImage, \
dbgEvent.u.CreateProcessInfo.lpStartAddress
invoke MessageBox, 0, addr buffer, addr AppName, MB_OK+MB_ICONINFORMATION
.elseif dbgEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT
.if dbgEvent.u.Exception.pExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT
; 首次中斷點例外 (INT 3) 必須用 DBG_CONTINUE
invoke ContinueDebugEvent, dbgEvent.dwProcessId, dbgEvent.dwThreadId, DBG_CONTINUE
.continue
.endif
.elseif dbgEvent.dwDebugEventCode == CREATE_THREAD_DEBUG_EVENT
invoke MessageBox, 0, addr NewThreadMsg, addr AppName, MB_OK+MB_ICONINFORMATION
.elseif dbgEvent.dwDebugEventCode == EXIT_THREAD_DEBUG_EVENT
invoke MessageBox, 0, addr EndThreadMsg, addr AppName, MB_OK+MB_ICONINFORMATION
.endif
; 其他事件預設不處理,交給系統預設例外處理器
invoke ContinueDebugEvent, dbgEvent.dwProcessId, dbgEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
.endw
invoke CloseHandle, procInfo.hProcess
invoke CloseHandle, procInfo.hThread
.endif
invoke ExitProcess, 0
end start
ReadProcessMemory proto hProcess:DWORD, lpBaseAddress:DWORD, lpBuffer:DWORD, nSize:DWORD, lpNumberOfBytesRead:DWORD
WriteProcessMemory proto hProcess:DWORD, lpBaseAddress:DWORD, lpBuffer:DWORD, nSize:DWORD, lpNumberOfBytesWritten:DWORD
允許偵錯器讀寫 debuggee 的虛擬記憶體空間,實現變數檢視、代碼修補、Shellcode 注入等。
CONTEXT STRUCT
ContextFlags DWORD ? ; 指定要存取的暫存器組
; CONTEXT_DEBUG_REGISTERS
Dr0 DWORD ?; Dr1 DWORD ?; Dr2 DWORD ?; Dr3 DWORD ?; Dr6 DWORD ?; Dr7 DWORD ?
; CONTEXT_FLOATING_POINT
FloatSave FLOATING_SAVE_AREA <>
; CONTEXT_SEGMENTS
SegGs DWORD ?; SegFs DWORD ?; SegEs DWORD ?; SegDs DWORD ?
; CONTEXT_INTEGER
RegEdi DWORD ?; RegEsi DWORD ?; RegEbx DWORD ?; RegEdx DWORD ?; RegEcx DWORD ?; RegEax DWORD ?
; CONTEXT_CONTROL
RegEbp DWORD ?; RegEip DWORD ?; RegCs DWORD ?; RegFlags DWORD ?; RegEsp DWORD ?; RegSs DWORD ?
; CONTEXT_EXTENDED_REGISTERS
ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?)
CONTEXT ENDS
使用要點:
ContextFlags(如 CONTEXT_FULL 或 CONTEXT_CONTROL)。align dword),否則 NT 系列會失敗。RegEip 可改變執行流程;修改通用暫存器可改變函數參數/返回值。; 偵錯器附著到已運行的 win.exe (含 jmp $ 無限迴圈)
.if dbgEvent.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT
mov ctx.ContextFlags, CONTEXT_CONTROL
invoke GetThreadContext, dbgEvent.u.CreateProcessInfo.hThread, addr ctx
; 方法 1:用 NOP 覆蓋 jmp $ (EB FE)
mov word ptr patchBytes, 9090h
invoke WriteProcessMemory, dbgEvent.u.CreateProcessInfo.hProcess, ctx.RegEip, addr patchBytes, 2, NULL
; 方法 2:直接將 EIP + 2 跳過指令
; add ctx.RegEip, 2
; invoke SetThreadContext, dbgEvent.u.CreateProcessInfo.hThread, addr ctx
invoke MessageBox, 0, addr PatchedMsg, addr AppName, MB_OK+MB_ICONINFORMATION
.endif
x86 EFLAGS 第 8 位(bit 8, 值 100h)為 Trap Flag。設定後,CPU 每執行一條指令產生 EXCEPTION_SINGLE_STEP (DBG_EVENT_CODE = EXCEPTION_DEBUG_EVENT, ExceptionCode = 0x80000004)。
EXCEPTION_BREAKPOINT (INT 3, debuggee 進入點) 時:
GetThreadContext(..., CONTEXT_CONTROL) 取得 EFLAGSOR ctx.RegFlags, 100h 設定 TFSetThreadContext 寫回ContinueDebugEvent(..., DBG_CONTINUE)EXCEPTION_SINGLE_STEP:
.elseif dbgEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT
.if dbgEvent.u.Exception.pExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT
mov ctx.ContextFlags, CONTEXT_CONTROL
invoke GetThreadContext, procInfo.hThread, addr ctx
or ctx.RegFlags, 100h
invoke SetThreadContext, procInfo.hThread, addr ctx
invoke ContinueDebugEvent, dbgEvent.dwProcessId, dbgEvent.dwThreadId, DBG_CONTINUE
.continue
.elseif dbgEvent.u.Exception.pExceptionRecord.ExceptionCode == EXCEPTION_SINGLE_STEP
inc TotalInstructions
invoke GetThreadContext, procInfo.hThread, addr ctx
or ctx.RegFlags, 100h
invoke SetThreadContext, procInfo.hThread, addr ctx
invoke ContinueDebugEvent, dbgEvent.dwProcessId, dbgEvent.dwThreadId, DBG_CONTINUE
.continue
.endif
.endif
⚠ 效能警告:單步追蹤極慢(每指令一次上下文切換),僅適合分析小程式或特定代碼段。大型程式可能需數分鐘至數小時。
EXCEPTION_BREAKPOINT 是系統插入的 INT 3,**必須用 DBG_CONTINUE**,否則 Windows NT 會拒絕繼續執行。DBG_CONTINUE(偵錯器已處理,debuggee 不再見到例外)DBG_EXCEPTION_NOT_HANDLED(交給 debuggee 自己的 SEH/VEH)CreateProcess 預設偵錯子進程,若不需則加上 DEBUG_ONLY_THIS_PROCESS。lpNumberOfBytesRead/Written,部分頁面可能不可存取(保護頁、未映射)。Wow64GetThreadContext / Wow64SetThreadContext 並處理 WOW64 CONTEXT 結構。Win32 偵錯 API 提供了構建完整偵錯器的所有基礎塊:事件迴路、記憶體/上下文操作、執行控制。本文從最小原型到單步追蹤,展示了核心用法與關鍵陷阱。掌握這些 API,即可開發自動化分析工具、動態測試框架、惡意代碼行為監控系統等進階應用。