首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 組合語言

Win32 偵錯 API 完整教學:從基礎架構到單步追蹤實作

     編輯:組合語言

Win32 偵錯 API 概觀

Windows 提供一組原生 API,讓使用者模式程式能扮演偵錯器角色,控制另一個進程(被偵錯程式,debuggee)。核心能力包括:

  • 建立或附著進程進行偵錯
  • 接收偵錯事件(例外、建立/結束執行緒、載入/卸載 DLL、輸出偵錯字串)
  • 讀寫目標進程記憶體、取得/設定執行緒上下文(暫存器)
  • 單步追蹤、斷點管理、繼續執行控制

核心流程四步驟

  1. 取得 debuggee:CreateProcess(..., DEBUG_PROCESS, ...) 或 DebugActiveProcess(pid)
  2. 等待事件:WaitForDebugEvent(&DEBUG_EVENT, INFINITE)
  3. 處理事件:依 DEBUG_EVENT.dwDebugEventCode 分派處理
  4. 繼續執行: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_EVENTDLL 載入u.LoadDll (LOAD_DLL_DEBUG_INFO)
UNLOAD_DLL_DEBUG_EVENTDLL 卸載u.UnloadDll (UNLOAD_DLL_DEBUG_INFO)
EXCEPTION_DEBUG_EVENT例外發生u.Exception (EXCEPTION_DEBUG_INFO)
OUTPUT_DEBUG_STRING_EVENTOutputDebugStringu.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 / WriteProcessMemory

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 注入等。

GetThreadContext / SetThreadContext

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

單步追蹤實作:利用 Trap Flag (TF)

x86 EFLAGS 第 8 位(bit 8, 值 100h)為 Trap Flag。設定後,CPU 每執行一條指令產生 EXCEPTION_SINGLE_STEP (DBG_EVENT_CODE = EXCEPTION_DEBUG_EVENT, ExceptionCode = 0x80000004)。

單步追蹤演算法

  1. 收到第一個 EXCEPTION_BREAKPOINT (INT 3, debuggee 進入點) 時:
    • GetThreadContext(..., CONTEXT_CONTROL) 取得 EFLAGS
    • OR ctx.RegFlags, 100h 設定 TF
    • SetThreadContext 寫回
    • ContinueDebugEvent(..., DBG_CONTINUE)
  2. 後續每收到 EXCEPTION_SINGLE_STEP:
    • 指令計數器 +1
    • 重複步驟 1.2–1.4 維持單步模式(TF 在例外後自動清除)

完整指令計數器範例

.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

⚠ 效能警告:單步追蹤極慢(每指令一次上下文切換),僅適合分析小程式或特定代碼段。大型程式可能需數分鐘至數小時。

重要細節與最佳實務

  1. 首次中斷點處理:debuggee 啟動時的第一個 EXCEPTION_BREAKPOINT 是系統插入的 INT 3,**必須用 DBG_CONTINUE**,否則 Windows NT 會拒絕繼續執行。
  2. 例外處理策略:
    • 偵錯器想處理例外 → DBG_CONTINUE(偵錯器已處理,debuggee 不再見到例外)
    • 偵錯器不處理 → DBG_EXCEPTION_NOT_HANDLED(交給 debuggee 自己的 SEH/VEH)
  3. 子進程偵錯:CreateProcess 預設偵錯子進程,若不需則加上 DEBUG_ONLY_THIS_PROCESS。
  4. 記憶體讀寫錯誤處理:檢查返回值與 lpNumberOfBytesRead/Written,部分頁面可能不可存取(保護頁、未映射)。
  5. 64 位元偵錯 32 位元進程:需使用 Wow64GetThreadContext / Wow64SetThreadContext 並處理 WOW64 CONTEXT 結構。

總結

Win32 偵錯 API 提供了構建完整偵錯器的所有基礎塊:事件迴路、記憶體/上下文操作、執行控制。本文從最小原型到單步追蹤,展示了核心用法與關鍵陷阱。掌握這些 API,即可開發自動化分析工具、動態測試框架、惡意代碼行為監控系統等進階應用。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved