Tutorial 29: API de Depuración de Win32 Parte 2

Continuamos con el tema de win32 debug API. En este tutorial, aprenderemos como depurar el depurando [debugee].

Baja el ejemplo

Teoría:

En el tutorial previo, aprendimos como cargar el debuggee y a manejar eventos de depuración que ocurren en su proceso. Para que sea útil, nuestro programa debe ser capaz de modificar el proceso depurado. Hay varias APIs para realizar este propósito.

Ejemplo:

El primer ejemplo demuestra el uso de DebugActiveProcess. Primero necesitas correr un proceso objeto llamado win.exe que va en un bucle infinito justo antes de que la ventana sea mostrada en el monitor. Luego corres el ejemplo, se anexará a win.exe y modificará el código de win.exe de manera que win.exe salga del bucle infinito y muestre su ventana.

.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

;--------------------------------------------------------------------
; El código fuente parcial de win.asm, nuestro debuggee. Es realmente
; el ejemplo de ventyana simple en el tutorial 2 con un bucle infinito
; insertado justo antes de que entre el bucle de mensajes.
;----------------------------------------------------------------------

......
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 $ <---- Aquí está nuestro bucle infinito. Ensambla a 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

Análisis:

invoke FindWindow, addr ClassName, NULL

Nuestro programa necesita anexarse él mismo al depurando [debuggee] con DebugActiveProcess lo cual requiere el Id del proceso del depurando. Podemos obtener el Id del proceso llamando a GetWindowThreadProcessId que en cambio necesita del manejador [handle] de ventana como parámetro. Así que necesitamos obtener primero el manejador de ventana.

Con FindWindow, podemos especificar el nombre de la clase de ventana que necesitamos. Regresa el manejador de la ventana creada por esa clase. Si regresa NULL, no hay ninguna ventana de esa clase.

.if eax!=NULL
    invoke GetWindowThreadProcessId, eax, addr ProcessId
    mov ThreadId, eax
    invoke DebugActiveProcess, ProcessId

Después de que obtengamos el Id del proceso, podemos llamar a DebugActiveProcess. Luego introducimos el bucle de depuración que espere por los eventos de depuración.

       .if DBEvent.dwDebugEventCode==CREATE_PROCESS_DEBUG_EVENT
          mov context.ContextFlags, CONTEXT_CONTROL
          invoke GetThreadContext,DBEvent.u.CreateProcessInfo.hThread, addr context           

Cuando obtenemos CREATE_PROCESS_DEBUG_INFO, significa que el depurando está suspendido, listo para que nosotros hagamos la cirugía sobre él. En este ejemplo, podemos sobreescribir la instrucción del bucle infinito en el depurando (0EBh 0FEh) con NOPs ( 90h 90h).
Primero, necesitamos obtener la dirección de la instrucción. Puesto que el depurando está ya en el bucle por el tiempo que nuestro programa está anexo a él, eip siempre apuntará a la instrucción. Todo lo que necesitamos hacer es obtener el valor de eip. Usamos GetThreadContext para alcanzar esa meta. Ponemos el miembro ContextFlags a CONTEXT_CONTROL para decirle a GetThreadContext que queremos llenar los miembros del registro de control "control" de la estructura CONTEXT.

          invoke WriteProcessMemory, DBEvent.u.CreateProcessInfo.hProcess, context.regEip ,addr buffer, 2, NULL

Ahora que obtenemos el valor de eip, podemos llamar a WriteProcessMemory para sobreescribir la instrucción "jmp $" con NOPs, ayudar efectivamente de esta manera a que el depurando salga del bucle infinito. Después de que desplegamos el mensaje al usuario y luego llmamos a ContinueDebugEvent para resumir el debuggee. Puesto que la instrucción "jmp $" está sobreescrita por NOPs, el debuggee será capaz de continuar mostrando su ventana e introducir su bucle de mensajes. La evidencia es que veremos su ventana en el monitor.

El otro ejemplo usa una aproximación al problema levemente diferente para detener [break] el debuggee fuera del bucle infinito.

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

Todavía se llama a GetThreadContext para obtener el valor actual de eip pero en vez de sobreescribir la instrucción "jmp $", se incrementa en 2 el valor de regEip para "saltar por encima" ["skip over"] de la instrucción. El resultado es que cuando el archivo en depuración [debuggee] vuelve a ganar el control, resume la ejecución en la siguiente instrucción después de "jmp $".

Ahora puedes ver el poder de Get/SetThreadContext. También puedes modificar las otras imágenes de registros y sus valores serán reflejados de regerso al depurando. Incluso puedes insertar la instrucción int 3h para poner puntos de quiebre [breakpoints] en el proceso debuggee.



Index

Next

[Iczelion's Win32 Assembly Homepage]

n u M I T_o r's   Programming Page

Este tutorial, original de Iczelion, ha sido traducido por:   n u M I T_o r

www.000webhost.com