Tutorial 30: Win32 Debug API part 3

In this tutorial, we continue the exploration of win32 debug api. Specifically, we will learn how to trace the debuggee.

Theory:

If you have used a debugger before, you would be familiar with tracing. When you "trace" a program, the program stops after executing each instruction, giving you the chance to examine the values of registers/memory. Single-stepping is the official name of tracing.
The single-step feature is provided by the CPU itself. The 8th bit of the flag register is called trap flag. If this flag(bit) is set, the CPU executes in single-step mode. The CPU will generate a debug exception after each instruction. After the debug exception is generated, the trap flag is cleared automatically.
We can also single-step the debuggee, using win32 debug api. The steps are as follows:
  1. Call GetThreadContext, specifying CONTEXT_CONTROL in ContextFlags, to obtain the value of the flag register.
  2. Set the trap bit in regFlag member of the CONTEXT structure
  3. call SetThreadContext
  4. Wait for the debug events as usual. The debuggee will execute in single-step mode. After it executes each instruction, we will get EXCEPTION_DEBUG_EVENT with EXCEPTION_SINGLE_STEP value in u.Exception.pExceptionRecord.ExceptionCode
  5. If you need to trace the next instruction, you need to set the trap bit again.

Example:

________________________________________________________________________________________
; Equates:

[OFN_FLAGS &OFN_FILEMUSTEXIST+&OFN_PATHMUSTEXIST+&OFN_LONGNAMES+&OFN_EXPLORER+&OFN_HIDEREADONLY]

________________________________________________________________________________________
; Data:

[UserFileFilter: 0 # 50] [FullChoosenFile: 0 #50] [ChoosenFile: 0 #20]

[OPENFILENAME:  len  hwndFileOwner: 0  OF_hInstance: 0  FilterStrings
                userFileFilter  200  &NULL  FullChoosenFile  200  ChoosenFile
                200  &NULL  AppName  OFN_FLAGS
                nFileOffsetinChoosenFile: W§ 0  nFileExtensioninChoosenFile: 0
                DefaultExtension: D§ &NULL
                HookCustomData:  &NULL  HookProcPtr: &NULL  HookTemplateName: 0]
 

[AppName: 'Win32 Debug Example no.4' 0
 FilterStrings: B§ 'Executable Files' 0 '*.exe' 0
                   'All Files' 0 '*.*' 0 0
 ExitProc:         "The debuggee exits
Total Instructions executed : %lu",0

 ProcessInfo: "File Handle: %lx
Process Handle: %lx
Thread Handle: %lx
Image Base: %lx
Start Address: %lx", 0]

[TotalInstruction: 0]
 

[buffer: B§ 0 #512]

[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
                                             0
                                             0
                                             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 'Comdlg32.GetOpenFileNameA' OPENFILENAME
 
    ...If eax e &TRUE
        call 'KERNEL32.GetStartupInfoA' STARTUPINFO
        call 'KERNEL32.CreateProcessA' FullChoosenFile &NULL &NULL &NULL &FALSE,
                                       &DEBUG_PROCESS+&DEBUG_ONLY_THIS_PROCESS,
                                       &NULL &NULL STARTUPINFO  PROCESS_INFORMATION

        .While eax e &TRUE
L0:         call 'KERNEL32.WaitForDebugEvent' DEBUG_EVENT &INFINITE
 
            .If D§DE_dwDebugEventCode = &EXIT_PROCESS_DEBUG_EVENT
 
                call 'USER32.wsprintfA'  buffer  ExitProc D§TotalInstruction
                pop eax, eax, eax
                call 'USER32.MessageBoxA' 0 Buffer AppName &MB_ICONINFORMATION | jmp L9>>

            .Else_If D§DE_dwDebugEventCode = &EXCEPTION_DEBUG_EVENT
                If D§E_pExceptionRecord e &EXCEPTION_BREAKPOINT
                    mov D§C_ContextFlags &CONTEXT_CONTROL
                    call 'KERNEL32.GetThreadContext' D§PI_hThread  CONTEXT
                    or D§C_regFlag 0100
                    call 'KERNEL32.SetThreadContext' D§PI_hThread  CONTEXT

                    call 'KERNEL32.ContinueDebugEvent' D§DE_dwProcessId  D§DE_dwThreadId,
                                                      &DBG_CONTINUE
                  jmp L0<<

                Elseif D§E_pExceptionRecord e &EXCEPTION_SINGLE_STEP
                    inc D§TotalInstruction
                    call 'KERNEL32.GetThreadContext' D§PI_hThread  CONTEXT
                    or D§C_regFlag 0100
                    call 'KERNEL32.SetThreadContext' D§PI_hThread  CONTEXT
 
                    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
    ...End_If
 
L9: call 'Kernel32.CloseHandle' D§PI_hProcess
    call 'Kernel32.CloseHandle' D§PI_hThread
 
    call 'KERNEL32.ExitProcess' 0

Analysis:

The program shows the openfile dialog box. When the user chooses an executable file, it executes the program in single-step mode, couting the number of instructions executed until the debuggee exits.
 

            .Else_If D§DE_dwDebugEventCode = &EXCEPTION_DEBUG_EVENT
                If D§E_pExceptionRecord e &EXCEPTION_BREAKPOINT

We take this opportunity to set the debuggee into single-step mode. Remember that Windows sends an EXCEPTION_BREAKPOINT just before it executes the first instruction of the debuggee.
 

                    mov D§C_ContextFlags &CONTEXT_CONTROL
                    call 'KERNEL32.GetThreadContext' D§PI_hThread  CONTEXT
 

We call GetThreadContext to fill the CONTEXT structure with the current values in the registers of the debuggee. More specifically, we need the current value of the flag register.

                    or D§C_regFlag 0100

We set the trap bit (8th bit) in the flag register image.
 

                    call 'KERNEL32.SetThreadContext' D§PI_hThread  CONTEXT

                    call 'KERNEL32.ContinueDebugEvent' D§DE_dwProcessId  D§DE_dwThreadId,
                                                      &DBG_CONTINUE

Then we call SetThreadContext to overwrite the values in the CONTEXT structure with the new one(s) and call ContinueDebugEvent with DBG_CONTINUE flag to resume the debuggee.
 

                Elseif D§E_pExceptionRecord e &EXCEPTION_SINGLE_STEP
                    inc D§TotalInstruction

When an instruction is executed in the debuggee, we receive an EXCEPTION_DEBUG_EVENT. We must examine the value of u.Exception.pExceptionRecord.ExceptionCode. If the value is EXCEPTION_SINGLE_STEP, then this debug event is generated because of the single-step mode. In this case, we can increment the variable TotalInstruction by one because we know that exactly one instruction was executed in the debuggee.
 

                    call 'KERNEL32.GetThreadContext' D§PI_hThread  CONTEXT
                    or D§C_regFlag 0100
                    call 'KERNEL32.SetThreadContext' D§PI_hThread  CONTEXT
 
                    call 'KERNEL32.ContinueDebugEvent' D§DE_dwProcessId  D§DE_dwThreadId,
                                                      &DBG_CONTINUE

Since the trap flag is cleared after the debug exception is generated, we must set the trap flag again if we want to continue in single-step mode.
Warning: Don't use the example in this tutorial with a large program: tracing is SLOW. You may have to wait for ten minutes before you can close the debuggee.


[Iczelion's Win32 Assembly Homepage]