Creating A Window With Rust¶
If you've been following along from Creating A DLL With Rust, you should know how to do the following:
- Create a Windows dynamic-link library (DLL) using Rust
- Inject DLLs into processes using Process Hacker
- Inject DLLs into processes using Rust
- Create application windows using Rust
- Override a window's behavior using subclassing
In today's exercise, we are going to skip a step and learn about windows: how they work, how to create them, and how to customize them using something called subclassing.
What Are Windows?¶
We spend all day interacting with windows, but how much do we really know about them?
By the end of this series we'll be elbow deep in them, so it's worth spending a moment to understand them more intimately.
Fortunately, Microsoft’s documentation on this topic is fantastic: About Windows.
Take a few moments to peruse and come back here. I'll wait.
Ok, sketching out what we just read, GUI applications look something like this:
- Every application has a WinMain entry-point function
- Every application must create at least one window
- Every window belongs to one window class
- Every window has one unique window handle
- Every window has one GUI thread
- Every GUI thread has one message queue
- Every window class contains one window procedure
And, Windows is a message-based system, where:
- Every message sent or posted to a window is processed by its window procedure
- Every message posted to a window is placed in its message queue
- Every window must remove and process messages posted to its message queue
Further Reading¶
- GetMessage#Remarks
- When can a thread receive window messages?
- Windows and Messages
- Windows Programming/Message Loop Architecture
Overall Structure¶
Your First Windows Program presents a prototypical GUI application:
#include <windows.h>
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow)
{
// Register the window class.
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME;
RegisterClass(&wc);
// Create the window.
HWND hwnd = CreateWindowEx(
...
CLASS_NAME, // Window class
...
);
if (hwnd == NULL)
{
return 0;
}
ShowWindow(hwnd, nCmdShow);
// Run the message loop.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
...
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
With the code's comments delineating three functional areas:
1. The Window Class¶
Creates and registers a "window class", which is simply a WNDCLASS
struct.
At minimum we have to provide:
- a pointer to a WNDPROC callback function
- a handle to the .exe/.dll containing the WNDPROC
- a unique class name
// Register the window class.
const wchar_t CLASS_NAME[] = L"Sample Window Class";
WNDCLASS wc = { };
wc.lpfnWndProc = WindowProc; // Pointer to "window procedure"
wc.hInstance = hInstance;
wc.lpszClassName = CLASS_NAME; // Unique class name
RegisterClass(&wc); // Tell Windows about the new window class
2. Window Instantiation¶
Creates a new window, using the specs we defined in WNDCLASS
, and displays the window to the screen:
// Create the window.
HWND hwnd = CreateWindowEx(
...
CLASS_NAME, // Window class
...
);
if (hwnd == NULL)
{
return 0;
}
// Display window to screen
ShowWindow(hwnd, nCmdShow);
CreateWindowEx
returns a "handle" aka “window handle", to the created window. Window handles are unique to each created window, allowing us to reference them in our code.
3. The Message Loop¶
Continuously calls GetMessage
to grab messages sent to the window and dispatch them to the window's Window Procedure using DispatchMessage
:
// Run the message loop.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
The Window Procedure¶
Receives and processes the window's messages, handing-off unprocessed messages to the operating system by calling DefWindowProc
:
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
...
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
Further Reading¶
Actually Creating The Window¶
The windows-rs
crate provides a create_window
sample, a Rust version of what we examined above:
use windows::{
core::*, Win32::Foundation::*, Win32::Graphics::Gdi::*,
Win32::System::LibraryLoader::GetModuleHandleA,
Win32::UI::WindowsAndMessaging::*,
};
fn main() -> Result<()> {
unsafe {
let instance = GetModuleHandleA(None)?;
debug_assert!(instance.0 != 0);
let window_class = s!("window");
let wc = WNDCLASSA {
hCursor: LoadCursorW(None, IDC_ARROW)?,
hInstance: instance,
lpszClassName: window_class,
style: CS_HREDRAW | CS_VREDRAW,
lpfnWndProc: Some(wndproc),
..Default::default()
};
let atom = RegisterClassA(&wc);
debug_assert!(atom != 0);
CreateWindowExA(
WINDOW_EX_STYLE::default(),
window_class,
s!("This is a sample window"),
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT,
CW_USEDEFAULT,
800,
600,
None,
None,
instance,
None,
);
let mut message = MSG::default();
while GetMessageA(&mut message, HWND(0), 0, 0).into() {
DispatchMessageA(&message);
}
Ok(())
}
}
extern "system" fn wndproc(window: HWND, message: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
unsafe {
match message {
WM_PAINT => {
println!("WM_PAINT");
ValidateRect(window, None);
LRESULT(0)
}
WM_DESTROY => {
println!("WM_DESTROY");
PostQuitMessage(0);
LRESULT(0)
}
_ => DefWindowProcA(window, message, wparam, lparam),
}
}
}
Getting the Code¶
The windows-rs
repo is pretty large, so if you want to save time, you can git clone
just the create_window
folder from my repo here: peddamat/create_window
Cloning it into our workspace folder:
$ git clone https://github.com/peddamat/create_window.git
$ tree
.
├── Cargo.lock
├── Cargo.toml
├── create_window
├── hello
├── hello_runner
└── target
Updating Your Workspace¶
Add the crate to your workspace's Cargo.toml
:
Running the Code¶
Now, do a cargo run
:
$ cargo run
error: `cargo run` could not determine which binary to run. Use the `--bin` option to specify a binary, or the `default-run` manifest key.
available binaries: create_window, hello_runner
Ah, so cargo run --bin create_window
it is!
$ cargo run --bin create_window
warning: C:\Users\me\source\blog_qa\create_window\Cargo.toml: dependency (windows-sys) specified without providing a local path, Git repository, or version to use. This will be considered an error in future versions
Updating crates.io index
Compiling windows-sys v0.45.0
Compiling create_window v0.0.0 (C:\Users\me\source\blog_qa\create_window)
Finished dev [unoptimized + debuginfo] target(s) in 4.91s
Running `target\debug\create_window.exe`
WM_PAINT
And now we're cooking with windows!
Further Reading¶
- Module 1. Your First Windows Program
- Raymond Chen: The scratch program
- winit - Cross-platform window creation and management in Rust
Window Messages¶
As we learned earlier, Windows is message-based, meaning, all user input and requests are sent to the window using messages.
”The system” (Windows) internally defines 100’s of messages, but we’re usually only interested in a handful of them.
To get a better handle on which messages matter, let's see them with our own eyes using Window Detective.
Window Detective¶
It looks like this:
Use the 'Pick window' tool to "pick" the create_window
window, it should have a red outline:
Now right-click on the highlighted row and click "View Properties", which brings up the window's property inspector on the right:
Now right-click again and select "Messages...", and move your mouse over the create_window
window. You should see messages flooding in:
You can filter messages by clicking the filter icon:
Reduce the signal-to-noise ratio by selectively excluding messages:
Which will help reveal which events are received and emitted when manipulating a window:
Spend a few minutes (or hours) playing around. See what messages are sent when dragging and/or maximizing a window.
Further Reading¶
Window Procedure¶
Window Procedures states:
Every window has an associated window procedure — a function that processes all messages sent or posted to all windows of the class.
All aspects of a window's appearance and behavior depend on the window procedure's response to these messages.
Seems pretty important, eh?
Yes, the window procedure is the beating heart of the window, where all system-generated messages are eventually delivered to and processed from.
Let’s take things for a spin by handling the WM_PAINT and WM_WINDOWPOSCHANGING messages in our window procedure.
Customizing Our Window¶
WM_PAINT¶
The WM_PAINT
message indicates that our window needs to be repainted. By responding to it, we can modify what's drawn to the screen.
Let’s do so and add a bit of color to our window.
First, replace the use
section:
use windows::{
core::*, Win32::Foundation::*, Win32::Graphics::Gdi::ValidateRect,
Win32::System::LibraryLoader::GetModuleHandleA,
Win32::UI::WindowsAndMessaging::*,
};
With:
use windows::{
core::*,
Win32::Foundation::*,
Win32::Graphics::Gdi::*,
Win32::System::LibraryLoader::GetModuleHandleA,
Win32::UI::WindowsAndMessaging::*,
};
Then replace the WM_PAINT
section in wndproc
with:
WM_PAINT => {
println!("WM_PAINT");
let mut msg = String::from("ZOMG!");
let mut ps = PAINTSTRUCT::default();
let psp = &mut ps as *mut PAINTSTRUCT;
let rp = &mut ps.rcPaint as *mut RECT;
let hdc = BeginPaint(window, psp);
let brush = CreateSolidBrush(COLORREF(0x0000F0F0));
FillRect(hdc, &ps.rcPaint, brush);
DrawTextA(hdc, msg.as_bytes_mut(), rp, DT_SINGLELINE | DT_CENTER | DT_VCENTER );
EndPaint(window, &ps);
LRESULT(0)
}
A cargo run --bin create_window
results in:
Sweet.
WM_WINDOWPOSCHANGING¶
Raymond Chen gives this example on WM_WINDOWPOSCHANGING
, which we can implement by adding the following to wnd_proc
:
WM_WINDOWPOSCHANGING => {
let data = lparam.0 as *mut WINDOWPOS;
let data = data.as_mut().unwrap();
data.flags |= SWP_NOSIZE | SWP_NOMOVE;
}
Window Subclassing¶
We now have a basic understanding of how a window works, how its constructed, and how its window procedure affects its presentation and behavior.
Yet, our knowledge is limited to changing the appearance of windows that we create or have the source code to. To do the same for other people's windows (including our own), we need to use a technique called subclassing:
Subclassing is a technique that allows an application to intercept and process messages sent or posted to a particular window before the window has a chance to process them. By subclassing a window, an application can augment, modify, or monitor the behavior of the window...
Cool, cool.
An application subclasses a window by replacing the address of the window's original window procedure with the address of a new window procedure, called the subclass procedure. Thereafter, the subclass procedure receives any messages sent or posted to the window.
Ah, got it.
...you cannot subclass a window or class that belongs to another application. All subclassing must be performed within the same process.
BOOM! (the crowd goes wild)
See, in Injecting The GD Thing we learned how to inject a DLL into another application's process space.
So, in theory, if we could construct a DLL that automatically subclasses an application’s main window.
We could then inject it into another application’s process space.
Which, in theory, would allow us to subclass any window!
So, then, how do we subclass?!
SetWindowLong¶
"About Window Procedures" directs us to SetWindowLong.
SetWindowsLong informs us:
Note
This function has been superseded by the SetWindowLongPtr function. To write code that is compatible with both 32-bit and 64-bit versions of Windows, use the SetWindowLongPtr function.
SetWindowLongPtr¶
SetWindowLongPtr says it: changes an attribute of the specified window. The function also sets a value at the specified offset in the extra window memory.
You use the function like this:
where:
hwnd
: a handle to the window we want to subclassGWLP_WNDPROC
: "Sets a new address for the window procedure"wnd_proc
: the replacement window procedureprev_wndproc
: a pointer to the previous "window procedure"
Replacement Window Procedure¶
wnd_proc
is simply a "window procedure" with one crucial difference: it calls CallWindowProc
instead of DefWindowProcW
.
extern "system" fn wnd_proc(
window: HWND,
message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
unsafe {
match message {
// WM_PAINT => { }
_ => ()
}
CallWindowProcW(PREV_WNDPROC, window, message, wparam, lparam)
}
}
CallWindowProcA¶
CallWindowProc is used to pass messages to the previous window procedure, i.e. prev_wndproc
, and remember, we must call CallWindowProcW
on any messages we do not process.
Result¶
If the function succeeds, it returns a pointer to the previous window procedure.
If the function fails, it returns 0.
Actually Subclassing A Window¶
Let's refactor our create_window
example to use SetWindowLongPtr
.
First, let's add our skeleton wnd_proc
to main.rs
:
extern "system" fn wnd_proc(
window: HWND,
message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
unsafe {
match message {
// i.e. WM_PAINT => { }
_ => ()
}
CallWindowProcW(PREV_WNDPROC, window, message, wparam, lparam)
}
}
We can immediately see a problem, we've gotta get PREV_WNDPROC
into wnd_proc
. Since we're still learning, let's stick a gross global variable above our main
:
Now, let's update our fn main()
to grab the window handle
returned by CreateWindowExA
and pass it to a SetWindowLongPtrA
call:
let handle = CreateWindowExA(
...
None,
);
let result = SetWindowLongPtrW(handle, GWLP_WNDPROC, wnd_proc as isize);
let prev_wnd_proc = transmute::<isize, WNDPROC>(result);
let mut message = MSG::default();
...
Finally, let's add this to the top of main.rs
:
and a cargo check
to confirm things compile:
Great, now let's tranplant our WM_PAINT
into wnd_proc
:
extern "system" fn wnd_proc(
window: HWND,
message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
unsafe {
match message {
WM_PAINT => {
let mut msg = String::from("ZOMG!");
let mut ps = PAINTSTRUCT::default();
let psp = &mut ps as *mut PAINTSTRUCT;
let rectp = &mut ps.rcPaint as *mut RECT;
let hdc = BeginPaint(window, psp);
let brush = CreateSolidBrush(COLORREF(0x0000F0F0));
FillRect(hdc, &ps.rcPaint, brush);
DrawTextA(hdc,
msg.as_bytes_mut(),
rectp,
DT_SINGLELINE | DT_CENTER | DT_VCENTER
);
EndPaint(window, &ps);
return LRESULT(0);
}
WM_WINDOWPOSCHANGING => {
let data = lparam.0 as *mut WINDOWPOS;
let data = data.as_mut().unwrap();
data.flags |= SWP_NOSIZE | SWP_NOMOVE;
return LRESULT(0);
}
WM_NCDESTROY => {
let result = transmute::<WNDPROC, isize>(PREV_WNDPROC);
SetWindowLongPtrW(window, GWLP_WNDPROC, result);
return DefWindowProcA(window, message, wparam, lparam);
}
_ => ()
}
CallWindowProcW(PREV_WNDPROC, window, msg, wparam, lparam)
}
}
Hitting cargo run --bin create_window
yields:
Doneski!
Further Reading¶
Subclassing via DLL¶
Ok, this is where things start to come together. Remember how we mentioned that our ultimate goal is to hijack the window of any application via DLL injection?
Well, let's start by moving stuff from create_window\src\main.rs
to hello\src\lib.rs
.
Refactoring create_window¶
First, add a dependency on the hello
crate to create_window's
Cargo.toml:
Replacing the SetWindowLongPtr
call in create_window\src\main.rs
with a LoadLibraryA
call which loads hello.dll
:
...
LoadLibraryA(PCSTR("hello.dll\0".as_ptr()));
//let result = SetWindowLongPtrW(handle, GWLP_WNDPROC, wnd_proc as isize);
//PREV_WNDPROC = transmute::<isize, WNDPROC>(result);
...
We can clean-up the use
statements:
use windows::{
core::*,
Win32::Foundation::*,
Win32::Graphics::Gdi::*,
Win32::System::LibraryLoader::{GetModuleHandleA, LoadLibraryA},
Win32::UI::WindowsAndMessaging::*,
};
Refactoring hello.dll¶
First, let's put our SetWindowLongPtr
call into attach
:
...
static mut PREV_WNDPROC: WNDPROC = None;
...
fn attach() {
unsafe {
let result = SetWindowLongPtrW(handle, GWLP_WNDPROC, wnd_proc as isize);
PREV_WNDPROC = transmute::<isize, WNDPROC>(result);
};
}
While we're on the topic, do we need a detach
? Of course!
fn detach() {
unsafe {
let handle = find_window_by_pid(GetCurrentProcessId()).unwrap();
SetWindowLongPtrW(handle, GWLP_WNDPROC, transmute::<WNDPROC, isize>(PREV_WNDPROC));
};
}
And update DllMain
:
Now let's cut-and-paste wnd_proc
and stick it under detach
:
...
extern "system" fn wnd_proc(
window: HWND,
message: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
unsafe {
match message {
WM_PAINT => {
let mut msg = String::from("ZOMG!");
let mut ps = PAINTSTRUCT::default();
let psp = &mut ps as *mut PAINTSTRUCT;
let rectp = &mut ps.rcPaint as *mut RECT;
let hdc = BeginPaint(window, psp);
let brush = CreateSolidBrush(COLORREF(0x0000F0F0));
FillRect(hdc, &ps.rcPaint, brush);
DrawTextA(hdc,
msg.as_bytes_mut(),
rectp,
DT_SINGLELINE | DT_CENTER | DT_VCENTER
);
EndPaint(window, &ps);
return LRESULT(0);
}
WM_WINDOWPOSCHANGING => {
let data = lparam.0 as *mut WINDOWPOS;
let data = data.as_mut().unwrap();
data.flags |= SWP_NOSIZE | SWP_NOMOVE;
return LRESULT(0);
}
WM_NCDESTROY => {
let result = transmute::<WNDPROC, isize>(PREV_WNDPROC);
SetWindowLongPtrW(window, GWLP_WNDPROC, result);
return DefWindowProcA(window, message, wparam, lparam);
}
_ => ()
}
CallWindowProcW(PREV_WNDPROC, window, message, wparam, lparam)
}
}
Things are looking good, except we have one major problem, SetWindowLongPtr
needs a handle to a window to subclass, specifically, for hello.dll
, that would be a handle to the main GUI window of the application it's been injected into.
However, LoadLibrary
doesn't provide a means of passing arguments, so we can't pass it from create_window
... it seems that hello.dll
is going to have to dig up the window handle all by itself.
Now, this is a challenging problem, so we're going to use something ready baked, however, I'll dissect the code in a follow-up article.
Ok, paste the following after wnd_proc
:
pub fn find_window_by_pid(pid: u32) -> Result<HWND> {
let mut data = EnumWindowsData {
wanted_pid: pid,
handle: HWND::default(),
found: false,
};
unsafe {
EnumWindows(
Some(enum_windows_callback),
LPARAM(&mut data as *mut EnumWindowsData as isize)
);
};
if !data.found {
return Err(Error::new( ErrorKind::NotFound, "Can't find the window!"));
}
Ok(data.handle)
}
#[derive(Default)]
struct EnumWindowsData {
wanted_pid: u32,
handle: HWND,
found: bool,
}
unsafe extern "system" fn enum_windows_callback(handle: HWND, lparam: LPARAM) -> BOOL {
let data = lparam.0 as *mut EnumWindowsData;
let mut data = data.as_mut().unwrap();
let mut pid = MaybeUninit::<u32>::zeroed();
GetWindowThreadProcessId(handle, Some(pid.as_mut_ptr()));
let pid = pid.assume_init();
if pid == data.wanted_pid
&& GetWindow(handle, GW_OWNER).0 == 0
&& IsWindowVisible(handle).as_bool()
{
data.handle = handle;
data.found = true;
return BOOL(0);
}
BOOL(1)
}
Now, finish up by using find_window_by_pid
to satisfy SetWindowLongPtr
in attach
and detach
.
After cleaning up our use
statements, things should look like:
use windows::{ Win32::Foundation::*, Win32::System::SystemServices::*, };
use windows::Win32::System::Threading::GetCurrentProcessId;
use windows::{
Win32::Graphics::Gdi::*,
Win32::UI::WindowsAndMessaging::*,
};
use std::mem::{transmute, MaybeUninit};
use std::io::{Result, Error, ErrorKind};
static mut PREV_WNDPROC: WNDPROC = None;
#[no_mangle]
#[allow(non_snake_case, unused_variables)]
extern "system" fn DllMain(
dll_module: HINSTANCE,
call_reason: u32,
_: *mut ())
-> bool
{
match call_reason {
DLL_PROCESS_ATTACH => attach(),
DLL_PROCESS_DETACH => detach(),
_ => ()
}
true
}
fn attach() {
unsafe {
let handle = find_window_by_pid(GetCurrentProcessId()).unwrap();
let result = SetWindowLongPtrW(handle, GWLP_WNDPROC, wnd_proc as isize);
PREV_WNDPROC = transmute::<isize, WNDPROC>(result);
};
}
fn detach() {
unsafe {
let handle = find_window_by_pid(GetCurrentProcessId()).unwrap();
SetWindowLongPtrW(handle, GWLP_WNDPROC, transmute::<WNDPROC, isize>(PREV_WNDPROC));
};
}
extern "system" fn wnd_proc(
window: HWND,
msg: u32,
wparam: WPARAM,
lparam: LPARAM,
) -> LRESULT {
unsafe {
match msg {
WM_PAINT => {
let mut msg = String::from("ZOMG!");
let mut ps = PAINTSTRUCT::default();
let psp = &mut ps as *mut PAINTSTRUCT;
let rectp = &mut ps.rcPaint as *mut RECT;
let hdc = BeginPaint(window, psp);
let brush = CreateSolidBrush(COLORREF(0x0000F0F0));
FillRect(hdc, &ps.rcPaint, brush);
DrawTextA(hdc,
msg.as_bytes_mut(),
rectp,
DT_SINGLELINE | DT_CENTER | DT_VCENTER
);
EndPaint(window, &ps);
return LRESULT(1);
}
WM_WINDOWPOSCHANGING => {
let data = lparam.0 as *mut WINDOWPOS;
let data = data.as_mut().unwrap();
data.flags |= SWP_NOSIZE | SWP_NOMOVE;
return LRESULT(0);
}
WM_NCDESTROY => {
let result = transmute::<WNDPROC, isize>(PREV_WNDPROC);
SetWindowLongPtrW(window, GWLP_WNDPROC, result);
return DefWindowProcA(window, message, wparam, lparam);
}
_ => ()
}
CallWindowProcW(PREV_WNDPROC, window, msg, wparam, lparam)
}
}
pub fn find_window_by_pid(pid: u32) -> Result<HWND> {
let mut data = EnumWindowsData {
wanted_pid: pid,
handle: HWND::default(),
found: false,
};
unsafe {
EnumWindows(
Some(enum_windows_callback),
LPARAM(&mut data as *mut EnumWindowsData as isize)
);
};
if !data.found {
return Err(Error::new( ErrorKind::NotFound, "Can't find the window!"));
}
Ok(data.handle)
}
#[derive(Default)]
struct EnumWindowsData {
wanted_pid: u32,
handle: HWND,
found: bool,
}
unsafe extern "system" fn enum_windows_callback(handle: HWND, lparam: LPARAM) -> BOOL {
let data = lparam.0 as *mut EnumWindowsData;
let mut data = data.as_mut().unwrap();
let mut pid = MaybeUninit::<u32>::zeroed();
GetWindowThreadProcessId(handle, Some(pid.as_mut_ptr()));
let pid = pid.assume_init();
if pid == data.wanted_pid
&& GetWindow(handle, GW_OWNER).0 == 0
&& IsWindowVisible(handle).as_bool()
{
data.handle = handle;
data.found = true;
return BOOL(0);
}
BOOL(1)
}
Hitting cargo run
in the Create Window
crate results in:
Amazing!
Our little hello.dll
now automatically overrides the window procedure of any application it's loaded into!
A Taste Of DLL Injection¶
Injecting into create_window.exe
¶
Let's double-confirm this by:
- comment out
LoadLibrary
increate_window
- do a
cargo run --bin create_window
- inject
hello.dll
intocreate_window.exe
using Process Hacker
Pretty cool, eh?
Note: For the subclass to be called, you have to trigger a 'WM_PAINT' message, which can be done by resizing the create_window.exe
window.
Injecting into notepad.exe
¶
Try injecting hello.dll
into notepad.exe
.
Part 3 is here: Injecting DLLs With Rust
Having trouble? The code this series can be found here: peddamat/how-to-create-a-dll-using-rust