
Трассировка стека вызовов для Rust
Unwinder предоставляет полноценную реализацию техники SilentMoonWalk, позволяющую добиться полного и стабильного спуфинга стека вызовов в Rust.
Данная техника обладает следующими особенностями:
Благодарность создателям техники SilentMoonWalk:
И, конечно, огромная благодарность namazso за тред в Twitter, который вдохновил весь этот проект.
Чтобы подключить этот крейт к вашему проекту, добавьте следующую строку в ваш cargo.toml и выполните сборку в режиме release:
[dependencies]
unwinder = "=0.1.4"
Основная функциональность этого крейта обёрнута в два макроса:
call_function!() позволяет выполнять любую произвольную функцию с чистым стеком вызовов.indirect_syscall!() выполняет указанный (непрямой) системный вызов с чистым стеком вызовов.Для использования любого из этих макросов необходимо импортировать тип данных std::ffi::c_void.
Оба макроса возвращают *mut c_void, который можно использовать для получения значения, возвращённого выполненной функцией. Более подробная информация приведена в разделе с примерами.
Этот макрос используется для вызова любой нужной функции с чистым стеком вызовов. Макрос ожидает следующие параметры:
usize, isize или указатель.bool), указывающее, нужно ли сохранять кадр стартовой функции. Если вы не уверены в этом, установите его в false — это всегда гарантирует корректный стек вызовов.Этот макрос используется для выполнения любого нужного непрямого системного вызова с чистым стеком вызовов. Макрос ожидает следующие параметры:
bool), указывающее, нужно ли сохранять кадр стартовой функции. Если вы не уверены в этом, установите его в false — это всегда гарантирует корректный стек вызовов.Чтобы передавать этим двум макросам аргументы различных типов, необходимо учитывать следующее:
usize (u8-u64, i8-i64, bool и т.д.), может передаваться макросам напрямую.&str и String) должны передаваться как указатель.ptr::null(), ptr::null_mut() и т.д.) передаются как 0 (независимо от того, u8 это, u16, i32 или любой другой тип).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);
Обратите внимание, что макрос возвращает *mut c_void, который можно напрямую преобразовать в HANDLE, поскольку оба типа данных имеют одинаковый размер. Это позволяет получить значение, возвращённое функцией OpenProcess, — новый дескриптор целевого процесса.
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);
Обратите внимание, что макрос возвращает *mut c_void, который можно использовать для получения NTSTATUS, возвращённого функцией NtDelayExecution.
Процесс спуфинга может быть выстроен в цепочку любое количество раз без аномального увеличения размера стека вызовов. Поток выполнения при этом также сохраняется. Ниже приведён пример:
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.
}
}
Если установить второй параметр в true (в обоих макросах), процесс спуфинга попытается сохранить в стеке вызовов кадр стартового адреса потока, чтобы повысить легитимность.