
Call-Stack-Spoofing für Rust
Unwinder bietet eine vollständige Weaponization der SilentMoonWalk-Technik und ermöglicht vollständiges und stabiles Call-Stack-Spoofing in Rust.
Diese Technik weist die folgenden Eigenschaften auf:
Dank an die Ersteller der SilentMoonWalk-Technik:
Und natürlich ein großes Lob an namazso für den Twitter-Thread, der dieses gesamte Projekt inspiriert hat.
Importieren Sie diese Crate in Ihr Projekt, indem Sie die folgende Zeile zu Ihrer cargo.toml hinzufügen und im release-Modus kompilieren:
[dependencies]
unwinder = "=0.1.4"
Die Hauptfunktionalität dieser Crate ist in zwei Makros gekapselt:
call_function!() ermöglicht die Ausführung einer beliebigen Funktion mit einem sauberen Aufrufstapel.indirect_syscall!() führt den angegebenen (indirekten) Syscall mit einem sauberen Aufrufstapel aus.Um eines dieser Makros verwenden zu können, muss der Datentyp std::ffi::c_void importiert werden.
Beide Makros geben einen *mut c_void zurück, der verwendet werden kann, um den von der ausgeführten Funktion zurückgegebenen Wert abzurufen. Detailliertere Informationen finden Sie im Abschnitt Beispiele.
Dieses Makro wird verwendet, um eine beliebige gewünschte Funktion mit einem sauberen Aufrufstapel aufzurufen. Das Makro erwartet die folgenden Parameter:
usize, isize oder als Zeiger übergeben werden.Dieses Makro wird verwendet, um einen beliebigen gewünschten indirekten Syscall mit einem sauberen Aufrufstapel auszuführen. Das Makro erwartet die folgenden Parameter:
Um Argumente verschiedener Typen an diese beiden Makros zu übergeben, müssen die folgenden Überlegungen berücksichtigt werden:
usize konvertiert werden kann (u8-u64, i8-i64, bool usw.), kann direkt an die Makros übergeben werden.&str und String) müssen als Zeiger übergeben werden.ptr::null(), ptr::null_mut() usw.) werden als 0 übergeben (egal ob u8, u16, i32 oder irgendein anderer Typ).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);
Beachten Sie, dass das Makro einen *mut c_void zurückgibt, der direkt in einen HANDLE konvertiert werden kann, da beide Datentypen dieselbe Größe haben. Dadurch kann auf den von OpenProcess zurückgegebenen Wert zugegriffen werden, also auf das neue Handle zum Zielprozess.
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);
Beachten Sie, dass das Makro einen *mut c_void zurückgibt, mit dem der von NtDelayExecution zurückgegebene NTSTATUS abgerufen werden kann.
Der Spoofing-Prozess kann beliebig oft verkettet werden, ohne dass die Größe des Aufrufstapels abnormal zunimmt. Auch der Ausführungsfluss bleibt erhalten. Der folgende Code ist ein Beispiel dafür:
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.
}
}