Continuamos con el tema de win32 debug API. En este tutorial, aprenderemos como depurar el depurando [debugee].
Baja el ejemplo
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.
ReadProcessMemory proto hProcess:DWORD, lpBaseAddress:DWORD, lpBuffer:DWORD, nSize:DWORD, lpNumberOfBytesRead:DWORD
hProcess
es el manejador al proceso que quieres leer.
lpBaseAddress es la dirección
del proceso objeto [target process] que quieres comenzar a leer. Por ejemplo,
si quieres leer 4 bytes del proceso en depuración comenzando en 401000h,
el valor en este parámetro debe ser 401000h.
lpBuffer es la dirección del
buffer que recibirá los bytes leídos del proceso.
nSize es el número de bytes que
quieres leer
lpNumberOfBytesRead es la dirección
de la variable de tamaño dword que recibe el número de bytes
realmente leídos. Si no esrelevante para tí, puedes usar NULL.
Las siguientes dos funciones de la API necesitan un poco de explicación. Bajo un sistema operativo multitarea como Windows, pueden haber varios programas corriendo al mismo tiempo. Windows da a cada hilo un pedazo de tiempo. Cuando expira ese lapso, Windows paraliza el hilo presente y conmuta a otro hilo que tiene la prioridad más alta. Justo antes de conmutar al otro hilo, Windows salva los valores de los registros del hilo presente de manera que cuando llegue el momento de resumir el hilo, Windows pueda restaurar el último *entorno* del hilo. Los valores salvados de los registros son llamados colectivamente en un contexto.
Volvamos a nuestro asunto. Cuando ocurre un evento de depuración, Windows suspende el proceso en depuración. El contexto del proceso en depuración es salvado. Como el proceso en depuración es suspendido, podemos estar seguros de que los valores en el contexto se mantendrá sin alteración. Podemos obtener los valores en el contexto con GetThreadContext y podemos cambiarlos con SetThreadContext.
Estas dos APIs son muy poderosas. Con ellas tienes en tus dedos [fingertips] el poder de una VxD [the VxD-like power] sobre los procesos en depuración: puedes alterar los valores del registro salvado y justo antes de que el proceso en depuración resuma la ejecución, los valores del contexto serán re-escritos dentro de los registros. Cualquier cambio que hagas al contexto es reflejado en el proceso en depuración. Piensa en ello: ¡incluso puedes alterar el valor del registro eip y desviar el flujo de la ejecución a donde quieras! No serás capaz de hacer eso bajo circunstancias normales.
GetThreadContext proto hThread:DWORD, lpContext:DWORD
hThread
es el manejador del hilo del que quieres obtener el contexto
lpContext es la dirección de
la estructura CONTEXT que será
llenada cuando la función regresa satisfactoriamente.
SetThreadContext tiene exactamente los mismos parámetros. Vemos que la estructura CONTEXT se ve más o menos como:
Como puedes observar, los miembros de esta estructuras son imitaciones de los registros del procesador real. Antes de que puedas usar esta estructura, necesitas especificar cuáles grupos de registros quieres leer/escribir en el miembro ContextFlags. Por ejemplo, si quieres leer/escribir todos los registros, debes especificar CONTEXT_FULL en ContextFlags. Si quieres sólo leer/escribir regEbp, regEip, regCs, regFlag, regEsp or regSs, debes especificar CONTEXT_CONTROL en ContextFlags.
Una cosa que debes recordar cuando uses la estrucura CONTEXT: debe ser alineada en el límite de una dword sino obtendrás extraños resultados bajo NT. Debes especificar "align dword" justo arriba de la línea que lo declara, como este:
align dword
MyContext CONTEXT <>
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
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.
[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