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 messageboxAdd 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
hwndis a window handle type (HWND).lptextandlpcaptionare pointers to null-terminated ASCII strings (PCSTR).
Understanding Message Box Styles and Results
MESSAGEBOX_STYLEenumerates message box styles (e.g., information, warning).MESSAGEBOX_RESULTrepresents 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
HWNDtype. - In Rust (using the
windowscrate), it’s often treated as a raw pointer (*mut c_void).
- In Windows, every window has a unique handle represented by the
-
The Special Case of
0:- Passing
HWND(0)to theMessageBoxAfunction signifies that we want a top-level message box (one without a parent window).
- Passing
-
PCSTR:- Stands for “Pointer to Constant String”. Specifically, a null-terminated ASCII string.
- Windows APIs often work with this string format.
- We use
PCSTRto safely pass Rust strings to theMessageBoxAfunction.
-
MB_OK:- A constant telling the
MessageBoxAfunction to include a single “OK” button.
- A constant telling the
-
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 theunsafeblock 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 Type | Rust Equivalent | Notes |
|---|---|---|
| HWND | *mut c_void | Raw pointer to a window handle |
| HANDLE | *mut c_void | Generic handle type (windows, files, etc.) |
| HINSTANCE | *mut c_void | Handle to an instance of a module |
| INT | i32 | Signed 32-bit integer |
| UINT | u32 | Unsigned 32-bit integer |
| BOOL | bool | Boolean value (true/false) |
| DWORD | u32 | Double-word (32-bit) unsigned integer |
| WORD | u16 | Single-word (16-bit) unsigned integer |
| BYTE | u8 | 8-bit unsigned integer |
| LONG | i32 | Signed 32-bit integer |
| ULONG | u32 | Unsigned 32-bit integer |
| LPSTR | *const u8 | Pointer to a null-terminated ASCII string |
| LPWSTR | *const u16 | Pointer to a null-terminated wide string (Unicode) |
| LPCSTR | *const u8 | Constant pointer to a null-terminated ASCII string |
| LPCWSTR | *const u16 | Constant 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!