Tutorial 28: Win32 Debug API Part 1 Tutorial 29: Win32 Debug API Part 2 Tutorial 30: Win32 Debug API part 3

We continue with the subject of win32 debug API. In this tutorial, we will learn how to modify the debuggee process.

Download the example

Theory:

In the previous tutorial, we know how to load the debuggee and handle debug events that occur in its process. In order to be useful, our program must be able to modify the debuggee process. There are several APIs just for this purpose.

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

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. You must put "align dword" just above the line that declares it, like this:

align dword MyContext CONTEXT <>

Example:

The first example demonstrates the use of DebugActiveProcess. First, you need to run a target named win.exe which goes in an infinite loop just before the window is shown on the screen. Then you run the example, it will attach itself to win.exe and modify the code of win.exe such that win.exe exits the infinite loop and shows its own window.

.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 .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 $ <; ;---- Here's our infinite loop. It assembles to EB FE 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

Analysis:

invoke FindWindow, ADDR ClassName, NULL

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!=NULL invoke GetWindowThreadProcessId, eax, ADDR ProcessId mov ThreadId, eax invoke DebugActiveProcess, ProcessId

After we obtain the process Id, we can call DebugActiveProcess. Then we enter the debug loop waiting for the debug events.

.IF DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT mov context.ContextFlags, CONTEXT_CONTROL invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, \ ADDR 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.

invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, \ context.regEip,ADDR 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.

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

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.


Tutorial 28: Win32 Debug API Part 1 Overview Tutorial 30: Win32 Debug API part 3