
Sfrutta driver firmati vulnerabili per bypassare le callback del kernel EDR, le callback degli oggetti, il provider ETW TI e gli hook in userland per il dump della memoria di LSASS e l'estrazione delle credenziali.
EDRSandBlast è uno strumento scritto in C che sfrutta un driver vulnerabile firmato per bypassare i rilevamenti EDR (callback Notify Routine, Object Callback e provider ETW TI) e le protezioni di LSASS. Sono implementate anche molteplici tecniche di unhooking in userland per eludere il monitoraggio in userland.
Alla data del rilascio, una combinazione di tecniche in userland (--usermode) e kernel-land (--kernelmode) è stata utilizzata per dumpare la memoria di LSASS sotto l'esame dell'EDR, senza essere bloccata né generare eventi correlati a "OS Credential Dumping" nella console (cloud) del prodotto. I test sono stati eseguiti su 3 diversi prodotti EDR e hanno avuto successo in ogni caso.
I prodotti EDR utilizzano i callback "Notify Routines" del kernel su Windows per essere notificati dal kernel dell'attività di sistema, come la creazione di processi e thread e il caricamento di immagini (exe / DLL).
Questi callback del kernel sono definiti dal kernel, solitamente dal driver che implementa i callback, utilizzando una serie di API documentate (nt!PsSetCreateProcessNotifyRoutine, nt!PsSetCreateThreadNotifyRoutine, ecc.). Queste API aggiungono routine di callback fornite dal driver a array non documentati di routine nello spazio del kernel:
PspCreateProcessNotifyRoutine per la creazione di processiPspCreateThreadNotifyRoutine per la creazione di threadPspLoadImageNotifyRoutine per il caricamento di immaginiEDRSandBlast enumera le routine definite in questi array e rimuove qualsiasi routine di callback collegata a un elenco predefinito di driver EDR (più di 1000 driver di prodotti di sicurezza supportati, vedere la sezione Rilevamento dei driver e processi EDR). L'enumerazione e la rimozione sono possibili grazie allo sfruttamento di una primitiva arbitraria di lettura/scrittura della memoria del kernel fornita dallo sfruttamento di un driver vulnerabile (vedere la sezione Driver vulnerabili).
Gli offset degli array suddetti vengono recuperati utilizzando molteplici tecniche, fare riferimento alla sezione Offset.
I prodotti EDR (e anche EPP) registrano spesso "Object callback" tramite l'uso dell'API del kernel nt!ObRegisterCallbacks. Questi callback consentono al prodotto di sicurezza di essere notificato ad ogni generazione di handle su specifici tipi di oggetto (i callback di oggetti relativi a processi, thread e desktop sono ora supportati da Windows). Una generazione di handle può avvenire all'apertura di un oggetto (chiamata a OpenProcess, OpenThread, ecc.) così come alla duplicazione di handle (chiamata a DuplicateHandle, ecc.).
Essendo notificato dal kernel per ciascuna di queste operazioni, un prodotto di sicurezza può analizzare la legittimità della creazione dell'handle (ad es. un processo sconosciuto sta tentando di aprire LSASS) e persino bloccarlo se viene rilevata una minaccia.
Ad ogni registrazione di callback utilizzando ObRegisterCallbacks, un nuovo elemento viene aggiunto alla lista doppiamente collegata CallbackList presente nell'oggetto _OBJECT_TYPE che descrive il tipo di oggetto interessato dal callback (sia esso un processo, un thread o un desktop). Sfortunatamente, questi elementi sono descritti da una struttura che non è documentata né pubblicata nei file dei simboli da Microsoft. Tuttavia, studiandola da varie versioni di ntoskrnl.exe sembra indicare che la struttura non sia cambiata tra (almeno) Windows 10 build 10240 e 22000 (dal 2015 al 2022).
La struttura menzionata, che rappresenta una registrazione di callback di oggetto, è la seguente:```C typedef struct OB_CALLBACK_ENTRY_t { LIST_ENTRY CallbackList; // linked element tied to _OBJECT_TYPE.CallbackList OB_OPERATION Operations; // bitfield : 1 for Creations, 2 for Duplications BOOL Enabled; // self-explanatory OB_CALLBACK* Entry; // points to the structure in which it is included POBJECT_TYPE ObjectType; // points to the object type affected by the callback POB_PRE_OPERATION_CALLBACK PreOperation; // callback function called before each handle operation POB_POST_OPERATION_CALLBACK PostOperation; // callback function called after each handle operation KSPIN_LOCK Lock; // lock object used for synchronization } OB_CALLBACK_ENTRY;
La struttura `OB_CALLBACK` menzionata sopra è anch'essa non documentata, ed è definita dal seguente:```C
typedef struct OB_CALLBACK_t {
USHORT Version; // usually 0x100
USHORT OperationRegistrationCount; // number of registered callbacks
PVOID RegistrationContext; // arbitrary data passed at registration time
UNICODE_STRING AltitudeString; // used to determine callbacks order
struct OB_CALLBACK_ENTRY_t EntryItems[1]; // array of OperationRegistrationCount items
WCHAR AltitudeBuffer[1]; // is AltitudeString.MaximumLength bytes long, and pointed by AltitudeString.Buffer
} OB_CALLBACK;
Per disabilitare i callback degli oggetti registrati da EDR, in EDRSandblast sono implementate tre tecniche; tuttavia al momento solo una è abilitata.
Enabled di OB_CALLBACK_ENTRYQuesta è la tecnica predefinita abilitata in EDRSandblast. Per rilevare e disabilitare i callback degli oggetti correlati a EDR, viene esplorata la lista CallbackList situata negli oggetti _OBJECT_TYPE associati ai tipi Process e Thread. Entrambi i _OBJECT_TYPE sono puntati da simboli globali pubblici nel kernel, PsProcessType e PsThreadType.
Si assume che ogni elemento della lista corrisponda alla struttura OB_CALLBACK_ENTRY descritta sopra (assunzione che sembra valere almeno in tutte le build di Windows 10 al momento della scrittura). Le funzioni definite nei campi PreOperation e PostOperation vengono localizzate per verificare se appartengono a un driver EDR e, in tal caso, i callback vengono semplicemente disabilitati attivando/disattivando il flag Enabled.
Sebbene sia una tecnica abbastanza sicura, ha l'inconveniente di basarsi su una struttura non documentata; per ridurre il rischio di manipolazione non sicura di questa struttura, vengono eseguiti controlli di base per validare che alcuni campi abbiano i valori attesi:
Enabled è TRUE o FALSE (non ridere, un BOOL è un int, quindi potrebbe essere qualsiasi cosa diversa da 1 o 0);Operations è OB_OPERATION_HANDLE_CREATE, OB_OPERATION_HANDLE_DUPLICATE o entrambi;ObjectType punta a PsProcessType o PsThreadType.CallbackList di thread e processoUn'altra strategia che non si basa su una struttura non documentata (ed è quindi teoricamente più robusta rispetto ai cambiamenti del kernel NT) è lo scollegamento dell'intera CallbackList sia per i processi che per i thread. L'oggetto _OBJECT_TYPE è il seguente:```C
struct _OBJECT_TYPE {
LIST_ENTRY TypeList;
UNICODE_STRING Name;
[...]
_OBJECT_TYPE_INITIALIZER TypeInfo;
[...]
LIST_ENTRY CallbackList;
}
Facendo in modo che i puntatori `Flink` e `Blink` della `LIST_ENTRY` del `CallbackList` puntino alla `LIST_ENTRY` stessa, la lista viene effettivamente resa vuota. Poiché la struttura `_OBJECT_TYPE` è pubblicata nei simboli del kernel, la tecnica non si basa su offset/strutture hardcoded. Tuttavia, presenta alcuni svantaggi.