
Spoofing de pilha de chamadas para Rust
O Unwinder fornece uma weaponização completa da técnica SilentMoonWalk, permitindo obter spoofing de pilha de chamadas completo e estável em Rust.
Esta técnica possui as seguintes características:
Créditos aos criadores da técnica SilentMoonWalk:
E, claro, um enorme agradecimento a namazso pelo thread no Twitter que inspirou todo este projeto.
Importe esta crate para o seu projeto adicionando a seguinte linha ao seu cargo.toml e compile no modo release:
[dependencies]
unwinder = "=0.1.4"
A funcionalidade principal desta crate foi encapsulada em duas macros:
call_function!() permite executar qualquer função arbitrária com uma pilha de chamadas limpa.indirect_syscall!() executa o syscall indireto especificado com uma pilha de chamadas limpa.Para usar qualquer uma destas macros, é necessário importar o tipo de dados std::ffi::c_void.
Ambas as macros retornam um *mut c_void que pode ser usado para recuperar o valor retornado pela função executada. Informações mais detalhadas na seção de exemplos.
Esta macro é usada para chamar qualquer função desejada com uma pilha de chamadas limpa. A macro espera os seguintes parâmetros:
usize, isize ou um ponteiro.Esta macro é usada para realizar qualquer syscall indireto desejado com uma pilha de chamadas limpa. A macro espera os seguintes parâmetros:
Para passar argumentos de diferentes tipos para estas duas macros, as seguintes considerações devem ser levadas em conta:
usize (u8-u64, i8-i64, bool, etc.) pode ser passado diretamente para as macros.&str e String) devem ser passadas como um ponteiro.ptr::null(), ptr::null_mut(), etc.) são passados como 0 (não importa se é u8, u16, i32 ou qualquer outro).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);
Observe que a macro retorna um *mut c_void que pode ser convertido diretamente para um HANDLE, já que ambos os tipos de dados têm o mesmo tamanho. Isso permite acessar o valor retornado por OpenProcess, que é o novo handle para o processo alvo.
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);
Observe que a macro retorna um *mut c_void que pode ser usado para recuperar o NTSTATUS retornado por NtDelayExecution.
O processo de spoofing pode ser concatenado qualquer número de vezes sem um incremento anormal no tamanho da pilha de chamadas. O fluxo de execução também será preservado. O código a seguir é um exemplo disso:
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 você definir o segundo parâmetro como true (em ambas as macros), o processo de spoofing tentará manter o frame do endereço de início da thread na pilha de chamadas para aumentar a legitimidade.
