
Spoofing dello stack di chiamate per Rust
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:
un ringraziamento ai creatori della tecnica SilentMoonWalk:
E naturalmente un enorme ringraziamento a namazso per il thread su Twitter che ha ispirato l'intero progetto.
Importa questa crate nel tuo progetto aggiungendo la seguente riga al tuo cargo.toml e compila in modalità release:
[dependencies]
unwinder = "=0.1.4"
La funzionalità principale di questa crate è stata racchiusa in due macro:
call_function!() consente di eseguire qualsiasi funzione arbitraria con una call stack pulita.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.
Questa macro viene utilizzata per chiamare qualsiasi funzione desiderata con una call stack pulita. La macro richiede i seguenti parametri:
usize, isize o puntatore.Questa macro viene utilizzata per eseguire qualsiasi syscall indiretta desiderata con una call stack pulita. La macro richiede i seguenti parametri:
Per passare argomenti di tipi diversi a queste due macro, è necessario tenere in considerazione le seguenti indicazioni:
usize (u8-u64, i8-i64, bool, ecc.) può essere passato direttamente alle macro.&str e String) devono essere passate come puntatore.ptr::null(), ptr::null_mut(), ecc.) vengono passati come 0 (indipendentemente dal fatto che siano u8, u16, i32 o qualsiasi altro).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);
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.
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.
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:
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.
}
}
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à.