In my previous blog {link here}, I explained how to perform process injection using windows API. In this blog, we would dive a bit more deeper and learn the intricacies for DLL injection.
In this blog we will first learn how to create a DLL in rust and then using windows API we will inject the generated dll into remote process.
Let’s start with the theory first.
Theory DLL Creation
The heart of every DLL is the DllMain function, an entry point that the system calls for various purposes, such as when the DLL is loaded or unloaded, or when new threads are created or terminated.
Crafting a DLL in Rust
Rust, known for its safety guarantees and performance, also offers excellent tools for system-level programming, making it a suitable choice for creating DLLs intended for injection. Here’s how you can create a simple DLL in Rust:
Step 1: Setup Your Environment
First, ensure Rust is installed on your system. You can set it up from the official Rust website.
Step 2: Writing the DLL Code
The following is an example of a Rust code snippet that could be used to create a DLL. This DLL will simply pop up a message box — a benign operation that serves as a proof of concept.
use std::ffi::c_void;
use windows::{
core::PCSTR,
Win32::{
UI::WindowsAndMessaging::{
MessageBoxA,
MESSAGEBOX_STYLE,
MESSAGEBOX_RESULT,
},
Foundation::{HWND, BOOL, HINSTANCE},
},
};
#[no_mangle]
extern "C" fn main() {
unsafe {
MessageBoxA(
HWND(0),
PCSTR("I'm from DLL!\x00".as_ptr()),
PCSTR("UH oh\x00".as_ptr()),
MESSAGEBOX_STYLE(0),
);
};
}
#[no_mangle]
#[allow(non_snake_case)]
extern "system" fn DllMain(_hinst: HINSTANCE, reason: u32, _reserved: *mut c_void) -> BOOL {
match reason {
_ => {
println!("DLL_THREAD_ATTACH");
BOOL(1)
}
}
}
Step 3: Compile the DLL
Run the command cargo build --release to compile your project. The output will be a DLL file located in the target/release directory.
Detailed Breakdown of the DLL Code
rust
Copy code
use std::ffi::c_void;
- Purpose: This line imports the
c_voidtype from the Rust standard library.c_voidis a type that represents the Cvoidtype, which is used in FFI (Foreign Function Interface) to represent a lack of type. It is typically used in pointers to unknown or generic data.
rust
Copy code
use windows::{ core::PCSTR, Win32::{ UI::WindowsAndMessaging::{ MessageBoxA, MESSAGEBOX_STYLE, MESSAGEBOX_RESULT, }, Foundation::{HWND, BOOL, HINSTANCE}, }, };
- Purpose: This block imports specific types and functions from the
windowscrate, which provides bindings to the Windows API.PCSTRis used for pointer to a constant null-terminated string of 8-bit Windows (ANSI) characters.MessageBoxAis a function that creates, displays, and operates a message box.MESSAGEBOX_STYLEis a type that represents the style of the message box (e.g., with OK buttons, icons).HWND,BOOL, andHINSTANCEare types representing a handle to a window, a boolean value, and a handle to an instance/module respectively.
rust
Copy code
#[no_mangle] extern "C" fn main() {
- Purpose: The
#[no_mangle]attribute is used to tell the Rust compiler not to mangle the name of the function. This is important for FFI as it allows the function to be called from other languages (like C) or from the system that expects a specific function name.extern "C"specifies the calling convention (C in this case) for the function, which affects how the function’s arguments are passed and how the function’s return value is handled.
rust
Copy code
`unsafe { MessageBoxA( HWND(0), PCSTR("I'm from DLL!\x00".as_ptr()), PCSTR("UH oh\x00".as_ptr()), MESSAGEBOX_STYLE(0), ); };`
- Purpose: This block calls the
MessageBoxAfunction within anunsafeblock. Unsafe is required because direct manipulation of pointers and calls to external C functions can lead to memory safety issues, which Rust’s safe system does not guarantee against.HWND(0)specifies that the message box has no owner window.PCSTR("I'm from DLL!\x00".as_ptr())andPCSTR("UH oh\x00".as_ptr())are the message and title of the box, converted to C-style strings (null-terminated).MESSAGEBOX_STYLE(0)specifies the style of the message box.0typically means a default style (OK button only).
rust
Copy code
#[no_mangle] #[allow(non_snake_case)] extern "system" fn DllMain(_hinst: HINSTANCE, reason: u32, _reserved: *mut c_void) -> BOOL {
- Purpose: Another function defined with
#[no_mangle]to prevent name mangling.#[allow(non_snake_case)]tells the Rust compiler not to warn about the naming convention (Rust typically uses snake_case).extern "system"specifies the system’s default calling convention, which is crucial for DLL entry points. It’s used here as a DLL entry function, which is called by Windows when the DLL is loaded or unloaded.
rust
Copy code
`match reason { _ => { println!("DLL_THREAD_ATTACH"); BOOL(1) } } }`
- Purpose: This function is the entry point for the DLL. It matches on the
reasonthe DLL is called (e.g., when the DLL is loaded, a thread is created, etc.). For simplicity, this code logs when a thread is attached and always returnsBOOL(1), indicating success. This setup is typical for initializations that may be required when the DLL is first loaded.
This detailed explanation should give you a comprehensive understanding of each part of the code and its importance in the broader context of Windows DLL operations and Rust’s interaction with native code through FFI.
Important take aways in Rust
I a