Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
Strumenti/GitHubGitHub/mgeeky/threadstackspoofer
Frameworks per Penetration TestingFramework di ExploitMemory ForensicsShellcodeAnalisi MalwareRed TeamingSviluppo Payload
GitHubmgeeky/threadstackspoofer

ThreadStackSpoofer

Thread Stack Spoofing - PoC per una tecnica avanzata di evasione in-memory che permette di nascondere meglio l'allocazione di memoria dello shellcode iniettato da scanner e analisti.

Vedi Repository
1.2k19244 anni faRevisionato da Kitploit

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

Thread Stack Spoofing / Call Stack Spoofing PoC

Una implementazione PoC per una tecnica avanzata di evasione in-memory che falsifica lo Stack dei Thread. Questa tecnica permette di bypassare le regole di esame della memoria basate sui thread e nascondere meglio gli shellcode mentre sono in memoria di processo.

Introduzione

Questa è un'implementazione d'esempio per la tecnica Thread Stack Spoofing che mira a eludere Analisti Malware, AV e EDR che cercano riferimenti ai frame dello shellcode nella call stack di un thread esaminato. L'idea è nascondere i riferimenti allo shellcode sulla call stack del thread, mascherando così le allocazioni che contengono il codice del malware.

Questa implementazione, insieme al mio ShellcodeFluctuation, fornisce alla comunità Offensive Security implementazioni di esempio per recuperare il terreno rispetto all'offerta dei prodotti C2 commerciali, in modo da non essere da meno nei nostri tooling Red Team. 💪

L'implementazione è cambiata

L'implementazione attuale differisce pesantemente da quella originariamente pubblicata. Questo perché ho realizzato che esiste un approccio molto più semplice per terminare l'elaborazione della call stack del thread e nascondere i frame relativi allo shellcode, semplicemente scrivendo 0 all'indirizzo di ritorno del primo frame che controlliamo:

root@kitploit:~
void WINAPI MySleep(DWORD _dwMilliseconds)
{
    [...]
    auto overwrite = (PULONG_PTR)_AddressOfReturnAddress();
    const auto origReturnAddress = *overwrite;
    *overwrite = 0;

    [...]
    *overwrite = origReturnAddress;
}

La precedente implementazione, che utilizzava StackWalk64, è accessibile in questo commit c250724.

Questa implementazione è molto più stabile e funziona bene sia in Debug che in Release su due architetture - x64 e x86.

Demo

Ecco come potrebbe apparire una call stack quando NON è falsificata:

not-spoofed

Questa, invece, quando la falsificazione dello stack del thread è abilitata:

spoofed

Sopra possiamo vedere che l'ultimo frame sulla nostra call stack è la nostra callback MySleep. Ci si potrebbe chiedere se ciò porti immediatamente a nuovi IOC? Le regole di caccia possono cercare thread con call stack che non si srotolano nei seguenti punti di ingresso attesi del thread, situati nelle librerie di sistema:

root@kitploit:~
kernel32!BaseThreadInitThunk+0x14
ntdll!RtlUserThreadStart+0x21

Tuttavia la call stack del thread falsificato potrebbe sembrare strana a prima vista; un breve esame del mio sistema ha mostrato che ci sono anche altri thread che non si srotolano fino ai punti di ingresso sopra:

legit call stack

Lo screenshot sopra mostra un thread di Total Commander x64 non modificato. Come possiamo vedere, la sua call stack assomiglia molto alla nostra in termini di frame iniziali della call stack.

Perché dovremmo preoccuparci di falsificare attentamente la nostra call stack quando ci sono processi che mostrano tratti che possiamo semplicemente imitare?

Come funziona?

L'algoritmo approssimativo è il seguente:

  1. Leggere il contenuto dello shellcode dal file.
  2. Acquisire tutti i puntatori a funzione necessari da dbghelp.dll, chiamare SymInitialize
  3. Agganciare kernel32!Sleep puntando alla nostra callback.
  4. Iniettare e lanciare lo shellcode tramite VirtualAlloc + memcpy + CreateThread. Il thread dovrebbe partire dalla nostra funzione runShellcode per evitare che l'Indirizzo di Inizio del Thread punti in un luogo inaspettato e anomalo (come ntdll!RtlUserThreadStart+0x21).
  5. Non appena Beacon tenta di dormire, la nostra callback MySleep viene invocata.
  6. Quindi sovrascriviamo l'ultimo indirizzo di ritorno sullo stack con 0, che dovrebbe effettivamente terminare la call stack.
  7. Infine viene effettuata una chiamata a ::SleepEx per permettere al Beacon di dormire mentre attende ulteriori comunicazioni.
  8. Dopo che Sleep è terminato, ripristiniamo gli indirizzi di ritorno originali delle funzioni precedentemente salvati e l'esecuzione riprende.

Gli indirizzi di ritorno delle funzioni sono sparsi in tutta l'area di memoria dello stack del thread, puntati dal registro RBP/EBP. Per trovarli sullo stack, dobbiamo prima raccogliere i puntatori ai frame, poi dereferenziarli per sovrascriverli:

stack frame

(l'immagine sopra è stata presa dal post di Eli Bendersky intitolato Stack frame layout on x86-64)

root@kitploit:~
	*(PULONG_PTR)(frameAddr + sizeof(void*)) = Fake_Return_Address;

L'implementazione iniziale di ThreadStackSpoofer faceva questo nelle funzioni walkCallStack e spoofCallStack, tuttavia l'implementazione attuale mostra che questi sforzi non sono necessari per mantenere una call stack furtiva.

Esempio di esecuzione

Caso d'uso:

root@kitploit:~
C:\> ThreadStackSpoofer.exe <shellcode> <spoof>

Dove:

  • <shellcode> è il percorso del file shellcode
  • <spoof> quando è 1 o true abiliterà la falsificazione dello stack del thread, qualsiasi altra cosa la disabilita.

Esempio di esecuzione che falsifica la call stack del thread di beacon:

root@kitploit:~
PS D:\dev2\ThreadStackSpoofer> .\x64\Release\ThreadStackSpoofer.exe .\tests\beacon64.bin 1
[.] Lettura dei byte dello shellcode...
[.] Aggancio di kernel32!Sleep...
[.] Iniezione dello shellcode...
[+] Shellcode ora in esecuzione.
[>] Indirizzo di ritorno originale: 0x1926747bd51. Terminazione della call stack...

===> MySleep(5000)

[<] Ripristino dell'indirizzo di ritorno originale...
[>] Indirizzo di ritorno originale: 0x1926747bd51. Terminazione della call stack...

===> MySleep(5000)

[<] Ripristino dell'indirizzo di ritorno originale...
[>] Indirizzo di ritorno originale: 0x1926747bd51. Terminazione della call stack...

Come lo uso?

Guarda il codice e la sua implementazione, comprendi il concetto e reimplementalo all'interno dei tuoi Loader di Shellcode che usi per le tue attività Red Team. Questa è un'ulteriore tecnica di evasione avanzata in-memory che aumenta le possibilità del tuo Team di non essere scoperto da Antivirus, EDR e Analisti Malware che esaminano i tuoi impianti.

Mentre sviluppi il tuo loader di shellcode avanzato, potresti anche voler implementare:

  • Crittografia dell'Heap del Processo - prendi ispirazione da questo post: Hook Heaps and Live Free - che può permetterti di eludere estrattori di configurazione di Beacon come BeaconEye
  • Cambiare la protezione delle pagine di memoria del tuo Beacon in RW (da RX/RWX) e crittografarne il contenuto - usando la tecnica Shellcode Fluctuation - proprio prima di dormire (che potrebbe eludere scanner come Moneta o pe-sieve)
  • Pulire eventuali residui del Reflective Loader per evitare rilevamenti basati su firme in-memory
  • Sganciare tutto ciò che potresti aver agganciato (come AMSI, ETW, WLDP) prima di dormire e poi riagganciarlo successivamente.

In realtà questa non è (ancora) una vera falsificazione dello stack

Come mi è stato fatto notare, la tecnica qui non è ancora veramente all'altezza del suo nome di falsificatore dello stack. Poiché stiamo semplicemente sovrascrivendo gli indirizzi di ritorno sullo stack del thread, non stiamo falsificando le restanti aree dello stack stesso. Inoltre stiamo lasciando la nostra call stack non srotolabile, rendendola anomala poiché il sistema non sarà in grado di percorrere correttamente l'intera catena dei frame della call stack.

Tuttavia sono consapevole di queste limitazioni; al momento l'ho lasciata così perché mi interessava principalmente eludere scanner automatizzati che potrebbero iterare sui processi, enumerare i loro thread, percorrere gli stack di quei thread e individuare eventuali indirizzi di ritorno che puntano a memoria non immagine (come SEC_PRIVATE - quella allocata dinamicamente da VirtuaAlloc e simili). Un analista malware concentrato noterebbe immediatamente l'anomalia e considererebbe il thread piuttosto insolito, dando la caccia al nostro impianto. Ne sono più che sicuro. Tuttavia, non credo che gli scanner automatizzati odierni come AV/EDR abbiano euristiche implementate che percorrano effettivamente lo stack di ogni thread per verificare se è srotolabile ¯\_(ツ)_/¯.

Sicuramente questo progetto (e l'implementazione commerciale trovata nei framework C2) fornisce argomenti ai vendor di AV & EDR per considerare l'implementazione di euristiche appropriate che coprano questa tecnica di evasione così innovativa.

Per migliorare questa tecnica, si può puntare a un vero Thread Stack Spoofer inserendo frame di stack fittizi accuratamente costruiti, stabiliti in un processo di srotolamento inverso. Leggi di più su questa idea qui sotto.

Implementare un vero Thread Stack Spoofer

Una conversazione di ore con namazso mi ha insegnato che, per puntare a un vero falsificatore dello stack del thread, dovremmo invertire il processo di srotolamento della call stack su x64. Innanzitutto, bisogna prendere atto del processo di srotolamento dello stack spiegato in (a) qui sotto. Quando il sistema attraversa la call stack del thread su architettura x64, non si basa semplicemente sugli indirizzi di ritorno sparsi nello stack del thread, ma piuttosto:

  1. prende l'indirizzo di ritorno
  2. tenta di identificare la funzione che contiene quell'indirizzo (con RtlLookupFunctionEntry)
  3. Quella funzione restituisce le strutture RUNTIME_FUNCTION, UNWIND_INFO e UNWIND_CODE. Queste strutture descrivono dove sono l'indirizzo di inizio della funzione, l'indirizzo di fine e dove sono tutte le sequenze di codice che modificano RBP o RSP.
  4. Il sistema ha bisogno di conoscere tutte le modifiche ai puntatori di stack e frame avvenute in ogni funzione lungo la Call Stack per poi annullare virtualmente queste modifiche e ripristinare virtualmente i puntatori dello stack quando si verifica una chiamata al frame della call stack elaborato (questo è implementato in RtlVirtualUnwind)
  5. Il sistema elabora tutti gli UNWIND_CODE che la funzione esaminata presenta per calcolare con precisione la posizione dell'indirizzo di ritorno di quel frame e il valore del puntatore allo stack.
  6. Attraverso questa emulazione, il sistema è in grado di percorrere la catena della call stack verso il basso e "srotolare" efficacemente la call stack.

Per interferire con questo processo avremmo bisogno di invertirlo avendo la nostra forma invertita di RtlVirtualUnwind. Dovremmo iterare sulle funzioni definite in un modulo (diciamo kernel32), scansionare i codici UNWIND_CODE di ogni funzione ed emularli attentamente all'indietro (rispetto a RtlVirtualUnwind e precisamente RtlpUnwindPrologue) per trovare le posizioni sullo stack dove mettere i nostri indirizzi di ritorno fittizi.

namazso menziona la necessità di introdurre 3 frame di stack fittizi per cucire bene la call stack:

  1. Un frame "desincronizzato" (consideralo come un gadget-frame) che si srotola diversamente rispetto al chiamante della nostra MySleep (avendo un UWOP - codice di operazione di srotolamento diverso). Lo facciamo cercando in tutte le funzioni di un modulo, esaminando i loro UWOP, calcolando quanto grande dovrebbe essere il frame fittizio. Questo frame deve avere UWOPS diversi da quelli del chiamante della nostra MySleep.
  2. Il prossimo frame che vogliamo trovare è una funzione che si srotola facendo un pop in RBP dallo stack - fondamentalmente tramite codice UWOP_PUSH_NONVOL.
  3. Terzo frame, abbiamo bisogno di una funzione che ripristini RSP da RBP tramite il codice UWOP_SET_FPREG

L'RSP ripristinato deve essere impostato con l'RSP preso da dove il flusso di controllo è entrato nella nostra MySleep in modo che tutti i nostri frame diventino nascosti, come risultato dello srotolamento del terzo gadget lì.

Per iniziare il processo, si può iterare sul .pdata dell'eseguibile dereferenziando la voce della directory dei dati IMAGE_DIRECTORY_ENTRY_EXCEPTION. Considera l'esempio qui sotto:

root@kitploit:~
    ULONG_PTR imageBase = (ULONG_PTR)GetModuleHandleA("kernel32");
    PIMAGE_NT_HEADERS64 pNthdrs = PIMAGE_NT_HEADERS64(imageBase + PIMAGE_DOS_HEADER(imageBase)->e_lfanew);

    auto excdir = pNthdrs->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION];
    if (excdir.Size == 0 || excdir.VirtualAddress == 0)
        return;

    auto begin = PRUNTIME_FUNCTION(excdir.VirtualAddress + imageBase);
    auto end = PRUNTIME_FUNCTION(excdir.VirtualAddress + imageBase + excdir.Size);

    UNWIND_HISTORY_TABLE mshist = { 0 };
    DWORD64 imageBase2 = 0;

    PRUNTIME_FUNCTION currFrame = RtlLookupFunctionEntry(
        (DWORD64)caller,
        &imageBase2,
        &mshist
    );

    UNWIND_INFO *mySleep = (UNWIND_INFO*)(currFrame->UnwindData + imageBase);
    UNWIND_CODE myFrameUwop = (UNWIND_CODE)(mySleep->UnwindCodes[0]);

    log("1. MySleep RIP UWOP: ", myFrameUwop.UnwindOpcode);

    for (PRUNTIME_FUNCTION it = begin; it < end; ++it)
    {
        UNWIND_INFO* unwindData = (UNWIND_INFO*)(it->UnwindData + imageBase);
        UNWIND_CODE frameUwop = (UNWIND_CODE)(unwindData->UnwindCodes[0]);

        if (frameUwop.UnwindOpcode != myFrameUwop.UnwindOpcode)
        {
            // Trovata funzione candidata per un gadget frame di desincronizzazione

        }
    }

Il processo è un po' contorto, ma si riduce a invertire il processo di srotolamento della call stack del thread sostituendo frame arbitrari dello stack con altri accuratamente selezionati, in un approccio simile a ROP.

Questo PoC non replica questo algoritmo, perché la mia comprensione attuale mi permette di accettare la call stack che termina su un frame basato su EXE e non voglio complicare eccessivamente né i miei loader di shellcode né questo PoC. Lascio l'esercizio di implementare questo e condividerlo pubblicamente al lettore interessato. O forse mi siederò e proverò a farlo io stesso, dato un po' più di tempo libero :)

Ulteriori informazioni:

  • a) Gestione delle eccezioni x64 - Spiegazione del processo di srotolamento dello stack
  • b) Implementazione di esempio di RtlpUnwindPrologue e RtlVirtualUnwind
  • c) Sezione .pdata
  • d) Un'altra implementazione di esempio di RtlpUnwindPrologue

Parola di cautela

Se hai intenzione di aggiungere questa funzionalità ai tuoi loader di shellcode / tooling, assicurati di EVITARE di sganciare kernel32.dll. Un tentativo di sganciare kernel32 ripristinerà la funzionalità originale di Sleep, impedendo che la nostra callback venga chiamata. Se la nostra callback non viene chiamata, il thread non sarà in grado di falsificare il proprio stack da solo.

Se è questo che desideri, potresti aver bisogno di eseguire un altro thread di watchdog, assicurandoti che lo stack del thread del Beacon venga falsificato ogni volta che dorme.

Se stai usando Cobalt Strike e un BOF unhook-bof di Raphael Mudge, assicurati di dare un'occhiata alla mia Pull Request che aggiunge un parametro opzionale al BOF per specificare le librerie che non devono essere sganciate.

In questo modo puoi mantenere i tuoi agganci in kernel32:

root@kitploit:~
beacon> unhook kernel32
[*] Esecuzione unhook.
    Salterò questi moduli: wmp.dll, kernel32.dll
[+] host chiamato a casa, inviati: 9475 byte
[+] ricevuto output:
ntdll.dll            <.text>
Unhook completato.

unhook-bof modificato con opzione per ignorare moduli specificati


Considerazione finale

Questo PoC è stato progettato per funzionare con gli shellcode di Beacon di Cobalt Strike. È noto che Beacon chiama kernel32!Sleep per attendere ulteriori istruzioni dal suo C2. Questo loader sfrutta questo fatto agganciando Sleep per eseguire le sue operazioni di manutenzione.

Questa implementazione potrebbe non funzionare con altri shellcode sul mercato (come Meterpreter) se non usano Sleep per raffreddarsi. Poiché questo è solo un Proof of Concept che mostra la tecnica, non ho intenzione di aggiungere supporto per nessun altro framework C2.

Quando comprendi il concetto, sarai sicuramente in grado di tradurlo nei requisiti del tuo shellcode e adattare la soluzione a tuo vantaggio.

Per favore, non aprire issue su Github relative a "questo codice non funziona con lo shellcode XYZ", verranno chiuse immediatamente.


☕ Mostra Supporto ☕

Questo e altri progetti sono il risultato di notti insonni e molto duro lavoro. Se ti piace quello che faccio e apprezzi che restituisco sempre qualcosa alla comunità, Considera l'idea di offrirmi un caffè (o meglio una birra) solo per dire grazie! 💪


Autore

root@kitploit:~
   Mariusz Banach / mgeeky, 21
   <mb [at] binary-offensive.com>
   (https://github.com/mgeeky)
Scarica lo strumento