Detection e function hooking
好的,我现在需要帮用户总结一篇文章的内容,控制在100字以内。用户给的原文是意大利语的,但看起来主要讨论的是EDR(终端检测与响应)的工作原理,特别是函数钩子技术。文章还提到了如何绕过这些检测机制,比如通过使用未被修改的ntdll.dll来避免被EDR拦截。 首先,我需要理解文章的主要内容。文章解释了EDR如何通过在内存中修改ntdll.dll的函数入口来拦截系统调用,从而收集 telemetry 数据。然后,作者通过一个实验展示了如何绕过这种钩子技术,具体方法是加载原始的ntdll.dll文件,从而避免被EDR检测到。 接下来,我要将这些信息浓缩到100字以内。需要涵盖EDR的工作机制、函数钩子技术、实验过程以及绕过方法。 可能的结构是:介绍EDR使用函数钩子拦截系统调用收集数据;实验展示了如何绕过这一机制;结论指出这种方法虽然有效但复杂。 现在组织语言: 文章探讨了EDR通过函数钩子技术拦截系统调用以收集数据的工作原理,并通过实验展示了如何利用原始ntdll.dll绕过检测。结果显示这种方法可行但复杂。 检查字数:大约60字左右,符合要求。 </think> 文章探讨了EDR通过函数钩子技术拦截系统调用以收集数据的工作原理,并通过实验展示了如何利用原始ntdll.dll绕过检测。结果显示这种方法可行但复杂。 2026-3-9 16:33:8 Author: roccosicilia.com(查看原文) 阅读量:2 收藏

L’introduzione fatta ai concetti di detection mi serviva per iniziare a discutere del funzionamento degli EDR partendo da una base comune di comprensione dell’architettura. Come ho anticipato questi post hanno lo scopo di discutere anche le possibile tecniche di bypass delle logiche di detection con degli esempi pratici e mi baserò molto sul funzionamento di EDR e SIEM.

Un aspetto che ho citato in diverse occasioni è la capacità degli EDR di mettere a disposizione molta telemetria riguardo a ciò che avviene sugli host. Le informazioni vengono raccolte dai sensori attivi sugli endpoint attraverso diverse funzioni, una di queste è il function hooking: gli EDR moderni si “interpongono” tra i programmi e le chiamate di sistema al fine di intercettare le richieste alle funzioni base del sistema operativo (ho iper-semplificato, ma ora vado un po’ nel dettaglio).

Prendiamo una delle funzioni più monitorate dagli EDR: VirtualAlloc, usata per allocare memoria, operazione comunissima nei payload malevoli.

Un flusso normale (senza EDR) potrebbe essere:

Il flusso con un EDR attivo potrebbe essere qualcosa di simile:

Quando l’EDR si installa esegue diverse modifiche sul sistema, ad esempio modifica – in memoria – primi byte delle funzioni sensibili in ntdll.dll (la libreria che fa da ponte verso il kernel) sostituendoli con un JMP verso il proprio codice. L’EDR esegue la sua analisi, e se tutto è ok chiama la funzione originale che ha salvato da parte prima di patchare.

Possiamo verificare questa caratteristica degli EDR analizzando il comportamento a runtime di un processo che esegue una syscall e la verifica la possiamo fare “in profondità” analizzando il comportamento del processo con un debugger.

Lab Test: syscall

Ma quanto utile è avere un lab e tutti gli strumenti per approfondire questi temi! Chi si occupa di ricerca pura sono certo che si diverte come un matto ad eseguire questi test 🙂 nel mio caso di tratta di momenti di studio e ricerca personale che cerco di condividervi e che potete replicare abbastanza facilmente (rif. al tema HomeLab).

Per prima cosa dobbiamo analizzare il comportamento di un processo su un sistema che non ha un agent EDR installato. Nel mio lab si tratta della macchina che simula un ambiente desktop Windows 11 che utilizzo anche per la compilazione in ambiente Microsoft.

Nel contesto di laboratorio i sistemi desktop vengono usati, solitamente, con utenti di dominio non privilegiati, ma per il test in questione non è rilevante. Va in ogni caso installato un debugger e per il lab in questione ho deciso di usare IDA Free (v9.3 al momento disponibile, richiede python) che conto di usare anche per altri test che ho in mente di fare. Ovviamente serve anche avere il necessario per compilare un piccolo programma in C, io uso Visual Studio Code sia sul mio laptop che sulla macchina Win11 in questione dove ho anche installato MSYS2 che trovo molto comodo. Voi fate come vi pare 🙂

C’è abbondante documentazione su IDA nel web ma se volete approfittare (come ho fatto il) per un approfondimento sul mondo del reverse engineering vi suggerisco un libro per iniziare che mi ha dato qualche base che mi torna ancora utile considerando che io non sono un reverse engineer: “The IDA PRO book” di cui comprai la seconda edizione.

Il codice per il programma di test che dovrà eseguire l’azione di creazione di un file è il seguente:

Compilando ed eseguendo questo programma otterremo la creazione di un file nel path “C:\Users\Public\test.txt” e se ne facciamo il debug potremo osservare la chiamata “pulita” alla syscall per la creazione del file: 𝑵𝒕𝑪𝒓𝒆𝒂𝒕𝒆𝑭𝒊𝒍𝒆.

Vi risparmio i singoli passaggi che troverete nel video di sintesi, faccio solo presente che per analizzare cosa avviene a livello di syscall bisogna eseguire il debug del programma ed andare a “scartabellare” in quello che succede quando vengono chiamate le funzioni di sistema. Ad un certo punto dell’esecuzione si arriva alla chiamata che ci interessa in quanto è una delle chiamate che, in presenza di EDR, verrebbero intercettate.

Quello che si osserva nel comportamento a runtime è una chiamata alla syscall che utilizza il syscall number “55” che, per la versione di Windows che stiamo usando, corrispondere esattamente a NtCreateFile.


Piccolo approfondimento sulle syscall per capire questo passaggio.

Quando un’applicazione userland deve accedere a risorse gestite dal kernel, come il filesystem, la memoria o i socket di rete, non può farlo direttamente. Il processore lavora in due modalità distinte: user mode (ring 3), dove girano le applicazioni, e kernel mode (ring 0), dove opera il sistema operativo.

Questa separazione è un meccanismo di protezione fondamentale: un processo userland non può leggere o scrivere memoria del kernel, né invocare direttamente le sue funzioni. Questa netta separazione in Windows nasce con i sistemi operativi NT ed è stato ciò che ha iniziato a dare stabilità al sistema riducendo drasticamente i BSOD.

Il meccanismo che permette ai due domini di interagire è la system call: quando un’applicazione deve eseguire un’operazione privilegiata, carica in un registro dedicato (eax su architettura x64 come di vede anche nello screenshot) un numero intero che identifica univocamente l’operazione che si vuole eseguire, poi esegue l’istruzione syscall. Il processore salva il contesto corrente, eleva il privilegio a ring 0 e trasferisce il controllo al kernel che legge il numero in eax per determinare quale operazione eseguire.

Questo numero, il syscall number, è la chiave del meccanismo. Microsoft non rilascia documentazione ufficiale pubblica sui syscall number, inoltre questo numero varia tra versioni del sistema operativo. NtCreateFile, ad esempio, ha syscall number 0x52 su Windows Server 2012 R2, 0x53 su Windows 10 fino alla versione 21H2, e 0x55 su Windows 10 22H2 e Windows 11. Microsoft si riserva il diritto di modificarlo ad ogni release, motivo per cui il codice che lo usa (il nostro programmino) deve recuperarlo dinamicamente a runtime invece di hardcodarlo. Esistono diversi progetti che tengono traccia dei syscall number in quanto una delle tecniche di bypass, che personalmente non amo in quanto la trovo parecchio macchinosa, prevedere la chiamata diretta alla funzione desiderata usando il syscall number specifico per il sistema operativo su cui stiamo facendo girare il nostro programma.


Ora che abbiamo visto cosa succede senza EDR, su un sistema di cui non abbiamo quindi nessuna telemetria, vediamo cosa succedere su una macchina dove abbiamo installato un agente che utilizza il fuction-hooking per raccogliere la telemetria del sistema.

Nota: per questo test non possiamo usare Elastic in quanto l’agente utilizza altre tecniche per registrare la telemetria considerate più difficili da aggirare.

Lab Test: hooking

Il comportamento di Elastic lo riprenderò più aventi in quanto per questo test ci è poco utile, infatti se verifichiamo il debug del nostro programma di test troveremo esattamente la stessa situazione – no hooking – ma il dato di telemetria è comunque disponibile sul SIEM.

Per osservare quello che avviene quando si esegue una syscall in un sistema in cui l’EDR fa massiccio uso del function hooking bisogna necessariamente selezionare un prodotto che utilizzi abbondantemente questa modalità di registrazione degli eventi ed in particolare, visto il test che stiamo facendo, per la chiamata relativa a NtCreateFile.

Non è importante sapere di che EDR si tratta, ai fini del test uno vale l’altro, ciò che conta è confermare la presenza della jump che intercetta la syscall:

Come ci si aspettava al posto dei comandi che chiamano la syscall troviamo un’istruzione jmp il cui scopo è deviare il programma verso il codice dell’EDR, raccogliere la telemetria e successivamente far proseguire il programma richiamando la syscall originale per consentire la corretta esecuzione.

Come accennavo nell’introduzione del post questo meccanismo permette all’EDR di registrare tutte le chiamate alle syscall ed i relativi parametri, cosa che da ampia visibilità su cosa fanno i processi che vengono avviati sulla macchina. Non si tratta di per se di un meccanismo di protezione, questi dati servono ad avere una visione globale del comportamento dei processi e possono essere correlati con altri dati per comprendere se quello che si sta osservando è una minaccia oppure no.

Aggirare il controllo

Per gli EDR la visibilità sul sistema è tutto: perdere telemetria è la peggio cosa che possa succedere in quanto le regole di detection si basano sulla disponibilità del dato. È quindi abbastanza ovvio che i bad actor siano costantemente alla ricerca di metodologie per aggirare i controlli.

Nel caso del function hooking ci sono diversi metodologie e quella che personalmente ho trovato più elegante prevede l’utilizzo di una copia pulita di ntdll.dll. Considerando che le istruzioni della DLL vengono modificate in memoria (il jmp lo aggiunge l’EDR nel codice caricato in memoria) mentre il file originale in C:\Windows\System32\ntdll.dll non viene modificato, si potrebbe fare in modo che il nostro programma utilizzi le istruzioni della DLL originale invece che della versione patched in memoria.

Il codice che ne deriva è un po’ più complesso del precedente e mi sono fatto aiutare da Claude per renderlo leggibile e commentato al meglio.

Il codice è un po’ lungo e lo trovate nella repo del mio progetto HAPpy nella directory demo.

Su questo programmino ci sarebbero diverse cose da dire ma andiamo troppo fuori tema e rischiamo di aprire una parentesi infinita. Ci basta sapere che codice, oltre a caricare ed utilizzare la DLL da disco, esegue anche la print dei riferimenti della funzione hooked e della funzione “pulita”. Seguendo l’esecuzione del codice con IDA si arriva al punto in cui troviamo la syscall che è un po’ di versa da come l’avevamo vista precedentemente:

Questa volta troviamo direttamente le istruzioni della DLL originale (rif. al primo debug del codice di test):

Ora abbiamo la certezza che il programma ha caricato la DLL da disco con le sue istruzioni originali quindi l’EDR non sarà in grado di intercettare la chiamata alla funzione NtCreateFile e non ci sarà telemetria su questo elemento.

Per osservare meglio il comportamento a runtime ho aggiunto n getchar() prima della chiamata alla funzione CleanNtCreateFile(). In questo modo possiamo avviare il programma che si fermerà, naturalmente, dopo la lettura degli indirizzi di memoria per la funzione NtCreateFile hooked e la funzione NtCreateFile pulita.

Con il programma in pausa “forzata” possiamo riprendere IDA ed agganciare il processo che nel mio caso è test.exe. Sapendo l’indirizzo della funzione NtCreateFile pulita possiamo mettere un breakpoint in questo punto dell’esecuzione (0x400000), in questo modo il debug si fermerà alla funzione pulita prima di eseguirla, cosa che indica che il bypass è avvenuto con successo.

In caso contrario il programma sarebbe andato alla funzione in memoria ed il breakpoint non sarebbe stato eseguito in quanto si trova in un’area che il programma avrebbe ignorato. In questo caso possiamo dire “missione compiuta”: avrete sicuramente riconosciuto il syscall number 55 🙂

Va comunque considerato che questo è un programma di test in laboratorio dove sono presenti diverse syscall, la stessa lettura del file da disco richiede chiamate di sistema che vengono ovviamente intercettate in quanto non gestiste dal codice. Un malware pensato per aggirare il function hooking dovrebbe quindi implementare diverse funzioni pulite da utilizzare e sarebbe comunque vincolato alla lettura delle DLL da disco, evento che verrebbe comunque tracciato.

Conclusioni

È chiaro che la tecnica, per quanto elegante, richiede la scrittura di molto codice ed una profonda conoscenza del funzionamento del sistema operativo. Inoltre, tema che ho in roadmap per i prossimi post, gli EDR moderni non si basano solo sul function hooking per raccogliere telemetria dal sistema operativo, questo è solo uno dei metodi a disposizione e come abbiamo visto è relativamente semplice da aggirare.

Personalmente ho utilizzato questa parte dello studio come la fase di riscaldamento per iniziare a mettere il naso dentro il funzionamento di questi sistemi ed iniziare a comprenderne meccanismi e limiti e con i prossimi step inizieremo ad analizzare le funzioni più evanzate.


文章来源: https://roccosicilia.com/2026/03/09/detection-e-function-hooking/
如有侵权请联系:admin#unsafe.sh