Dynamically invoke arbitrary unmanaged code.

Related tags

Utilities DInvoke_rs
Overview

DInvoke_rs

Rust port of Dinvoke. DInvoke_rs may be used for many purposes such as PE parsing, dynamic exported functions resolution, dynamically loading PE plugins at runtime, API hooks evasion and more. This project is meant to be used as a template (just add your own Rust code on top of it) or as a nested crate that can be imported on your own project (remove the src package and compile it as a lib).

Features:

  • Dynamically resolve and invoke undocumented Windows APIs from Rust.
  • Primitives allowing for strategic API hook evasion.
  • Direct syscall execution from Rust.
  • Manually map PE modules from disk or directly from memory.
  • PE headers parsing.

TODO:

  • Map PE modules into sections backed by arbitrary modules on disk.
  • PE headers manipulation.

Credit

All the credits go to the creators of the original C# implementation of this tool:

I just created this port as a way to learn Rust myself and with the idea of facilitate to the Red Team community the transition from more common and known languages (like C++ or C#) to Rust to develop their hacking tools.

Compile requirements

Since we are using LITCRYPT plugin to obfuscate string literals, it is required to set up the environment variable LITCRYPT_ENCRYPT_KEY before compiling the code:

set LITCRYPT_ENCRYPT_KEY="yoursupersecretkey"

If you dont set up this environment variable you will se a lot of errors at the time of opening the project or importing the code into your own crate. This feature will probably be removed in the near future since LLVM string obfuscation may be a better approach.

Example 1 - Resolving Exported Unmanaged APIs

The example below demonstrates how to use DInvoke_rs to dynamically find and call exports of a DLL.

  1. Get the base address of ntdll.dll by walking the Process Environment Block.
  2. Use get_function_address() to find an export within ntdll.dll by name. This is done by walking and parsing the module's EAT.
  3. Use get_function_address_by_ordinal() to find an export within ntdll.dll by ordinal. This is done by dynamically calling LdrGetProcedureAddress.
fn main() {

    // Dynamically obtain the base address of ntdll.dll. 
    let ntdll = 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::get_function_address(ntdll, "NtCreateThread");
        if nt_create_thread != 0
        {
            println!("The address where NtCreateThread is located at is 0x{:X}", nt_create_thread);
        }

        // Dynamically obtain the address of a function by ordinal.
        let ordinal_8 = dinvoke::get_function_address_by_ordinal(ntdll, 8);
        if ordinal_8 != 0 
        {
            println!("The function with ordinal 8 is located at 0x{:X}", ordinal_8);
        }
    }   
}

Example 2 - Invoking Unmanaged Code

In the example below, we use DInvoke_rs to dynamically call RtlAdjustPrivilege in order to enable SeDebugPrivilege for the current process token. This kind of execution will bypass any API hooks present in Win32. Also, it won't create any entry on the final PE Import Address Table, making it harder to detect the PE behaviour without executing it.

fn main() {

    // Dynamically obtain the base address of ntdll.dll. 
    let ntdll = 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 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 enabled: *mut u8 = std::mem::transmute(&u8::default()); 
            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!"),
            }
        } 
    }   
}

Example 3 - Executing direct syscall

In the next example, we use DInvoke_rs to execute the syscall that corresponds to function NtQueryInformationProcess. Since the macro dinvoke::execute_syscall!() dynamically allocates and executes the shellcode required to perform the desired syscall, all hooks present in ntdll.dll are bypassed.

use std::mem::size_of;
use bindings::Windows::Win32::System::Threading::{GetCurrentProcess, PROCESS_BASIC_INFORMATION};
use data::{NtQueryInformationProcess, PVOID};

fn main() {

    unsafe 
    {
        let function_type:NtQueryInformationProcess;
        let mut ret: Option<i32> = None; //NtQueryInformationProcess returns a NTSTATUS, which is a i32.
        let handle = GetCurrentProcess();
        let process_information: PVOID = std::mem::transmute(&PROCESS_BASIC_INFORMATION::default()); 
        let return_length: *mut u32 = std::mem::transmute(&u32::default());
        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."),
        }  

    }

Example 4 - Manual PE mapping

In this last example, DInvoke_rs is used to manually map a fresh copy of ntdll.dll, without any EDR hooks. Then that fresh ntdll.dll copy can be used to execute any desired function.

This manual map can also be executed from memory (use manually_map_module() in that case), allowing the classic reflective dll injection.

fn main() {

    unsafe 
    {

        let ntdll = manualmap::read_and_map_module("C:\\Windows\\System32\\ntdll.dll").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 enabled: *mut u8 = std::mem::transmute(&u8::default()); 
        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!"),
        }

    }
}
You might also like...
Simplified glue code generation for Deno FFI libraries written in Rust.

deno_bindgen This tool aims to simplify glue code generation for Deno FFI libraries written in Rust. Quickstart # install CLI deno install -Afq -n den

Easy to use Rust i18n library based on code generation

rosetta-i18n rosetta-i18n is an easy-to-use and opinionated Rust internationalization (i18n) library powered by code generation. rosetta_i18n::include

A document-code sync tools for document engineering.

Writing A document-code sync tools for document engineering. Writing 是一个自动 “文档-代码” 同步工具。解析 Markdown 中的代码定义,读取目标代码,并嵌入到新的文档中。 Language parse support by

Clean up the lines of files in your code repository

lineman Clean up the lines of files in your code repository NOTE: While lineman does have tests in place to ensure it operates in a specific way, I st

A simple code boilerplate generator written in Rust.

💻 Cgen What is Cgen? A modern, cross-platform, multi-language boilerplate generator aimed to make your code generation less hectic! If you wish to su

Experimental Rust tool for generating FFI definitions allowing many other languages to call Rust code

Diplomat is an experimental Rust tool for generating FFI definitions allowing many other languages to call Rust code. With Diplomat, you can simply define Rust APIs to be exposed over FFI and get high-level C, C++, and JavaScript bindings automatically!

A code coverage tool for Rust projects

Tarpaulin Tarpaulin is a code coverage reporting tool for the Cargo build system, named for a waterproof cloth used to cover cargo on a ship. Currentl

My solutions to the Advent of Code 2020.

AdventofCode2021 My solutions to the advent of code event 2021. For this edition I chose to solve the puzzles using Rust 🦀 . Day Name Link Solution 1

My solutions to the advent of code 2021 problems.

Advent of Code 2021 My solutions to the AOC 2021 problems in Rust. Solutions Task Status Day 1 ✔️ , ✔️ Day 2 ✔️ , ✔️ Day 3 ✔️ , ✔️ Day 4 ✔️ , ✔️ Day 5

Comments
  • How can I pass strings using dynamic_invoke?

    How can I pass strings using dynamic_invoke?

            let cmd: String = String::from("Echo dynamically loaded");
    
            let func_ptr:  unsafe extern "Rust" fn (*const c_char) -> *const c_char; // Function header
            let ret: Option<i32>; // The value that the called function will return
            unsafe {
                let parameter1 = cmd.as_ptr() as *const c_char;
                
                dinvoke::dynamic_invoke!(overload.1, "command", func_ptr, ret, parameter1);
            }
    

    It throws

       |
    86 |             dinvoke::dynamic_invoke!(overload.1, "command", func_ptr, ret, parameter1);
       |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `i32`, found *-ptr
       |
       = note:     expected type `i32`
               found raw pointer `*const i8
    
    opened by TheOnlyArtz 3
  • Enhancement : Adding other examples in readme

    Enhancement : Adding other examples in readme

    Hello !

    Thank you for the repo :) Could you provide more examples of using the repo ? Especially how to add other API type in the data folder, that would be amazing :)

    You can add me on Discord nariod#4621

    Cheers !

    opened by Nariod 2
Owner
Kurosh Dabbagh Escalante
nt authority\kurosh
Kurosh Dabbagh Escalante
This crate allows you to safely initialize Dynamically Sized Types (DST) using only safe Rust.

This crate allows you to safely initialize Dynamically Sized Types (DST) using only safe Rust.

Christofer Nolander 11 Dec 22, 2022
A dynamically prioritizable priority queue.

bheap A generic binary max heap implementation for implementing a dynamically prioritizable priority queue. This implementation uses a vector as the u

Arindam Das 4 Sep 21, 2022
Migrate C code to Rust

C2Rust helps you migrate C99-compliant code to Rust. The translator (or transpiler) produces unsafe Rust code that closely mirrors the input C code. T

Immunant 3k Jan 8, 2023
Mix async code with CPU-heavy thread pools using Tokio + Rayon

tokio-rayon Mix async code with CPU-heavy thread pools using Tokio + Rayon Resources Documentation crates.io TL;DR Sometimes, you're doing async stuff

Andy Barron 74 Jan 2, 2023
Minimal, flexible framework for implementing solutions to Advent of Code in Rust

This is advent_of_code_traits, a minimal, flexible framework for implementing solutions to Advent of Code in Rust.

David 8 Apr 17, 2022
Waits until the exit code of a program is zero

Waitz A rust utility to wait that a program exits with 0. You need to wait for something to start up and don't know when it finishes?

Max Strübing 15 Apr 10, 2022
Sample code for compute shader 101 training

Sample code for Compute Shader 101 This repo contains sample code to help you get started writing applications using compute shaders.

Google Fonts 332 Jan 1, 2023
Detect if code is running inside a virtual machine (x86 and x86-64 only).

inside-vm Detect if code is running inside a virtual machine. Only works on x86 and x86-64. How does it work Measure average cpu cycles when calling c

null 34 Oct 3, 2022
Doku is a framework for building documentation with code-as-data methodology in mind.

Doku is a framework for building documentation with code-as-data methodology in mind. Say goodbye to stale, hand-written documentation - with D

ANIXE 73 Nov 28, 2022
A tool to run web applications on AWS Lambda without changing code.

AWS Lambda Adapter A tool to run web applications on AWS Lambda without changing code. How does it work? AWS Lambda Adapter supports AWS Lambda functi

AWS Samples 321 Jan 2, 2023