
Analisi statica di un malware con REA, Ghidra e Codex
Dal campione di MalwareBazaar agli indicatori utili alla difesa: installazione di REA e analisi statica di un loader Windows, con evidenze e limiti.
Indice
In questo articolo voglio mostrare come utilizzare REA e Codex per analizzare staticamente un malware. L’obiettivo è comprenderne il funzionamento per ricavare informazioni utili alla difesa: dove potrebbe scrivere file, quali processi coinvolge, con quali server comunica e quali tracce cercare su un sistema sospetto.
Partiamo da un campione proveniente da MalwareBazaar, il progetto di abuse.ch, inizialmente indicato come infostealer. Questa etichetta è il punto di partenza dell’indagine: vedremo che il codice disponibile permette di confermare alcune capacità, mentre altre dipendono da componenti remoti che non abbiamo acquisito.
Il caso è stato analizzato il 17 ottobre 2026, senza eseguire il malware e senza contattare i suoi endpoint. I risultati descritti di seguito provengono dal report tecnico del caso; questo articolo ne riprende le parti più utili per capire il metodo e le implicazioni operative.
Per le indicazioni specifiche sull’installazione di REA e Ghidra su macOS Apple Silicon e sulla preparazione del laboratorio, consulta l’appendice in fondo all’articolo.
Il prompt che avvia l’indagine
Il campione è identificato da questo SHA-256:
5ecd3cbaa22a3298feebd051e9230103009dcd55eeaeeedc8f6473387eae0d55
La richiesta iniziale a Codex era semplice: usare REA per ispezionare l’eseguibile e produrre un report su cartelle di installazione, processi coinvolti, destinazioni cablate, DGA e meccanismi di aggiornamento.
Per riutilizzare il metodo conviene rendere espliciti anche i vincoli. Questo è un prompt riformulato a partire dalla richiesta originale, con un percorso da sostituire:
Usa REA per analizzare staticamente /percorso/del/caso/sample.exe.
Il campione è sospettato di essere un infostealer: verifica che cosa
supporta questa classificazione nel codice disponibile.
Lavora nel laboratorio Docker predisposto, senza rete, con campione
in sola lettura. Verifica l’isolamento prima di aprire il campione.
Non eseguire il malware o il codice estratto, non usare Wine e non
contattare endpoint presenti nel campione.
Produci un report Markdown con:
- hash e identificazione del campione;
- directory e file creati o soltanto referenziati;
- processi avviati, enumerati o modificati;
- persistenza, distinguendo configurazione e creazione dimostrata;
- domini, URL, eventuale DGA e meccanismi di cambio server;
- download di payload ed eventuali aggiornamenti;
- indicatori utili alla difesa, con ruolo e riferimenti al codice;
- limiti dell’analisi e componenti non disponibili.
Distingui le operazioni ricavate dal codice dai comportamenti osservati.
Conserva le evidenze e arresta il container al termine.
Nel lavoro svolto, l’agente ha prodotto un riepilogo in chat e un report più esteso, con artefatti, riferimenti al codice e verifiche. Il riepilogo aiuta a orientarsi; il report permette di valutare perché una conclusione sia sostenibile.
Che cosa fanno REA, Ghidra e Codex
Il reverse engineering di un eseguibile richiede di ricostruire il comportamento di un programma a partire dagli artefatti disponibili. Un disassemblatore traduce le istruzioni macchina in assembly; un decompilatore prova a ricavarne una rappresentazione più leggibile, simile al codice sorgente. Questa rappresentazione richiede comunque interpretazione e verifica.
Ghidra, sviluppato dalla Research Directorate della NSA, è un framework di reverse engineering che comprende disassemblaggio, decompilazione, grafi e scripting, utilizzabile anche in modalità automatizzata.
REA mette a disposizione dell’agente strumenti per interrogare binari e altri artefatti attraverso MCP, il protocollo con cui Codex comunica con strumenti esterni. Può usare Ghidra come motore per l’analisi nativa e dispone anche di strumenti per l’analisi statica JavaScript. Il collegamento all’agente e le istruzioni operative sono descritti nella guida ufficiale di installazione di REA.
Codex organizza le interrogazioni, segue gli indizi e produce una spiegazione. Il valore del risultato dipende dalle evidenze che accompagnano quella spiegazione: una stringa trovata nel file, da sola, non dimostra che una certa operazione venga eseguita.
In questo caso, per esempio, l’eseguibile Windows incorpora un runtime Bun e un bundle JavaScript. Dopo l’ispezione iniziale del PE, il lavoro si è concentrato sul codice applicativo estratto. Non abbiamo decompilato integralmente i circa 98 MB dell’eseguibile nativo.
Che cosa contiene davvero il campione
Il file è un PE Windows x64 di 98.092.544 byte, con runtime Bun e JavaScript incorporato. Dal contenitore è stato estratto un bundle di 345.177 byte, su 4.059 righe.
La classificazione sostenuta dal codice è quella di un loader, cioè un componente che prepara il caricamento di altri programmi. Il bundle contiene raccolta di informazioni del sistema, acquisizione dello schermo, contatti HTTPS, download e decifratura di payload PE, caricamento in memoria e un ramo dedicato a un componente chiamato “miner”.
Il nome “miner” compare nella configurazione e nei messaggi del programma. Senza il relativo payload non possiamo confermare il mining effettivo, né identificare pool o wallet. Allo stesso modo, il sospetto di infostealer non permette di attribuire al payload assente il furto di password, cookie o portafogli di criptovalute.
Il flusso ricostruito parte da controlli ambientali, contatta il server per ottenere un valore necessario alla decifratura, prepara telemetria e screenshot, quindi prevede il download dei componenti. I PE vengono decifrati con AES-256-GCM usando una chiave derivata da un seed remoto e da una costante locale. La costante presente nel campione, da sola, non basta a recuperare i contenuti mancanti.
È una catena condizionata da risposte valide e dal completamento delle fasi precedenti. La lettura del codice non dimostra che sia stata completata su una vittima.
Cartelle, file e persistenza
Il ramo di distribuzione prevede la creazione della directory e la scrittura di un componente in:
%LOCALAPPDATA%\Microsoft\EdgeWebView\msedge_proxy.exe
Il loader tenta poi di avviare quel file e ne cerca il processo. Questo è un comportamento esplicito del codice, subordinato alla disponibilità dei payload e alla prosecuzione del flusso. Non è la prova di un file osservato su una macchina compromessa, né indica che il campione copi se stesso in quel percorso.
Altri due valori vengono passati a uno stadio remoto:
%LOCALAPPDATA%\Packages\Microsoft.MicrosoftEdge.Stable_8wekyb3d8bbwe\AC\Temp
EdgeUpdateService
La prima è una directory designata per un watchdog, un componente incaricato di sorvegliare o rilanciare altri processi. EdgeUpdateService è il nome previsto per un task nella configurazione. Nel JavaScript disponibile non è dimostrata la creazione di quel task, e il nome non prova l’installazione di un servizio Windows.
Esistono anche un file sentinella, 98765.txt, e un log, loader_debug.log, nella directory del loader. Il codice controlla la presenza della sentinella e, se la trova, abilita il log e salta un controllo ambientale. Non crea direttamente la sentinella.
Sono dettagli utili in un’indagine, ma hanno significati diversi: percorso di scrittura, parametro consegnato a un altro componente e file cercato dal programma non sono evidenze intercambiabili.
Quali processi coinvolge
Il loader contiene un caricatore manuale di PE: alloca memoria, copia sezioni, risolve import e avvia codice attraverso un thread nel proprio processo. Nel bundle esaminato non emerge un’iniezione in un processo browser esterno.
L’enumerazione dei processi serve anche a cercare msedge_proxy.exe dopo il tentativo di avvio. Non dimostra che il malware scelga browser o antivirus da terminare.
Un dettaglio significativo riguarda ExitProcess e TerminateProcess: il loader ne modifica localmente il comportamento nel proprio processo. In particolare, la patch a TerminateProcess restituisce successo senza effettuare la terminazione. Trovare il nome di questa API non autorizza quindi a concludere che il campione uccida altri processi.
Server cablati, Telegram e DGA
Il bundle contiene un server principale e quattro destinazioni di riserva. Gli indirizzi sono riportati in forma disattivata tipograficamente:
| Ruolo | Indicatore |
|---|---|
| Server principale | nightly-sun[.]cc |
| Riserva | cold-signal[.]pw |
| Riserva | iron-pulse[.]cc |
| Riserva | amm-frosty[.]lol |
| Riserva | bears-speed[.]lol |
| Recupero di una destinazione alternativa | hxxps://t[.]me/s/spllcrash |
Sono valori ricavati dal campione, non una verifica della loro attuale raggiungibilità o titolarità. Durante l’analisi non li abbiamo contattati.
Se i tentativi iniziali non restituiscono un seed valido, il codice prevede di leggere la pagina pubblica del canale Telegram, estrarre un metadato HTML e decifrarne il contenuto per ricavare un nuovo indirizzo. Se il recupero fallisce, prova i domini di riserva.
Questo meccanismo consente di cambiare server mediante contenuti remoti. Non è stato individuato un DGA nel bundle analizzato: un Domain Generation Algorithm produrrebbe nomi di dominio attraverso un algoritmo, per esempio a partire dalla data o da un seed. Qui vediamo destinazioni fisse e recupero di un indirizzo remoto. La conclusione non si estende ai payload non disponibili.
Tra i percorsi HTTP figurano /sys/pull/health, /cdn/sync/blob, /app/task/submit, /v2/info/pack e /hub/init/gate, con ruoli rispettivamente legati a seed, payload, invio report e componenti aggiuntivi. L’identificativo di build è 1d940c97b823.
Il codice usa anche servizi pubblici per conoscere l’IP esterno. Il loro utilizzo da parte del loader non li rende infrastruttura malevola. Per la stessa ragione, la presenza del canale Telegram può orientare una ricerca mirata, mentre il blocco indiscriminato dell’intera piattaforma sarebbe una decisione molto più ampia.
Che cosa raccoglie e che cosa può aggiornare
Il primo report preparato dal loader comprende IP pubblico, informazioni su CPU, RAM e sistema operativo, hostname, username, dati dei controlli ambientali, timestamp e screenshot. L’immagine viene ridotta a una larghezza massima di 480 pixel, codificata e inserita nel report: non compare un salvataggio dello screenshot su disco.
Il codice prevede l’invio cifrato di questi dati al server. Un secondo report contiene l’esito dichiarato delle operazioni e verifiche su file e processo. Questi campi descrivono ciò che il programma intende comunicare; non sono misurazioni raccolte eseguendolo nel laboratorio.
Compaiono anche controlli su profili Chrome, Firefox, Edge e Brave, oltre a Desktop, file recenti e directory temporanea. Nel codice esaminato servono a valutare quanto l’ambiente sia popolato e plausibile. Non dimostrano la lettura dei database di password o cookie dei browser.
Quanto agli aggiornamenti, il server può fornire payload differenti e il canale Telegram può indicare una nuova destinazione. Non emerge invece un meccanismo dimostrato di aggiornamento autonomo dell’eseguibile del loader. Il parametro v degli URL contiene un identificativo fisso di build: da solo non costituisce un confronto fra versioni.
Verificare le conclusioni dell’agente
L’offuscamento del JavaScript comprende tabelle di stringhe, rotazioni, RC4, Base64 e XOR. La ricostruzione è stata fatta con decoder dell’analista, senza eseguire il sorgente malevolo attraverso eval, import o una VM JavaScript.
Sono stati controllati gli hash, l’intervallo di estrazione del bundle, i checksum delle quattro rotazioni e 81 valori di una tabella RC4, confrontati anche con un’implementazione Python indipendente. La vista decodificata mantiene riferimenti a righe e offset, così da poter risalire all’artefatto originale. Queste verifiche sostengono parti precise dell’analisi; non provano l’equivalenza completa di tutta la deoffuscazione.
Proprio la verifica ha evidenziato un’anomalia: alcuni nomi decifrati usati nei binding FFI del modulo anti-VM risultano incoerenti con i rispettivi punti d’uso. Questo suggerisce che parte dell’inizializzazione possa non funzionare nella build esaminata. Il report conserva l’anomalia e lascia aperta la verifica dinamica degli effetti complessivi, senza “correggere” i valori per farli coincidere con quelli attesi.
L’analisi statica stabilisce quali operazioni siano descritte dal codice e con quali condizioni. L’esecuzione effettiva, la persistenza realizzata e le capacità degli stadi remoti restano domande distinte. Al termine del lavoro il container è stato arrestato, conservando report e prove.
Dall’analisi alla threat intelligence
Un possibile impiego di questo approccio è una pipeline di raccolta e analisi. Una spamtrap, per esempio, può raccogliere messaggi e allegati sospetti; un parser di posta come mailparser può estrarne gli artefatti. L’arrivo nella spamtrap è un criterio di selezione, non una conferma automatica di malevolenza.
I file selezionati potrebbero essere identificati tramite hash e passati a laboratori isolati, dove un agente produce report e indicatori strutturati. Prima di alimentare un sistema di blacklist interno, ogni indicatore dovrebbe conservare provenienza, ruolo, data dell’analisi e livello di confidenza.
Nel nostro caso l’hash identifica il file preciso; i domini e gli endpoint possono orientare controlli nei log di rete; il percorso completo di msedge_proxy.exe può guidare ricerche sugli endpoint. Il solo nome del processo, invece, non basta a dichiarare un’infezione: occorre correlarlo a percorso, hash, origine del processo e comunicazioni.
Un flusso operativo potrebbe quindi essere:
- raccogliere e selezionare gli artefatti sospetti;
- registrarne hash, provenienza e data;
- eseguire l’analisi statica in un laboratorio isolato;
- validare le evidenze e qualificare gli indicatori;
- distribuire gli indicatori approvati ai sistemi di rilevamento o blocco.
È una proposta di utilizzo del metodo, non una pipeline implementata e misurata in questo caso. Il contributo dell’agente è rendere più rapido e ordinato il lavoro di esplorazione, lasciando verificabili le conclusioni.
Utilità in un’analisi forense
Durante un incidente, una prima analisi di questo tipo può fornire domande concrete: esiste il file nel percorso indicato? Quale processo lo ha creato? I log mostrano contatti con i domini o gli endpoint estratti? Sono presenti task compatibili con il nome passato allo stadio remoto?
Il report orienta le ricerche su disco, memoria, telemetria endpoint e log di rete. L’assenza di un indicatore non esclude la compromissione, soprattutto quando il campione può scaricare componenti diversi; la presenza di un nome comune richiede correlazioni prima di formulare una conclusione.
In questo caso REA e Codex hanno aiutato a passare da un eseguibile di grandi dimensioni a una ricostruzione documentata del loader, delle sue destinazioni e dei suoi limiti. La possibilità di tornare dal risultato al codice è ciò che rende il report utile anche dopo la prima lettura.
Maggiori dettagli sul report tecnico, sugli artefatti e sui riferimenti al codice possono essere forniti privatamente.
Appendice: installazione e laboratorio
Installare gli strumenti su macOS Apple Silicon
La procedura seguente ricostruisce la configurazione usata sul Mac, con percorsi generici nell’area utente. È riferita a macOS Apple Silicon: non la presentiamo come una procedura già collaudata su Windows o Linux.
Le versioni di riferimento sono REA 6.5.0, Ghidra 12.1.4, Temurin JDK 25.0.4.1 e Node.js 24.21.0. Sono versioni fissate per riprodurre l’ambiente descritto. La variante che include la skill è stata controllata in anteprima tramite il piano di setup; l’intera sequenza non è stata ripetuta su un Mac pulito.
1. Preparare il Mac
Servono Codex già installato e autenticato, accesso a Internet per scaricare gli strumenti e gli strumenti di compilazione Apple. Verifichiamo architettura e toolchain:
uname -m
xcode-select -p
Su Apple Silicon il primo comando deve restituire arm64. Se gli strumenti Apple non sono installati:
xcode-select --install
Attendere il completamento prima di proseguire. I passaggi successivi vanno eseguiti nella stessa sessione del terminale, così da conservare le variabili d’ambiente:
mkdir -p "$HOME/.local/bin"
mkdir -p "$HOME/.local/opt/jdks"
mkdir -p "$HOME/Downloads/rea-setup"
cd "$HOME/Downloads/rea-setup"
2. Installare Java
Scarichiamo il JDK ARM64 dalla release ufficiale Eclipse Temurin:
curl -fL \
'https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25.0.4.1%2B1/OpenJDK25U-jdk_aarch64_mac_hotspot_25.0.4.1_1.tar.gz' \
-o temurin-jdk.tar.gz
echo '61979887f7506a24a57439ff99adb8b3a7fc89977d9cfe3b8984f58a981b7b9d temurin-jdk.tar.gz' \
| shasum -a 256 -c -
Il digest è quello registrato per l’archivio della nostra installazione. Se la verifica non restituisce OK, fermarsi e controllare il download. Poi:
tar -xzf temurin-jdk.tar.gz
ditto "jdk-25.0.4.1+1/Contents/Home" \
"$HOME/.local/opt/jdks/temurin-25.0.4.1-arm64"
export JAVA_HOME="$HOME/.local/opt/jdks/temurin-25.0.4.1-arm64"
export PATH="$JAVA_HOME/bin:$PATH"
java -version
javac -version
3. Installare Ghidra e compilare i componenti nativi
Scarichiamo la distribuzione pubblica dalla release Ghidra 12.1.4. Gli archivi denominati “Source code” sono diversi dalla distribuzione pronta all’uso.
curl -fL \
'https://github.com/NationalSecurityAgency/ghidra/releases/download/Ghidra_12.1.4_build/ghidra_12.1.4_PUBLIC_20260921.zip' \
-o ghidra.zip
echo 'ddac49f903da9d5bac833e5cc79395098b9c33cfd3279be5f31bd00387d2d4db ghidra.zip' \
| shasum -a 256 -c -
Anche qui attendiamo OK. La destinazione deve essere una cartella nuova: non sovrapporre versioni differenti di Ghidra.
unzip -q ghidra.zip
ditto ghidra_12.1.4_PUBLIC "$HOME/.local/opt/ghidra_12.1.4_PUBLIC"
ln -s ghidra_12.1.4_PUBLIC "$HOME/.local/opt/ghidra_latest"
export GHIDRA_INSTALL_DIR="$HOME/.local/opt/ghidra_latest"
(
cd "$GHIDRA_INSTALL_DIR/support/gradle"
./gradlew --no-daemon buildNatives
)
Il wrapper può scaricare Gradle. Attendere BUILD SUCCESSFUL, quindi verificare il decompilatore:
file "$GHIDRA_INSTALL_DIR/Ghidra/Features/Decompiler/build/os/mac_arm_64/decompile"
Il risultato deve indicare un eseguibile Mach-O ARM64. Per provare l’interfaccia grafica:
"$GHIDRA_INSTALL_DIR/ghidraRun"
4. Installare Node.js e REA
Per gestire Node usiamo NVM. Se è già installato, saltiamo il download e l’esecuzione del suo installer:
curl -fL \
https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh \
-o "$HOME/Downloads/rea-setup/install-nvm.sh"
bash "$HOME/Downloads/rea-setup/install-nvm.sh"
Carichiamo NVM e selezioniamo la versione usata sul Mac:
export NVM_DIR="$HOME/.nvm"
. "$NVM_DIR/nvm.sh"
nvm install 24.21.0
nvm use 24.21.0
node --version
npm --version
npm install --global \
--prefix "$HOME/.local/opt/rea" \
--ignore-scripts \
rea-agents@6.5.0
Il prefisso appartiene all’utente, quindi non serve sudo. Creiamo un comando che selezioni esplicitamente Node, Java e Ghidra:
cat > "$HOME/.local/bin/rea" <<'EOF'
#!/bin/sh
set -eu
export JAVA_HOME="$HOME/.local/opt/jdks/temurin-25.0.4.1-arm64"
export GHIDRA_INSTALL_DIR="$HOME/.local/opt/ghidra_latest"
export REA_ANALYSIS_PROVIDER=ghidra
export PATH="$HOME/.nvm/versions/node/v24.21.0/bin:$JAVA_HOME/bin:$PATH"
exec "$HOME/.nvm/versions/node/v24.21.0/bin/node" \
"$HOME/.local/opt/rea/lib/node_modules/rea-agents/scripts/rea.mjs" \
"$@"
EOF
chmod +x "$HOME/.local/bin/rea"
export PATH="$HOME/.local/bin:$PATH"
rea --version
La versione attesa è 6.5.0. Se ~/.local/bin non è già nel PATH, aggiungere questa riga a ~/.zshrc per i terminali futuri:
export PATH="$HOME/.local/bin:$PATH"
5. Collegare REA a Codex
Controlliamo Ghidra e visualizziamo il piano prima di applicarlo:
rea doctor --provider ghidra --json
rea setup --client codex --skill=true --dry-run --json
Il piano deve includere la registrazione MCP e l’installazione della skill. Per applicarlo:
rea setup --client codex --skill=true --yes --json
Il server MCP espone gli strumenti; la skill fornisce le istruzioni operative all’agente. Usiamo la forma esplicita --skill=true, verificata nel piano di setup di questa versione.
Quando Node è gestito da NVM, Codex avviato dall’interfaccia grafica può avere un PATH diverso da quello del terminale. Se la registrazione generata non seleziona il runtime desiderato, possiamo adattare le sezioni REA in ~/.codex/config.toml:
[mcp_servers.rea]
command = "/Users/NOME_UTENTE/.nvm/versions/node/v24.21.0/bin/node"
args = [
"/Users/NOME_UTENTE/.local/opt/rea/lib/node_modules/rea-agents/scripts/rea.mjs",
"mcp"
]
startup_timeout_sec = 30
tool_timeout_sec = 450
[mcp_servers.rea.env]
GHIDRA_INSTALL_DIR = "/Users/NOME_UTENTE/.local/opt/ghidra_latest"
JAVA_HOME = "/Users/NOME_UTENTE/.local/opt/jdks/temurin-25.0.4.1-arm64"
REA_ANALYSIS_PROVIDER = "ghidra"
Sostituire NOME_UTENTE con il proprio nome e modificare le sezioni esistenti, senza duplicarle o cancellare gli altri server. I percorsi sono assoluti: non presumere che $HOME venga espanso nel TOML.
Nella documentazione ufficiale MCP di Codex sono descritti comando, argomenti, ambiente e timeout. startup_timeout_sec riguarda l’avvio del server MCP; tool_timeout_sec riguarda le singole chiamate. I 450 secondi lasciano margine alle operazioni di analisi e all’avvio di Ghidra richiesto da una chiamata: sono una scelta del nostro ambiente, non un requisito universale.
Completiamo i controlli:
rea doctor --client codex --provider ghidra --json
rea doctor --skill --json
rea mcp doctor --json
Cercare healthy: true nei controlli selezionati, quindi chiudere e riaprire Codex. Un primo prompt può essere:
Usa REA per verificare la versione del server e la disponibilità di Ghidra,
senza aprire alcun file.
Questi controlli verificano la disponibilità degli strumenti. L’analisi di un programma costituisce una verifica successiva.
Preparare il laboratorio di analisi
L’installazione sul Mac serve a rendere disponibili gli strumenti all’agente. L’analisi del malware è stata svolta in un laboratorio Linux/amd64 separato, in Docker Desktop, con REA 6.5.0 e Node 24.22.0. La configurazione dell’host e quella del container sono due ambienti distinti.
Un eseguibile Windows può essere studiato staticamente da Linux: per leggere struttura, stringhe e codice non occorre avviarlo su Windows. Nel nostro caso non è stato usato Wine e non sono stati eseguiti il campione, il JavaScript estratto o eventuali PE.
Prima dell’analisi erano state preparate l’immagine e le dipendenze. Durante l’ispezione, il container aveva rete none, filesystem radice in sola lettura, utente non privilegiato, capability rimosse e no-new-privileges. Il campione era montato in sola lettura; le scritture erano limitate alla cartella del caso e alle aree temporanee del container.
Avviare Docker non applica automaticamente queste condizioni. Il laboratorio va configurato e controllato esplicitamente. Il container limita l’accesso degli strumenti al sistema e alla rete; in questo lavoro il vincolo principale resta l’analisi esclusivamente statica.
Anche la rete del laboratorio e la connessione usata da Codex sono piani diversi: il container può essere offline mentre l’agente comunica con il servizio del modello. “Offline” non significa quindi che l’intero flusso AI sia privo di comunicazioni esterne; occorre considerarlo quando si trattano campioni o evidenze riservati.