Sei appena stato vettorializzato utilizzando gestori di eccezioni vettoriali (VEH) per l'evasione della difesa e l'injection di processo

Responsabile che fa una presentazione per i responsabili di progetto in ufficio

Autore

Joshua Magri

Senior Managing Security Consultant

Adversary Services, IBM X-Force Red

Negli ultimi anni, i gestori di eccezioni vettoriali (VEH) hanno ricevuto molta attenzione da parte del settore della sicurezza offensiva, ma VEH viene utilizzato nei malware da oltre un decennio. VEH fornisce agli sviluppatori un modo semplice per intercettare le eccezioni e modificare i contesti dei registri, quindi è naturale che rappresentino un bersaglio facile per gli sviluppatori di malware. Nonostante tutta l'attenzione ricevuta, nessuno aveva pubblicizzato un modo per aggiungere manualmente un Vectored Exception Handler senza fare affidamento sulle API Windows integrate che a volte sono collegate ai prodotti Rilevamento e risposta degli endpoint (EDR)

Nel 2015, un utente di UnKnoWnCheaTsuser ha pubblicato frammenti di codice per manipolare la lista VEH e, più recentemente, nel 2024, un ricercatore di nome mannyfreddy ha pubblicato un blog che approfondisce come funzionano i Vectored Exception Handlers. Il blog di Mannyfreddy ha anche parlato di come manipolare la lista VEH ed ha persino esplorato come utilizzare i Vectored Exception Handlers per la remote process injection.

Nel 2022, mi sono interessato ai Vectored Exception Handlers dopo che rad9800 ha pubblicato una proof of concept per percorrere la lista dei Vectored Exception Handler e chiamare l'API RemoveVectoredExceptionHandler su ciascun handler registrato per cancellare la lista. Questo mi ha portato a sviluppare un metodo per manipolare manualmente la lista VEH e un metodo per usare VEH per eseguire la threadless process injection. Poiché le informazioni su queste tecniche stanno iniziando ad essere condivise pubblicamente, ho pensato che fosse giunto il momento di rendere pubblica la mia ricerca in questo settore.

In questo post esamineremo come manipolare manualmente la lista dei Vectored Exception Handler di Windows e come i Vectored Exception Handler possano essere utilizzati per eludere le difese ed eseguire l'iniezione di processo. Puoi trovare il codice allegato a questo post del blog qui.

Cosa sono i Vectored Exception Handler?

I Vectored Exception Handler sono un meccanismodi Windows che estende la gestione strutturata delle eccezioni (SEH). In breve, permettono agli sviluppatori di registrare una funzione che verrà chiamata quando viene generata un'eccezione in un processo. Questa funzione riceverà informazioni sull'eccezione e sullo stato dei registri quando l'eccezione si è verificata.

Vectored Exception Handler sono memorizzati in una lista e, quando viene generata un'eccezione, viene chiamato il primo gestore di eccezioni nella lista. In genere, si scrive un VEH per cercare specifici tipi di eccezioni che si prevede di gestire. Se il tuo gestore viene chiamato e il codice di errore non ti interessa, puoi dire al processo di continuare a percorrere la lista per trovare un gestore in grado di gestire l'errore. Se si tratta di un errore che vuoi gestire, puoi fare ciò che è necessario e comunicare al processo che l'errore è stato gestito, e l'esecuzione riprenderà. Se l'intera lista VEH viene percorsa e nessun gestore ordina al processo di continuare l'esecuzione, allora il processo verrà terminato.

Il grafico seguente mostra l'aspetto del VEH. Il gestore di eccezioni inizierà dalla testa dell'elenco e poi percorrerà ogni elemento alla ricerca di un gestore appropriato. Se arriva di nuovo al List Head, allora il processo è terminato.

Diagramma di una struttura di liste con doppio collegamento. Inizia con una testa di lista che punta al primo nodo, seguita da due nodi aggiuntivi. Ogni nodo contiene campi etichettati Flink, Blink, Reserved, Ref e Pointer to VEH. Le frecce indicano i collegamenti in avanti e indietro tra i nodi, con l'ultimo nodo che rimanda alla Testa dell'elenco.

Come posso aggiungere un Vectored Exception Handler?

Puoi trovare alcuni esempi di codice di Microsoft qui. In breve, puoi creare un Vectored Exception Handler creando una funzione che prende un puntatore a una _EXCEPTION_POINTERS struct come argomento e poi chiama l'API Windows AddVectoredExceptionHandler per registrare il gestore delle eccezioni. Di seguito sono riportati gli argomenti per la funzione AddVectoredExceptionHandler.

Frammento di codice che mostra la dichiarazione della funzione per AddVectoredExceptionHandler. Restituisce un PVOID e accetta due parametri: ULONG First e PVECTORED_EXCEPTION_HANDLER Handler.

Il primo argomento indica alla funzione se inserire il nuovo gestore all'inizio dell'elenco dei gestori di eccezioni. Se non lo inserisci come primo handler, verrà inserito in fondo alla lista. Il secondo argomento è un puntatore all'handler delle eccezioni da chiamare.

Ricorda che, sebbene la tua funzione di handler debba prendere una _EXCEPTION_POINTERS struct come argomento, in realtà non devi conformarti a questo prototipo se il tuo handler non ha bisogno di argomenti. Ciò significa che è possibile avere indirizzi di memoria arbitrari denominati Vectored Exception Handlers. Vedremo le implicazioni di questo più avanti.

In che modo EDR utilizza i Vectored Exception Handlers?

Alcuni prodotti EDR registreranno i propri Vectored Exception Handlers. Un caso d'uso comune è quello di posizionare trappole PAGE_GUARD su determinate regioni della memoria. Quando si accede a una regione di memoria con la protezione PAGE_GUARD, si genera un'eccezione e il prodotto rilevamento e risposta degli endpoint (EDR) può ispezionare ciò che ha generato l'eccezione per decidere se è dannoso o meno.

Ad esempio, lo shellcode accederà alla Export Address Table (EAT) per Kernel32.dll per risolvere indirizzi di funzione. Tuttavia, anche la legittima funzione GetProcAddress esegue questa operazione. Inserendo una trappola PAGE_GUARD sul Kernel32.dll, un EDR può analizzare se l'accesso viene eseguito o meno da un modulo legittimo o da una regione di memoria non supportata. Nel secondo caso, è un segnale di potenziale malware. Yarden Shafir ha discusso uno scenario simile in questo eccellente postsul blog.

Dal momento che i fornitori di rilevamento e risposta degli endpoint (EDR) utilizzano i Vectored Exception Handlers, è nel loro interesse assicurarsi che l'elenco VEH non venga manomesso. Se riuscissi ad aggiungere un gestore delle eccezioni all'inizio dell'elenco, semplicemente non potresti mai passare l'esecuzione al gestore dell'EDR. In almeno un prodotto popolare che abbiamo testato, una chiamata a AddVectoredExceptionHandler fa sempre sì che il VEH venga aggiunto alla fine della lista, indipendentemente dal fatto che tu abbia detto a Windows di aggiungerlo all'inizio.

Manipolazione manuale della lista VEH

Poiché chiamare l'API AddVectoredExceptionHandler (che a sua volta chiama RtlAddVectoredExceptionHandler) non è un'opzione, possiamo semplicemente (per così dire) reimplementarla.

Come mostrato nel grafico precedente, la lista Vectored Exception Handler è memorizzata come una lista a doppio collegamento. Una lista a doppio collegamento è una struttura dati in cui ogni voce ha un puntatore alla voce successiva, un puntatore alla voce precedente e poi alcuni dati. In questo caso, i dati sono un'altra struct contenente informazioni per il Vectored Exception Handler.

Diagramma di una struttura di liste con doppio collegamento. Inizia con una testa di lista che punta al primo nodo, seguita da due nodi aggiuntivi. Ogni nodo contiene campi etichettati Flink e Blink; gli ultimi due nodi includono anche una sezione Dati. Le frecce indicano i collegamenti in avanti e indietro tra i nodi, e l'ultimo nodo rimanda alla Testa dell'elenco.

Fonte grafica: https://www.osronline.com/article.cfm%5Earticle=499.htm

Ogni singolo Vectored Exception Handlers si presenta in questo modo.

Frammento di codice che mostra la definizione di una struttura C denominata _VECTXCPT_CALLOUT_ENTRY. Include i campi: LIST_ENTRY ListEntry, PVOID ref, int reserved e PVECTORED_EXCEPTION_HANDLER VectoredHandler. Il typedef crea VECTXCPT_CALLOUT_ENTRY e un tipo di puntatore PVECTXCPT_CALLOUT_ENTRY.

L'elemento LIST_ENTRY contiene i nostri puntatori Flink/Blink, un contatore di riferimento, un valore riservato che in realtà non importa e, infine, un puntatore alla funzione che dovrebbe essere chiamata. Solo che questo puntatore non è in realtà un puntatore, ma un puntatore codificato. I puntatori possono essere codificati/decodificati utilizzando le funzioni API di Windows EncodePointer/DecodePointer .

Capire la lista di Vectored Exception Handler

Esistono due metodi per individuare l'elenco dei Vectored Exception Handler. Si basa sull'uso di euristiche come l'identificazione di una funzione che fa riferimento alla variabile LdrpVectorHandlerList e la lettura dei byte per trovare l'indirizzo. Il secondo metodo consiste nel registrare un nuovo Vectored Exception Handler e percorrere la lista doppiamente collegata finché non identifichiamo un puntatore alla sezione .data di NTDLL, che dovrebbe essere la testa della lista collegata. Quest'ultimo è il metodo documentato da rad9800 e quello che preferisco, in quanto non dobbiamo preoccuparci degli offset o dei modelli di byte che cambiano tra le versioni di Windows.

Mixture of Experts | 12 dicembre, episodio 85

Decoding AI: Weekly News Roundup

Unisciti al nostro gruppo di livello mondiale di ingegneri, ricercatori, leader di prodotto e molti altri mentre si fanno strada nell'enorme quantità di informazioni sull'AI per darti le ultime notizie e gli ultimi insight sull'argomento.

Inserimento di elementi nella lista del Vectored Exception Handler

Una volta identificata la testa dell'elenco dei gestori di eccezioni vettoriali, possiamo iniziare a manipolarla. Potremmo semplicemente dirottare l'elenco VEH puntando le voci Flink e Blink dell'elenco Head verso il nostro nuovo gestore di eccezioni, visualizzato di seguito. Questo farà sì che il nostro VEH sia l'unica voce nella lista.

Diagramma di un elenco collegato con una Testa elenco, tre nodi gestori legittimi e un nodo gestore maligno collegato.

Il pericolo di questo approccio è che, se viene sollevata un'eccezione che il tuo gestore delle eccezioni non può gestire, il tuo processo verrà terminato. I processi legittimi usano anche Vectored Exception Handler per rilevare errori che si aspettano vengano lanciati, quindi cortocircuitare la lista probabilmente non è l'approccio migliore. Invece, possiamo aggiornare correttamente l'elenco per inserire prima il nostro gestore di eccezioni.

Diagramma di una lista collegata con una Testa elenco, un nodo handler malevolo e tre nodi handler legittimi collegati in sequenza.

Con questo approccio, possiamo gestire gli errori che ci interessano e passare tutto il resto al gestore di eccezioni successivo.

Abuso dei Vectored Exception Handlers per l'iniezione del processo

Come abbiamo visto, implementare una nostra versione dell'API AddVectoredExceptionHandler non è poi così complicato. Ma, cosa ancora più importante, non ha richiesto alcuna interazione con il kernel, a parte la chiamata a NtProtectVirtualMemory per modificare le protezioni della memoria sulla sezione .mrdata di NTDLL. Poiché tutte le informazioni che il processo usa quando chiama i gestori di eccezioni vettoriali sono memorizzate all'interno del processo, questo rappresenta un ottimo obiettivo come tecnica di iniezione di processi senza thread.

Che cos'è la threadless process injection? Ceri Coburn ne ha parlato nel suo intervento del 2023 al Bsides Cymru, "Aghi senza filo". Stranamente, questo discorso è uscito poco prima che stavo per tenere un discorso a una conferenza interna di IBM per dimostrare la mia nuova tecnica di iniezione che non richiedeva una primitiva di esecuzione.

Riassumendo, le tradizionali tecniche di iniezione di processo richiedono un modo per:

  • Assegnare memoria nel processo remoto
  • Scrivi il tuo codice nella memoria allocata
  • Proteggere la memoria nel processo remoto in modo che sia eseguibile
  • Esegui il tuo codice nel processo remoto

Possiamo mescolare e abbinare queste primitive per ottenere tecniche diverse, e alcune tecniche non richiedono tutti i passaggi. Ad esempio, se si alloca memoria nel processo remoto come RWX, non sarà necessario modificare la protezione in un secondo momento. Oppure, se chiami NtMapViewOfSection allora la memoria viene allocata e scritta nel processo remoto nello stesso passaggio. Ma l'unica cosa che tutte le tecniche tradizionali di iniezione di processo richiedono è un primitivo per l'esecuzione. In genere si tratta di CreateRemoteThread/QueueUserAPC/SetThreadContext (o dei loro equivalenti nella funzione Nt). Di conseguenza, queste primitive di esecuzione vengono attentamente esaminate dai prodotti di sicurezza per eventuali usi dannosi. Chiamare una primitiva di esecuzione che abbia come target la memoria non supportata in un processo remoto è un ottimo modo per far intercettare il tuo beacon.

Allora, che ne dici di saltare del tutto la primitiva di esecuzione? Con i gestori di eccezioni vettoriali, funziona come segue:

  1. Identifica la lista VEH nel nostro processo locale, poiché l'indirizzo sarà lo stesso nel processo remoto.
  2. Assegna/Scrivi/Proteggi il nostro shellcode nel processo remoto con le primitive che preferisci.
  3. Assegna spazio per una nuova struttura Vectored Exception Handler nel processo remoto.
  4. Chiama EncodeRemotePointer per ottenere un puntatore codificato per l'indirizzo dove hai scritto il tuo shellcode.
  5. Assegni spazio per un puntatore e un int nel processo remoto (ne abbiamo bisogno per i due attributi riservati della voce VEH).
  6. Aggiorna la nuova voce VEH con gli attributi Flink/Blink validi, aggiorna il puntatore e aggiorna i due attributi riservati per puntare alla memoria allocata in precedenza.
  7. Controlla il bit IsUsingVEH nel blocco ambiente di processo remoto (PEB) e impostalo, se necessario.
  8. Imposta una trappola PAGE_GUARD su una regione di memoria che verrà eseguita dal processo.

L'ultimo passaggio è quello più importante, che ci permette di bypassare la necessità di una primitiva di esecuzione attivando un'eccezione nel processo remoto. Esistono diversi modi per farlo, ma la trappola PAGE_GUARD è, a mio parere, il modo migliore. Ho implementato tecniche di iniezione sia per processi nuovi che esistenti utilizzando trappole PAGE_GUARD.

Se stai generando un nuovo processo, puoi farlo apparire con stato in sospeso e piazzare una trappola al punto di ingresso. In genere, la creazione di un processo con stato in sospeso e la sua manipolazione la faranno etichettare per il comportamento di "process hollowing". Tuttavia, poiché non stiamo scrivendo su nessuna sezione .text o utilizzando primitive di esecuzione, non dovremmo essere colpiti da questo rilevamento. Ma come sempre, testa questo nel tuo laboratorio.

L'injection in un processo in esecuzione è un po' più complessa, ma ho scoperto che il modo più semplice è:

  1. Scegliere un thread nel processo.
  2. Sospendere il thread.
  3. Ottenere il contesto del thread.
  4. Imposta una trap PAGE_GUARD nel RIP del thread.
  5. Riprendi il thread.

Questa tecnica può essere un po' instabile se si esegue shellcode puro, poiché dirotta il thread, il che può far crashare il processo. Ho trovato più affidabile aggiungere uno shellcode di bootstrapping che implementi un vero Vectored Exception Handler che crea un nuovo thread per il tuo shellcode e poi restituisce l'esecuzione del codice al thread come al solito. Questa creazione di thread locale non sarà soggetta allo stesso controllo della creazione di un thread remoto.

L'ultima considerazione per entrambe le tecniche è che ogni volta che si verifica un errore nel processo, il tuo VEH verrà chiamato e il tuo shellcode verrà eseguito. Ciò può comportare la creazione di un intero gruppo di beacon in un unico processo, che alla fine si blocca. Ho trovato che la soluzione a questo problema è o il bootstrap shellcode menzionato sopra, che può controllare che l'eccezione sia una trappola PAGE_GUARD, oppure rimuovere il tuo Vectored Exception Handler dal beacon appena generato. Questo può essere fatto eseguendo un BOF per percorrere la lista VEH, identificare il tuo handler (un puntatore codificato verso la memoria non supportata) e rimuoverlo tramite manipolazione manuale, oppure semplicemente chiamando RemoveVectoredExceptionHandler su di esso.

Altri modi per attivare eccezioni remote

Credo che PAGE_GUARD trap siano il metodo migliore per generare eccezioni remote poiché è una chiamata NtProtectVirtualMemory molto semplice, la trap viene rimossa dopo la generazione dell'eccezione e non richiede una primitiva di scrittura o esecuzione. Tuttavia, ci sono altri modi per attivare un'eccezione remota, per varietà:

  • Per un processo appena nato e sospeso, imposta il bit BeingDebugged nel PEB su true. Quando il processo riprende, verrà chiamato il tuo gestore delle eccezioni. Dovrai usare uno stub di shellcode di CreateThread per evitare il blocco del loader.
  • Usa una primitiva di esecuzione che mira a un indirizzo non valido nel processo. Potrebbe non attivare il rilevamento e risposta degli endpoint (EDR) poiché la primitiva di esecuzione non ha effettivamente come obiettivo la memoria dannosa.
  • Imposta le protezioni di pagina non eseguibili in una sezione .text della procedura remota e annullala dopo l'attivazione della sua eccezione.
  • Scrivi alcune istruzioni non valide in una sezione .text del processo remoto.

Non penso che nessuna di queste sia una buona idea (tranne forse la prima, che ho testato con successo), ma il punto è che non è necessario utilizzare necessariamente una trappola PAGE_GUARD.

Nota per Windows Server 2012

Come sempre, Windows Server 2012 non si integra bene con le tecniche descritte sopra, ma non è poi così difficile farlo funzionare. In Windows Server 2012, nella struttura VEH manca una delle due voci riservate presenti in altre versioni di Windows. Inoltre, l'elenco VEH non si trova nel file .mrdata. ma nella sezione sezione .data.

Considerazioni sul rilevamento

La rilevazione della manipolazione del VEH può essere effettuata utilizzando le stesse tecniche descritte in questo post per accedere alla lista dei VEH. I prodotti di sicurezza che utilizzano i VEH sono tipicamente configurati per garantire che siano la prima voce presente. Se non è così, potrebbe essersi verificato qualcosa di malevolo. Tuttavia, questo può causare problemi se due prodotti sono in esecuzione in parallelo ed entrambi si aspettano di essere la prima voce dell'elenco.

NCC Group ha svolto un'eccellente ricerca sull'enumerazione dei gestori di eccezioni vettoriali in tutti i processi e sull'identificazione di tutti i gestori che puntano alla memoria unbacked. Come sempre, la memoria eseguibile unbacked è un indicatore abbastanza buono di un comportamento dannoso. Event Tracing for Windows Threat Intelligence (ETWTi) può essere utilizzato anche per identificare l'allocazione, la scrittura e la protezione di shellcode nella memoria unbacked. Allo stesso modo, gli eventi ETWTi per le scritture di memoria remota nella sezione .mrdata di un processo dovrebbero essere un indicatore di alto segnale/basso rumore.

Soluzioni correlate
IBM Guardium

Proteggi i dati più critici: scopri, monitora e proteggi le informazioni sensibili in diversi ambienti, automatizzando la conformità e riducendo i rischi.

Esplora IBM Guardium
Soluzioni di sicurezza aziendale

Trasforma il tuo programma di sicurezza con le soluzioni offerte dal più grande provider di sicurezza aziendale.

    Esplora le soluzioni per la sicurezza
    Servizi di cybersecurity

    Trasforma il tuo business e gestisci i rischi con la consulenza sulla cybersecurity, il cloud e i servizi di sicurezza gestiti.

    Scopri i servizi di cybersecurity
    Fasi successive

    Automatizza la protezione dei dati, il rilevamento delle minacce e la conformità per proteggere la tua azienda sia in ambienti cloud che on-premises.

    1. Esplora IBM Guardium
    2. Scopri le soluzioni di cybersecurity