
Dynamisch beliebigen nicht verwalteten Code aufrufen
Rust-Port von Dinvoke. DInvoke_rs kann für viele Zwecke verwendet werden, beispielsweise für das Parsen von PE-Dateien, die dynamische Auflösung exportierter Funktionen, das dynamische Laden von PE-Plugins zur Laufzeit, die Umgehung von API-Hooks und mehr.
Features:
Alle Credits gebühren den Erstellern der ursprünglichen C#-Implementierung dieses Tools:
Importieren Sie diese Crate in Ihr Projekt, indem Sie die folgende Zeile zu Ihrer cargo.toml hinzufügen:```rust
[dependencies]
dinvoke_rs = "0.2.2"
# Beispiele
## Auflösen exportierter APIs
Das folgende Beispiel zeigt, wie Sie mit DInvoke_rs Exporte einer DLL dynamisch finden und aufrufen können (in diesem Fall `ntdll.dll`).
1) Ermitteln Sie die Basisadresse von ntdll.
2) Verwenden Sie `get_function_address()`, um einen Export innerhalb von `ntdll.dll` anhand des Namens zu finden. Dies wird erreicht, indem die EAT der DLL durchlaufen und geparst wird.
3) Sie können einen Export auch anhand der Ordinalzahl finden, indem Sie `get_function_address_by_ordinal()` aufrufen.```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);
}
}
}
Im folgenden Beispiel verwenden wir DInvoke_rs, um RtlAdjustPrivilege dynamisch aufzurufen und so SeDebugPrivilege für das Token des aktuellen Prozesses zu aktivieren. Diese Art der Ausführung umgeht alle in Win32 vorhandenen API-Hooks. Außerdem wird kein Eintrag in der Import Address Table der finalen PE erstellt, was es schwieriger macht, das Verhalten der PE ohne Ausführung zu erkennen.```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!"),
}
}
}
}
## Ausführen eines indirekten Syscalls
Im nächsten Beispiel verwenden wir DInvoke_rs, um den Syscall auszuführen, der der Funktion `NtQueryInformationProcess` entspricht. Da das Makro `execute_syscall!()` den für die Ausführung des gewünschten Syscalls erforderlichen Shellcode dynamisch allokiert und ausführt, werden alle in `ntdll.dll` vorhandenen Hooks umgangen. Der allokierte Speicher wird freigegeben, sobald der Syscall zurückkehrt, wodurch die dauerhafte Präsenz von Speicherseiten mit Ausführungsberechtigung vermieden wird.```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 diesem Beispiel wird DInvoke_rs verwendet, um eine frische Kopie von ntdll.dll manuell zu mappen, ohne jegliche EDR-Hooks. Diese frische ntdll.dll-Kopie kann dann verwendet werden, um jede gewünschte Funktion auszuführen.
Dieses manuelle Mapping kann auch aus dem Speicher heraus ausgeführt werden (verwenden Sie in diesem Fall manually_map_module()), was die klassische reflektive DLL-Injection ermöglicht.```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();