
Invoquer dynamiquement du code non managé arbitraire
Port Rust de Dinvoke. DInvoke_rs peut être utilisé à de nombreuses fins, comme l'analyse de PE, la résolution dynamique de fonctions exportées, le chargement dynamique de plugins PE au runtime, l'évasion de hooks API et plus encore.
Fonctionnalités :
Tout le crédit revient aux créateurs de l'implémentation C# originale de cet outil :
Importez cette crate dans votre projet en ajoutant la ligne suivante à votre cargo.toml :```rust
[dependencies]
dinvoke_rs = "0.2.2"
# Exemples
## Résolution des API exportées
L'exemple ci-dessous montre comment utiliser DInvoke_rs pour trouver et appeler dynamiquement les exports d'une DLL (`ntdll.dll` dans ce cas).
1) Obtenir l'adresse de base de ntdll.
2) Utiliser `get_function_address()` pour trouver un export dans `ntdll.dll` par nom. Ceci est réalisé en parcourant et en analysant l'EAT de la DLL.
3) Vous pouvez également trouver un export par ordinal en appelant `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);
}
}
}
Dans l'exemple ci-dessous, nous utilisons DInvoke_rs pour appeler dynamiquement RtlAdjustPrivilege afin d'activer SeDebugPrivilege pour le jeton du processus courant. Ce type d'exécution contournera tous les hooks API présents dans Win32. De plus, il ne créera aucune entrée dans la table d'adresses d'importation du PE final, ce qui rend plus difficile la détection du comportement du PE sans l'exécuter.```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!"),
}
}
}
}
## Exécution d'un syscall indirect
Dans l'exemple suivant, nous utilisons DInvoke_rs pour exécuter le syscall correspondant à la fonction `NtQueryInformationProcess`. Puisque la macro `execute_syscall!()` alloue et exécute dynamiquement le shellcode nécessaire pour effectuer le syscall souhaité, tous les hooks présents dans `ntdll.dll` sont contournés. La mémoire allouée est libérée une fois que le syscall retourne, évitant la présence permanente de pages mémoire avec permission d'exécution.```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."),
}
}
}
Dans cet exemple, DInvoke_rs est utilisé pour mapper manuellement une copie fraîche de ntdll.dll, sans aucun hook EDR. Ensuite, cette copie fraîche de ntdll.dll peut être utilisée pour exécuter n'importe quelle fonction souhaitée.
Ce mappage manuel peut également être exécuté depuis la mémoire (utilisez manually_map_module() dans ce cas), permettant d'effectuer l'injection DLL réflexive classique.```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();