Alcune delle tecniche di attacco più difficili da rilevare non puntano a sfondare le difese. Sfruttano funzionalità legittime di Windows per nascondere le proprie intenzioni agli strumenti di sicurezza, rendendo il rilevamento un problema di analisi più che di detection automatica.
Nella seguente analisi discuteremo di come i malware moderni implementano tecniche che, in alcuni casi, permettono di aggirare le soluzioni di Endpoint Security (EDR), e di cosa serve, concretamente, per intercettarli. Prima di analizzare in profondità le singole tecniche, alcune nozioni di base utili per seguire con maggiore facilità il resto dell’articolo.
Malware Development: terminologia di base
Payload, il cuore di ogni malware
Il primo concetto da chiarire è il Payload, la parte che definisce cosa un malware fa davvero. Tradotto letteralmente significa “carico utile”; un esempio del termine arriva dall’ambito aerospaziale, dove indica ciò che un vettore trasporta fino al bersaglio. Nel contesto dei malware il senso è esattamente lo stesso: il payload è la componente che esegue l’azione malevola sulla macchina bersaglio, distinta dal vettore che la consegna (un allegato e-mail, un exploit, un dropper).
Concretamente un payload può assumere molte forme:
- un eseguibile
- uno shellcode iniettato in memoria
- uno script (PowerShell, VBS, Python, JavaScript)
- un comando o una sequenza di comandi
Parliamo genericamente di “azione malevola” e non di “danno” perché il payload non distrugge necessariamente nulla. In molti casi è semplicemente un comando PowerShell o uno script VBS che scarica ed esegue qualcos’altro, ad esempio installa AnyDesk o un altro software di accesso remoto legittimo, usandolo come backdoor. Per la vittima non c’è nessun file “virus” sospetto da trovare, e il payload finale è un software perfettamente lecito, usato in modo illecito.
Shellcode, il codice puro
Lo shellcode è un piccolo blocco di codice macchina (istruzioni già pronte per essere eseguite dalla CPU) pensato per essere iniettato direttamente nella memoria di un processo ed eseguito sul posto.
Il nome viene dal suo scopo originale: aprire una shell (un prompt dei comandi) sfruttando una vulnerabilità. Oggi il termine si usa in senso più ampio per indicare qualsiasi codice di questo tipo, indipendentemente da cosa fa.
La differenza rispetto a un normale eseguibile è che lo shellcode non è un file .exe con tutta la sua struttura: è una sequenza grezza di byte, come:

Non viene lanciato dal sistema operativo, ma scritto in un’area di memoria a cui poi l’esecuzione viene “dirottata”. Nella pratica uno shellcode è:
- Piccolo, spesso poche centinaia di byte, perché deve entrare in spazi limitati.
- Autosufficiente, perché non può contare sul normale caricamento delle librerie.
- Spesso “camuffato” (encoded), per evitare di contenere byte che lo farebbero bloccare, come il null byte “\x00”.
Nel rapporto con il payload, lo shellcode molto spesso è il payload, o ne è il primo pezzo: quel frammento iniziale che, una volta in esecuzione, si occupa di scaricare o decifrare il resto.
Syscall, il ponte tra programma e Kernel
Una Syscall (o System Call) è il meccanismo con cui un programma chiede al Kernel di eseguire un’operazione per suo conto. Aprire un file, allocare memoria, creare un processo, scrivere su disco: tutte queste cose un programma non può farle da solo, deve chiederle al Kernel tramite una syscall.
Il motivo è che Windows (come ogni sistema operativo moderno) divide l’esecuzione in due “mondi”: User mode, dove girano i normali programmi con permessi limitati, e Kernel mode, dove gira il cuore del sistema operativo con accesso completo all’hardware e alla memoria. La syscall è il ponte che permette di passare dal primo al secondo, in modo controllato.
Su Windows una syscall non viene quasi mai chiamata direttamente. Il percorso tipico è a strati; qui viene mostrato ciò che una semplice creazione di un file fa su Windows:



Il programma chiama una funzione “amichevole” come CreateFile, che a sua volta chiama una funzione Nt* dentro “ntdll.dll”, e solo quest’ultima esegue l’istruzione syscall vera e propria che porta in Kernel mode.
Questo dettaglio è importante perché su questi strati intermedi si appoggiano molti antivirus ed EDR: piazzano i loro “ganci” (hook) dentro ““ntdll.dll”” per controllare cosa sta facendo un programma prima che la syscall raggiunga il Kernel. Da qui nascono tecniche offensive come le direct syscall, che saltano gli strati intermedi e invocano l’istruzione “syscall” direttamente, lasciando l’EDR a guardare una porta da cui nessuno sta più passando.
API Hooking, la tecnica di intercettazione delle chiamate
L’API Hooking è la tecnica con cui si intercetta una chiamata a una funzione, la si devia verso codice personalizzato, e poi (opzionalmente) la si lascia continuare verso la destinazione originale.
In pratica è come mettere un investigatore privato nel mezzo di una chat: il mittente invia un messaggio, l’investigatore ascolta (e magari modifica il messaggio), poi inoltra la chiamata al destinatario. Nessuno dei due sa cosa è successo nel mezzo.
Nel contesto di Windows il punto più comune dove viene applicato è proprio ““ntdll.dll””. Un EDR o un antivirus modifica le prime istruzioni di una funzione come “NtCreateFile” o “NtAllocateVirtualMemory” inserendo un salto (“JMP”) verso il proprio codice.



Se la chiamata sembra sospetta, l’EDR può bloccarla, loggarla, o terminare il processo. Se sembra legittima, la lascia passare.
Questo meccanismo viene però usato anche in senso offensivo:
- Un malware può hookare funzioni per nascondersi, ad esempio intercettando le chiamate che elencano i processi in esecuzione e rimuovendo sé stesso dalla lista.
- Può hookare funzioni per rubare dati, ad esempio intercettando le chiamate di lettura di un browser per catturare password o token prima che vengano cifrati.
Il rovescio della medaglia, dal punto di vista difensivo, è che gli hook sono rilevabili: un programma può controllare se le prime istruzioni di una funzione in ““ntdll.dll”” sono state modificate rispetto a quello che c’è su disco. Questa tecnica si chiama unhooking, e chiude il cerchio con le direct syscall: entrambe nascono dalla stessa esigenza, aggirare i controlli piazzati negli strati intermedi.
Tecniche di evasione
Le tecniche descritte sotto sono spesso usate in catena (approccio multi-stage): ogni fase ha lo scopo di ridurre la visibilità, aumentare la resilienza all’analisi e rendere più difficile scrivere regole efficaci di detection/prevention. Raramente vengono usate singolarmente: la caratteristica predominante dei malware sviluppati negli ultimi anni è proprio la complessità multi-stage, in cui una serie di tecniche e step vengono eseguiti con lo scopo di rendere il malware più difficile da analizzare e di conseguenza più difficile bloccare con regole efficaci.
IAT Hiding
L’IAT (Import Address Table) è una sezione presente in ogni eseguibile Windows che elenca tutte le funzioni esterne che il programma utilizza, funzioni che non sono scritte nel programma stesso, ma che appartengono a librerie di sistema come “kernel32.dll” o “user32.dll”.
Un esempio concreto: se un software vuole mostrare un pop-up sul desktop, non reinventa la ruota, chiama semplicemente “MessageBox”, una funzione della Windows API. Facendolo, quella funzione viene registrata nell’IAT come dipendenza, ed è lì per chiunque voglia leggerla.
Il problema, dal punto di vista di chi scrive malware, è che alcune funzioni della WinAPI sono immediatamente sospette. Prendete “CreateRemoteThread”: già dal nome è difficile giustificarla in un software legittimo. Quella funzione crea un thread all’interno di un processo che non appartiene al chiamante, ed è uno strumento classico per l’iniezione di codice. Se appare nell’IAT di un eseguibile sconosciuto, è una bandiera rossa immediata per qualsiasi analista o soluzione di sicurezza.
Per questo i malware developer cercano di nascondere i loro import, evitando che funzioni sospette compaiano nell’IAT in chiaro. Questo è l’IAT Hiding. Di seguito uno schema di esempio per l’IAT di “chrome.exe”:



La struttura è quella tipica di un eseguibile: ogni voce corrisponde a una funzione esterna dichiarata, leggibile da qualsiasi strumento di analisi.
Dynamic Function Name Resolution
Una delle implementazioni più comuni è la Dynamic Function Name Resolution: evitare di importare la funzione a compile-time e risolverla invece dinamicamente a runtime. Di seguito un esempio minimale di come nascondere una specifica funzione dall’IAT:



Gli step logici sono i seguenti:
- Definire la firma della funzione e creare un tipo custom (“fnMessageBoxW”)
- Caricare la DLL che la contiene con “LoadLibraryA”
- Risolvere l’indirizzo della funzione con “GetProcAddress”
- Chiamare la funzione tramite il puntatore
Questo esempio mostra come nascondere dall’IAT la funzione “MessageBoxW”. Se usiamo il tool di Microsoft “dumpbin.exe” sull’eseguibile compilato, otteniamo un Import Address Table simile alla seguente:



Come mostra lo schema, nessuna voce dell’IAT rivela che sia stata chiamata la funzione “MessageBoxW”. Apportando una modifica al codice, è possibile vedere come sarebbe l’IAT se non risolvessimo dinamicamente la funzione.



Per confronto, questa è la struttura dell’IAT quando la funzione viene importata in modo tradizionale:



Procedendo in questo modo, la funzione chiamata non comparirà nell’IAT dell’eseguibile compilato. Analisti e sistemi di sicurezza non troveranno traccia dell’import sospetto in fase di analisi statica.
L’IAT Hiding nella sua forma base punta a nascondere il maggior numero possibile di funzioni dagli occhi di analisti e sistemi di difesa. È una tecnica prevalentemente passiva e riduce i segnali visibili prima ancora che il malware compia qualsiasi azione.
Come difendersi dall’IAT Hiding
L’analisi statica degli import non è sufficiente, in quanto un eseguibile con IAT pulita può nascondere decine di funzioni sensibili. La difesa efficace si sposta sul comportamento a runtime: monitorare le chiamate a LoadLibrary e GetProcAddress, correlare la risoluzione dinamica di funzioni sospette con le azioni successive del processo, e costruire un quadro che il singolo evento non consente di vedere.
Direct Syscall e Indirect Syscall
A differenza dell’IAT Hiding, che è una tecnica prevalentemente passiva, le Direct e Indirect Syscall sono proattive e continue: entrano in gioco ogni volta che il malware deve compiere un’operazione sensibile, per tutta la durata della sua esecuzione.
Il punto di partenza è quello che abbiamo visto nella sezione sulle Syscall: gli EDR piazzano i loro hook dentro “ntdll.dll”, intercettando le chiamate prima che raggiungano il Kernel. La domanda che si pone un malware developer è semplice: e se saltassimo “ntdll.dll” completamente?
Questa è esattamente l’idea alla base delle Direct Syscall: invece di passare per “ntdll.dll” e i suoi hook, il malware esegue l’istruzione “syscall” direttamente, specificando manualmente il numero identificativo della funzione che vuole chiamare (il cosiddetto SSN, Syscall Service Number). L’EDR non vede nulla perché il suo hook non è mai stato attraversato.
Le Indirect Syscall nascono come evoluzione della stessa idea, con una differenza sottile ma importante: invece di eseguire l’istruzione “syscall” nel proprio codice (cosa che alcuni EDR riescono a rilevare analizzando da dove viene invocata), il malware salta direttamente all’istruzione “syscall” che si trova già dentro “ntdll.dll”, dopo il punto in cui è piazzato l’hook. Il risultato è lo stesso, ma l’esecuzione appare più legittima agli occhi di chi monitora.
Direct Syscall
Il codice che scriviamo viene convertito dal compilatore in Assembly, e anche una System Call ha la sua definizione in Assembly. Di base una system call ha una struttura simile a questa:



La tabella seguente mostra la corrispondenza tra alcune funzioni Nt* e i rispettivi SSN su una specifica versione di Windows, a titolo esemplificativo:



C’è però un problema: anche una singola minor version di Windows può cambiare gli SSN. Le opzioni sono due: scrivere il malware con in mente una versione specifica di Windows, oppure risolvere in modo dinamico il valore SSN leggendo “ntdll.dll” a runtime. La seconda opzione è quella più usata, perché rende il codice portabile su versioni diverse del sistema operativo.
Complicazioni: anche se questo metodo sembra infallibile, ha una falla significativa. Un software “normale” invoca queste operazioni passando per le API (e quindi per il contesto di “ntdll.dll”). Con le direct syscall, invece, è il nostro eseguibile a eseguire direttamente le istruzioni, cosa che può alzare un allarme per gli EDR più sofisticati.
Indirect Syscall
Il secondo metodo è simile al precedente, ma invece di eseguire l’istruzione “syscall” dal nostro eseguibile, la facciamo eseguire da “ntdll.dll”. Come? Saltando (via “JMP”) direttamente all’istruzione “syscall” che si trova già dentro “ntdll.dll”, dopo il punto in cui è stato installato l’hook.
Questo permette di:
- Saltare qualsiasi API Hook che l’EDR possa aver installato.
- Far apparire l’istruzione come se venisse eseguita dal contesto di “ntdll.dll”.
Lo schema seguente illustra il flusso completo della tecnica, con il salto diretto all’istruzione syscall all’interno di ntdll.dll:



Il salto non si limita a “ntdll.dll”: è possibile puntare a qualsiasi istruzione “syscall” presente in qualsiasi DLL di sistema.
La tecnica non offre problemi con gli EDR di livello base, ma gli EDR più avanzati dispongono di moduli a livello di Kernel o monitorano gli eventi forniti dall’ETW (Event Tracing for Windows). È il caso, ad esempio, di Cortex XDR e SentinelOne XDR.
Come difendersi da Direct e Indirect Syscall
Il punto critico è spostare il monitoraggio a livello di Kernel, dove le syscall non possono essere aggirate in user mode. La telemetria ETW genera però volumi enormi di eventi: distinguere un’attività legittima da un’evasione sofisticata richiede la capacità di leggere sequenze di comportamenti nel tempo, non il singolo evento. Verificare che le istruzioni in “ntdll.dll” in memoria corrispondano a quelle su disco, il cosiddetto unhooking detection, è un altro controllo che rientra in un’analisi approfondita dell’infrastruttura.
Decifratura su GPU: nascondere il payload dove nessuno guarda
La maggior parte delle tecniche di cifratura dei payload opera interamente nella RAM del processo: il codice cifrato viene caricato, decifrato, ed eseguito tutto nello stesso posto. Gli EDR moderni lo sanno, e ci piazzano gli occhi sopra.
L’idea qui è diversa: spostare la fase di decifratura sulla GPU, lasciando che il payload cifrato viva nel binario e che ilplaintext non compaia mai nell’heap o in buffer gestiti dal codice utente.
Come funziona la decifratura su GPU
Il flusso è composto da tre momenti distinti.
- Il payload cifrato sta fermo nel binario. Il ciphertext e la chiave risiedono nella sezione “.data” dell’eseguibile, cifrati. Un’analisi statica del file non trova nulla di leggibile.
- La GPU decifra in silenzio. Tramite DirectX 11, il ciphertext e la chiave vengono caricati in VRAM. Una compute shader esegue la decifratura interamente sulla GPU, fuori dalla portata degli hook user-mode con cui gli EDR monitorano le API crittografiche standard di Windows (“BCryptDecrypt”, “CryptUnprotectData”, ecc.).
- Il risultato torna in RAM solo al momento dell’esecuzione. Il plaintext decifrato viene copiato dalla VRAM in una singola pagina di memoria, resa eseguibile solo in quel momento, e subito azzerata dopo l’esecuzione.
Perché la tecnica è rilevante
La tecnica ha due caratteristiche specifiche che la rendono interessante dal punto di vista della ricerca difensiva: la decifratura avviene in un contesto che gli strumenti tradizionali non monitorano attivamente (la VRAM e il driver grafico), e il plaintext del payload non transita mai per l’heap del processo, riducendo la superficie esposta agli scanner in memoria.
Il limite principale è che il readback dalla VRAM alla RAM è inevitabile: prima o poi il codice deve tornare nella memoria di sistema per essere eseguito. L’obiettivo non è eliminare questo passaggio, ma ridurre al minimo il tempo in cui il payload è esposto in chiaro.
Cosa significa per chi difende
Tecniche come questa spingono verso un monitoraggio più profondo, a livello di driver grafico e chiamate DXGI/D3D, che oggi è ancora un’area poco presidiata dalla telemetria standard degli EDR. Conoscerle è il primo passo per costruire contromisure efficaci.
Come difendersi dalla decifratura su GPU
Il payload deve tornare in RAM prima di essere eseguito: è il passaggio inevitabile su cui si concentra la difesa. Rilevare pagine di memoria rese eseguibili a runtime tramite VirtualProtect, monitorare i trasferimenti inattesi tra VRAM e memoria di sistema, e tenere sotto osservazione le chiamate alle API DirectX in contesti di processo anomali sono i controlli più efficaci. Si tratta di un’area che richiede visibilità profonda sull’infrastruttura e capacità di analisi che vanno oltre la telemetria standard.
Il filo comune: perché serve un monitoraggio continuo
Le tre tecniche descritte in questo articolo hanno in comune una caratteristica: sono progettate per sembrare normali. Sfruttano funzionalità legittime di Windows, operano negli strati che gli strumenti automatici monitorano con più difficoltà, e producono segnali che isolati non bastano a innescare un allarme.
La risposta non è uno strumento migliore, ma analisi umana continuativa sulla telemetria, capacità di correlare eventi distanti nel tempo e nell’infrastruttura, e competenza per riconoscere i pattern prima che diventino incidenti. Questa competenza si costruisce anche attraverso un’attività continuativa di Cyber Threat Intelligence, che traduce la ricerca offensiva, come quella descritta in questo articolo in indicatori operativi che alimentano il monitoraggio quotidiano. Un Security Operations Center (SOC) attivo 24/7 con visibilità kernel-level e analisti specializzati è oggi l’unica risposta operativa in grado di coprire l’intera superficie che queste tecniche sfruttano.
Implementazione di riferimento
Un’implementazione completa della tecnica descritta in questo articolo, inclusi il codice host in C e la compute shader HLSL, è disponibile su GitHub: dovelus/GPU-RC4-Payload-Decryption