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
Unwinder — Call stack spoofing for Rust | Kitploit
Strumenti/GitHubGitHub/kudaes/unwinder
IDS/IPS EvasionPost-ExploitationRed TeamingPayload DevelopmentAdversarial Attack
GitHubkudaes/unwinder

Unwinder

Call stack spoofing for Rust

Vedi Repository
384361 anno 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

Contenuto

  • SilentMoonWalk
    • Descrizione
    • Crediti
    • Utilizzo
      • Macro call_function!()
      • Macro indirect_syscall!()
      • Passaggio dei parametri
    • Esempi
      • Chiamare kernel32.dll!Sleep()
      • Chiamare kernel32.dll!OpenProcess()
      • Chiamare NtDelayExecution() come syscall indiretta
      • Concatenare chiamate di macro
    • Considerazioni
      • Frame iniziale
      • PoC
  • Stack replacement
    • Descrizione
    • Utilizzo
    • Esempio pratico
    • Osservazioni

SilentMoonWalk

Descrizione

Unwinder fornisce una piena weaponizzazione della tecnica SilentMoonWalk, consentendo di ottenere uno spoofing completo e stabile della call stack in Rust.

Questa tecnica presenta le seguenti caratteristiche:

  • Supporto per eseguire qualsiasi funzione arbitraria con un massimo di 11 parametri.
  • Supporto per eseguire syscall indirette (senza allocazioni aggiuntive nell'heap) con un massimo di 11 parametri.
  • La crate consente di recuperare il valore restituito dalle funzioni chiamate tramite essa.
  • Il processo di spoofing può essere concatenato un numero qualsiasi di volte senza aumentare la dimensione della call stack.
  • Viene utilizzata la TLS per aumentare l'efficienza durante il processo di spoofing.
  • dinvoke_rs viene utilizzata per effettuare qualsiasi chiamata API Windows richiesta dalla crate.

Crediti

un ringraziamento ai creatori della tecnica SilentMoonWalk:

  • KlezVirus
  • Waldo-IRC
  • Trickster0

E naturalmente un enorme ringraziamento a namazso per il thread su Twitter che ha ispirato l'intero progetto.

Utilizzo

Importa questa crate nel tuo progetto aggiungendo la seguente riga al tuo cargo.toml e compila in modalità release:

root@kitploit:~
[dependencies]
unwinder = "=0.1.4"

La funzionalità principale di questa crate è stata racchiusa in due macro:

  • La macro call_function!() consente di eseguire qualsiasi funzione arbitraria con una call stack pulita.
  • La macro indirect_syscall!() esegue la syscall indiretta specificata con una call stack pulita.

Per utilizzare una qualsiasi di queste macro è necessario importare il tipo di dato std::ffi::c_void.

Entrambe le macro restituiscono un *mut c_void che può essere utilizzato per recuperare il valore restituito dalla funzione eseguita. Maggiori dettagli nella sezione esempi.

Macro call_function

Questa macro viene utilizzata per chiamare qualsiasi funzione desiderata con una call stack pulita. La macro richiede i seguenti parametri:

  • Il primo parametro è l'indirizzo di memoria da chiamare dopo lo spoofing della call stack. Questo parametro deve essere passato come usize, isize o puntatore.
  • Il secondo parametro è un bool che indica se mantenere o meno il frame della funzione iniziale. Se non sei sicuro, impostalo a false, cosa che garantisce sempre una buona call stack.
  • I parametri successivi sono gli argomenti da inviare alla funzione una volta che la call stack è stata spoofata.

Macro indirect_syscall

Questa macro viene utilizzata per eseguire qualsiasi syscall indiretta desiderata con una call stack pulita. La macro richiede i seguenti parametri:

  • Il primo parametro è una stringa che contiene il nome della funzione NT di cui si desidera eseguire la syscall.
  • Il secondo parametro è un bool che indica se mantenere o meno il frame della funzione iniziale. Se non sei sicuro, impostalo a false, cosa che garantisce sempre una buona call stack.
  • I parametri successivi sono gli argomenti da inviare alla funzione NT.

Passaggio dei parametri

Per passare argomenti di tipi diversi a queste due macro, è necessario tenere in considerazione le seguenti indicazioni:

  • Qualsiasi tipo di dato base convertibile in usize (u8-u64, i8-i64, bool, ecc.) può essere passato direttamente alle macro.
  • Le struct e le union di dimensione 8, 16, 32 o 64 bit vengono passate come se fossero interi della stessa dimensione.
  • Le struct e le union con dimensione superiore a 64 bit devono essere passate come puntatore.
  • Le stringhe (&str e String) devono essere passate come puntatore.
  • I puntatori nulli (ptr::null(), ptr::null_mut(), ecc.) vengono passati come 0 (indipendentemente dal fatto che siano u8, u16, i32 o qualsiasi altro).
  • I parametri a virgola mobile e a doppia precisione non sono attualmente supportati.
  • Qualsiasi altro tipo di dato deve essere passato come puntatore.

Esempi

Chiamare Sleep

root@kitploit:~
let k32 = dinvoke_rs::dinvoke::get_module_base_address("kernel32.dll");
let sleep = dinvoke_rs::dinvoke::get_function_address(k32, "Sleep"); // Memory address of kernel32.dll!Sleep() 
let miliseconds = 1000i32;
unwinder::call_function!(sleep, false, miliseconds);

Chiamare OpenProcess

root@kitploit:~
let k32 = dinvoke_rs::dinvoke::get_module_base_address("kernel32.dll"); 
let open_process: isize = dinvoke_rs::dinvoke::get_function_address(k32, "Openprocess");
let desired_access: u32 = 0x1000;
let inherit = 0i32;
let pid = 20628i32;
let handle = unwinder::call_function!(open_process, false, desired_access, inherit, pid); // returns *mut c_void
let handle: HANDLE = std::mem::transmute(handle);
println!("Handle id: {:x}", handle.0);

Nota che la macro restituisce un *mut c_void che può essere convertito direttamente in un HANDLE poiché entrambi i tipi di dato hanno la stessa dimensione. Questo consente di accedere al valore restituito da OpenProcess, ovvero il nuovo handle al processo target.

Chiamare NtDelayExecution come syscall indiretta

root@kitploit:~
let large = 0x8000000000000000 as u64; // Sleep indefinitely
let large: *mut i64 = std::mem::transmute(&large);
let alertable = false;
let ntstatus = unwinder::indirect_syscall!("NtDelayExecution", false, alertable, large); // returns *mut c_void
println!("ntstatus: {:x}", ntstatus as i32);

Nota che la macro restituisce un *mut c_void che può essere utilizzato per recuperare l'NTSTATUS restituito da NtDelayExecution.

Concatenare chiamate di macro

Il processo di spoofing può essere concatenato un numero qualsiasi di volte senza un incremento anomalo della dimensione della call stack. Anche il flusso di esecuzione verrà preservato. Il codice seguente è un esempio:

root@kitploit:~
fn main()
{
	function_a();
}

fn function_a()
{
	unsafe
	{
		let func_b = function_b as usize;
		call_function!(func_b, false);
		println!("function_a done.");
	}
}

fn function_b()
{
	unsafe
	{
		let func_c = function_c as usize;
		call_function!(func_c, false);
		println!("function_b done.")
	}
}

fn function_c()
{
	unsafe
	{
		let large = 0x0000000000000000 as u64; // Don't sleep so we return to function_b, allowing to check the execution flow preservation.
		let large: *mut i64 = std::mem::transmute(&large);
		let alertable = false;
		let ntstatus = unwinder::indirect_syscall!("NtDelayExecution", false, alertable, large);
		println!("ntstatus: {:x}", (ntstatus as usize) as i32); //NTSTATUS is a i32, although that second casting is not really required in this case.
	}
}

Considerazioni

Frame iniziale

Se imposti il secondo parametro a true (in entrambe le macro), il processo di spoofing proverà a mantenere nella call stack il frame dell'indirizzo di inizio del thread per aumentare la legittimità.

Call stack spoofata mantenendo il modulo principale.

A volte, la funzione di inizio del thread non esegue una call a una funzione successiva (ad esempio viene eseguita un'istruzione jmp), il che significa che non viene pushato alcun indirizzo di ritorno nello stack. In quello scenario (e anche se imposti il secondo parametro a false), la call stack spoofata inizierà dal frame di BaseThreadInitThunk.

Call stack spoofata senza modulo principale.

PoC

Per testare l'implementazione della tecnica, è stato utilizzato PE-sieve con il flag /threads. I risultati del test mostrano come l'ispezione della call stack non riveli la presenza del payload quando vengono utilizzate le funzionalità di questa crate. Come si può vedere nella seconda immagine, il payload viene rilevato quando unwinder non viene utilizzato.

Risultati di PE-sieve quando unwinder viene utilizzato. Risultati di PE-sieve quando unwinder non viene utilizzato.

Stack replacement

Descrizione della tecnica

Questa è un'alternativa a SilentMoonWalk per lo spoofing della call stack che consente di mantenere una call stack pulita durante l'esecuzione del programma. L'idea principale alla base di questa tecnica è che ogni funzione chiamata all'interno del modulo si occupa dell'indirizzo di ritorno precedentemente pushato, trovando a runtime una funzione legittima con la stessa dimensione del frame dell'indirizzo di ritorno da spoofare. Una volta individuata una funzione legittima con la stessa dimensione del frame, viene calcolato un offset al suo interno e l'indirizzo finale viene utilizzato per sostituire l'ultimo indirizzo di ritorno, nascondendo qualsiasi voce anomala nella call stack e mantenendola unwindable. L'indirizzo di ritorno originale viene memorizzato da unwinder e viene riposizionato al posto giusto nello stack prima dell'esecuzione di un'istruzione di ritorno, consentendo di continuare il normale flusso del programma.

Sostituzione dello stack

Questa è una funzionalità sperimentale che, sebbene sia completamente funzionante, è ancora in fase di sviluppo e ricerca, quindi assicurati di testare il tuo codice se decidi di integrarla.

Come usarla

Per utilizzare la funzionalità di sostituzione dello stack, aggiungi la seguente riga al tuo cargo.toml e compila in modalità release:

root@kitploit:~
[dependencies]
unwinder = {version = "0.1.4", features = ["Experimental"]}

La funzionalità principale di questa caratteristica è stata racchiusa nelle seguenti macro:

  • La coppia di macro start_stack_replacement!()/end_replacement!() indica a unwinder di avviare/terminare il processo di sostituzione dello stack. Queste due macro devono essere chiamate nel punto di ingresso del codice (ad esempio nelle funzioni esportate della dll).
  • La coppia di macro replace_and_continue!()/restore!() esegue la sostituzione/ripristino dell'ultimo indirizzo di ritorno.
  • Infine, la coppia di macro replace_and_call!()/replace_and_syscall!() viene utilizzata per eseguire la sostituzione dello stack quando si vogliono chiamare funzioni al di fuori del modulo corrente (ad esempio quando si usa l'API Windows o si chiama codice di altre dll). Entrambe queste macro restituiscono un *mut c_void contenente il valore restituito dalla funzione chiamata in questo modo (cioè funzionano allo stesso modo descritto per le macro call_function e indirect_syscall usate per eseguire SilentMoonWalk).

Per usare queste macro è necessario importare il tipo di dato std::ffi::c_void. Tutte le funzioni che utilizzano una di queste macro dovrebbero essere marcate con gli attributi #[no_mangle] o #[inline(never)] per impedire al compilatore Rust di inlinearle durante il processo di ottimizzazione.

Prima di addentrarci in un esempio pratico che mostra come usare tutto questo, diamo una rapida occhiata alla coppia di macro replace_and_call/replace_and_syscall e a come passare loro gli argomenti previsti.

replace_and_call

Questa macro viene utilizzata per chiamare qualsiasi funzione desiderata al di fuori del modulo corrente con una call stack pulita, utilizzando la sostituzione dello stack. La macro richiede i seguenti parametri:

  • Il primo parametro è l'indirizzo di memoria della funzione da chiamare. Questo parametro deve essere passato come usize, isize o puntatore.
  • I parametri successivi sono gli argomenti da inviare alla funzione specificata. Seguono le stesse regole indicate nella sezione Passaggio dei parametri.

replace_and_syscall

Questa macro viene utilizzata per eseguire qualsiasi syscall indiretta desiderata con una call stack pulita, utilizzando la sostituzione dello stack. La macro richiede i seguenti parametri:

  • Il primo parametro è una stringa che contiene il nome della funzione NT di cui si desidera eseguire la syscall.
  • I parametri successivi sono gli argomenti da inviare alla funzione NT. Seguono le stesse regole indicate nella sezione Passaggio dei parametri.

Esempio

Penso che il modo migliore per mostrare come vengono usate queste macro sia un esempio pratico. Supponiamo di creare una dll che verrà iniettata riflessivamente in memoria. Questa dll esporterà due funzioni ExportA e ExportB, quindi considereremo queste due funzioni come punti di ingresso del modulo. Entrambe devono chiamare la macro start_stack_replacement subito all'inizio e devono anche chiamare la macro inversa end_replacement prima di restituire. La macro start_stack_replacement si aspetta come argomento l'indirizzo di base del modulo, oppure puoi passare 0 se non conosci quell'indirizzo a runtime; la macro proverà a scoprirlo da sola.

root@kitploit:~
#[no_mangle]
fn ExportedA(base_address: usize) -> bool
{
    unwinder::start_replacement!(base_address);
    ...
    unwinder::end_replacement!();

    true
}

#[no_mangle]
fn ExportedB() -> bool
{
    unwinder::start_replacement!(0);
    ...
    unwinder::end_replacement!();

    true
}

Avviare il processo di sostituzione dello stack comporta la creazione manuale di una nuova stack che verrà utilizzata fino alla chiamata della macro end_replacement. L'immagine seguente illustra cosa succede dietro le quinte:

Sostituzione dello stack

Anche se teoricamente non sarebbe necessario creare una nuova stack da zero, ho deciso di implementare il processo in questo modo per garantire stabilità e prevenire qualsiasi rottura.

Ora, supponiamo che la nostra funzione ExportedA faccia diverse chiamate ad altre due funzioni interne. Queste due funzioni interne sono responsabili della sostituzione/ripristino dell'indirizzo di ritorno originale che punterà a qualche punto all'interno di ExportedA, spezzando la call stack a meno che non ce ne occupiamo noi. Questo processo di sostituzione implica avvolgere il codice della nostra funzione interna tra le macro replace_and_continue e restore:

root@kitploit:~
#[no_mangle]
fn ExportedA(base_address: usize) -> bool
{
    unwinder::start_replacement!(base_address);
    let ret_a = internal_a();
    let ret_b = internal_b(ret_a);
    unwinder::end_replacement!();

    ret_b
}

#[inline(never)] // This attribute is mandatory
fn internal_a() -> bool
{
    unwinder::replace_and_continue();
    ...
    unwinder::restore();
    
    some_value
} 

#[inline(never)] // This attribute is mandatory
fn internal_b(value: bool) -> bool
{
    unwinder::replace_and_continue();
    ...
    unwinder::restore();
    
    some_value
} 

Infine, entrambe le funzioni internal_a e internal_b fanno uso di alcune funzionalità dell'API Windows. Per mantenere la call stack unwindable, queste chiamate dovrebbero essere effettuate tramite le macro replace_and_call (chiamata normale) o replace_and_syscall (syscall indiretta).

root@kitploit:~
#[no_mangle] // This attribute is mandatory
fn ExportedA(base_address: usize) -> bool
{
    unwinder::start_replacement!(base_address);
    let ret_a = internal_a();
    let ret_b = internal_b(ret_a);
    unwinder::end_replacement!();

    ret_b
}

#[inline(never)] // This attribute is mandatory
fn internal_a() -> bool
{
    unwinder::replace_and_continue();
    ...
    let module_name = "advapi32.dll";
    let module_name = CString::new(module_name.to_string()).expect("");
    let module_name_ptr: *mut u8 = std::mem::transmute(module_name.as_ptr());
    let k32 = dinvoke_rs::dinvoke::get_module_base_address("kernel32.dll");
    let load_library = dinvoke_rs::dinvoke::get_function_address(k32, "LoadLibraryA");
    let ret = unwinder::replace_and_call!(load_library, module_name_ptr); // Load a dll with an unwindable call stack
    println!("advapi.dll base address: 0x{:x}", ret as usize);
    ...
    unwinder::restore();
    
    some_value
} 

#[inline(never)] // This attribute is mandatory
fn internal_b(value: bool) -> bool
{
    unwinder::replace_and_continue();
    ...
    let large = 0xFFFFFFFFFF676980 as u64; // Sleep one second
    let large: *mut i64 = std::mem::transmute(&large);
    let alertable = false;
    let ntstatus = unwinder::replace_and_syscall!("NtDelayExecution", alertable, large);
    println!("ntstatus: {:x}", ntstatus as usize);
    ...
    unwinder::restore();
    
    some_value
} 

Osservazioni

Poiché questa è una funzionalità in via di sviluppo, occorre tenere in considerazione alcune cose:

  • Se rimuovi gli header del tuo PE durante il processo di caricamento, devi passare alla macro start_stack_replace l'indirizzo di base del modulo. Al momento non sarà in grado di trovarlo da sola (verrà risolto nel prossimo aggiornamento).
  • Nel caso te lo stessi chiedendo, la sostituzione dello stack utilizza la stessa combinazione di jmp rbx + frame di occultamento della tecnica SilentMoonWalk. Questo accade solo quando si usano le macro replace_and_call e replace_and_syscall ed è previsto che venga modificato nel prossimo aggiornamento.
  • Sia la macro replace_and_call che la macro replace_and_syscall restituiscono un *mut c_void che può essere utilizzato per recuperare il valore restituito dalla funzione eseguita tramite esse. Questo è lo stesso comportamento descritto per le macro call_function e indirect_syscall.
  • Le macro replace_and_call e replace_and_syscall accettano fino a 11 argomenti.

Per favore segnalami qualsiasi bug possa sorgere utilizzando questa funzionalità.

Scarica lo strumento