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

Win32 除錯 API 實戰(二):記憶體讀寫與執行緒上下文操作

     編輯:組合語言

前言

承接上一篇的除錯事件迴圈,本文將探討如何「動態修改」被除錯程序:讀寫其虛擬記憶體、取得/設定執行緒上下文(暫存器值)。這是實現斷點、修補程式碼、動態分析的核心能力。

核心 API 介紹

1. 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
  • hProcess:目標程序控制代碼(需 PROCESS_VM_READ/WRITE 權限,除錯時自動擁有)。
  • lpBaseAddress:目標程序虛擬位址空間中的起始位址。
  • lpBuffer:本地緩衝區位址。
  • nSize:讀寫位元組數。
  • lpNumberOfBytesRead/Written:實際傳輸量指標,可為 NULL。

這兩個 API 讓除錯器能像操作自身記憶體一樣存取目標程序的位址空間,是掃描記憶體、修改資料、植入斷點指令(0xCC)的基礎。

2. GetThreadContext / SetThreadContext

GetThreadContext proto hThread:DWORD, lpContext:DWORD
SetThreadContext proto hThread:DWORD, lpContext:DWORD

當除錯事件發生時,Windows 暫停目標執行緒並保存其上下文(所有暫存器值)。透過這兩個 API,除錯器可:

  • 讀取/修改通用暫存器(EAX, EBX, ECX, EDX, ESI, EDI, EBP, ESP)
  • 讀取/修改指令指標 EIP(改變執行流程)
  • 讀取/修改旗標暫存器 EFLAGS(如啟用單步追蹤的 TF 位)
  • 存取段暫存器、除錯暫存器(DR0-DR7)、浮點/SSE 暫存器等

CONTEXT 結構與 ContextFlags

CONTEXT STRUCT
    ContextFlags dd ?
    ; CONTEXT_DEBUG_REGISTERS
    iDr0 dd ?; iDr1 dd ?; iDr2 dd ?; iDr3 dd ?; iDr6 dd ?; iDr7 dd ?
    ; CONTEXT_FLOATING_POINT
    FloatSave FLOATING_SAVE_AREA <>
    ; CONTEXT_SEGMENTS
    regGs dd ?; regFs dd ?; regEs dd ?; regDs dd ?
    ; CONTEXT_INTEGER
    regEdi dd ?; regEsi dd ?; regEbx dd ?; regEdx dd ?; regEcx dd ?; regEax dd ?
    ; CONTEXT_CONTROL
    regEbp dd ?; regEip dd ?; regCs dd ?; regFlag dd ?; regEsp dd ?; regSs dd ?
    ; CONTEXT_EXTENDED_REGISTERS
    ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?)
CONTEXT ENDS

ContextFlags 決定要存取哪些暫存器組:

  • CONTEXT_CONTROL:EIP, EFLAGS, ESP, EBP, CS, SS(最常用)
  • CONTEXT_INTEGER:通用整數暫存器
  • CONTEXT_FULL:CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_SEGMENTS
  • 呼叫 API 前必須正確設定 ContextFlags,且 CONTEXT 結構必須 雙字對齊(建議前加 align dword),否則在 NT 核心下會失敗。

範例一:使用 DebugActiveProcess 附著並修補無限迴圈

目標程式 win.exe 在建立視窗前故意放置 jmp $ (EB FE) 無限迴圈。除錯器附著後,在 CREATE_PROCESS_DEBUG_EVENT 時讀取 EIP(指向該迴圈),用 WriteProcessMemory 將兩個位元組覆寫為 NOP (90 90),目標程式即可跳出迴圈正常顯示視窗。

.if DBEvent.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT
    mov context.ContextFlags, CONTEXT_CONTROL
    invoke GetThreadContext, DBEvent.u.CreateProcessInfo.hThread, addr context
    invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, \
            context.regEip, addr buffer, 2, NULL  ; buffer = 9090h
    invoke MessageBox, 0, addr TargetPatched, addr AppName, MB_OK+MB_ICONINFORMATION

範例二:修改 EIP 直接跳過指令

不修改記憶體,改直接操作上下文:add context.regEip, 2 後呼叫 SetThreadContext,執行緒恢復時 EIP 已指向 jmp $ 之後的指令,效果相同但不動程式碼區。

.if DBEvent.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT
    mov context.ContextFlags, CONTEXT_CONTROL
    invoke GetThreadContext, DBEvent.u.CreateProcessInfo.hThread, addr context
    add context.regEip, 2
    invoke SetThreadContext, DBEvent.u.CreateProcessInfo.hThread, addr context
    invoke MessageBox, 0, addr LoopSkipped, addr AppName, MB_OK+MB_ICONINFORMATION

進階應用與技巧

  • 軟體斷點:在目標位址寫入 0xCC (INT 3),觸發 EXCEPTION_BREAKPOINT。需保存原位元組以便恢復。
  • 硬體斷點:設定 DR0-DR3 為監視位址,DR7 控制條件(執行/讀寫/存取),不修改程式碼,上限 4 個。
  • 動態修補 IAT/導入表:重導向 API 呼叫至自訂函數(Hook)。
  • 修改條件判斷:在比較指令後修改 EFLAGS(如將 ZF 置 1/0),改變分支走向。

完整範例程式碼(DebugActiveProcess 版)

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\comdlg32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
includelib \masm32\lib\user32.lib

.data
AppName db "Win32 Debug Example no.2",0
ClassName db "SimpleWinClass",0
SearchFail db "Cannot find the target process",0
TargetPatched db "Target patched!",0
buffer dw 9090h          ; 兩個 NOP

.data?
DBEvent DEBUG_EVENT <>
ProcessId dd ?
ThreadId dd ?
align dword
context CONTEXT <>

.code
start:
    invoke FindWindow, addr ClassName, NULL
    .if eax != NULL
        invoke GetWindowThreadProcessId, eax, addr ProcessId
        mov ThreadId, eax
        invoke DebugActiveProcess, ProcessId

        .while TRUE
            invoke WaitForDebugEvent, addr DBEvent, INFINITE
            .break .if DBEvent.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT

            .if DBEvent.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT
                mov context.ContextFlags, CONTEXT_CONTROL
                invoke GetThreadContext, DBEvent.u.CreateProcessInfo.hThread, addr context
                invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, \
                        context.regEip, addr buffer, 2, NULL
                invoke MessageBox, 0, addr TargetPatched, addr AppName, MB_OK+MB_ICONINFORMATION

            .elseif DBEvent.dwDebugEventCode == EXCEPTION_DEBUG_EVENT
                .if DBEvent.u.Exception.pExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT
                    invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_CONTINUE
                    .continue
                .endif
            .endif

            invoke ContinueDebugEvent, DBEvent.dwProcessId, DBEvent.dwThreadId, DBG_EXCEPTION_NOT_HANDLED
        .endw
    .else
        invoke MessageBox, 0, addr SearchFail, addr AppName, MB_OK+MB_ICONERROR
    .endif
    invoke ExitProcess, 0
end start

總結

Read/WriteProcessMemory 與 Get/SetThreadContext 賦予除錯器「上帝視角」:任意讀寫虛擬記憶體、完全掌控暫存器狀態。配合除錯事件迴圈,即可實現斷點管理、程式碼修補、執行流程劫持、動態分析等核心功能。下一篇將實作單步追蹤與指令計數器。

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