¡Acceso ilimitado 24/7 a todos nuestros libros y vídeos! Descubra la Biblioteca Online ENI. Pulse aquí
¡Acceso ilimitado 24/7 a todos nuestros libros y vídeos! Descubra la Biblioteca Online ENI. Pulse aquí
  1. Libros
  2. Hacking y Forensic
  3. Depuración en Windows
Extrait - Hacking y Forensic Desarrolle sus propias herramientas en Python
Extractos del libro
Hacking y Forensic Desarrolle sus propias herramientas en Python Volver a la página de compra del libro

Depuración en Windows

Introducción

En este capítulo, vamos a usar PyDbg inspirado en el Libro Gray Hat Python de Justin Seitz, capítulo 4. Estamos en el límite del Hacking y del Forensic. PyDbg ayudará a localizar los datos en los programas, depurar y también a realizar fuzzing, hook de aplicación, etc.

Trabajaremos aquí bajo Windows y por mi parte, en Linux, yo emplearé una máquina virtual VMware para escribir y probar los ejercicios y ejemplos de este capítulo.

Se requiere un conocimiento previo de los sistemas Windows, desensamblador y por supuesto de Python.

Debemos saber cómo asociar el depurador a un proceso bien sea abriendo el ejecutable y arrancándolo, o bien enganchándose al proceso (el programa ya está en ejecución).

Vamos a conectar al proceso cuando este nos permita no tener en cuenta el inicio de la aplicación y centrarnos en una parte específica del código.

Cuando abriremos el programa en el depurador (debugger), controlaremos el proceso desde el arranque; podremos, por lo tanto ver lo que se carga, lo cual resulta muy útil en el análisis de malwares y virus.

El módulo ctypes de Python

El módulo ctypes permite interactuar con librerías escritas en lenguaje C.

Utilizaremos en adelante el módulo ctypes para nuestro depurador (debugger). 

El módulo ctypes proporciona métodos para cargar librerías escritas en C y llamar a las funciones de estas librerías, tipos de datos compatibles con el lenguaje C para intercambiar y recuperar las variables con estas funciones.

El módulo ctypes está disponible a partir de la versión 2.5 de Python.

El módulo ctypes exporta un objeto cdll que permite cargar una librería. En el entorno Windows, disponemos además de los objetos windll y oledll.

Objeto cdll proporciona un método LoadLibrary() que permite cargar una librería dinámica.


From ctypes import *  
cdll.LoadLibrary("libc6.so.6") # Linux  
print windll.kernel32 # windows  
print cdll.msvcrt # windows  
libc=cdll.msvcrt
 

Una vez que hemos cargado una librería empleando el método LoadLibrary(), le asignamos un objeto que permitirá llamar a las funciones definidas por la librería.

Este objeto actuará como envoltorio (wrapper) para acceder a las funciones de la librería, pasando a través de punteros.


print windll.kernel32.GetModuleHandleA # Muestra <_FuncPtr object 
at 0x...>  
print hex(windll.kernel32.GetModuleHandleA(None)) # llamada...

Primer enfoque

¿Cómo crear un proceso en Windows en un depurador?

La función se denomina CreateProcessA(), a la que podemos transmitir los parámetros.

Podremos encontrar en Internet, y en particular en el sitio de MSDN, detalles sobre esta función si así lo queremos.

Veremos aquí los parámetros a transmitir que nos servirán luego.

Los parámetros utilizados son:

  • lpApplicationName

  • lpCommandLine

  • dwCreationFlags

  • lpStartupInfo

  • lpProcessInformation

Los demás parámetros podrán asignarse con NULL.

Los dos primeros parámetros nos permitirán proporcionar la ruta de la aplicación y los comandos que vamos a pasar (en línea de comandos) si es necesario.

dwCreationFlags nos servirá para indicar al proceso que deberá arrancar con el debugger (en modo debug) y los dos últimos parámetros son punteros a las estructuras (STARTUPINFO y PROCESS_INFORMATION) que determinarán la forma en que el proceso debe comenzar y nos darán información tras el arranque del proceso.

Vamos a crear en Python tres programas, un script que definirá las dos estructuras que hemos mencionado antes (mis_definiciones_debugger.py), un script que será el depurador (mi_debugger.py) y un script de prueba para verificar el buen funcionamiento de los dos scripts anteriores.

mis_definiciones_debugger.py


from ctypes import *  
  
WORD = c_ushort  
DWORD = c_ulong  
LPBYTE = POINTER(c_ubyte)  
LPTSTR = POINTER(c_char)  
HANDLE = c_void_p  
  
DEBUG_PROCESS = 0x00000001  
CREATE_NEW_CONSOLE = 0x00000010  
class STARTUPINFO(Structure):  
      _fields_ = [  
               ("cb", DWORD),  
               ("lpReserved",LPTSTR),  
               ("lpDesktop",LPTSTR),  
               ("lpTitle",LPTSTR),  
               ("dwX",DWORD),  
               ("dwY",DWORD),  
               ("dwXSize",DWORD),  
          ...

Estado de los registros

Una de las principales ventajas de un depurador es poder visualizar el contenido de los registros. Cuando aparece una excepción, debemos ser capaces de determinar el estado de la pila desde cualquier lugar y en cualquier momento.

Para hacer esto, debemos obtener la información del hilo (thread) actual.

La función OpenThread() nos puede ayudar aquí, indicando los parámetros adecuados. Esta función es muy similar a OpenProcess() pero en lugar de transmitirle el PID, tendremos que transmitirle el TID (Thread Identifier).

1. Enumeración de los hilos (threads)

Por esto, debemos ser capaces de enumerar todos los hilos (threads) del proceso. Para esto tenemos a nuestra disposición la función CreateToolhelp32Snapshot() de la DLL kernel32.dll. Podremos, por tanto, obtener por ejemplo la lista de procesos, hilos (threads) y las DLL cargadas.

El parámetro dwFlags indicará a la función lo que deseamos ver (threads, DLL, proceso, heap).

Iniciaremos este parámetro con TH32CS_SNAPTHREAD (valor 0x00000004) para ver los hilos.

El otro parámetro, th32ProcessID, es simplemente el PID del proceso.

Cuando la función tiene éxito, nos devuelve el handle del objeto snapshot.

Una vez que tenemos la lista de threads, podemos enumerarlos.

Para comenzar la enumeración, utilizaremos Thread32First() que recibe dos parámetros: Hsnapshot, el handle devuelto...

Los eventos del debugger

Vamos a regresar a la función WaitForDebugEvent() que nos devolverá la estructura DEBUG_EVENT.

Esta estructura contiene mucha información donde dwDebugEventCode centrará en particular nuestra atención, ya que nos indica el tipo de evento que tuvo lugar.

Mirando el valor de dwDebugEventCode, nos permitirá determinar el evento.

Código del evento

Valor del código

Valor de la unión u

0x1

EXCEPTION_DEBUG_EVENT

u.Exception

0x2

CREATE_THREAD_DEBUG_EVENT

u.CreateThread

0x3

CREATE_PROCESS_DEBUG_EVENT

u.CreateProcessInfo

0x4

EXIT_THREAD_DEBUG_EVENT

u.ExitThread

0x5

EXIT_PROCESS_DEBUG_EVENT

u.ExitProcess

0x6

LOAD_DLL_DEBUG_EVENT

u.LoadDll

0x7

UNLOAD_DLL_DEBUG_EVENT

u.UnloadDll

0x8

OUTPUT_DEBUG_STRING_EVENT

u.DebugString

0x9

RIP_EVENT

u.RipInfo

Añadimos ahora algunas definiciones a mi_debugger3.py, que se convierte en mi_debugger_final.py

Usaremos mi_debugger_final.py hasta el final de este capítulo, las funciones que veremos más adelante ya se han definido.

mi_debugger_final.py


from ctypes import *  
from mis_definiciones_debugger_final import *  
  
import sys  
import time  
kernel32 = windll.kernel32  
  
class debugger():  
  
    def __init__(self):  
        self.h_process       =     None  
        self.pid             =     None  
        self.debugger_active =     False  
        self.h_thread        =     None  
        self.context         =     None  
        self.breakpoints     =     {}  
        self.first_breakpoint=     True  
        self.hardware_breakpoints = {}  
          
  
        system_info = SYSTEM_INFO()  
        kernel32.GetSystemInfo(byref(system_info))  
        self.page_size = system_info.dwPageSize  
          
  
        self.guarded_pages...

Los puntos de parada (breakpoints)

1. Puntos de parada software

Para colocar puntos de parada, debemos ser capaces de escribir y leer en la memoria. 

La función ReadProcessMemory() nos puede ayudar, así como WriteProcessMemory().

Empleando estas dos funciones, podemos inspeccionar la memoria.

Los parámetros que debemos proporcionar son lpBaseAddress (dirección donde queremos empezar a leer o escribir), lpBuffer (puntero hacia el dato que queremos leer o escribir), nSize (número total de bytes que queremos leer o escribir).

Usando estas dos funciones, podremos emplear puntos de parada con facilidad en nuestro debugger.

Por lo general, los puntos de parada se ubican en una llamada de función. Para el ejercicio, utilizaremos la llamada a printf().

Para determinar la dirección virtual de una función, utilizaremos GetProcessAddress() que se exportará de kernel32.dll.

Necesitaremos la cabecera de la función de la llamada en la que deseamos colocar el punto de parada; GetModuleHandle() nos ayudará.

Puede ver las definiciones de read_process_memory(), write_process_memory(), bp_set() y func_resolve() en mi_debugger_final.py si desea conocer más detalles al respecto.

Para permitirnos probar el script que hará el bucle con un printf que llamaremos bucle_printf.py, vamos a escribir otro script, mi_test5.py.

mi_test5.py


import mi_debugger_final  
debugger=mi_debugger_final.debugger()  ...

La librería PyDbg

Hemos visto en las secciones anteriores cómo configurar puntos de parada, pero ¿cómo hacerlo con PyDbg?

Aquí nos podrá ayudar la función bp_set(address, descriptor= ’’ ’’, restore= True,handler=NONE).

El argumento address es la dirección donde queremos configurar el punto de parada software.

El parámetro descriptor es opcional y puede utilizarse para dar un nombre al punto de parada.

El parámetro restore determina si el punto de parada debe ser restaurado a cero de forma automática, después de haber recuperado la cabecera y el parámetro handler define a qué función llamar.

Toda la información de contexto, hilos y procesos será provista por esta clase al llamar a la función.

Usaremos el script bucle_printf.py de las secciones anteriores y, mediante un nuevo script, vamos a leer los valores del contador y sustituirlos por un valor aleatorio comprendido entre 1 y 100.

Vamos a consultar, leer y manipular los datos del proceso objetivo en tiempo real.

printf_random.py


from pydbg import *  
from pydbg.defines import *  
  
import struct  
import random  
  
def printf_randomizer(dbg):  
      
    # Lectura del valor del contador en ESP + 0x8 como un DWORD  
    parameter_addr = dbg.context.Esp + 0x8  
    counter = dbg.read_process_memory(parameter_addr,4)  
    counter = struct.unpack("L",counter)[0]  
    print "Counter: %d" % int(counter)  
    random_counter = random.randint(1,100)  
    random_counter = struct.pack("L",random_counter)[0]  
    dbg.write_process_memory(parameter_addr,random_counter)  
    return DBG_CONTINUE  
  
dbg = pydbg()  
pid = raw_input("Enter the printf_loop.py PID: ")  
dbg.attach(int(pid))  
printf_address = dbg.func_resolve("msvcrt","printf")  
dbg.bp_set(printf_address,description="printf_address",  
handler=pri ntf_randomizer)  
dbg.run()
 

Resultado obtenido por el bucle_printf.py


E:\temp\cap4> python bucle_printf.py  
iteración 0 del bucle  ...

Puesta en práctica: Hooking

Enunciado

Requisitos previos: PyDbg

Objetivo: crear un hook de Internet Explorer.

Enunciado:

Un hook (literalmente gancho o anzuelo) permite al usuario de un software personalizar el funcionamiento de este último, haciendo que se ejecuten acciones adicionales en momentos determinados.

Deseamos interceptar (sniff) las conexiones SSL de Internet Explorer que utiliza CryptoAPI de Microsoft.

Solución

hook_ssl_pydbg.py


from pydbg import *  
from pydbg.defines import *  
import utils  
import sys  
import re  
import struct  
  
  
def ssl_sniff_encryptmessage( dbg, args ):  
    buffer = ""  
    addr_bufflen=dbg.context.Esp + 0x2C  
    bufflen=struct.unpack('L',dbg.read_process_memory(addr_bufflen , 4))[0] 
    addr=dbg.context.Esp + 0x34  
    addr_buffer = struct.unpack('L',dbg.read_process_memory(addr, 4))[0]  
    try:  
      buffer=dbg.read_process_memory(addr_buffer,bufflen)  
      buffer=re.sub('.*gzip.*\r\n','',buffer)  
      dbg.write_process_memory(addr_buffer, buffer)  
    except:  
      pass  
    print "Comando: \n\n%s" % buffer  ...