
API Python per l'uso con la specifica External C2 di Cobalt Strike.
Framework Python per costruire e utilizzare interfacce per trasferire dati tra framework, con un focus specifico sul fungere da estensione per framework di Command and Control.
Attualmente, questo è pensato solo come un'implementazione della specifica External C2 di Cobalt Strike come descritto in questo documento di specifica, ma è soggetto a modifiche man mano che il progetto matura.
Un immenso ringraziamento va a xychix. Questo progetto non sarebbe stato affatto possibile senza i loro preziosi contributi. In pratica, questo progetto è una ricostruzione ed estensione del progetto External C2 di Outflank
Questo progetto è composto dalle seguenti parti principali:
Il builder legge un file di configurazione e usa le opzioni configurate per generare una build sostituendo i marker all'interno degli skeleton.
Il builder può essere usato con build_files.py. Una configurazione di esempio del builder è fornita come sample_builder_config.config.sample.
Gli skeleton sono i diversi "scheletri" di codice che il builder popola dinamicamente per generare una build completamente utilizzabile. Gli skeleton contengono marker che verranno sostituiti con valori utilizzabili dal builder. Ci sono tre diversi 'tipi' di skeleton:
Un marker può essere inserito in qualsiasi file all'interno di uno skeleton e verrà sostituito con un valore specificato nella configurazione del builder. Come buona pratica, i marker non dovrebbero mai essere usati per scrivere direttamente variabili, ma dovrebbero essere usati solo per impostare valori. Se il valore di un marker deve essere riutilizzato, si dovrebbe optare per memorizzare il valore in una variabile e fare riferimento ad essa, invece di riutilizzare lo stesso marker.
Il formato del marker è: ```[var:::identifier_for_the_marker]```
Le stringhe verranno scritte in uno skeleton direttamente racchiuse tra apici singoli, mentre i numeri verranno scritti così come sono.
Nel caso in cui una stringa nella configurazione sia racchiusa tra doppi apici, la stringa verrà scritta direttamente nel file racchiusa tra doppi apici, e gli apici singoli esterni verranno rimossi.
Questa relazione può essere dimostrata come segue:
#################
# Skeleton Code #
#################
# Skeleton contains the following line of code:
foo = ```[var:::bar]```
##############
# End Result #
##############
# Stored in config as:
# foo = bar
# Written as:
foo = 'bar'
# Stored in config as:
# foo = "bar"
# Written as:
foo = "bar"
# Stored in config as:
# foo = 2
# Written as:
foo = 2
# Stored in config as:
# foo = "2"
# Written as:
foo = "2"
I framework sono l'applicazione di base che determina quali dati vengono usati da transport ed encoder e come questi dati vengono usati. Ciò che un determinato framework fa effettivamente non ha molta importanza, purché esista una logica per importare e usare encoder e transport. La maggior parte delle parti essenziali di un framework (principalmente la logica del client) verrà salvata come skeleton, con un'interfaccia per interagire con la parte server salvata come oggetto framework di base.
Generalmente, un framework contiene un server e un client e usa encoder e transport per inoltrare dati tra loro.
Ci sono alcuni aspetti fondamentali da considerare quando si costruisce un framework:
framework è responsabile di garantire che l'encoder sia reso disponibile al transport per essere utilizzato.framework usa una relazione client-server, dovrebbero essere organizzati di conseguenza in modo appropriato.client di un framework, quindi se vuoi che le cose siano riconfigurabili sul client, questo deve essere in grado di farlo durante l'esecuzione senza alcuna interazione diretta.skeleton del server perché l'utente finale interagirà direttamente con il server di un framework. Invece, scegli sia di leggere le opzioni da una configurazione, sia di dare all'utente finale la possibilità di modificare le opzioni (come un timer di blocco o la verbosità) durante l'esecuzione.skeleton di un framework verrà elaborato dal builder, iterando attraverso ogni file al suo interno, quindi se un determinato argomento deve essere configurabile al momento della build, può essere fatto facilmente.server di un framework dovrebbe poter essere interfacciato da un comune framework_manager.Il server è l'applicazione che fa da intermediario nella comunicazione tra il client e il c2 server. La logica del server è principalmente statica. La logica del server per il framework cobalt_strike, indicato come third-party Client Controller nella specifica, è mostrata di seguito:
encodertransporttransportencodertransporttransportencoderUn server dovrebbe supportare la capacità di gestire più client (non ancora implementata) ed essere interfacciato da un framework_manager.
Il client è essenzialmente il payload che viene eseguito sull'endpoint. La logica del client per il framework cobalt_strike è principalmente statica ed è mostrata di seguito:
transporttransporttransport per nuovi tasktransportIl client utilizza l'encoder e il transport specificati per inoltrare dati tra sé e il rispettivo server.
Gli encoder ricevono dati e poi li modificano per prepararli all'invio tramite il transport, oppure decodificano i dati ricevuti tramite il transport riportandoli alla loro forma grezza per essere interpretati da qualsiasi componente del framework li stia utilizzando.
Gli encoder dovrebbero aspettarsi di essere interfacciati direttamente dal transport e gestire i dati in modo indipendente da framework e componente.
I transport hanno il ruolo di inviare e ricevere dati attraverso un canale di comunicazione e di interfacciarsi con l'encoder per garantire che i dati vengano trasformati nel formato necessario. I transport dovrebbero aspettarsi di ricevere dati da un componente del framework o tramite il canale di comunicazione e avere la capacità di inoltrare dati attraverso il canale di comunicazione. I transport sono responsabili di chiamare l'encoder per codificare o decodificare i dati quando necessario.
I transport dovrebbero aspettarsi di essere interfacciati direttamente dal componente framework e gestire i dati in modo indipendente da framework e componente.
Per prima cosa, determina quale modulo di trasporto e di codifica vuoi usare. Per il seguente esempio useremo transport_imgur e encoder_lsbjpg.
Successivamente, crea un builder_config.config in base alle tue esigenze; fai riferimento alla configurazione di esempio e al template forniti per capire come fare.
Genera una build con build_files.py. Ad esempio, si può generare una build nella directory builds usando encoder_lsbjpg e transport_imgur per Cobalt Strike, in modalità verbosa, con il seguente comando:
python build_files.py -b builds -f cobalt_strike -c sample_builder_config.config.sample -e encoder_lsbjpg -t transport_imgur -v
Sulla macchina che esegue il server, esegui:
python server.py
Per un output più verboso, puoi eseguire:
python server.py -v
Per un output più verboso e output aggiuntivi utili per il debug, puoi eseguire:
python server.py -d
Successivamente, esegui il client sull'endpoint designato.
Se tutto ha funzionato, un nuovo beacon verrà registrato nella console di Cobalt Strike con cui potrai interagire.
Perché hai scritto questo?: Non c'erano molte implementazioni rilasciate della specifica di Cobalt Strike e, tra quelle rilasciate, o non sono in un linguaggio che conosco o non hanno la modularità e l'astrazione che stavo cercando.
Perché Python 2?: Sono pigro ed è facile implementare nuovi canali di trasporto e codifica.
Il tuo codice fa schifo: Questa non è una domanda.
Posso inviare nuovi moduli di trasporto e/o encoder?: Sì, per favore! Invia una pull request e sarò felice di esaminarla.
Come compilo il client in un eseguibile che posso distribuire?:
L'ho testato con successo su Kali; per ricreare il mio ambiente, assicurati di avere veil-evasion installato e di aver completato la sua configurazione. Dovrebbe aver impostato un ambiente wine con Python installato che ha tutte le dipendenze di cui hai bisogno. Potresti dover installare anche il modulo pefile in questo ambiente.
Poi puoi andare nella directory del client per cui vuoi generare un eseguibile ed eseguire:
chmod +x compile_dll.sh
./compile_dll.sh
wine "C:\\Python27\\python.exe" /usr/share/veil/pyinstaller/pyinstaller.py -F -r c2file.dll -w --key "ayyyyyyylmao" client.py
Sostituisci il valore di key con quello che vuoi. Dovresti vedere l'eseguibile del client nella directory dist/. Se vuoi generare un eseguibile che fornisce una console da usare per il debug, compila l'eseguibile con wine "C:\\Python27\\python.exe" /usr/share/veil/pyinstaller/pyinstaller.py -F -r c2file.dll -c client.py