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_void type from the Rust standard library. c_void is a type that represents the C void type, 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 windows crate, which provides bindings to the Windows API.
    • PCSTR is used for pointer to a constant null-terminated string of 8-bit Windows (ANSI) characters.
    • MessageBoxA is a function that creates, displays, and operates a message box.
    • MESSAGEBOX_STYLE is a type that represents the style of the message box (e.g., with OK buttons, icons).
    • HWND, BOOL, and HINSTANCE are 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 MessageBoxA function within an unsafe block. 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()) and PCSTR("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. 0 typically 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 reason the 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 returns BOOL(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

Theory DLL Injection