Definizione del caricatore riflettente di scintille di cobalto

Un uomo con una felpa nera con cappuccio che usa un laptop in strada, raffigurante un hacker

Sebbene le componenti di AI e machine learning di nuova generazione delle soluzioni di sicurezza continuino a migliorare le capacità di rilevamento basate sul comportamento, alla base molti si affidano ancora al rilevamento basato sulle firme. Cobalt Strike, un popolare framework di comando e controllo (C2) utilizzato sia dagli attori delle minacce che dai red team sin dal suo debutto, continua ad essere pesantemente firmato dalle soluzioni di sicurezza.

Per continuare l'uso operativo di Cobalt Strike in passato, noi del team IBM X-Force Red Adversary Simulation abbiamo investito importanti sforzi di ricerca e sviluppo per personalizzare Cobalt Strike con strumenti interni. Alcuni dei nostri strumenti interni specifici per Cobalt Strike hanno versioni pubbliche, come "InlineExecute-Assembly", "CredBandit", and "BokuLoader". Negli ultimi due anni, data la firma eccessiva di Cobalt Strike, ne abbiamo limitato l'uso alla simulazione di attori di minacce meno sofisticati e abbiamo invece sfruttato altri C2 di terze parti e interni quando eseguiamo esercitazioni di red team più avanzate.

Grazie agli sforzi di ricerca e sviluppo, abbiamo riscontrato un maggiore successo operativo nelle esercitazioni avanzate di red team con:

  • Attrezzi interni personalizzati.
  • Caricatori interni personalizzati.
  • Framework C2 interno personalizzato.
  • Continua ad investire nell'espansione delle funzionalità e della segretezza dei framework C2 alternativi di terze parti.

Tuttavia, sono ancora molti gli attori delle minacce che sfruttano copie pirata di Cobalt Strike, e resta importante essere in grado di simularli. Per i red team disposti a impegnarsi in ricerca e sviluppo, è possibile che riescano comunque a ottenere successo operativo con Cobalt Strike simulando questi avversari. Inoltre, Cobalt Strike è un ottimo strumento di apprendimento, che può essere utilizzato dai nuovi arrivati per acquisire esperienza pratica con un framework C2 attraverso corsi di formazione per red team.

Mentre continuiamo ad espandere le nostre capacità C2, condividiamo alcuni insight su come abbiamo costruito il framework Cobalt Strike in passato, in particolare sviluppando caricatori riflettenti personalizzati. È inoltre pensato per aiutare i difensori a comprendere il funzionamento di Cobalt Strike, al fine di creare rilevamenti più affidabili.

Le ultime notizie nel campo della tecnologia, supportate dalle analisi degli esperti

Resta al passo con le tendenze più importanti e interessanti del settore relative ad AI, automazione, dati e oltre con la newsletter Think. Leggi l' Informativa sulla privacy IBM.

Grazie per aver effettuato l'iscrizione!

L'abbonamento sarà fornito in lingua inglese. Troverai un link per annullare l'iscrizione in tutte le newsletter. Puoi gestire i tuoi abbonamenti o annullarli qui. Per ulteriori informazioni, consulta l'Informativa sulla privacy IBM.

Basandosi sul framework con caricatori riflettenti

Questo post sul blog è il primo di una serie che funge da introduzione e tratta le basi dello sviluppo di un caricatore riflettente Cobalt Strike. Man mano che procederemo con questa serie, costruiremo su queste basi e faremo riferimento a questo post.

Entro la fine di questa serie, puntiamo a creare un caricatore riflettente che si integri con le caratteristiche di evasione esistenti di Cobalt Strike e le migliora persino con tecniche avanzate attualmente non presenti nello strumento. Nei prossimi post approfondiremo lo sviluppo di caratteristiche specifiche di evasione e come implementarle nel nostro caricatore riflettente Cobalt Strike.

Per iniziare, questo post tratterà:

  • Problemi con il caricamento di un impianto C2 dal disco con il caricatore DDL di Windows.
  • I concetti e le meccaniche del processo di caricamento riflettente di Cobalt Strike.
  • I requisiti di progettazione necessari per costruire un caricatore riflettente efficace.
  • Le fasi coinvolte nel processo di caricamento riflettente.

Mentre esploriamo il caricamento riflettente di Cobalt Strike attraverso la lente di uno sviluppatore di strumenti di sicurezza offensiva, metteremo in evidenza le opportunità di rilevamento ed elusione. Alcuni aspetti dello sviluppo saranno omessi o semplificati e ti invitiamo a colmare le lacune eseguendo il debug dei progetti di caricamento riflettente, ricostruendoli da zero o cercando formazione.

Caricamento della DLL del beacon

L'impianto Cobalt Strike C2, noto come Beacon, è una libreria Windows Dynamic-Link (DLL), e la funzionalità di utilizzare il nostro caricatore DLL in Cobalt Strike è nota come User-Defined Reflective Loader (UDRL).

Il caricatore DDL di Windows integrato

Tipicamente, il caricatore DDL di Windows integrato è responsabile del caricamento delle DLL nello spazio di memoria virtuale di un processo. Il caricatore DDL di Windows esiste principalmente nello spazio utente, anche se attraversa lo spazio kernel quando si mappano le DLL dal disco.

L'uso del caricatore DDL di Windows presenta alcuni inconvenienti quando viene utilizzato durante le simulazioni degli avversari:

  • La DLL non elaborata deve essere presente nel file system.
  • La DLL non elaborata deve essere priva di offuscamento.
  • Gli eventi di caricamento delle immagini del kernel sono attivati dal caricatore DDL di Windows.

Pertanto, utilizzare il caricatore DDL di Windows per caricare la nostra DLL beacon non è una soluzione ideale. Per superare queste sfide, carichiamo la DLL del beacon dalla memoria con un caricatore riflettente.

I tre principali punti di rilevamento evitati dal carico riflettente sono:

  1. Evitare malware con firma sul file system.
  2. Evitare eventi di caricamento delle immagini del kernel, che possono essere monitorati da soluzioni di sicurezza.
  3. Evitare la nostra DLL di impianto C2 elencata nel Process Environment Block (PEB).

Caricatore riflettente vs caricatore DLL di Windows

Il caricamento riflettente può essere concepito come il semplice caricamento di una DLL raw direttamente dalla memoria, anziché dal file system.

Il caricamento riflettente e il caricatore DDL di Windows integrato servono entrambi allo stesso scopo: caricare una DLL dal formato file grezzo nello spazio di memoria virtuale di un processo. Tuttavia, il caricamento riflettente ha un vantaggio fondamentale rispetto al caricatore DDL di Windows: non richiede che il file DLL esista nel file system. Questo caricamento in memoria consente un numero illimitato di fasi di caricamento della catena, in quanto la DLL dell'impianto C2 può essere nascosta all'interno di strati di crittografia e codifica nella memoria del processo.

Formato file raw vs formato indirizzo virtuale

Un concetto chiave da comprendere quando si carica una DLL è sapere che la DLL verrà formattata in modo diverso su disco rispetto alla memoria. Le principali differenze tra la DLL in formato file raw e quella in formato indirizzo virtuale sono:

Formato file raw:

  • Il formato della DLL così come esiste su un file system.
  • Le sezioni della DLL sono strettamente compresse tra loro.
  • Gli offset si basano sull'inizio del file DLL raw così come esisterebbe sul disco.
  • Questo formato occupa meno spazio in memoria.

Formato dell'indirizzo virtuale:

  • Formato della DLL così come esiste nello spazio di memoria virtuale di un processo.
  • Le sezioni sono distanziate.
  • Gli offset sono indirizzi virtuali relativi (RVA).
  • Quando viene eseguito in un processo, la DLL e altri moduli determinano le posizioni tramite RVA.
  • Questo formato occupa più spazio di memoria.

Beacon grezzo vs beacon virtuale

Esaminando una DLL beacon HTTP nello strumento  PE-Bear di Aleksandra Doniec, vediamo le differenze tra l'indirizzamento raw e quello virtuale per ciascuna sezione della DLL:

Tabella che elenca gli indirizzi grezzi e virtuali di ogni sezione della DLL beacon.

Tabella che elenca gli indirizzi grezzi e virtuali di ogni sezione della DLL beacon.

Questa DLL beacon HTTP/S è 0x52000  byte (327KB ) in dimensione quando viene caricata nello spazio di memoria virtuale di un processo, rispetto a 0x44000  byte (272KB ) nella dimensione esistente nel file system. Questa differenza di dimensioni è dovuta al fatto che le sezioni sono distanziate nel formato dell'indirizzo virtuale, mentre nel formato del file raw sono strettamente unite.

PE-Bear fornisce una rappresentazione visiva della nostra DLL beacon così come esiste nel formato file raw rispetto al formato dello spazio di indirizzo virtuale:

Rappresentazione visiva della DLL beacon in formato raw rispetto al formato virtuale

Rappresentazione visiva della DDL beacon in formato raw (sinistra) rispetto al formato virtuale (destra)

Caricamento del beacon con il caricatore DLL di Windows

Sebbene non sia la mossa più saggia da compiere durante una simulazione avversaria, trasferire su disco una DLL beacon non elaborata, senza offuscamento, e caricarla con un caricatore DDL di Windows è un ottimo modo per sfatare i miti del caricamento del beacon e di quello della DLL. In sostanza, il beacon è solo una DLL. Il caricatore DDL di Windows e un caricatore riflettente caricano semplicemente una DLL in un processo.

Per caricare la DLL beacon con il caricatore DDL di Windows, eseguiamo i seguenti passaggi:

  1. Genera una DLL beacon non elaborata senza offuscamento.
  2. Crea un programma che:
    1. Utilizza l'API LoadLibrary per caricare il nostro DLL beacon dal disco.
    2. Esegue il nostro beacon chiamando il punto di ingresso della DLL beacon virtuale.
  3. Mettiamo il nostro programma eseguibile e la nostra DLL beacon nella stessa cartella.
  4. Eseguiamo il nostro programma.

Generazione di una DLL beacon raw priva di offuscamento

Per prima cosa, disabilitiamo tutte le opzioni di Malleable PE che rendono la nostra DLL beacon non caricabile dalla DLL di Windows. Per fare ciò, modifichiamo il nostro profilo Malleable C2 e disabilitiamo le opzioni di evasione di Malleable PE situate nel blocco di stage:

Blocco di stage del profilo Malleable C2 modificato per disattivare le funzioni di evasione di Cobalt Strike.

Blocco di stage del profilo Malleable C2 modificato per disattivare le funzioni di evasione di Cobalt Strike.

Dopo aver modificato il profilo, riavviamo il server Cobalt Strike Team, fornendo il nostro no_evasion.profile  profilo come argomento.

Schermata acquisita per il post sul blog

Ci colleghiamo al Team Server con il client Cobalt Strike. Quindi creiamo unWindows Stageless Payload  con l'opzione di output impostata su Raw  e un listener impostato su https . Salviamo il payload come beacon.dll .

Schermata della creazione di una DLL beacon "raw stageless" dal client Cobalt Strike

Schermata della creazione di una DLL beacon "raw stageless" dal client Cobalt Strike

Creazione del nostro programma caricatore DLL beacon

Utilizzando il codice sottostante, creiamo un programma C chiamato loadBeaconDLL.c  e lo compiliamo:

Codice C di Windows per caricare la DLL beacon dal disco utilizzando il caricatore DLL di Windows.

Codice C di Windows per caricare la DLL beacon dal disco utilizzando il caricatore DLL di Windows.

Utilizziamo l'API Kernel32.LoadLibraryA  per caricare la nostra DLL beacon non elaborata dal disco. Questa API chiamerà il caricatore DLL di Windows integrato, che caricherà la nostra DLL beacon dal disco nello spazio di memoria virtuale del processo host.

Come parte integrante del processo di caricamento, il caricatore DLL di Windows inizializzerà la nostra DLL beacon chiamando il suo punto di ingresso con DLL_PROCESS_ATTACH (1)  come argomento.

Dopo che il caricatore DLL di Windows avrà caricato e inizializzato la nostra DLL beacon nello spazio di memoria virtuale del processo, dovremo nuovamente chiamare il punto di ingresso della DLL beacon virtuale con l'argomento 0x4.

Il nostro programma deve conoscere il punto di ingresso della DLL beacon virtuale per poterla eseguire. Questa operazione può essere eseguita in modo dinamico all'interno del programma, analizzando le intestazioni della DLL beacon virtuale per trovare l'indirizzo virtuale relativo (RVA) del punto d'ingresso, oppure possiamo vedere rapidamente qual è e codificare il valore.

Per la nostra prova di concetto, scopriremo manualmente e codificheremo in modo rigido il punto di ingresso RVA della DLL beacon nel nostro programma. Utilizzando PE-Bear scopriamo che il punto di ingresso del RVA al beacon è 0x1D840 :

Schermata della ricerca del punto di ingresso DLL beacon RVA utilizzando PE-Bear

Schermata della ricerca del punto di ingresso DLL beacon RVA utilizzando PE-Bear

Le LoadLibraryA  L'API restituisce l'indirizzo di base della nostra DLL beacon virtuale. Semplicemente aggiungiamo questo al punto di ingresso RVA per determinare il punto di ingresso.

Con il codice pronto all'uso, compiliamo il nostro programma C in un eseguibile Windows:

Comando usato per compilare il nostro programma.

Comando usato per compilare il nostro programma.

Posizionamento del nostro programma e della DLL beacon sul file system

Inserendo la DLL beacon e il programma eseguibile di caricamento del beacon nella stessa directory, il caricatore DLL di Windows sarà in grado di rilevare la nostra DLL mentre esegue la sua routine di caricamento.

Mettiamo entrambi beacon.dll  e loadBeaconDLL.exe  nel file system all'interno della stessa directory:

La DLL beacon e il programma di caricamento si trovano nella stessa directory.

La DLL beacon e il programma di caricamento si trovano nella stessa directory.

Esecuzione del nostro programma

Dal desktop di Windows, facciamo doppio clic sul nostro loadBeaconDLL.exe per programmare e stabilire una connessione beacon attiva al nostro Team Server.

Connessione riuscita al C2 Team Server dal beacon DLL caricato tramite il caricatore DLL di Windows.

Connessione riuscita al C2 Team Server dal beacon DLL caricato tramite il caricatore DLL di Windows.

Caricamento riflettente Cobalt Strike

Cobalt Strike utilizza una versione modificata del progetto Caricamento riflettente di Stephen Fewer. Questo leggendario DLL loader in memoria ha più di dieci anni ed è stato utilizzato in Metasploit e in altri notevoli strumenti di sicurezza offensiva.

Considerazioni sull'uso dell'UDRL

Nel corso degli anni, il caricatore riflettente Cobalt Strike è stato migliorato per gestire tutte le caratteristiche di evasione del Malleable PE che Cobalt Strike ha da offrire. Il principale svantaggio dell'utilizzo di un UDRL (User-Defined Reflective Loader) personalizzato è che le caratteristiche di evasione del Malleable PE possono essere supportate o meno già di default.

Alcune caratteristiche di evasione sono completamente implementate quando si utilizza un UDRL, in quanto vengono inserite nella DLL beacon dal motore Malleable PE di Cobalt Strikes durante la creazione del payload del beacon. Tuttavia, attualmente caratteristiche come obfuscate devono essere gestite dall'UDRL, mentre altre come sleepmask e cleanup possono essere gestite tramite beacon con una corretta integrazione UDRL.

Metodi di caricamento riflettente

Metodo originale del caricatore riflettente

Il progetto Caricatore riflettente originale richiede la compilazione di ReflectiveLoader  nel progetto DLL e la sua esportazione all'interno della nostra DLL dell'impianto C2.

Poi un altro progetto è responsabile di:

  1. Scoprire l'indirizzo virtuale dell'esportazione di  ReflectiveLoader  .
  2. Esecuzione dell'esportazione di ReflectiveLoader  , che restituisce il punto di ingresso alla nostra DLL caricata.
  3. Chiamata del punto di ingresso della DLL caricata in modo riflettente.
Diagramma del caricatore riflettente originale, che carica una DLL nella memoria virtuale.

Diagramma del caricatore riflettente originale, che carica una DLL nella memoria virtuale.

Metodo di caricamento riflettente anteposto

Un metodo alternativo consiste nell'anteporre il caricatore riflettente alla DLL, consentendo di caricare qualsiasi DLL non gestita senza bisogno di compilare la DLL dal codice sorgente. Si tratta di un metodo di caricamento riflettente robusto, in grado di caricare qualsiasi file PE (EXE o DLL).

Diagramma di un caricatore riflettente anteposto a una DLL, che carica una DLL nella memoria virtuale.

Diagramma di un caricatore riflettente anteposto a una DLL, che carica una DLL nella memoria virtuale.

Metodo di caricamento riflettente di Cobalt Strike

L'implementazione del carico riflettente da Cobalt Strike utilizza un ibrido dei due metodi sopra menzionati. Questo metodo di caricamento riflettente potrebbe essere familiare a chi conosce come il Meterpreter di Metasploit effettua il carico riflettente.

Come il metodo originale del caricatore riflettente, la funzione ReflectiveLoader  viene compilata ed esportata all'interno della DLL beacon originale. Quando un operatore genera un payload beacon dal client Cobalt Strike, il motore Malleable PE di Cobalt Strike modifica la DLL beacon raw per informare il caricatore riflettente sulle opzioni Malleable PE da utilizzare. L'intestazione DoS (Denial-of-Service) del beacon viene aggiornata per chiamare l'esportazione ReflectiveLoader in un offset codificato. I byte iniziali aggiornati dell'intestazione DOS del beacon, che chiamano l'esportazione ReflectiveLoader  , verranno indicati in questo blog come "stub del caricatore riflettente della chiamata".

Quando un UDRL viene caricato in Cobalt Strike e un operatore genera un payload beacon dal client Cobalt Strike, il motore PE Malleable di Cobalt Strike aggiorna il shellcode del caricatore riflettente all'offset del file raw dell'esportazione ReflectiveLoader  .

Quando il motore Malleable PE completa l'applicazione delle patch alla DLL beacon raw, la DLL del beacon raw viene fornita all'operatore in un formato eseguibile simile a uno shellcode.

Schema del caricatore riflettente Cobalt Strike, caricamento della DLL beacon nella memoria virtuale.

Schema del caricatore riflettente Cobalt Strike, caricamento della DLL beacon nella memoria virtuale.

Stub del caricatore riflettente dei beacon

Osservando i byte iniziali nel disassemblatore PE-Bear possiamo vedere che la DLL beacon stessa è eseguibile:

Lo stub del caricatore riflettente della chiamata viene mostrato come codici di operazioni di assemblaggio eseguibili.

Lo stub del caricatore riflettente della chiamata viene mostrato come codici di operazioni di assemblaggio eseguibili.

I byte iniziali MZAR  sono personalizzabili tramite le opzioni Malleable PE nel profilo C2 di Cobalt Strikes. Questi byte devono essere eseguibili e non comportano alcuna operazione (nop ).

Dopo l'esecuzione di un'opzione anteposta nops  e dei magic bytes, lo stub del caricatore riflettente della chiamata:

  • Crea uno stack frame.
  • Utilizza l'indirizzamento relativo RIP per determinare l'indirizzo base della DLL beacon raw.
  • Chiama l'esportazione di ReflectiveLoader   all'offset del file raw 0x16E3C  conosciuto.
  • Chiama il punto di ingresso della DLL beacon caricata.

Confermiamo che l'offset del file raw per l'esportazione ReflectiveLoader  è 0x16E3C  esaminando la directory di esportazione delle DLL beacon:

Schermata dell'utilizzo di PE-Bear per determinare l'offset del file raw dell'esportazione ReflectiveLoader.

Schermata dell'utilizzo di PE-Bear per determinare l'offset del file raw dell'esportazione ReflectiveLoader.

Poiché esiste all'interno della directory di esportazione, l'indirizzo per l'esportazione di ReflectiveLoader l'esportazione è in formato RVA, riferendosi alla DLL beacon nel suo stato virtuale. Poiché l'esportazione di ReflectiveLoader  è eseguibile, sappiamo che esiste all'interno della sezione.text della DLL beacon.

Per scoprire l'offset del file raw dell'esportazione di ReflectiveLoader  , dobbiamo prima conoscere la differenza tra le sezioni .text   con indirizzo virtuale e indirizzo raw. Con la differenza nota, possiamo semplicemente sottrarla dall'RVA di esportazione ReflectiveLoader  , per scoprire l'offset del file raw dell'esportazione ReflectiveLoader  .

Gli indirizzi virtuali e grezzi per la sezione .text  sono elencati nelle intestazioni di sezione della DLL beacon:

Indirizzi grezzi e virtuali della sezione .text della DLL beacon.

Indirizzi grezzi e virtuali della sezione .text della DLL beacon.

La differenza tra i due è di 0xC00  byte. Sottraendo il RVA di esportazione ReflectiveLoader  di 0x17A3C  dalla differenza, scopriamo che l'offset del file raw è 0x16E3C .

Possiamo confermarlo in PE-Bear facendo clic con il tasto destro del mouse sulla funzione RVA di esportazione ReflectiveLoader  e su Follow RVA:17A3C . Il visualizzatore esadecimale nel widget sopra passerà alla visualizzazione dell'esportazione ReflectiveLoader  all'offset del file raw.

In sintesi, il flusso del processo di caricamento riflettente di Cobalt Strike è:

  • Un thread esegue la DLL beacon raw.
  • Lo stub del caricatore riflettente della chiamata chiama ReflectiveLoader  con un offset di file raw noto.
  • Il caricatore riflettente carica la DLL beacon raw nella memoria virtuale del processo host.
  • Dopo il caricamento, il caricatore riflettente restituisce il punto di ingresso della DLL beacon virtuale allo stub del caricatore riflettente della chiamata.
  • Lo stub del caricatore riflettente chiama il punto di ingresso della DLL beacon virtuale.
Diagramma che mostra le fasi principali di come Cobalt Strike esegue il caricamento riflettente della DLL beacon.

Diagramma che mostra le fasi principali di come Cobalt Strike esegue il caricamento riflettente della DLL beacon.

Requisiti di progettazione del caricatore riflettente

Codice indipendente dalla posizione

Poiché il nostro caricatore riflettente viene eseguito prima del caricamento della DLL beacon, il codice del caricatore riflettente deve essere puro shellcode.

Il modo più semplice per creare uno shellcode complesso è scriverlo in C senza dipendenze esterne. Quindi il file C viene compilato in un file oggetto. Tutto deve essere incluso nella sezione text del file oggetto. Infine, estraiamo la sezione.text per ottenere lo shellcode del caricatore riflettente.

Come Cobalt Strike inserisce la nostra UDRL

Il motore Malleable PE di Cobalt Strike si occuperà di ottenere il codice shellcode dal nostro file oggetto del caricatore riflettente e di inserirlo nella DLL beacon raw con l'offset del file raw dell'esportazione ReflectiveLoader  . Tutto ciò avviene nello script UDRL Aggressor come mostrato di seguito:

Script Aggressor per scrivere shellcode del caricatore riflettente nella DLL beacon raw utilizzando Cobalt Strike.

Script Aggressor per scrivere shellcode del caricatore riflettente nella DLL beacon raw utilizzando Cobalt Strike.

Il nostro script UDRL Aggressor fa in modo che Cobalt Strike scriva lo shellcode del caricatore riflettente eseguendo questi passaggi:

  • Apriamo un$handle al nostro file oggetto UDRL con ilopenf funzioni aziendali.
  • Con il file$handle leggiamo il flusso di byte e lo salviamo nella$data variabile array di byte.
  • Quindi chiudiamo il file$handle con ilclosef funzioni aziendali.
  • La funzione
    <a href="https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics_aggressor-scripts/as-resources_functions.htm#extract_reflective_loader">extract_reflective_loader</a>
    
    Cobalt Strike Aggressor integrata analizza il nostro file oggetto UDRL dall'$data array di byte, individua la.text sezione dal nostro file oggetto UDRL, estrae la sezione .text e la salva in$loader variabile array di byte.
  • La funzione
    <a href="https://hstechdocs.helpsystems.com/manuals/cobaltstrike/current/userguide/content/topics_aggressor-scripts/as-resources_functions.htm#setup_reflective_loader">setup_reflective_loader</a>
    
    La funzione Cobalt Strike Aggressor utilizzerà il motore Malleable PE per scoprire l'offset del file raw della nostra esportazioneReflectiveLoader e aggiornare lo shellcode UDRL da$loader variabile array di byte.
  • Infine, restituiamo la DLL beacon modificata a Cobalt Strike e salviamo il nostro file dal client.

Fasi di caricamento riflettente

Cobalt Strike ha fatto il lavoro per noi riguardo all'estrazione della sezione .text sezione dal nostro file oggetto del caricatore riflettente, aggiornando lo shellcode del caricatore riflettente e chiamandolo con lo stub del caricatore riflettente situato nell'intestazione DLL beacon.

Ecco le fasi che dobbiamo sviluppare per caricare il beacon in modo riflettente:

  1. Trova la DLL beacon raw
  2. Analizza le intestazioni della DLL beacon
  3. Assegna la memoria per la DLL beacon virtuale
  4. Carica le sezioni nello spazio di memoria virtuale
  5. Carica le dipendenze della DLL
  6. Risolvi la tabella degli indirizzi di importazione
  7. Risolvi i trasferimenti
  8. Esegui il beacon

Fase 1: ricerca dell'indirizzo base della DLL beacon non elaborata

Ci sono diversi metodi che possiamo usare per scoprire l'indirizzo della DLL beacon raw in memoria. Alcuni metodi sono:

  • Ricerca a ritroso delle intestazioni MZ e PE
  • Ricerca a ritroso di un uovo
  • Ottieni l'indirizzo di base DLL del beacon raw dallo stub del chiamante del caricatore riflettente

Trovare la nostra posizione nella memoria

Quando usiamo un metodo che ricerca a ritroso, dobbiamo prima ottenere l'indirizzo corrente del puntatore di istruzioni del thread (RIP ). Possiamo usare questo semplice trucco per getRip :

  1. Nel nostro UDRL creiamo una funzione chiamata getRip .
  2. Chiamiamo getRip  che spingerà l'indirizzo successivo al "call getRip " nella parte superiore dello stack. Questo è l'indirizzo di ritorno.
  3. Poi nella funzione getRip  semplicemente copiamo l'indirizzo di ritorno del chiamante dalla parte superiore dello stack.
  4. Nel codice x64 Windows C, le funzioni possono restituire un valore. Questo valore restituito viene restituito al chiamante tramite il registro RAX  . Spostando l'indirizzo di ritorno del chiamante nel registro RAX  , stiamo restituendo l'indirizzo di ritorno del chiamante al chiamante stesso.
Codice assembly Intel x64 per ottenere l'indirizzo base della DLL beacon raw dal registro RDI.

Codice assembly Intel x64 per ottenere l'indirizzo base della DLL beacon raw dal registro RDI.

Caccia a ritroso per le intestazioni MZ e PE

Il progetto originale del caricatore riflettente cerca a ritroso i collettori MZ e PE. Queste intestazioni sono diventate dei punti di rilevamento. Per superare questo problema, Cobalt Strike ha aggiunto le caratteristiche di evasione Malleable PE magic_mz  e magic_pe  .

La documentazione di Cobalt Strike afferma che l'opzione magic_mz  :

  • "Sovrascrivi i primi byte (inclusa l'intestazione MZ) della DLL beacon riflettente. Sono necessarie istruzioni valide. Segui le istruzioni che modificano lo stato della CPU con le istruzioni che annullano la modifica."

Una volta configurati i byte MZ--  del ransomware Akira nell'offset del file raw 0x00  e il PE00  all'offset del file raw 0x80  sono noti al caricatore riflettente. Vengono inseriti nella DLL beacon dal motore Malleable PE.

Questi byte devono essere in qualche modo univoci, altrimenti il caricatore riflettente non sarà in grado di trovarli. Inoltre, i byte per l'intestazione MZ devono essere senza spese operative e devono essere eseguibili. Non possono essere valori come 0x00  o il beacon potrebbe bloccarsi. Questo potrebbe essere un potenziale punto di rilevamento.

Caccia a ritroso per un uovo

Dopo aver scoperto questo potenziale punto di rilevamento, ho sviluppato un metodo diverso, ma simile, per trovare l'indirizzo base della DLL beacon raw. Questo metodo utilizza un cacciatore di uova capace di cercare a ritroso da RIP , che cerca due istanze ripetute di un uovo unico a 64 bit presso il notobeacon.dll+0x50  offset del file raw.

L'indirizzo beacon.dll+0x50  è stato scelto perché questa è la posizione del banner "Questo programma non può essere eseguito in modalità DoS", non necessario quando si carica in modo riflessivo il beacon.

Poiché non abbiamo facile accesso al motore Java Malleable PE, lo script UDRL Aggressor BokuLoader.cna  può essere utilizzato per scrivere l'uovo 0xB0C0ACDC  nel beacon. Il codice qui sotto mostra come la DLL beacon raw possa essere modificata per contenere l'uovo:

Script Aggressor per scrivere un uovo nella DLL del beacon raw e visualizzare le modifiche nella console dello script Cobalt Strike.

Script Aggressor per scrivere un uovo nella DLL del beacon raw e visualizzare le modifiche nella console dello script Cobalt Strike.

Il codice UDRL deve conoscere il valore egg scritto nella DLL beacon raw dallo script UDRL. Con l'uovo conosciuto, il cacciatore di uova cerca a ritroso due istanze dell'uovo, come si vede nel codice qui sotto:

Codice assembly Intel x64 per un cacciatore di uova che cerca a ritroso due istanze di un uovo a 64 bit.

Codice assembly Intel x64 per un cacciatore di uova che cerca a ritroso due istanze di un uovo a 64 bit.

  • Sia lo script aggressor UDRL che il codice C UDRL possono essere modificati per utilizzare uova diverse.

Ora che le intestazioni MZ e PE non sono più utilizzate, possiamo eliminarle nello script UDRL Aggressor:

Script Aggressor per mascherare MZ, PE e byte inutilizzati del banner DOS situato nelle intestazioni della DLL beacon non elaborata.

Script Aggressor per mascherare MZ, PE e byte inutilizzati del banner DOS situato nelle intestazioni della DLL beacon non elaborata.

Ottenere l'indirizzo base raw della DLL beacon dallo stub del caricatore riflettente della chiamata

Esiste anche un altro modo, specifico di Cobalt Strike, per scoprire l'indirizzo di base della DLL beacon raw. Come abbiamo visto sopra, i byte iniziali nello stub del caricatore riflettente della chiamata memorizzano l'indirizzo base della DLL beacon raw nel registro RDI  prima di chiamare il caricatore riflettente. Piuttosto che cercare a ritroso da RIP per qualche uovo, possiamo semplicemente ottenere il valore dal registro RDI  all'inizio del nostro codice riflettente del caricatore.

Per esaminare ulteriormente questo aspetto nel debugger, generiamo un beacon, anteponiamo un punto di interruzione (0xCC ) e apriamo il beacon in x64dbg. Poiché il punto di interruzione è preceduto, l'indirizzo base del beacon raw è al +1  della memoria allocata. Come abbiamo visto in precedenza, lo stub del caricatore riflessivo di chiamate utilizzaRIP l'indirizzamento relativo per ottenere l'indirizzo di base grezzo della DLL beacon:

Schermata X64dbg del passaggio attraverso lo stub del caricatore riflessivo per vedere che l'indirizzo di base della DLL beacon raw viene salvato nel registro RDI prima di chiamare il caricatore riflettente.

Schermata X64dbg del passaggio attraverso lo stub del caricatore riflessivo per vedere che l'indirizzo di base della DLL beacon raw viene salvato nel registro RDI prima di chiamare il caricatore riflettente.

Di seguito è riportato un esempio pratico di come ottenere l'indirizzo base della DLL beacon raw dallo stub del caricatore riflettente della chiamata:

Codice C di assemblaggio in linea per ottenere l'indirizzo di base della DLL raw beacon dal registro RDI.

Codice C di assemblaggio in linea per ottenere l'indirizzo di base della DLL raw beacon dal registro RDI.

Fase 2: analisi delle intestazioni della DLL beacon

Con l'indirizzo base della DLL del beacon raw, ora possiamo ottenere i valori necessari per caricare il beacon nello spazio di indirizzi virtuale del processo.

La tabella seguente elenca i valori di cui abbiamo bisogno dalle intestazioni della DLL beacon raw, le posizioni in cui li troveremo e i relativi tipi:.

Tabella che elenca i valori di intestazione della DLL beacon raw, utili per il caricamento della DLL beacon.

Tabella che elenca i valori di intestazione della DLL beacon raw, utili per il caricamento della DLL beacon.

Evasioni

Non tutti i contenuti delle intestazioni sono necessari per caricare la DLL beacon. I valori richiesti possono essere reimpacchettati o oscurati. I valori non richiesti possono essere rimossi o randomizzati.

Fase 3: allocazione della memoria per un beacon virtuale

Una volta che conosciamo l'intestazione della DLL del beacon raw della romSizeOfImagef dobbiamo allocare una memoria di queste dimensioni. Questo spazio di memoria conterrà la nostra DLL beacon virtuale.

Possono essere utilizzati diversi metodi per allocare la memoria per la DLL beacon virtuale. Metodi diversi utilizzeranno tipi diversi di memoria. I diversi metodi supportati dal caricatore riflettente predefinito di Cobalt Strike sono:

Tabella che mostra le opzioni di allocazione della memoria di Cobalt Strike per la DLL beacon virtuale.

Tabella che mostra le opzioni di allocazione della memoria di Cobalt Strike per la DLL beacon virtuale.

Evasioni

Con UDRL è possibile fare un ulteriore passo avanti. In alternativa è possibile utilizzare la versione NTAPI di queste funzioni. Inoltre, le funzioni NTAPI potrebbero essere richiamate tramite chiamate di sistema dirette o indirette, che possono aiutare o meno a rafforzare le capacità di evasione.

Quando il metodo allocatore è impostato su VirtualAlloc  nel profilo Cobalt Strike Malleable C2, attualmente il progetto BokuLoader utilizzerà una chiamata di sistema diretta per NtAllocateVirtualMemory  per allocare memoria per la DLL beacon virtuale:

Esempio di codice dal progetto BokuLoader che mostra una chiamata di sistema diretta utilizzata per allocare memoria per la DLL beacon virtuale.

Esempio di codice dal progetto BokuLoader che mostra una chiamata di sistema diretta utilizzata per allocare memoria per la DLL beacon virtuale.

  • Il numero di chiamata di sistema viene scoperto tramite il metodo HellsGate.
  • Se esiste un hook userland nello stub della chiamata di sistema, si utilizza il metodo HalosGate.

L'immagine seguente mostra un esempio di codice che utilizza i metodi HellsGate e HalosGate per determinare i numeri di chiamata del sistema:

Esempio di codice dal progetto BokuLoader che mostra come vengono scoperte le chiamate di sistema dal processo.

Esempio di codice dal progetto BokuLoader che mostra come vengono scoperte le chiamate di sistema dal processo.

Fase 4: Caricamento delle sezioni nello spazio di memoria virtuale

Ora che abbiamo allocato la memoria per la nostra DLL del beacon virtuale, dobbiamo copiare le sezioni del beacon dagli offset di file raw, così come esistono nella DLL beacon raw, alla memoria allocata nei relativi offset virtuali.

Se allocassimo la nostra memoria conREADWRITE dovremo tracciare l'indirizzo della sezione .text e le sue dimensioni. Prima di chiamare il punto di ingresso della DLL virtuale beacon dovremo modificare le protezioni di memoria della sezione .text eseguibile.

Assegnare la nostra memoria con READWRITE_EXECUTE facilita il processo di caricamento riflessivo, ma aumenta le possibilità di rilevamento da parte delle soluzioni di sicurezza.

Di seguito è riportato un esempio semplificato di codice, tratto dal progetto BokuLoader, che dimostra questo.

Esempio di codice dal progetto BokuLoader che mostra le sezioni copiate dalla DLL beacon raw alla DLL beacon virtuale.

Esempio di codice dal progetto BokuLoader che mostra le sezioni copiate dalla DLL beacon raw alla DLL beacon virtuale.

Evasioni

Alcune caratteristiche di evasione riguardanti le sezioni di carico sono:

  • Non copiare le intestazioni del beacon nella DLL beacon virtuale.
  • Deallocazione dello spazio di memoria nella DLL del beacon virtuale in cui dovrebbero essere presenti le intestazioni.

Nel progetto pubblico BokuLoader, le intestazioni per la DLL beacon non vengono copiate dalla DLL beacon raw alla DLL beacon virtuale. Attualmente i primi 0x1000  byte della DLL beacon virtuale sono nulli (0x00‘s ). Dai miei test, il beacon non dipende dalle sue intestazioni dopo essere stato caricato correttamente nella memoria virtuale. Evitare di copiare le intestazioni può aiutare a eludere gli scanner in memoria, ma questi byte nulli potrebbero anche rappresentare un potenziale punto di rilevamento.

Un'altra possibile opportunità di evasione è far criptare le sezioni dallo script UDRL Aggressor. Le sezioni potevano essere decriptate in memoria dall'UDRL, utilizzando una chiave condivisa tra l'UDRL e lo script Aggressor UDRL.

Fase 5: Caricamento delle dipendenze DLL

Per funzionare correttamente, il beacon HTTP/S x64 si basa su quattro DLL. Se queste DLL non sono attualmente caricate nel processo, il nostro caricatore riflettente dovrà caricarle.

Le quattro DLL sono elencate nella cartella di importazione delle DLL beacon HTTP/S:

Schermata di PE-Bear che elenca le DLL dalla directory di importazione della DLL del beacon.

Schermata di PE-Bear che elenca le DLL dalla directory di importazione della DLL del beacon.

Il caricatore riflettente integrato di Cobalt Strike utilizza l'API kernel32.LoadLibraryA per caricare le DLL.

Evasioni

Il caricamento delle DLL può essere effettuato in vari modi diversi, con diverse considerazioni sulla sicurezza operativa. Alcuni metodi sono:

Se la DLL è già presente nel processo, è comunque possibile utilizzare le API di Windows sopra indicate per ottenere gli indirizzi di base della DLL, anche se ciò potrebbe attivare avvisi di rilevamento indesiderati.

In alternativa, il PEB contiene un puntatore alla struttura 

<a title="https://learn.microsoft.com/it-it/windows/win32/api/winternl/ns-winternl-peb_ldr_data" href="https://learn.microsoft.com/it-it/windows/win32/api/winternl/ns-winternl-peb_ldr_data">_PEB_LDR_DATA</a>

. All'interno, c'è un elenco collegato di tutte le DLL caricate nel processo e delle loro informazioni relative (

InMemoryOrderModuleList

). BokuLoader utilizza questa funzionalità per scoprire le informazioni DLL, evitando chiamate API non necessarie.

Se la DLL non esiste nel InMemoryOrderModuleList , attualmente BokuLoader utilizza l'API NTDLL.LdrLoadDll  per caricare la dipendenza DLL nella memoria, utilizzando il caricatore DLL di Windows integrato.

Il caricamento riflettente annidato non può essere utilizzato facilmente per caricare le dipendenze DLL perché i caricatori riflettenti in genere non registrano la DLL nel processo. Il codice esterno alla DLL non può utilizzare correttamente una DLL caricata in modo riflettente. Il progetto DarkLoadLibrary potrebbe essere in grado di caricare correttamente una DLL in memoria senza attivare un evento di caricamento dell'immagine del kernel.

Esempio di codice dal progetto BokuLoader che mostra come gli indirizzi di base delle DLL caricate possono essere risolti esaminando InMemoryOrderModuleList.

Esempio di codice dal progetto BokuLoader che mostra come gli indirizzi di base delle DLL caricate possono essere risolti esaminando InMemoryOrderModuleList.

Fase 6: Risoluzione della tabella degli indirizzi di importazione

Una volta caricate le DLL richieste nel processo, è necessario risolvere le API elencate nella directory di importazione. Gli indirizzi API dovranno quindi essere scritti nella tabella degli indirizzi di importazione (IAT) della DLL del beacon virtuale. In questo modo il beacon sa a quale indirizzo saltare quando deve chiamare API come WININET.HttpSendRequest

La voce di importazione dovrà essere risolta tramite la stringa ordinale o del nome.

Nell'immagine sottostante, vediamo che la DLL del beacon Cobalt Strike utilizza una combinazione di ordinali e stringhe di nome per le voci di importazione:

Schermata di PE-Bear che mostra alcune voci di importazione per la DLL beacon che devono essere risolte tramite ordinale.

Schermata di PE-Bear che mostra alcune voci di importazione per la DLL beacon che devono essere risolte tramite ordinale.

Il caricatore riflettente Cobalt Strike integrato utilizza l'API Kernel32.GetProcAddress  per risolvere indirizzi virtuali per le voci di importazione.

Evasioni

Alcuni metodi di evasione per risolvere gli indirizzi API sono:

  • Implementazioni di codice personalizzato di GetProcAddress
  • NTDLL.LdrGetProcedureAddress

BokuLoader utilizza un'implementazione di codice personalizzata diGetProcAddress per risolvere l'indirizzo per la voce di importazione, gestendo sia le stringhe di nomi che gli ordinali.

Il NTDLL.LdrGetProcedureAddress è in grado di gestire sia stringhe di nomi che ordinali. Se l'indirizzo restituito per la voce di importazione è un forwarder di un'altra DLL, il valore predefinito di BokuLoader è NTDLL.LdrGetProcedureAddress per risolvere il forwarder.

Durante la scrittura dello IAT, è possibile implementare l'hooking scrivendo gli indirizzi virtuali delle funzioni hook che abbiamo implementato anziché l'indirizzo virtuale delle API previsto. Finché l'output atteso viene restituito al beacon quando viene chiamato l'indirizzo nell'IAC, possiamo eseguire codice aggiuntivo prima di tornare al beacon. I prossimi post e le pubbliche uscite di BokuLoader dimostreranno come possiamo utilizzare l'aggancio IAT per caratteristiche di evasione avanzate.

Con una recente release, il progetto pubblico BokuLoader supporta la funzionalità Malleable PE obfuscate dal profilo C2 di Cobalt Strike con un'implementazione personalizzata. Modificando la chiave di mascheramento nellaBokuLoader.cna Lo script UDRL Aggressor può essere migliorato scegliendo la tua chiave XOR a singolo byte.

Per quanto riguarda la sicurezza operativa, è importante sapere che i motori di pattern matching sono in grado di forzare le maschere XOR a singolo byte. I prossimi post dimostreranno come possiamo creare il nostro motore Malleable PE utilizzando la funzionalità di scripting Cobalt Strikes Aggressor per offuscare i beacon e superare il pattern matching.

Fase 7: Risoluzione dei trasferimenti

La DLL beacon presenta numerosi trasferimenti che devono essere risolti e scritti nella tabella dei trasferimenti di base della DLL del beacon virtuale prima di essere eseguita.

In PE-Bear possiamo vedere che la DLL del beacon ha di default l'indirizzo base dell'immagine di 0x180000000 :

Schermata da PE-Bear che mostra l'indirizzo base dell'immagine della DLL beacon.

Schermata da PE-Bear che mostra l'indirizzo base dell'immagine della DLL beacon.

Prima di iniziare a scrivere i trasferimenti, dobbiamo calcolare il delta tra l'indirizzo di base della nostra DLL beacon virtuale e l'indirizzo di base codificato.

Ad esempio, facciamo finta che l'indirizzo di base della nostra DLL beacon virtuale sia 0x7FFC44FE0000 . Sottraiamo l'indirizzo base codificato dall'indirizzo di base della DDL beacon virtuale per ottenere il delta dell'indirizzo base:

Schermata che ottiene il delta dell'indirizzo di base

Successivamente, per determinare l'indirizzo virtuale per ogni voce di trasferimento nella Tabella di trasferimento base, aggiungiamo il delta dell'indirizzo di riferimento base all'indirizzo di ingresso del trasferimento codificato per determinare il trasferimento all'interno della nostra DLL beacon virtuale.

Nell'immagine qui sotto possiamo vedere che le voci di trasferimento dei beacon sono scritte al contrario nel formato little-endian:

Schermata di PE-Bear che mostra alcune voci di trasferimento presenti in formato little-endian.

Schermata di PE-Bear che mostra alcune voci di trasferimento presenti in formato little-endian.

L'indirizzo codificato fisso per questa voce di trasferimento è 0x1800341C8 .

Aggiungiamo questo indirizzo al delta dell'indirizzo base, per ottenere l'indirizzo virtuale per il trasferimento così come esiste nella DLL beacon virtuale:

Schermata che aggiunge l'indirizzo al delta dell'indirizzo base, per ottenere l'indirizzo virtuale per il trasferimento così come esiste nella DLL beacon virtuale:

Per ogni voce di trasferimento dovremo verificare che il tipo sia

<a title="https://learn.microsoft.com/it-it/windows/win32/debug/pe-format" href="https://learn.microsoft.com/it-it/windows/win32/debug/pe-format">IMAGE_REL_BASED_DIR64 (0xA)</a>

. Se è falso, salteremo la scrittura del trasferimento.

Una volta determinato l'indirizzo virtuale del trasferimento così come esiste all'interno della DLL beacon virtuale, la scriviamo nello spazio di memoria che contiene l'indirizzo di ingresso del trasferimento codificato.

Se ti interessa saperne di più su come effettuare i trasferimenti PE, dai un'occhiata al codice della funzione doRelocations nel progetto pubblico BokuLoader. Prima di pubblicare questo post sul blog, ho modificato il codice dei trasferimenti da assembly a codice C, sperando che sia leggibile dagli esseri umani, per aiutare chi desidera conoscere i dettagli tecnici su come viene effettuato.

Fase 8: Esecuzione di beacon

L'esecuzione del beacon può essere suddivisa in tre fasi:

  • Assicurarsi che le sezioni DLL beacon virtuali abbiano i permessi di memoria corretti.
  • Inizializzazione del DLL beacon virtuale.
  • Chiamata del punto di ingresso della DLL beacon virtuale.

Rendere eseguibile un beacon virtuale

Se la memoria che abbiamo allocato per il nostro DLL beacon virtuale è READWRITE_EXECUTE , non è necessario modificare le protezioni della memoria per far funzionare correttamente il beacon senza bloccarsi.

Se abbiamo allocato la memoria del nostro beacon virtuale come non eseguibile (READWRITE ), dovremo cambiare la sezione .text  della nostra DLL beacon virtuale in eseguibile. La posizione e la dimensione virtuale della sezione .text  dovrebbe essere stata precedentemente salvata all'interno della nostra funzione principale UDRL come variabile.

Nel progetto pubblico BokuLoader, le modifiche alle protezioni della memoria vengono eseguite tramite chiamate dirette del sistema a NTProtectVirtualMemory , come si vede nell'esempio di codice qui sotto:

Esempio di codice dal progetto BokuLoader che dimostra la modifica della sezione .text della DLL beacon virtuale in eseguibile.

Esempio di codice dal progetto BokuLoader che dimostra la modifica della sezione .text della DLL beacon virtuale in eseguibile.

The .data  della nostra DLL beacon virtuale dovrebbe avere le autorizzazioni READWRITE . Se la sezione non è scrivibile, la nostra DLL beacon potrebbe bloccarsi durante l'esecuzione.

Inizializzazione della DLL beacon virtuale

Per eseguire correttamente la DLL beacon virtuale, è necessario anzitutto inizializzarla chiamando il punto di ingresso della DLL beacon virtuale. Il primo argomento è l'indirizzo base della DLL beacon virtuale. Il secondo argomento è il fwdReason  e dovrebbe essere impostato su DLL_PROCESS_ATTACH (1) .

Esempio di codice dal progetto BokuLoader che inizializza la DLL beacon virtuale.

Esempio di codice dal progetto BokuLoader che inizializza la DLL beacon virtuale.

Esecuzione della nostra DLL beacon virtuale

Dopo aver inizializzato la DLL beacon virtuale, possiamo restituire il punto di ingresso del beacon virtuale allo stub del caricatore riflettente della chiamata oppure possiamo chiamare il punto di ingresso della DLL beacon virtuale nel nostro UDRL con fwdReason  impostato su 0x4 .

A differenza di una tipica DLL in cui il primo argomento hinstDLL  a 

<a href="https://learn.microsoft.com/it-it/windows/win32/dlls/dllmain">DLLMAIN</a>

sarebbe l'indirizzo di base della DLL virtuale, il beacon prevede l'indirizzo di base della DLL beacon raw. Se non viene fornito, alcune funzioni di evasione di Malleable PE potrebbero non riuscire.

Esempio di codice dal progetto BokuLoader che mostra due modi diversi per eseguire la DLL beacon virtuale.

Esempio di codice dal progetto BokuLoader che mostra due modi diversi per eseguire la DLL beacon virtuale.

Considerazioni finali

Ci auguriamo che questo post sul blog aiuti sia i red team che i blue team a comprendere meglio Cobalt Strike e il processo di caricamento riflettente. Esistono ancora moltissime opportunità di evasione che possono essere implementate tramite il caricamento riflettente. Con una comprensione più profonda di questi concetti, le organizzazioni possono prepararsi meglio per una difesa efficace contro le minacce informatiche.

I prossimi post di questa serie si concentreranno sull'integrazione dell'UDRL con le attuali caratteristiche di evasione di Cobalt Strike, approfondiranno le caratteristiche di evasione non documentate già presenti nel BokuLoader pubblico, oltre a caratteristiche avanzate che non sono ancora state rilasciate al pubblico. Restate sintonizzati per informazioni e tecniche più approfondite su come portare il gioco Cobalt Strike a un livello superiore con lo sviluppo UDRL!

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.