
Invoca dinamicamente codice non gestito arbitrario
Porting Rust di Dinvoke. DInvoke_rs può essere usato per molti scopi come il parsing di PE, la risoluzione dinamica di funzioni esportate, il caricamento dinamico di plugin PE a runtime, l'evasione degli API hook e altro ancora.
Caratteristiche:
Tutto il merito va ai creatori dell'implementazione originale in C# di questo strumento:
Importa questa crate nel tuo progetto aggiungendo la seguente riga al tuo cargo.toml:```rust
[dependencies]
dinvoke_rs = "0.2.2"
# Esempi
## Risoluzione delle API esportate
L'esempio seguente dimostra come usare DInvoke_rs per trovare e chiamare dinamicamente le esportazioni di una DLL (in questo caso `ntdll.dll`).
1) Ottenere l'indirizzo di base di ntdll.
2) Usare `get_function_address()` per trovare un'esportazione all'interno di `ntdll.dll` per nome. Questo si ottiene attraversando e analizzando l'EAT della DLL.
3) È anche possibile trovare un'esportazione per ordinale chiamando `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);
}
}
}
Nell'esempio seguente, usiamo DInvoke_rs per chiamare dinamicamente RtlAdjustPrivilege al fine di abilitare SeDebugPrivilege per il token del processo corrente. Questo tipo di esecuzione bypasserà qualsiasi hook API presente in Win32. Inoltre, non creerà alcuna voce nella Import Address Table del PE finale, rendendo più difficile rilevare il comportamento del PE senza eseguirlo.```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!"),
}
}
}
}
## Esecuzione di syscall indiretta
Nel prossimo esempio, utilizziamo DInvoke_rs per eseguire la syscall corrispondente alla funzione `NtQueryInformationProcess`. Poiché la macro `execute_syscall!()` alloca ed esegue dinamicamente lo shellcode necessario per effettuare la syscall desiderata, tutti gli hook presenti in `ntdll.dll` vengono bypassati. La memoria allocata viene rilasciata quando la syscall restituisce il controllo, evitando la presenza permanente di pagine di memoria con permessi di esecuzione.```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."),
}
}
}
In questo esempio, DInvoke_rs viene utilizzato per mappare manualmente una copia pulita di ntdll.dll, senza hook EDR. Quella copia pulita di ntdll.dll può poi essere utilizzata per eseguire qualsiasi funzione desiderata.
Questa mappatura manuale può anche essere eseguita dalla memoria (in tal caso usare manually_map_module()), consentendo di effettuare la classica iniezione riflettente di 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();