
Spoofing de pile d'appels pour Rust
Unwinder fournit une weaponization complète de la technique SilentMoonWalk, permettant d'obtenir un spoofing complet et stable de la pile d'appels en Rust.
Cette technique présente les caractéristiques suivantes :
Merci aux créateurs de la technique SilentMoonWalk :
Et bien sûr, un grand merci à namazso pour le fil Twitter qui a inspiré tout ce projet.
Importez cette crate dans votre projet en ajoutant la ligne suivante à votre cargo.toml et compilez en mode release :
[dependencies]
unwinder = "=0.1.4"
La fonctionnalité principale de cette crate est encapsulée dans deux macros :
call_function!() permet d'exécuter toute fonction arbitraire avec une pile d'appels propre.indirect_syscall!() exécute le syscall (indirect) spécifié avec une pile d'appels propre.Pour utiliser l'une de ces macros, il est nécessaire d'importer le type std::ffi::c_void.
Les deux macros retournent un *mut c_void qui peut être utilisé pour récupérer la valeur retournée par la fonction exécutée. Des informations plus détaillées se trouvent dans la section des exemples.
Cette macro est utilisée pour appeler n'importe quelle fonction souhaitée avec une pile d'appels propre. La macro attend les paramètres suivants :
usize, isize ou un pointeur.Cette macro est utilisée pour effectuer n'importe quel syscall indirect souhaité avec une pile d'appels propre. La macro attend les paramètres suivants :
Afin de passer des arguments de différents types à ces deux macros, les considérations suivantes doivent être prises en compte :
usize (u8-u64, i8-i64, bool, etc.) peut être passé directement aux macros.&str et String) doivent être passées sous forme de pointeur.ptr::null(), ptr::null_mut(), etc.) sont passés comme 0 (peu importe qu'il s'agisse d'un u8, u16, i32 ou autre).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);
Remarquez que la macro retourne un *mut c_void qui peut être directement converti en HANDLE, car les deux types ont la même taille. Cela permet d'accéder à la valeur retournée par OpenProcess, qui est le nouveau handle du processus cible.
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);
Remarquez que la macro retourne un *mut c_void qui peut être utilisé pour récupérer le NTSTATUS retourné par NtDelayExecution.
Le processus de spoofing peut être concaténé un nombre illimité de fois sans augmentation anormale de la taille de la pile d'appels. Le flux d'exécution est également préservé. Le code suivant en est un exemple :
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.
}
}
Si vous définissez le deuxième paramètre sur true (pour les deux macros), le processus de spoofing tentera de conserver le cadre de l'adresse de démarrage du thread dans la pile d'appels afin d'accroître la légitimité.