Introduction to Windows API in Rust

In this series, we’ll explore using the Windows API directly from Rust. The Windows API offers powerful tools for interacting with the Windows operating system, enabling fine-grained control and access to unique capabilities. This blog post lays the groundwork for upcoming discussions on advanced techniques like DLL injection with Rust.

Getting Started

Create a new Rust project named messagebox using Cargo:

cargo new --bin messagebox

Add the windows crate dependency to your Cargo.toml file:

[dependencies.windows]
version = "0.53.0"
features = [
    "Win32_Foundation",
    "Win32_UI_WindowsAndMessaging",
]

Converting Function Signatures

The MessageBoxA function signature from the windows crate:

pub unsafe fn MessageBoxA<P0, P1, P2>(
    hwnd: P0, lptext: P1, lpcaption: P2, utype: MESSAGEBOX_STYLE
) -> MESSAGEBOX_RESULT
where
    P0: ::windows_core::IntoParam<super::super::Foundation::HWND>,
    P1: ::windows_core::IntoParam<::windows_core::PCSTR>,
    P2: ::windows_core::IntoParam<::windows_core::PCSTR>,

Traits and Parameter Conversion

The IntoParam trait converts Rust types into their Windows API equivalents. For example, P0, P1, and P2 are generic parameters that implement IntoParam, indicating conversion into the appropriate Windows API types.

Mapping Types to Windows API

  • hwnd is a window handle type (HWND).
  • lptext and lpcaption are pointers to null-terminated ASCII strings (PCSTR).

Understanding Message Box Styles and Results

  • MESSAGEBOX_STYLE enumerates message box styles (e.g., information, warning).
  • MESSAGEBOX_RESULT represents the user’s interaction result (e.g., pressing OK, Cancel).

Code

Now, let’s write the code to display a simple message box using the Windows API:

use windows::Win32::Foundation::HWND;
use windows::core::PCSTR;
use windows::Win32::UI::WindowsAndMessaging::{
    MessageBoxA, MB_ICONINFORMATION, MB_OK, MESSAGEBOX_RESULT, MESSAGEBOX_STYLE,
};

unsafe fn show_message_box(message: &str, title: &str) -> MESSAGEBOX_RESULT {
    let message_ptr = message.as_ptr();
    let title_ptr = title.as_ptr();

    MessageBoxA(
        HWND(0),
        PCSTR(message_ptr),
        PCSTR(title_ptr),
        MB_OK | MB_ICONINFORMATION,
    )

    // Alternate way by using MESSAGEBOX_STYLE
    // MessageBoxA(HWND(0), PCSTR(message.as_ptr()), PCSTR(title.as_ptr()), MESSAGEBOX_STYLE(0 | 64))
}

fn main() {
    let result = unsafe { show_message_box("Hello from Rust!\0", "My First MessageBox\0") };
// Do Something if needed on result.

}

Let’s break down the most important parts of this code:

MessageBoxA(

HWND(0),

PCSTR(message_ptr),

PCSTR(title_ptr),

MB_OK | MB_ICONINFORMATION,

)
  • Window Handles:
    • In Windows, every window has a unique handle represented by the HWND type.
    • In Rust (using the windows crate), it’s often treated as a raw pointer (*mut c_void).
  • The Special Case of 0:
    • Passing HWND(0) to the MessageBoxA function signifies that we want a top-level message box (one without a parent window).
  • PCSTR:
    • Stands for “Pointer to Constant String”. Specifically, a null-terminated ASCII string.
    • Windows APIs often work with this string format.
    • We use PCSTR to safely pass Rust strings to the MessageBoxA function.
  • MB_OK:
    • A constant telling the MessageBoxA function to include a single “OK” button.
  • MB_ICONINFORMATION:
    • A constant instructing the message box to display the standard “information” icon.
  • | (Bitwise OR Operator):
    • This operator lets us combine flags to customize the message box (e.g., buttons and icons).
  • The Importance of unsafe: We need the unsafe block because interacting directly with the Windows API can bypass Rust’s usual safety checks. We need to be extra careful!

Windows API Types and Rust Equivalents

When working with the Windows API in Rust, it’s essential to understand the mapping between Windows API types and their Rust equivalents. Here’s a table summarizing some common types:

Windows API TypeRust EquivalentNotes
HWND*mut c_voidRaw pointer to a window handle
HANDLE*mut c_voidGeneric handle type (windows, files, etc.)
HINSTANCE*mut c_voidHandle to an instance of a module
INTi32Signed 32-bit integer
UINTu32Unsigned 32-bit integer
BOOLboolBoolean value (true/false)
DWORDu32Double-word (32-bit) unsigned integer
WORDu16Single-word (16-bit) unsigned integer
BYTEu88-bit unsigned integer
LONGi32Signed 32-bit integer
ULONGu32Unsigned 32-bit integer
LPSTR*const u8Pointer to a null-terminated ASCII string
LPWSTR*const u16Pointer to a null-terminated wide string (Unicode)
LPCSTR*const u8Constant pointer to a null-terminated ASCII string
LPCWSTR*const u16Constant pointer to a null-terminated wide string (Unicode)

Next up, we’ll explore more advanced techniques like DLL injection and process hollowing using Rust. Stay tuned for the next installment!

Leave a Comment