
Strumento per effettuare un attacco man in the middle in memoria
Lo strumento MemITM (Mem In The Middle) è stato sviluppato per intercettare facilmente i "messaggi" nella memoria dei processi Windows. Abbiamo sviluppato molti strumenti di intercettazione personalizzati della memoria per catturare messaggi di rete prima della crittografia, o messaggi IPC, e per poterli ispezionare o alterare per eseguire del fuzzing. Ogni strumento era molto specifico, non generico, implementato in C/ASM e non era facile da usare/mantenere/adattare.
Lo strumento MemITM è stato sviluppato per risolvere questi problemi e consiste in:
Puoi scaricarlo (sorgenti + compilato) qui: https://github.com/Amossys/MemITM
Supponiamo di voler intercettare le scritture su file (ci sono modi più semplici per farlo, ma è per l'esempio) in un processo Windows specifico. Le scritture su file vengono eseguite dalla funzione WriteFile, esportata da kernelbase.dll, e segue lo schema BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, ...). WriteFile è una funzione __fastcall: lpBuffer è puntato da RDX e NumberOfBytesToWrite da R8. Intercettiamo questi buffer.
apri il tuo file "kernelbase.dll" in IDA Pro, carica lo script generate.idapython.py, e esegui semplicemente getHook(LocByName("WriteFile"), "rdx","r8") seguito da updateConfig("config.bin").
esegui un notepad e poi python memitm.py notepad.exe config.bin.
Questo è tutto! È stato posizionato un hook sulla funzione WriteFile, e le funzioni "logger" e "fuzzer" di memitm.py riceveranno una copia del buffer.
La funzione getHook permette di specificare i registri per il buffer e la sua dimensione, ma in alcuni casi potrebbe non essere così. Supponiamo di voler intercettare le syscall NtCreateFile e ottenere il nome del file. NtCreateFile segue lo schema NTSTATUS NtCreateFile( OUT PHANDLE FileHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, ...), e il nome del file può essere ottenuto tramite ObjectAttributes->ObjectName->Buffer (la dimensione è ObjectAttributes->ObjectName->Length * sizeof(WCHAR)).
getHook permette anche di specificare uno shellcode nel suo parametro t1customOpcodes invece dei registri. Questo shellcode deve:
RCX;RDX;Nel caso di NtCreateFile, possiamo eseguire questo codice ASM per fare il lavoro:
mov rax, [r8+0x10] ; rax ora è ObjectAttributes->ObjectName
mov rcx, [rax + 0x10] ; rcx ora è ObjectAttributes->ObjectName->Buffer
mov rdx, [rax] ; rdx ora è ObjectAttributes->ObjectName->Length
and rdx, 0xFFFF ; Length è un USHORT
shl rdx, 1 ; e deve essere *2
Assembliamolo (usando per esempio il sito web di disassemblaggio online): 498B4010488B4810488B104881E2FFFF000048D1E2. Ora la nostra chiamata getHook dovrebbe essere getHook(LocByName("NtCreateFile", t1customOpcodes="498B…E2").
Sì! Il file di config può contenere più definizioni di punti di intercettazione, e la funzione getHook aggiunge al file. Puoi impostare punti di intercettazione in più moduli. Ad esempio, puoi intercettare messaggi ALPC e IOCTL contemporaneamente.
Potrai differenziarli tramite il loro "ID messaggio" nei callback Python.
Non preoccuparti, c'è anche un semplice server HTTP (logserver.py) e la funzione httpNetSend che ti permettono di inviare i tuoi casi di test (e le modifiche) a un host remoto in tempo reale.
Beh, puoi iniziare usando la funzione bufferBitFlip per... capovolgere alcuni bit. La registrazione dei messaggi grezzi ti permetterà di scrivere il tuo dissector e iniziare a implementare manualmente un fuzzer migliore :). Ricorda: non puoi cambiare la dimensione del buffer!
Ad esempio, nel nostro esempio WriteFile, la seguente funzione fuzzer sostituirà "hello" con "world":
def fuzzer(data, msgID=None, pid = 0):
return data.replace("hello","world")
Ad esempio, per i messaggi ALPC, potresti avere la seguente configurazione 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
Lo script IDA Pro incorpora un semplice motore di "hooking", che permette di spostare diverse istruzioni in un'area specifica (derivato dal nostro strumento DIMCT). Deve gestire molti casi particolari, come istruzioni relative alla memoria, riferimenti incrociati, ecc. e probabilmente mostrerà molti "can't install hook here" se provi a intercettare blocchi di base molto piccoli (<5 byte per binari x86, <12 byte per x64) che contengono molteplici riferimenti incrociati. Per maggiori informazioni, leggi il codice sorgente :).
Genera le seguenti informazioni nel file di config:
RCX/RDX e chiama la routine di log della DLL;L'iniettore DLL è un semplice iniettore DLL (roba CreateRemoteThread/LoadLibraryA).
La DLL stessa inizializza un'area di memoria condivisa e aspetta i dati di config (l'area di memoria condivisa ha 3 campi generici: l'ID del messaggio della memoria condivisa, la dimensione del messaggio e il buffer del messaggio). Una volta ricevuti, li analizza e installa gli hook in memoria, e non sarà permesso alcun aggiornamento della config dopo questo (devi uccidere il processo se vuoi impostare nuovi hook). Qualsiasi hook posizionato finirà nella funzione DLL genericHookFunction. Questa funzione riempie semplicemente l'area di memoria (protetta con sezioni critiche) con i dati del buffer, la sua dimensione e l'ID del messaggio (impostato nei bit alti del campo ID del messaggio della memoria condivisa). Per notificare il processo Python, vengono utilizzati 2 eventi per segnalare nuovi messaggi e per attendere che il processo li patch (timeout di 2 secondi). Se il buffer è stato aggiornato, anche il buffer originale viene modificato.
Lo script memitm.py esegue il processo di iniezione e attende la creazione della memoria condivisa. Invia quindi la configurazione e attende l'evento del messaggio. Quando ricevuto, la memoria condivisa viene letta e le funzioni logger e fuzzer vengono chiamate con il buffer, il PID del processo e l'ID del messaggio. Se la funzione logger restituisce un buffer diverso da quello originale e la sua dimensione è uguale, viene scritto nell'area di memoria condivisa. L'evento "ACK" viene quindi segnalato, e lo script attende un nuovo messaggio.
Speriamo che questo strumento sia utile, qualsiasi contributo/revisione sarà apprezzata! Inoltre, se sei francese e ti piace Rennes, stiamo assumendo :)