Tutorial 29: Win32 Debug API Part 2

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

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.

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.
 

____________________________________________________________________________________________

; 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

Analysis:

    call 'USER32.FindWindowA' 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 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.


 

[Iczelion's Win32 Assembly Homepage]