
Tool zur Durchführung eines Man-in-the-Middle im Speicher
Das MemITM (Mem In The Middle) Tool wurde entwickelt, um "Nachrichten" im Speicher von Windows-Prozessen einfach abzufangen. Wir haben viele benutzerdefinierte Speicher-Interception-Tools entwickelt, um Netzwerknachrichten vor der Verschlüsselung oder IPC-Nachrichten zu erfassen und sie inspizieren oder verändern zu können, um Fuzzing durchzuführen. Jedes Tool war sehr spezifisch, nicht generisch, in C/ASM implementiert und nicht einfach zu verwenden/zu warten/anzupassen.
Das MemITM-Tool wurde entwickelt, um diese Probleme zu lösen, und besteht aus:
Sie können es hier herunterladen (Quellen + kompiliert): https://github.com/Amossys/MemITM
Angenommen, Sie möchten Dateischreibvorgänge in einem bestimmten Windows-Prozess abfangen (es gibt einfachere Wege, dies zu tun, aber das dient als Beispiel). Dateischreibvorgänge werden von der Funktion WriteFile durchgeführt, die von kernelbase.dll exportiert wird, und folgt dem Schema BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, ...). WriteFile ist eine __fastcall-Funktion: lpBuffer wird von RDX und NumberOfBytesToWrite von R8 gezeigt. Lassen Sie uns diese Puffer abfangen.
Öffnen Sie Ihre kernelbase.dll-Datei in IDA Pro, laden Sie das Skript generate.idapython.py und führen Sie einfach getHook(LocByName("WriteFile"), "rdx","r8") gefolgt von updateConfig("config.bin") aus.
Starten Sie einen Editor (notepad) und führen Sie dann python memitm.py notepad.exe config.bin aus.
Das war's! Ein Hook wurde auf die Funktion WriteFile gesetzt, und die "Logger"- und "Fuzzer"-Funktionen von memitm.py erhalten eine Kopie des Puffers.
Die Funktion getHook erlaubt die Angabe von Registern für den Puffer und seine Größe, aber in manchen Fällen ist das nicht der Fall. Angenommen, Sie möchten NtCreateFile-Syscalls abfangen und den Dateinamen erhalten. NtCreateFile folgt dem Schema NTSTATUS NtCreateFile( OUT PHANDLE FileHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, ...), und der Dateiname kann durch ObjectAttributes->ObjectName->Buffer (Größe ist ObjectAttributes->ObjectName->Length * sizeof(WCHAR)) erhalten werden.
getHook erlaubt auch die Angabe eines Shellcodes im Parameter t1customOpcodes anstelle von Registern. Dieser Shellcode muss:
RCX legen;RDX legen;Im Fall von NtCreateFile können wir einfach diesen ASM-Code ausführen, um die Arbeit zu erledigen:
mov rax, [r8+0x10] ; rax ist jetzt ObjectAttributes->ObjectName
mov rcx, [rax + 0x10] ; rcx ist jetzt ObjectAttributes->ObjectName->Buffer
mov rdx, [rax] ; rdx ist jetzt ObjectAttributes->ObjectName->Length
and rdx, 0xFFFF ; Length ist ein USHORT
shl rdx, 1 ; und muss *2 getan werden
Lassen Sie uns dies assemblieren (z. B. mit der Online-Disassemblierungs-Website): 498B4010488B4810488B104881E2FFFF000048D1E2. Unser getHook-Aufruf sollte nun sein: getHook(LocByName("NtCreateFile", t1customOpcodes="498B…E2").
Ja! Die Config-Datei kann mehrere Definitionen von Interceptionspunkten enthalten, und die Funktion getHook hängt sie an die Datei an. Sie können Interceptionspunkte in mehreren Modulen setzen. Sie können zum Beispiel gleichzeitig ALPC- und IOCTL-Nachrichten abfangen.
Sie können sie durch ihre "message ID" in den Python-Callbacks unterscheiden.
Keine Sorge, es gibt auch einen einfachen HTTP-Server (logserver.py) und die Funktion httpNetSend, die es Ihnen ermöglicht, Ihre Testfälle (und Änderungen) in Echtzeit an einen entfernten Host zu senden.
Nun, Sie können beginnen, die Funktion bufferBitFlip zu verwenden, um... ein paar Bits zu kippen. Das Loggen von Roh-Nachrichten ermöglicht es Ihnen, Ihren eigenen Dissector zu schreiben und manuell einen besseren Fuzzer zu implementieren :). Denken Sie daran: Sie können die Puffergröße nicht ändern!
Zum Beispiel wird in unserem WriteFile-Beispiel die folgende fuzzer-Funktion "hello" durch "world" ersetzen:
def fuzzer(data, msgID=None, pid = 0):
return data.replace("hello","world")
Zum Beispiel könnten Sie für ALPC-Nachrichten das folgende memitm.py-Setup haben:
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
Das IDA Pro-Skript enthält eine einfache "Hooking"-Engine, die es ermöglicht, mehrere Anweisungen in einen bestimmten Bereich zu verschieben (abgeleitet von unserem DIMCT-Tool). Es muss viele Randfälle behandeln, wie speicherrelative Anweisungen, Querverweise usw., und wird wahrscheinlich viele "can't install hook here"-Meldungen anzeigen, wenn Sie versuchen, wirklich kleine Basisblöcke (<5 Bytes für x86-Binärdateien, <12 Bytes für x64-Binärdateien) abzufangen, die mehrere Querverweise enthalten. Für weitere Informationen lesen Sie einfach den Quellcode :).
Es erstellt die folgenden Informationen in der Config-Datei:
RCX/RDX platziert und die DLL-Log-Routine aufruft;Der DLL-Injektor ist ein einfacher DLL-Injektor (CreateRemoteThread/LoadLibraryA-Zeug).