
Herramienta para realizar man-in-the-middle en memoria
La herramienta MemITM (Mem In The Middle) ha sido desarrollada para interceptar fácilmente "mensajes" en la memoria de procesos de Windows. Desarrollamos muchas herramientas personalizadas de intercepción de memoria para capturar mensajes de red antes del cifrado, o mensajes de IPC, y poder inspeccionarlos o alterarlos para hacer fuzzing. Cada herramienta era realmente personalizada, no genérica, implementada en C/ASM y no era fácil de usar/mantener/adaptar.
La herramienta MemITM ha sido desarrollada para abordar estos problemas, y consiste en:
Puedes descargarlo (fuentes + compilado) aquí: https://github.com/Amossys/MemITM
Supongamos que deseas interceptar escrituras de archivos (hay formas más simples de hacerlo, pero es para el ejemplo) en un proceso específico de Windows. Las escrituras de archivos se realizan mediante la función WriteFile, que es exportada por kernelbase.dll, y sigue el esquema BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, ...). WriteFile es una función __fastcall : lpBuffer es apuntado por RDX y NumberOfBytesToWrite por R8. Interceptemos estos búferes.
abre tu archivo "kernelbase.dll" en IDA Pro, carga el script generate.idapython.py, y simplemente ejecuta getHook(LocByName("WriteFile"), "rdx","r8") seguido de updateConfig("config.bin").
ejecuta un bloc de notas y luego, python memitm.py notepad.exe config.bin.
¡Eso es todo! Se ha colocado un hook en la función WriteFile, y las funciones "logger" y "fuzzer" de memitm.py recibirán una copia del búfer.
La función getHook permite especificar registros para el búfer y su tamaño, pero en algunos casos no es así. Supongamos que deseas interceptar llamadas al sistema NtCreateFile y obtener el nombre del archivo. NtCreateFile sigue el esquema NTSTATUS NtCreateFile( OUT PHANDLE FileHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, ...), y el nombre del archivo se puede obtener mediante ObjectAttributes->ObjectName->Buffer (el tamaño es ObjectAttributes->ObjectName->Length * sizeof(WCHAR)).
getHook también permite especificar un shellcode en su parámetro t1customOpcodes en lugar de registros. Este shellcode debe :
RCX;RDX;En el caso de NtCreateFile, podemos simplemente ejecutar este código ASM para hacer el trabajo:
mov rax, [r8+0x10] ; rax es ahora ObjectAttributes->ObjectName
mov rcx, [rax + 0x10] ; rcx es ahora ObjectAttributes->ObjectName->Buffer
mov rdx, [rax] ; rdx es ahora ObjectAttributes->ObjectName->Length
and rdx, 0xFFFF ; Length es un USHORT
shl rdx, 1 ; y debe ser *2
Montemos esto (usando por ejemplo el sitio web de desensamblado en línea) : 498B4010488B4810488B104881E2FFFF000048D1E2. Nuestra llamada a getHook debería ser ahora getHook(LocByName("NtCreateFile", t1customOpcodes="498B…E2").
¡Sí! El archivo de configuración puede incluir múltiples definiciones de puntos de intercepción, y la función getHook añade al archivo. Puedes establecer puntos de intercepción en múltiples módulos. Por ejemplo, puedes interceptar mensajes ALPC e IOCTL al mismo tiempo.
Podrás diferenciarlos por su "ID de mensaje" en los callbacks de Python.
No te preocupes, también hay un servidor HTTP simple (logserver.py) y la función httpNetSend que te permiten enviar tus casos de prueba (y modificación) a un host remoto en tiempo real.
Bueno, puedes empezar usando la función bufferBitFlip para... voltear varios bits. Registrar mensajes sin procesar te permitirá escribir tu propio disector y comenzar a implementar manualmente un mejor fuzzer :). Recuerda: ¡no puedes cambiar el tamaño del búfer!
Por ejemplo, en nuestro ejemplo de WriteFile, la siguiente función fuzzer reemplazará "hello" por "world":
def fuzzer(data, msgID=None, pid = 0):
return data.replace("hello","world")
Por ejemplo, para mensajes ALPC, podrías tener la siguiente configuración de memitm.py:
def logger(data, msgID=None, pid=0):
totalLen = 0
dataLen = 0
typ = 0
dataInfoOffset = 0
cid = 0
tid = 0
messageID = 0
clientViewSize = 0
# skip the PORT_MESSAGE header (0x18 bytes)
if len(data) > 0x18:
totalLen = struct.unpack("<H",data[:2])[0]
dataLen = struct.unpack("<H",data[2:4])[0]
typ = struct.unpack("<H",data[4:6])[0]
dataInfoOffset = struct.unpack("<H",data[6:8])[0]
zeroInit = struct.unpack("<L",data[4:8])[0]
cid = struct.unpack("<L",data[8:0xC])[0]
tid = struct.unpack("<L",data[0xC:0x10])[0]
messageID = struct.unpack("<L",data[0x10:0x14])[0]
clientViewSize = struct.unpack("<L",data[0x14:0x18])[0]
logMessage = "ALPC message : %x:%x - %x:%x - %d:%d - %x - %x" % (totalLen, dataLen, typ, dataInfoOffset, cid, tid, messageID, clientViewSize)
print logMessage
return
El script de IDA Pro incorpora un simple motor de "hooking", que permite mover varias instrucciones a un área específica (derivado de nuestra herramienta DIMCT). Debe manejar muchos casos límite, como instrucciones relativas a la memoria, referencias cruzadas, etc. y probablemente mostrará muchos "no se puede instalar hook aquí" si intentas interceptar bloques básicos realmente pequeños (<5 bytes para binarios x86, <12 bytes para x64) que contienen múltiples referencias cruzadas. Para más información, solo lee el código fuente :).
Genera la siguiente información en el archivo de configuración:
RCX/RDX y llama a la rutina de registro DLL;El inyector DLL es un simple inyector DLL (cosas de CreateRemoteThread/LoadLibraryA).