Win32除錯API 第二部分(轉)

heying1229發表於2007-07-28
Win32除錯API 第二部分:

  我們繼續Win32除錯API的話題。在本章中,我們將要學習如何修改被除錯程式。
下載 the example
理論:
在前面一章中,我們學會了如何裝載被除錯的程式以及如何處理程式中發生的事件。為了有實際用途,我們的程式應具有修改被除錯程式的能力。有好幾個API函式用於這一目的。

ReadProcessMemory該函式允許你去讀指定的程式的記憶體。函式原型如下:
ReadProcessMemory proto hProcess:DWORD, lpBaseAddress:DWORD, lpBuffer:DWORD, nSize:DWORD, lpNumberOfBytesRead:DWORD

hProcess 待讀程式的控制程式碼.
lpBaseAddress 目標程式中待讀記憶體起始地址。例如,如果你想要讀目標 程式中從地址401000h開始的4個位元組,該引數值應置為401000h。
lpBuffer 接收緩衝區地址
nSize 想要讀的位元組數。
lpNumberOfBytesRead 記錄實際讀取的位元組數的變數地址。如果對這個值 不關心,填入NULL即可。

WriteProcessMemory 是對應於ReadProcessMemory的函式,透過它 可以寫目標程式的記憶體。其引數和ReadProcessMemory 相同。
理解接下去的兩個函式需要一些程式上下文的有關背景知識。在象Windows這樣的 多工作業系統中,同一時間裡可能執行著幾個程式。Windows分配給每個執行緒一個 時間片,當時間片結束後,Windows將凍結當前執行緒並切換到下一具有最高優先順序的 執行緒。在切換之前,Windows將儲存當前程式的暫存器的 內容,這樣當在該執行緒再 次恢復執行時,Windows可以恢復最近一次執行緒執行的*環境*。儲存的暫存器內容總 稱為程式上下文。
現在回到我們的主題。當一個除錯事件發生時,Windows暫停被除錯程式,並儲存其 程式上下文。由於程式被暫停執行,我們可以確信其程式上下文內容將保持不變。 可以用GetThreadContext來獲取程式上下文內容,並且也可以用GetThreadContext 來修改程式上下文內容。
這兩個函式威力非凡。有了他們,對被除錯程式你就具有象VxD的能力: 如改變其寄 存器內容,而在被除錯程式恢復執行前,這些值將會寫回暫存器中。在程式上下文中 所做的任何改動,將都會反映到被除錯程式中。想象一下: 甚至可以改變eip暫存器 的內容,這樣你可以讓程式執行到你想要的任何地方! 在正常情況下是不可能做到這 一點的。

GetThreadContext proto hThread:DWORD, lpContext:DWORD

hThread 你想要獲得上下文的執行緒控制程式碼
lpContext 函式成功返回時用來儲存上下文內容的結構指標。

SetThreadContext 引數相同。讓我們來看看上下文的結構:

CONTEXT STRUCT

ContextFlags dd ?
;----------------------------------------------------------------------------------------------------------
;當ContextFlags包含CONTEXT_DEBUG_REGISTERS,返回本部分
;-----------------------------------------------------------------------------------------------------------
iDr0 dd ?
iDr1 dd ?
iDr2 dd ?
iDr3 dd ?
iDr6 dd ?
iDr7 dd ?

;----------------------------------------------------------------------------------------------------------
;當ContextFlags包含CONTEXT_FLOATING_POINT,返回本部分
;-----------------------------------------------------------------------------------------------------------

FloatSave FLOATING_SAVE_AREA <>

;----------------------------------------------------------------------------------------------------------
;當ContextFlags包含CONTEXT_SEGMENTS,返回本部分
;-----------------------------------------------------------------------------------------------------------
regGs dd ?
regFs dd ?
regEs dd ?
regDs dd ?

;----------------------------------------------------------------------------------------------------------
;當ContextFlags包含CONTEXT_INTEGER,返回本部分
;-----------------------------------------------------------------------------------------------------------
regEdi dd ?
regEsi dd ?
regEbx dd ?
regEdx dd ?
regEcx dd ?
regEax dd ?

;----------------------------------------------------------------------------------------------------------
;當ContextFlags包含CONTEXT_CONTROL,返回本部分
;-----------------------------------------------------------------------------------------------------------
regEbp dd ?
regEip dd ?
regCs dd ?
regFlag dd ?
regEsp dd ?
regSs dd ?

;----------------------------------------------------------------------------------------------------------
;當ContextFlags包含CONTEXT_EXTENDED_REGISTERS,返回本部分
;-----------------------------------------------------------------------------------------------------------
ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?) CONTEXT ENDS
可以看出,該結構中的成員是對實際處理器的暫存器的模仿。在使用該結構之前 要在ContextFlags 中指定哪些暫存器組用來讀寫。如要訪問所有的暫存器, 你可以置ContextFlags 為CONTEXT_FULL 。或者只訪問regEbp, regEip, regCs, regFlag, regEsp 或 regSs, 應置ContextFlags 為 CONTEXT_CONTROL 。

在使用結構CONTEXT 時還應記住: 它必須是雙字對齊的,否則在NT下將得 到奇怪的結果。可以在定義前加上"align dword"。例如:

align dword
MyContext CONTEXT <>

例:
第一個例子演示DebugActiveProcess的使用。首先,需要在Windows顯示在螢幕上以前執行一個待除錯程式win.exe,該程式將處於無限迴圈執行狀態中。然後你執行例子程式,它將把自己與win.exe連線起來,並且修改win.exe的程式碼,這樣win.exe將退出無限迴圈狀態而顯示自己的視窗。

.386
.model flat,stdcall
option casemap:none
include masm32includewindows.inc
include masm32includekernel32.inc
include masm32includecomdlg32.inc
include masm32includeuser32.inc
includelib masm32libkernel32.lib
includelib masm32libcomdlg32.lib
includelib masm32libuser32.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

.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

;--------------------------------------------------------------------
; The partial source code of win.asm, our debuggee. It's actually
; the simple window example in tutorial 2 with an infinite loop inserted
; just before it enters the message loop.
;----------------------------------------------------------------------

......
mov wc.hIconSm,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
invoke RegisterClassEx, addr wc
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL, hInst,NULL
mov hwnd,eax
jmp $ invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.while TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.break .if (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endw
mov eax,msg.wParam
ret
WinMain endp

分析:
invoke FindWindow, addr ClassName, NULL

我們的程式需要用DebugActiveProcess將自己繫結到被除錯程式,這需要知道被除錯程式的程式Id。用GetWindowThreadProcessId 可以得到該Id,該函式需要視窗控制程式碼作為引數,因此首先需要知道視窗控制程式碼。
用FindWindow, 我們先指定視窗類的名稱,返回的是該類建立的視窗控制程式碼。如 果返回NULL,則表明當前沒有該類的視窗。

.if eax!=NULL
invoke GetWindowThreadProcessId, eax, addr ProcessId
mov ThreadId, eax
invoke DebugActiveProcess, ProcessId

得到程式Id後,我們呼叫DebugActiveProcess。這樣就進入等待除錯事件的迴圈中。

.if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
mov context.ContextFlags, CONTEXT_CONTROL
invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context

當得到 CREATE_PROCESS_DEBUG_INFO, 這意味著被除錯程式已經被暫停執行了。 我們就可以對該程式動手術了。本例中,我們將用NOPs ( 90h 90h)覆蓋被除錯程式中的無 限迴圈指令(0EBh 0FEh) 。
首先,需要得到該指令的地址。由於在我們的程式繫結到被除錯程式時,被除錯程式已經 處於迴圈語句中了,eip總是指向該指令。我們所要做的是得到eip的值。我們將使用 GetThreadContext來達到此目的。將上下文結構成員中ContextFlags設定 為CONTEXT_CONTROL ,這樣告訴GetThreadContext我們需要它去填充上下 文結構的成員中的"控制"暫存器。

invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL

得到eip的值以後,可以呼叫WriteProcessMemory來用NOPs覆蓋"jmp $" 指令,這樣將使被除錯程式退出無限迴圈。在向使用者顯示了資訊之後,呼叫ContinueDebugEvent 來恢復被除錯程式的執行。由於指令"jmp $"已被Nops覆蓋,被除錯程式將繼續 顯示視窗,並進入訊息迴圈。證據是我們在螢幕上觀察到了次視窗。

另一個例子與此稍有不同,它是將被除錯程式從無限迴圈中中斷。

.......
.......
.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
.......
.......

這裡仍呼叫GetThreadContext來獲取eip值,但沒有去覆蓋"jmp $" 指令,而是將 regEip加2,從而"跳過"該指令。結果是當被除錯程式 重新獲得控制權時,將恢復執行在"jmp $"後的指令。

現在你可以體會到Get/SetThreadContext的威力了。你也可以修改其他暫存器映象,這些值將直接反映到被除錯程式中。甚至你可以把int 3h指令插入到被除錯程式中。產生斷點。

[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10172717/viewspace-928776/,如需轉載,請註明出處,否則將追究法律責任。

相關文章