ReadProcessMemory
proto hProcess:DWORD, lpBaseAddress:DWORD, lpBuffer:DWORD, nSize:DWORD,
lpNumberOfBytesRead:DWORD
hProcess
is the handle to the process you want to read.
lpBaseAddress
is the address in the target process you want to start reading. For example,
if you want to read 4 bytes from the debuggee process starting at 401000h,
the value in this parameter must be 401000h.
lpBuffer
is the address of the buffer to receive the bytes read from the process.
nSize
is the number of bytes you want to read
lpNumberOfBytesRead
is the address of the variable of dword size that receives the number of
bytes actually read. If you don't care about it, you can use NULL.
The next two API functions
need a little background on context. Under a multitasking OS like Windows,
there can be several programs running at the same time. Windows gives each
thread a timeslice. When that timeslice expires, Windows freezes the present
thread and switches to the next thread that has the highest priority. Just
before switching to the other thread, Windows saves values in registers
of the present thread so that when the time comes to resume the thread,
Windows can restore the last *environment* of that thread. The saved values
of the registers are collectively called a context.
Back to our subject. When
a debug event occurs, Windows suspends the debuggee. The debuggee's context
is saved. Since the debuggee is suspended, we can be sure that the values
in the context will remain unchanged . We can get the values in the context
with GetThreadContext and we can change
them with SetThreadContext.
These two APIs are very powerful.
With them, you have at your fingertips the VxD-like power over the debuggee:
you can alter the saved register values and just before the debuggee resumes
execution, the values in the context will be written back into the registers.
Any change you made to the context is reflected back to the debuggee. Think
about it: you can even alter the value of the eip register and divert the
flow of execution to anywhere you like! You won't be able to do that under
normal circumstance.
GetThreadContext proto hThread:DWORD, lpContext:DWORD
hThread
is the handle to the thread that you want to obtain the context from
lpContext
is the address of the CONTEXT structure
that will be filled when the function returns successfully.
SetThreadContext has exactly the same parameters. Let's see what a CONTEXT structure looks like:
As you can observe, the
members of this structures are mimics of the real processor's registers.
Before you can use this structure, you need to specify which groups of
registers you want to read/write in ContextFlags
member. For example, if you want to read/write all registers, you must
specify CONTEXT_FULL in ContextFlags.
If you want only to read/write regEbp, regEip, regCs, regFlag, regEsp or
regSs, you must specify CONTEXT_CONTROL
in ContextFlags.
One thing you must remember
when using the CONTEXT structure: it
must be aligned on dword boundary else you'd get strange results under
NT. Hopefully, Spasm always align a new Data set, when you open a Data
Bracket.
____________________________________________________________________________________________
; Equates:
[OFN_FLAGS &OFN_FILEMUSTEXIST+&OFN_PATHMUSTEXIST+&OFN_LONGNAMES+&OFN_EXPLORER+&OFN_HIDEREADONLY]
________________________________________________________________________________________
; Data:
[AppName: B§ 'Win32 Debug
Example no.2' 0
ClassName: 'SimpleWinClass'
0
SearchFail: "Cannot find
the target process
You have to run the target first
Oh! you! Stupid boy!!!!!!!!!!!!" 0
TargetPatched: 'Target patched!' 0]
[ProcessId: 0 ThreadId: 0 buffer: W§ 09090]
[PROCESS_INFORMATION: PI_hProcess: 0
PI_hThread: 0
PI_dwProcessId: 0 PI_dwThreadId: 0]
[STARTUPINFO: SI_cb: 0
SI_lpReserved: 0 SI_lpDesktop: 0
SI_lpTitle: 0 SI_dwX: 0
SI_dwY: 0
SI_dwXSize: 0 SI_dwYSize:
0 SI_dwXCountChars:
0
SI_dwYCountChars: 0 SI_dwFillAttribute: 0 SI_dwFlags:
0
SI_wShowWindow: W§ 0 SI_cbReserved2: W§ 0 SI_lpReserved2:
D§ 0
SI_hStdInput: 0 SI_hStdOutput: 0
SI_hStdError: 0]
[DEBUG_EVENT: DE_dwDebugEventCode: 0
DE_dwProcessId: 0
DE_dwThreadId: 0
DE_u: CPDI_hFile:
E_pExceptionRecord: 0
CPDI_hProcess: 0
CPDI_hThread: 0
CPDI_lpBaseOfImage: 0
CPDI_lpStartAddress: 0] [DamnedUnionTail: 0 #100]
; CREATE_PROCESS_DEBUG_INFO union Data (available
at 'DE_u'):
;
; HANDLE hFile;
; HANDLE hProcess;
; HANDLE hThread;
; LPVOID lpBaseOfImage;
; DWORD dwDebugInfoFileOffset;
; DWORD nDebugInfoSize;
; LPVOID lpThreadLocalBase;
; LPTHREAD_START_ROUTINE lpStartAddress;
; LPVOID lpImageName;
; WORD fUnicode;
; EXCEPTION_DEBUG_INFO union Data (available at
'DE_u'):
;
; EXCEPTION_RECORD ExceptionRecord;
; DWORD dwFirstChance;
; EXCEPTION_RECORD union Data (available at 'ExceptionRecord' of 'DE_u'):
; DWORD ExceptionCode;
; DWORD ExceptionFlags;
; struct _EXCEPTION_RECORD
*ExceptionRecord;
; PVOID ExceptionAddress;
; DWORD NumberParameters;
; DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
________________________________________________________________________________________
________________________________________________________________________________________
; This is the full structure of CONTEXT with nested structure unfolded:
[CONTEXT: C_ContextFlags: 0
C_iDr0: 0 C_iDr1: 0 C_iDr2: 0 C_iDr3: 0 C_iDr6:
0 C_iDr7: 0
FLOATING_SAVE_AREA: FSA_ControlWord: 0 FSA_StatusWord: 0
FSA_TagWord: 0
FSA_ErrorOffset: 0 FSA_ErrorSelector: 0 FSA_DataOffset: 0
FSA_DataSelector: 0]
[FSA_RegisterArea: B§ 0 #80]
[FSA_Cr0NpxState: 0
C_regGs: 0 C_regFs: 0 C_regEs: 0 C_regDs: 0
C_regEdi: 0 C_regEsi: 0
C_regEbx: 0 C_regEdx: 0 C_regEcx: 0 C_regEax: 0
C_regEbp: 0 C_regEip: 0
C_regCs: 0
C_regFlag: 0
C_regEsp: 0 C_regSs: 0][C_ExtendedRegisters: B§ 0 #512]
________________________________________________________________________________________
Main:
call 'USER32.FindWindowA'
ClassName &NULL
.If eax ne &NULL
call
'USER32.GetWindowThreadProcessId' eax ProcessId | mov D§ThreadId eax
call
'KERNEL32.DebugActiveProcess' D§ProcessId
.While
eax = &TRUE
L0:
call 'KERNEL32.WaitForDebugEvent' DEBUG_EVENT &INFINITE
..If D§DE_dwDebugEventCode = &EXIT_PROCESS_DEBUG_EVENT
jmp L9>>
..Else_If D§DE_dwDebugEventCode = &CREATE_PROCESS_DEBUG_EVENT
mov D§C_ContextFlags &CONTEXT_CONTROL
call 'KERNEL32.GetThreadContext' D§CPDI_hThread CONTEXT
call 'KERNEL32.WriteProcessMemory' D§CPDI_hProcess D§C_regEip
,
buffer 2 &NULL
call 'USER32.MessageBoxA' 0 TargetPatched AppName &MB_ICONINFORMATION
..Else_If D§DE_dwDebugEventCode = &EXCEPTION_DEBUG_EVENT
If D§E_pExceptionRecord = &EXCEPTION_BREAKPOINT
call 'KERNEL32.ContinueDebugEvent' D§DE_dwProcessId D§DE_dwThreadId,
&DBG_CONTINUE
jmp L0<<
End_If
..End_If
call 'KERNEL32.ContinueDebugEvent' D§DE_dwProcessId D§DE_dwThreadId,
&DBG_EXCEPTION_NOT_HANDLED
.End_While
.Else
call
'USER32.MessageBoxA' 0 SearchFail AppName &MB_ICONERROR
.End_If
L9: call 'KERNEL32.ExitProcess' 0
Our program needs to attach
itself to the debuggee with DebugActiveProcess
which requires the process Id of the debuggee. We can obtain
the process Id by calling GetWindowThreadProcessId
which in turn needs the window handle as its parameter. So we
need to obtain the window handle first.
With FindWindow,
we can specify the name of the window class we need. It returns the handle
to the window created by that window class. If it returns
NULL, no window of that class is present.
.If eax ne &NULL
call
'USER32.GetWindowThreadProcessId' eax ProcessId | mov D§ThreadId eax
call
'KERNEL32.DebugActiveProcess' D§ProcessId
After we obtain the process
Id, we can call DebugActiveProcess.
Then we enter the debug loop waiting for the debug events.
..Else_If D§DE_dwDebugEventCode = &CREATE_PROCESS_DEBUG_EVENT
mov D§C_ContextFlags &CONTEXT_CONTROL
call 'KERNEL32.GetThreadContext' D§CPDI_hThread CONTEXT
When we get
CREATE_PROCESS_DEBUG_INFO, it means the debuggee is suspended,
ready for us to do surgery upon its process. In this example, we will overwrite
the infinite loop instruction in the debuggee (0EBh 0FEh) with NOPs ( 90h
90h).
First, we need to obtain the
address of the instruction. Since the debuggee is already in the loop by
the time our program attached to it, eip will always point to the instruction.
All we need to do is obtain the value of eip. We use GetThreadContext
to achieve that goal. We set the ContextFlags
member to CONTEXT_CONTROL so as to
tell GetThreadContext that we want
it to fill the "control" register members of the
CONTEXT structure.
call 'KERNEL32.WriteProcessMemory' D§CPDI_hProcess D§C_regEip
,
buffer 2 &NULL
Now that we get the value of eip, we can call WriteProcessMemory to overwrite the "jmp $" instruction with NOPs, thus effectively help the debuggee exit the infinite loop. After that we display the message to the user and then call ContinueDebugEvent to resume the debuggee. Since the "jmp $" instruction is overwritten by NOPs, the debuggee will be able to continue with showing its window and enter the message loop. The evidence is we will see its window on screen.
The other example uses a slightly different approach to break the debuggee out of the infinite loop.
.......
.......
.Else_If D§DE_dwDebugEventCode = &CREATE_PROCESS_DEBUG_EVENT
mov D§C_ContextFlags &CONTEXT_CONTROL
call 'KERNEL32.GetThreadContext' D§CPDI_hThread CONTEXT
add D§C_regEip 2
call 'KERNEL32.SetThreadContext' D§CPDI_hThread CONTEXT
call 'USER32.MessageBoxA' 0 LoopSkipped AppName &MB_ICONINFORMATION
.......
.......
It still calls GetThreadContext to obtain the current value of eip but instead of overwriting the "jmp $" instruction, it increments the value of regEip by 2 to "skip over" the instruction. The result is that when the debuggee regains control , it resumes execution at the next instruction after "jmp $".
Now you can see the power of
Get/SetThreadContext. You can also modify the other register images as
well and their values will be reflected back to the debuggee. You can even
insert int 3h instruction to put breakpoints in the debuggee process.