
Esegui eseguibili Windows non gestiti nei Beacon di CobaltStrike
Inline-Execute-PE è una suite di file oggetto Beacon (BOF) e un corrispondente script Aggressor per CobaltStrike che consente agli Operatori di caricare eseguibili Windows non gestiti nella memoria del Beacon ed eseguirli, recuperando l'output e visualizzandolo nella console del Beacon.
Questo consente agli Operatori di utilizzare molti strumenti di terze parti (Mimikatz, Dsquery, strumenti Sysinternals, ecc.) senza doverli scrivere su disco, riformattarli in codice indipendente dalla posizione usando uno strumento come Donut, o creare un nuovo processo per eseguirli.
Questi eseguibili vengono mappati nella memoria del Beacon in modo da poter essere eseguiti ripetutamente senza doverli inviare sulla rete, allocare nuova memoria e creare un nuovo processo conhost.exe ogni volta.
Gli eseguibili caricati nei Beacon sono accessibili e possono essere eseguiti da tutti i client CobaltStrike connessi al Team Server di CobaltStrike.
Inline-Execute-PE è stato progettato per Beacon x64 ed eseguibili Windows C o C++ x64 compilati con Mingw o Visual Studio. Questo progetto non supporta eseguibili x86 o eseguibili x64 scritti in un linguaggio diverso o compilati con un compilatore diverso.

Clona il repository ed esegui opzionalmente make per ricompilare i BOF.
Carica Inline-Execute-PE.cna nel client CobaltStrike. Assicurati che la directory in cui è in esecuzione CobaltStrike sia scrivibile dal tuo utente; Inline-Execute-PE crea lì un file di testo (petable.txt) per garantire la disponibilità dei dati necessari al funzionamento di Inline-Execute-PE.
Inline-Execute-PE comprende 3 comandi orientati al target che eseguono BOF e 3 comandi interni che manipolano la struttura dati del progetto:
Orientati al target:
Struttura dati interna:
peload è l'inizio di Inline-Execute-PE. Questo comando viene utilizzato per caricare un PE nella memoria del Beacon. Esegue le seguenti azioni principali:
perun è il secondo passo di Inline-Execute-PE. Esegue le seguenti azioni principali:
peunload viene chiamato per rimuovere il PE dalla memoria del Beacon quando un Operatore ha finito o desidera caricare un PE diverso. Esegue le seguenti azioni principali:
petable viene utilizzato per visualizzare informazioni riguardanti tutti i PE attualmente caricati nei Beacon.
Ogni client CobaltStrike ha il proprio petable; Inline-Execute-PE fa di tutto per garantire la sincronizzazione dei suoi dati tra tutti i client CobaltStrike connessi in modo che i PE possano essere utilizzati da tutti gli Operatori. Per maggiori informazioni, vedi "Considerazioni progettuali e commento".

peconfig viene utilizzato per configurare le opzioni relative al funzionamento di Inline-Execute-PE. Le due opzioni attuali che possono essere modificate sono:
pebroadcast può essere utilizzato per trasmettere manualmente il contenuto del petable di un client a tutti gli altri client CobaltStrike connessi.
Ogni altro client CobaltStrike aggiornerà il proprio petable con i dati trasmessi. Non dovrebbe mai essere realmente necessario, ma la funzione esiste per sicurezza.
Usa peload per caricare un PE nella memoria del Beacon

In alternativa, se c'è un PE sulla macchina target che vuoi usare senza creare un nuovo processo, fornisci il percorso e lo switch --local

Chiama perun, passando eventuali argomenti al PE caricato

I doppi apici negli argomenti devono essere escaped con backslash

Se hai identificato che un PE causa problemi quando si tenta di liberare le DLL durante lo scaricamento, usa peconfig per impostare unloadlibraries su false

Una volta che hai finito di usare un PE, chiama peunload per pulirlo dal Beacon

Un PE diverso ora può essere caricato nel Beacon

Devi prestare attenzione agli argomenti della riga di comando che passi al PE; alcuni PE si bloccheranno immediatamente se forniti con argomenti errati, mentre altri verranno eseguiti all'infinito, causando la mancata ricontattazione del Beacon anche se il processo è ancora in esecuzione.
Ciò può essere visto con Mimikatz.exe quando 'exit' non viene specificato alla fine dell'elenco degli argomenti

...

Inline-Execute-PE terminerà il thread del PE in esecuzione dopo che è stato raggiunto il valore di timeout specificato. Ciò consente al Beacon di riprendere le comunicazioni normali (il Beacon non ricontatta fino al completamento del BOF perun). Mentre i normali comandi di CobaltStrike e altri BOF possono ancora essere utilizzati in questo Beacon, Inline-Execute-PE è ora disabilitato; quando un PE in esecuzione viene terminato in questo modo, sembra danneggiare stdout e stderr nel processo Beacon, e i PE caricati successivamente non funzionano correttamente.
Il PE può (e dovrebbe) essere comunque scaricato dalla memoria del Beacon, tuttavia guardando petable si vedrà che questo Beacon potrebbe non avere più PE aggiuntivi caricati al suo interno. 
È fondamentale testare i PE che si desidera eseguire utilizzando Inline-Execute-PE e prestare attenzione quando si forniscono argomenti della riga di comando a perun. Alcuni PE sono più tolleranti di altri.
Di seguito sono riportate, in nessun ordine particolare, alcune osservazioni fatte durante i test e lo sviluppo riguardo a determinati PE che gli utenti potrebbero voler caricare nel Beacon.
Gli IOC associati a Inline-Execute-PE includono, ma non sono limitati a:
Non ho sottoposto questo a un test completo contro un EDR durante lo sviluppo, in parte per pigrizia e in parte per mancanza di disponibilità di un ambiente di test. Tuttavia è stato testato contro l'ultima patch di Windows Defender (che nella mia esperienza è un buon prodotto AV).
Mimikatz.exe è probabilmente il PE più sospetto e conosciuto che viene in mente come candidato per l'uso con Inline-Execute-PE. Ho scoperto che la capacità di Windows Defender di rilevare Mimikatz in esecuzione usando Inline-Execute-PE dipendeva dal processo in cui era in esecuzione Beacon.
Un beacon in esecuzione in un eseguibile standalone (pensa a beacon.exe con artifact kit in modo che possa eseguire e funzionare normalmente oltre Defender) verrà rilevato quando si utilizza Mimikatz.exe con Inline-Execute-PE.
Un beacon in esecuzione in un processo Windows (iniettato in Explorer.exe, notepad.exe, ecc. o DLL sideloaded in un processo legittimo) NON verrà rilevato quando si utilizza Mimikatz.exe con Inline-Execute-PE.
Per quanto riguarda gli EDR che eseguono hooking a livello utente, come ho detto, non ho testato ma ho i seguenti pensieri generali:
Dato che il PE è in esecuzione all'interno del processo Beacon, che presumibilmente hai già disancorato/aggiornato NTDLL al suo interno, penso che non dovresti avere troppi problemi con le chiamate API effettuate dal PE che vengono segnalate. Gli stessi problemi riguardanti ciò che il PE effettivamente fa (tocca processi, modifica chiavi di registro, ecc.) si applicano ancora.
Un paio di mesi fa mi sono imbattuto in RunPE-In-Memory e ho pensato di provare a convertirlo in un BOF per CobaltStrike. Il viaggio che ne è seguito è stato molto più complesso e ha richiesto molto più tempo del previsto. Questo progetto è stato particolarmente impegnativo perché non è uno strumento autonomo di per sé, ma è uno strumento utilizzato per eseguire altri strumenti. Ciò richiede una grande flessibilità e sforzo verso la compatibilità con un'ampia gamma di PE e tutti i diversi modi in cui quei PE potrebbero svolgere lo stesso compito (ottenere argomenti, terminare, ecc.).
All'inizio, Inline-Execute-PE era stato immaginato come un BOF tutto-in-uno, responsabile del caricamento, dell'esecuzione e della liberazione di un PE in un Beacon. Circa 3 settimane dopo l'inizio del progetto, quando avevo un POC completato al ~75%, ho trovato Pezor che era stato rilasciato ~1,5 anni fa e faceva già quasi tutto ciò che stavo cercando di fare; la differenza principale era che Pezor chiamava Donut internamente per trasformare il PE in shellcode, piuttosto che mappare manualmente il PE originale in memoria.
Questa scoperta è stata gradita sotto un certo aspetto e deludente sotto un altro; era fenomenale avere un progetto maturo da cui trarre ispirazione e aiutarmi a superare alcuni punti critici nel mio codice, ma scoraggiante perché avevo effettivamente reinventato la ruota senza saperlo. Dopo aver letto su Pezor e aver pensato al suo design, ad alcuni aspetti di tradecraft e alle esigenze operative della mia organizzazione, ho modificato il corso di Inline-Execute-PE in quello che vedi oggi. Questa decisione è stata guidata da diversi fattori che verranno discussi di seguito, così come alcune delle scelte progettuali più curiose che potrebbero aver sollevato qualche sopracciglio per coloro che hanno letto fino a qui.
Esaminando la mia esperienza operativa, mi sono imbattuto in molteplici casi e strumenti in cui dovevo eseguire lo strumento ripetutamente; con Pezor, un Operatore deve inviare ripetutamente il PE sulla rete, creare un conhost.exe, allocare nuova memoria nel Beacon, ecc., il che mi è sembrato potenzialmente indesiderabile considerando AV/EDR. Questo modo di pensare ha portato all'idea di 'caricare' un PE nel Beacon, in modo simile a come puoi caricare un .PS1 nel Beacon per uso ripetuto. Il conhost.exe viene creato quando il PE viene caricato per la prima volta e persiste finché il PE è caricato in memoria; analogamente, la nuova memoria per il PE viene allocata una volta quando viene caricato per la prima volta, e ovviamente eviti di dover inviare il PE sulla rete ogni volta che vuoi usarlo. Il modello adottato da Inline-Execute-PE non è privo di difetti, che ho cercato di affrontare con vari gradi di successo.
Una scelta progettuale che dovrebbe saltare all'occhio è il fatto che Inline-Execute-PE mappa il PE nel Beacon DUE VOLTE. Ciò non è certamente desiderabile o una scelta che ho fatto volontariamente, ma è nata dalla necessità. Come accennato in precedenza, Inline-Execute-PE deve hookare diverse funzioni relative agli argomenti della riga di comando nel PE. Poiché il PE mappato viene eseguito all'interno del processo Beacon, il PE tenterà di utilizzare gli argomenti della riga di comando specificati nella sezione PROCESS_PARAMETERS del PEB; per aggirare questo problema, quando il PE chiama una delle varie funzioni che recuperano gli argomenti della riga di comando, dobbiamo indirizzare il PE verso le nostre funzioni personalizzate dove possiamo fornire gli argomenti desiderati così come passati da CobaltStrike usando perun.
Questo funziona bene, ma durante lo sviluppo ho notato qualcosa di strano con diversi PE. La prima volta che il PE veniva eseguito, la funzione personalizzata che abbiamo fornito all'IAT del PE veniva chiamata correttamente, tuttavia in tutte le volte successive in cui il PE veniva eseguito e venivano forniti argomenti diversi, il PE non chiamava la funzione personalizzata e quindi non riceveva gli argomenti passati da CobaltStrike. Non sono sicuro di cosa stia effettivamente accadendo sotto il cofano, ma sono portato a credere che dopo che il PE è stato eseguito una volta, copia gli argomenti della riga di comando da qualche parte in memoria, e nelle esecuzioni successive cerca quella posizione in memoria prima di chiamare le funzioni hookate per recuperare gli argomenti della riga di comando come ha fatto la prima volta. Ho corroborato questa teoria recuperando la posizione in memoria dove risiedeva un puntatore a un altro puntatore all'array di puntatori contenente gli argomenti, e modificando manualmente questa posizione in memoria per contenere il puntatore corretto ad ogni esecuzione. Questo ha funzionato per le funzioni __getmainargs e __wgetmainargs, ma altri PE chiamano funzioni alternative come __p___argv e __p___argc per le quali questo metodo non ha funzionato.
Per poter "ripristinare" il PE in uno stato in cui avrebbe effettivamente chiamato le funzioni hookate per recuperare gli argomenti, ho dovuto creare una seconda copia del PE in memoria durante peload. Anche questa copia è cifrata XOR e rimane con protezioni RX per l'intera durata del ciclo di vita di Inline-Execute-PE, semplicemente utilizzata per sovrascrivere la copia del PE che viene effettivamente eseguita usando perun. Come accennato, non è una soluzione perfetta, ma è una soluzione generica che copre tutti i PE senza bisogno di perdersi tra le erbacce cercando di trovare una soluzione per tutti i diversi PE là fuori e le diverse API che utilizzano.
Con uno dei principali punti di forza di Inline-Execute-PE che è la possibilità di eseguire strumenti senza creare nuovi processi, è un duro colpo dover... creare un nuovo processo (conhost.exe) per farlo. Questo requisito deriva dal fatto che i flussi standard (stdin/stdout/stderr) non vengono inizializzati nei programmi Windows a meno che non sia presente una console. Nel nostro caso non abbiamo affatto bisogno della console; i flussi standard vengono reindirizzati a una pipe anonima e catturati in questo modo, ma senza il conhost i flussi non vengono inizializzati e non possono essere reindirizzati.
Inline-Execute-PE affronta il problema del conhost nello stesso modo di Pezor, chiama AllocConsole e immediatamente dopo la nasconde alla vista usando ShowWindow. Su una VM Windows 11 con 8 GB di RAM non vedo mai la finestra della console lampeggiare e poi scomparire, ma il risultato può variare a seconda del sistema target.
Ho parlato con uno sviluppatore che lavora su un C2 commerciale molto avanzato che ha recentemente rilasciato un equivalente nativo (ok, una versione molto più avanzata) di Inline-Execute-PE e mi ha detto che sono riusciti a evitare di generare un conhost.exe "ingannando Windows facendogli credere di avere una console". Con questo indizio ho passato circa una settimana a setacciare Internet per documentazione su come i programmi Windows interagiscono con conhost, cercando di tracciare le chiamate API associate alle funzioni di scrittura e alla console in WinDBG, e persino esaminando il codice sorgente di Windows Terminal che sorprendentemente è disponibile su Github. Anche se ho imparato molto sul PEB e sulle cose relative ai flussi standard, sono uscito dall'altra parte di questo a mani vuote. Sospetto che la strada da percorrere potrebbe coinvolgere il patching di alcune funzioni relative alla console in kernel32, ma non lo so. Onestamente sono piuttosto deluso di non essere riuscito a trovare una soluzione qui, ma essendo autodidatta e con solo pochi anni di carriera alle spalle, è probabilmente prevedibile.### Timeout PE e Recupero
Tutti coloro che hanno mai provato a scrivere un BOF sanno che, nonostante tutti i vantaggi che ne derivano, un grande pericolo risiede nel fatto che un errore o un crash nel tuo BOF può e ucciderà il tuo Beacon. Il pericolo è amplificato in questo progetto dalla natura di quanto controllo gli utenti abbiano sui dati passati a Inline-Execute-PE e da quante poche misure di sicurezza possano essere facilmente o in modo affidabile messe in atto da me, lo sviluppatore. Gli utenti potrebbero, ad esempio, far crashare il loro Beacon caricando un PE x86 in un Beacon x64, o, molto più comunemente, passando argomenti errati al PE mappato, come ho accennato in precedenza. Sebbene non possa impedire agli utenti di far crashare i loro Beacon con argomenti errati per i loro PE, posso provare a recuperare il loro Beacon nel caso di un PE in esecuzione infinita, come nel caso di Mimikatz quando 'exit' non è specificato.
Idealmente sarei in grado di fermare l'esecuzione del PE, consentendo al Beacon di riprendere le normali funzioni, e poi immediatamente lasciare che l'utente riprovi con gli argomenti (si spera) corretti questa volta. In pratica, ho scoperto che terminare il PE sembra rompere i FILE* associati a stdout/stderr, e anche scaricare completamente il PE e poi ricaricarlo da capo non risolve il problema; sono rotti a livello di processo.
Per terminare un PE che continua a funzionare oltre l'opzione 'timeout', viene chiamato TerminateThread sull'handle restituito da CreateThread. Questo non consente al thread di uscire in modo pulito da nulla, quindi ha senso che alcune cose possano rompersi. Ho provato a mitigare questo implementando thread hijacking, con l'obiettivo di sospendere il thread del PE e reindirizzare la sua esecuzione all'API ExitThread(). La speranza era che, se fosse stato il thread ad avviare le procedure di uscita (al contrario di essere terminato forzatamente dall'esterno), ciò potesse consentire a stdout/stderr di continuare a funzionare, ma ho finito per avere lo stesso problema (oltre a sperimentare l'incapacità di sospendere il thread del PE nel caso di Mimikatz).
Non riuscendo a mitigare questo problema, ho scelto semplicemente di impedire agli utenti di continuare a eseguire il PE o di caricare ulteriori PE nel Beacon interessato (il che COMPORTEREBBE un crash). Questo è un altro caso in cui Inline-Execute-PE non raggiunge il livello che vorrei, ma mi sono accontentato del fatto che l'Operatore avrebbe almeno ancora il suo Beacon e potrebbe usarlo per le normali funzionalità.
Una parte impegnativa di questo progetto è stata garantire la disponibilità dei PE caricati nei Beacon a tutti i Client CobaltStrike connessi al Team Server. I dati di Inline-Execute-PE sono memorizzati in strutture create da Inline-Execute-PE.cna, che devono essere caricate in ogni Client che desidera utilizzare lo strumento; di conseguenza, queste strutture dati risiedono all'interno di ogni Client, non sul Team Server. Se questi dati risiedessero in un'unica posizione centrale (TS), sarebbe banale recuperarli da ogni Client e tutto ciò sarebbe un non problema; se il Team di CobaltStrike integrasse formalmente una funzionalità come Inline-Execute-PE in CobaltStrike, sono certo che questa sarebbe la direzione che prenderebbero. Ma essendo questo un add-on della comunità, ci accontentiamo di ciò che abbiamo.
Ci sono un paio di scenari diversi di cui preoccuparsi per garantire che ogni Client CobaltStrike abbia i dati più recenti e accurati riguardanti i PE caricati nei Beacon:
È stato adottato un approccio multi-prong per affrontare questi scenari. Per gestire il caso in cui un solo Client CobaltStrike è connesso al TS (ed è quindi l'unica entità che possiede i dati della petable), ogni volta che il Client altera la petable (peload, peconfig, peunload, ecc.), scrive anche il contenuto della sua petable in un file di testo locale situato nella directory di CobaltStrike. Se il Client esce/riavvia, o quando Inline-Execute-PE.cna viene ricaricato, tenterà prima di leggere dal file petable.txt locale per popolare la sua petable in memoria.
Quando più Client sono connessi a un TS e un nuovo Client si unisce (come da Event Log), ogni Client recupera un elenco di tutti gli utenti connessi al TS e lo ordina alfabeticamente. Il Client che è il primo in quell'elenco viene selezionato come Client "Broadcast" e, dopo aver aspettato 5 secondi (per consentire al nuovo Client di inizializzarsi e leggere il suo petable.txt locale), invierà messaggi (Actions) nell'Event Log per ogni voce nella sua petable. Tutti i client (eccetto quello che fa il broadcast) leggeranno questi messaggi e aggiorneranno le loro petable con le informazioni trasmesse; questo include l'aggiornamento delle voci esistenti e l'aggiunta di eventuali voci aggiuntive che le loro rispettive petable non contengono.
Le operazioni normali che coinvolgono Inline-Execute-PE si basano anche sull'invio di messaggi nell'Event Log. Quando il Client A esegue peload, viene trasmesso un messaggio contenente tutte le informazioni pertinenti della petable; TUTTI i client aggiornano le loro rispettive petable analizzando questi messaggi dell'Event Log trasmessi usando l'hook "on Event_Action". Vengono apportate modifiche anche ai dati di Inline-Execute-PE quando peload e peunload terminano l'esecuzione dei loro BOF; queste modifiche vengono comunicate da Beacon (ad esempio, dopo aver eseguito peload, Beacon richiama con la posizione di memoria della struct pMemAddrs) e come tali sono visibili a tutti i Client connessi, che aggiornano le loro rispettive petable usando l'hook "on Beacon_Output".
Questi sforzi separati combinati consentono a Inline-Execute-PE di sincronizzare in modo efficiente e affidabile i dati critici tra più Client.
Questo progetto non sarebbe stato possibile senza i seguenti progetti e risorse che sono stati ampiamente consultati e da cui hanno avuto origine parti fondamentali di questo progetto. Un grande ringraziamento agli autori per il loro codice e la loro visione.