Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
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.
Features:
- Dynamically resolve and invoke undocumented Windows APIs from Rust.
- Primitives allowing for strategic API hook evasion.
- Indirect syscalls. x64 only
- Manually map PE modules from disk or directly from memory.
- PE headers parsing.
- Map PE modules into sections backed by arbitrary modules on disk. Not Opsec
- Module fluctuation to hide mapped PEs (concurrency supported). Not Opsec
- Syscall parameters spoofing through exception filter + hardware breakpoints. x64 only
- Module stomping and shellcode fluctuation.
- Template stomping.
Credit
All the credits go to the creators of the original C# implementation of this tool:
Content
- 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
Usage
Import this crate into your project by adding the following line to your cargo.toml
:
= "0.1.6"
dinvoke_rs
Examples
Resolving Exported APIs
The example below demonstrates how to use DInvoke_rs to dynamically find and call exports of a DLL (ntdll.dll
in this case).
- Get ntdll's base address.
- Use
get_function_address()
to find an export withinntdll.dll
by name. This is achieved by walking and parsing the dll's EAT. - You can also find an export by ordinal by calling
get_function_address_by_ordinal()
.
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's Import Address Table, making it harder to detect the PE's behaviour without executing it.
Executing indirect syscall
In the next example, we use DInvoke_rs to execute the syscall corresponding to the function NtQueryInformationProcess
. Since the macro execute_syscall!()
dynamically allocates and executes the shellcode required to perform the desired syscall, all hooks present in ntdll.dll
are bypassed. The memory allocated is release once the syscall returns, avoiding the permanent presence of memory pages with execution permission.
use size_of;
use ;
use ;
Manual PE mapping
In this 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 to perform the classic reflective dll injection.
use PeMetadata;
Overload memory section
In the following sample, DInvoke_rs is used to create a file-backed memory section, overloading it afterwards by manually mapping a PE. The memory section will point to a legitimate file located in %WINDIR%\System32\
by default, but any other decoy module can be used.
This overload can also be executed mapping a PE from memory (as it is shown in the following example), allowing to perform the overload without writing the payload to disk.
use PeMetadata;
Module fluctuation
DInvoke_rs allows to hide mapped PEs when they are not being used, making it harder for EDR memory inspection to detect the presence of a suspicious dll in your process.
For example, lets say we want to map a fresh copy of ntdll.dll
in order to evade EDR hooks. Since two ntdll.dll
in the same process could be considered a suspicious behaviour, we can map ntdll and hide it whenever we are not using it. This is very similar to the shellcode fluctuation technique, althought in this scenario we can take advantage of the fact that we are mapping a PE into a legitimate file-backed memory section, so we can replace the ntdll's content with the content of the original decoy module that the section is pointing to.
use Manager;
Syscall parameters spoofing
In order to spoof the first 4 parameters of a syscall, DInvoke_rs has support for hardware breakpoints in combination with exception handlers. This allows to send not malicious parameters to a NT function, and after the EDR has inspected them, they are replaced by the original parameters before the syscall instruction is executed. For further information, check out the repository where the original idea comes from: TamperingSyscalls.
For now, this feature is implemented for the functions NtOpenProcess
, NtAllocateVirtualMemory
, NtProtectVirtualMemory
, NtWriteVirtualMemory
and NtCreateThreadEx
. In order to use it, it's just needed to activate the feature, set the exception handler and call the desired function through Dinvoke.
use ;
use ;
Module stomping and Shellcode fluctuation
Dinvoke_rs's overload crate now allows to perform module stomping by calling the managed_module_stomping()
function. The first parameter of this function is the shellcode's content. The other two parameters modify the behaviour of the function, allowing three different execution paths commented below.
The best way to use this function in my opinion is by loading a legitimate dll into the process and allow Dinvoke to determine a good spot in that dll to stomp your shellcode to it. This is done by passing the dll's base address as the third parameter of managed_module_stomping()
. The second argument must be zero. By doing this, Dinvoke will iterate over the dll's Exception data looking for a legitimate function big enough to stomp the shellcode on it.
let payload_content = download_function;
let my_dll = load_library_a;
let module = managed_module_stomping;
match module
You can also specify the exact location where you want the shellcode to be stomped to by passing the memory address as the second parameter:
let payload_content = download_function;
let my_dll = load_library_a;
let my_big_enough_function = get_function_address;
let module = managed_module_stomping;
match module
Finally, you can allow Dinvoke to automatically decide the address where the shellcode will be stomped to. This is done by iterating over the Exception data of all loaded modules until finding a suitable function. This option may bring unexpected behaviours, so I do not really recommend it unless you don't have other option.
let payload_content = download_function;
let module = managed_module_stomping;
match module
Once the shellcode has been stomped, you can use dmanager
crate to hide/restomp your shellcode, allowing to perfom shellcode fluctuation:
let payload_content = download_function;
let my_dll = load_library_a;
let overload = managed_module_stomping.unwrap;
let mut manager = new;
let _r = manager.new_shellcode.unwrap; // The manager will take care of the fluctuation process
let _r = manager.hide_shellcode.unwrap; // We restore the memory's original content and hide our shellcode
...
let _r = manager.stomp_shellcode.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 = transmute;
run;
let _r = manager.hide_shellcode.unwrap; // We hide the shellcode again
Template stomping
Template stomping is a derivative of the module stomping technique tailored specifically for DLLs. Right now this technique only allows to load a DLL into the current process, remote processes are not supported.
The main objective is to create a template from a DLL by replacing the content of the .text
section with arbitrary data, allowing to write the template on disk without raising alerts. This template is crafted in a way that it can be loaded into the process by calling LoadLibrary
. Then, the original .text
section content can be downloaded directly to the process' memory and stomped on the template's corresponding memory region. This technique can be effectively executed using two main functions: generate_template
and template_stomping
.
The generate_template
function is designed to create the template from the original DLL by extracting the .text
section content and replacing it with arbitrary data. This ensures that the template maintains its structure but contains no meaningful executable code, apart from the entry points and TLS callbacks, which are replaced with dummy but functional assembly instructions. The original .text
section content is saved separately in payload.bin
, and the final template file is saved in template.dll
.
Then the template can be saved on disk in the target system and can be loaded in the current process by calling LoadLibrary
. Once the template has been loaded by the SO, the next step involves stomping the original executable content stored in payload.bin
into the .text
section of the template. This process is performed by the template_stomping
function, which stomps the original executable content into the right memory region taking care of all the details involved in the process.
This technique allows to load a DLL into disk backed memory regions without writing the real executable content to the filesystem (removing the need of private memory regions and evading EDR's static/dynamic analysis) and also allows to keep a clean call stack during the execution of the DLL's code, unlike what happens when we load a DLL reflectively.