Lesson 29: Win32 debugging API second part
We continue Win32 debugging the topic of the API. In this chapter, we will learn how to modify the debugged procedure.
download
The Example
theory:
In the previous chapter, we learned how to load the debugged process and how to handle events that occur in the process. In order to have practical purposes, our program should have the ability to modify the debugged procedure. There are several API functions for this purpose.
ReadProcessMemory This function allows you to read the memory of the specified process. The function prototype is as follows: ReadProcessMemory Proto HProcess: DWORD, LPBASEADDRESS: DWORD, LPBUFFER: DWORD, NSIZE: DWORD, LPNUMBEROFBYTESREAD: DWORD HPROCESS Handle The handle of the process is read in the LPBaseAddress target process. For example, if you want to read 4 bytes from the address 401000h in the target process, this parameter value should be 401000h. The LPBuffer receives the number of bytes that the buffer address nsize wants to read. LPNUMBEROFBYTESREAD records the variable address of the number of bytes that actually read. If you don't care about this value, you can fill in NULL. WriteProcessMemory is a function corresponding to ReadProcessMemory, which can write memory process. Its parameters are the same as readprocessmemory. Understand the two functions of the next item require some background knowledge about the context. In the multi-task operating system like Windows, several programs may be run in the same time. Windows assigned to each thread a time slice. After the end of the intermediate film, Windows will freeze the current thread and switch to the next thread with the highest priority. Before switching, Windows will save the contents of the registers of the current process, so when the thread is restored to the run, Windows can restore the latest thread running * environment *. Saved register content is generally called process context. Go back to our theme. When a debug event occurs, Windows pauses the debugged process and saves its process context. Since the process is suspended, we can confirm that its process context will remain unchanged. You can use GetThreadContext to get the contents of the process, and you can also use GetThReadContext to modify the process context. These two functions are very powerful. With them, you have the ability to be a VXD for the debugged process: if you change its register content, these values will be written back to the register before the debugger recovers run. Any changes made in the process context will be reflected in the debugged program. Imagine: You can even change the contents of the EIP register so you can let the program run to anywhere you want! It is impossible to do this under normal circumstances. Getthread: DWORD, LPCONTEXT: DWORD HTHREAD Do you want to get the context thread handle LPCONText function successfully returns the structural pointer used to save the context content. The setthreadContext parameter is the same.
Let's take a look at the context structure: Context Struct ContextFlags Dd?; ----------------------------------- -------------------------------------------------- ---------------------; When ContextFlags contains context_debug_registers, return to this section; ------------------- -------------------------------------------------- -------------------------------------- IDR0 DD? IDR1 DD? IDR2 DD? Idr3 DD? IDR6 DD? Idr7 DD?; ---------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------- -------------; When ContextFlags contains context_floating_point, return to this section; --------------------------- -------------------------------------------------- ------------------------------ FLOATSAVE FLOATING_SAVE_AREA <>; --------------- -------------------------------------------------- -----------------------------------------; When ContextFlags contains context_segments, return this section ; ------------------------------------------------- -------------------------------------------------- -------- Reggs DD? Regfs DD? Reges Dd? Regds DD?; ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ----- -------------------------------------------------- ----------------------; When ContextFlags contains context_integer, return to this section; ------------------ -------------------------------------------------- --------------------------------------- RegeDi DD? Regesi DD? regebx dd? regedx DD Regecx DD? Regeax DD?; -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------- --------------; When ContextFlags contains Context_Control, return to this section; -------------------------- -------------------------------------------------- ------------------------------ Regebp DD? Regflag Dd? Regcs Dd? Regless DD?;
-------------------------------------------------- -------------------------------------------------- ------; When ContextFlags contains context_extended_registers, return this section; -------------------------------------------------------------------------------------------------- -------------------------------------------------- ---------------------- Extendedregisters DB Maximum_supported_extension dup (?) Context Ends can be seen that members in this structure are imitation of registers of the actual processor . Which register groups are specified in ContextFlags before using this structure. To access all registers, you can set the contextFlags for context_full. Or only access to RegeBp, Regeip, Regcs, Regflag, Regesp, or Regss, should check CONTEXTFLAGS to CONTEXT_CONTROL. You should also remember when using the structure context: it must be double-word alignment, otherwise we will get a strange result in NT. You can add "Align DWORD" before defining. For example: Align DWORDMYCONTEXT Context <> Example:
The first example demonstrates the use of DebugActiveProcess. First, you need to run a to-debr program Win.exe before Windows, which will be in an infinite loop running state. Then you run the example, it will connect yourself with Win.exe and modify the win.exe code so that Win.exe will exit the infinite loop state and display 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 "Can not 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 dbeivent.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 insert; 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 FEinvoke ShowWindow, hwnd, SW_SHOWNORMAL invoke UpdateWindow, hwnd .while TRUE invoke GetMessage, ADDR msg, NULL , 0,0 .break .if (! EAX) Invoke TranslateMessage, Addr Msg .Endw Mov Eax, Msg.wParam Ret Winmain Endp
analysis:
Invoke Findwindow, AddR ClassName, Null
Our program requires DebugActiveProcess to bind yourself to the debugged program, which requires the process ID of the debugged program. With getWindowThreadProcessID, you can get the ID, which requires a window handle as a parameter, so you first need to know the window handle. With FindWindow, let's first specify the name of the window class, and return to the window handle created by this class. If you return NULL, it indicates that there is currently no window.
. IF EAX! = Null Invoke GetWindowThreadProcessid, Eax, Addr Processid Mov Threadid, Eax Invoke DebugActiveProcess, Processid
After getting the process ID, we call DebugActiveProcess. This enters the loop waiting for the debug event. .IF dbEvent.dwdebugeventcode == create_process_debug_event mov context.contextFlags, Context_Control Invoke GetthreadContext, Dbevent.u.createProcessInfo.hthread, Addr Context
When CREATE_PROCESS_DEBUG_INFO, this means that the debug process has been suspended. We can operate their processes. In this example, we will cover the unlimited cyclic instructions in the debug process with NOPS (90h 90h). First, the address of the instruction is required. Since when we bind to the debugged program in our program, the debugger is already in a loop statement, and EIP always points to the instruction. What we have to do is to get the value of the EIP. We will use GetThReadContext to achieve this. Set the contextFlags in context structure to context_control, telling GetThreadContext we need it to fill the "control" register in members of the context structure.
Invoke WriteProcessMemory, Dbevent.u.createProcessInfo.hprocess, Context.Regeip, Addr Buffer, 2, Null
After getting the value of the EIP, you can call WriteProcessMemory to override the JMP $ "instruction with NOPS, which will make the debugger exits the infinite loop. After displaying information to the user, call ContinueDeBugevent to restore the running of the debugged program. Since the instruction "JMP $" has been overwritten by NOPS, the debugger will continue to display the window and enter the message loop. The evidence is that we observed the window on the screen.
Another example is slightly different, it is interrupt from the debugger from an 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 ............
Here is still called GetThreadContext to get the EIP value, but do not overwrite the JMP $ "instruction, but add regeip plus 2 to" skip "the instruction. The result is that when the debugger is re-obtained, the instruction after "JMP $" is resumed.
Now you can understand the power of get / setthreadContext. You can also modify other register images that will be directly reflected in the debugprue. Even you can insert the INT 3H instruction into the debugged process. Generate breakpoints.