
Invoca dinámicamente código no administrado arbitrario
Port en Rust de Dinvoke. DInvoke_rs puede usarse para muchos propósitos, como el análisis de PE, la resolución dinámica de funciones exportadas, la carga dinámica de plugins PE en tiempo de ejecución, la evasión de hooks de API y más.
Características:
Todos los créditos van para los creadores de la implementación original en C# de esta herramienta:
Importa este crate en tu proyecto añadiendo la siguiente línea a tu cargo.toml:```rust
[dependencies]
dinvoke_rs = "0.2.2"
# Ejemplos
## Resolución de APIs exportadas
El siguiente ejemplo demuestra cómo usar DInvoke_rs para encontrar y llamar dinámicamente exportaciones de una DLL (`ntdll.dll` en este caso).
1) Obtenga la dirección base de ntdll.
2) Use `get_function_address()` para encontrar una exportación dentro de `ntdll.dll` por nombre. Esto se logra recorriendo y analizando el EAT de la DLL.
3) También puede encontrar una exportación por ordinal llamando a `get_function_address_by_ordinal()`.```rust
fn main() {
// Dynamically obtain ntdll.dll's base address.
let ntdll = dinvoke_rs::dinvoke::get_module_base_address("ntdll.dll");
if ntdll != 0
{
println!("ntdll.dll base address is 0x{:X}", ntdll);
// Dynamically obtain the address of a function by name.
let nt_create_thread = dinvoke_rs::dinvoke::get_function_address(ntdll, "NtCreateThread");
if nt_create_thread != 0
{
println!("NtCreateThread is at address 0x{:X}", nt_create_thread);
}
// Dynamically obtain the address of a function by ordinal.
let ordinal_8 = dinvoke_rs::dinvoke::get_function_address_by_ordinal(ntdll, 8);
if ordinal_8 != 0
{
println!("The function with ordinal 8 is located at addresss 0x{:X}", ordinal_8);
}
}
}
En el siguiente ejemplo, usamos DInvoke_rs para llamar dinámicamente a RtlAdjustPrivilege con el fin de habilitar SeDebugPrivilege para el token del proceso actual. Este tipo de ejecución omitirá cualquier hook de API presente en Win32. Además, no creará ninguna entrada en la Tabla de Direcciones de Importación del PE final, lo que dificulta la detección del comportamiento del PE sin ejecutarlo.```rust
fn main() {
// Dynamically obtain ntdll.dll's base address.
let ntdll = dinvoke_rs::dinvoke::get_module_base_address("ntdll.dll");
if ntdll != 0
{
unsafe
{
let func_ptr: unsafe extern "system" fn (u32, u8, u8, *mut u8) -> i32; // Function header available at data::RtlAdjustPrivilege
let ret: Option<i32>; // RtlAdjustPrivilege returns an NSTATUS value, which in Rust can be represented as an i32
let privilege: u32 = 20; // This value matches with SeDebugPrivilege
let enable: u8 = 1; // Enable the privilege
let current_thread: u8 = 0; // Enable the privilege for the current process, not only for the current thread
let e = u8::default(); // https://github.com/Kudaes/rust_tips_and_tricks/tree/main#transmute
let enabled: *mut u8 = std::mem::transmute(&e);
dinvoke_rs::dinvoke::dynamic_invoke!(ntdll,"RtlAdjustPrivilege",func_ptr,ret,privilege,enable,current_thread,enabled);
match ret {
Some(x) =>
if x == 0 { println!("NTSTATUS == Success. Privilege enabled."); }
else { println!("[x] NTSTATUS == {:X}", x as u32); },
None => panic!("[x] Error!"),
}
}
}
}
## Ejecutando syscall indirecto
En el siguiente ejemplo, usamos DInvoke_rs para ejecutar la syscall correspondiente a la función `NtQueryInformationProcess`. Dado que la macro `execute_syscall!()` asigna y ejecuta dinámicamente el shellcode necesario para realizar la syscall deseada, todos los hooks presentes en `ntdll.dll` son omitidos. La memoria asignada se libera una vez que la syscall retorna, evitando la presencia permanente de páginas de memoria con permiso de ejecución.```rust
use std::mem::size_of;
use windows::Win32::System::Threading::{GetCurrentProcess, PROCESS_BASIC_INFORMATION};
use dinvoke_rs::data::{NtQueryInformationProcess, PVOID};
fn main() {
unsafe
{
let function_type:NtQueryInformationProcess;
let ret: Option<i32>; //NtQueryInformationProcess returns a NTSTATUS, which is a i32.
let handle = GetCurrentProcess();
let p = PROCESS_BASIC_INFORMATION::default();
let process_information: PVOID = std::mem::transmute(&p);
let r = u32::default();
let return_length: *mut u32 = std::mem::transmute(&r);
dinvoke_rs::dinvoke::execute_syscall!(
"NtQueryInformationProcess",
function_type,
ret,
handle,
0,
process_information,
size_of::<PROCESS_BASIC_INFORMATION>() as u32,
return_length
);
let pbi: *mut PROCESS_BASIC_INFORMATION;
match ret {
Some(x) =>
if x == 0 {
pbi = std::mem::transmute(process_information);
let pbi = *pbi;
println!("The Process Environment Block base address is 0x{:X}", pbi.PebBaseAddress as u64);
},
None => println!("[x] Error executing direct syscall for NtQueryInformationProcess."),
}
}
}
En este ejemplo, se utiliza DInvoke_rs para mapear manualmente una copia nueva de ntdll.dll, sin ningún gancho de EDR. Luego, esa copia nueva de ntdll.dll puede utilizarse para ejecutar cualquier función deseada.
Este mapa manual también puede ejecutarse desde memoria (use manually_map_module() en ese caso), lo que permite realizar la clásica inyección reflectiva de DLL.```rust
use dinvoke_rs::data::PeMetadata;
fn main() {
unsafe
{
let ntdll: (PeMetadata, usize) = dinvoke_rs::manualmap::read_and_map_module(r"C:\Windows\System32\ntdll.dll", true, false).unwrap();