ib_hook/inject/dll/dll.rs
1/*!
2Utility functions for the DLL part of DLL injection:
3- Unload self:
4 [`free_current_module_and_exit_thread()`]
5- Auto self unload, i.e. wait for the injector process and unload self:
6 - Blocking: [`wait_and_free_current_module()`]
7 - Non-blocking: [`spawn_wait_and_free_current_module_once!()`]
8- [`ThreadGuard`]:
9 A guard that terminates the thread when drop.
10
11See [`src/bin/inject-app-dll.rs`](https://github.com/Chaoses-Ib/IbDllHijackLib/blob/master/ib-hook/src/bin/inject-app-dll.rs)
12and [`examples/app-dll.rs`](https://github.com/Chaoses-Ib/IbDllHijackLib/blob/master/ib-hook/examples/app-dll.rs)
13for a complete example.
14
15## Pitfalls
16<div class="warning">
17
18`APPLY(None)` (`teardown`) should clean up all the references to the DLL's code,
19including hooks and threads. Otherwise, when the DLL is unloaded
20the process will crash due to memory access violation.
21
22One more pitfall is that Rust will not drop static variables for DLL,
23so you should either not use any static variables (that hold resources),
24or use [`macro@dtor`] to drop manually, for example:
25```no_run
26use std::cell::OnceCell;
27use ib_hook::inject::dll::dll::{dtor, ThreadGuard};
28
29static mut WAIT_AND_FREE: OnceCell<ThreadGuard> = OnceCell::new();
30
31dtor! {
32 #[dtor]
33 fn free() {
34 unsafe { &mut *&raw mut WAIT_AND_FREE }.take();
35 }
36}
37```
38Or, if the leaked resources won't matter, just ignoring this is fine.
39([`spawn_wait_and_free_current_module_once!()`] already handled its thread.)
40</div>
41*/
42use std::{
43 cell::OnceCell,
44 mem,
45 os::windows::io::{AsRawHandle, OwnedHandle},
46 thread,
47};
48
49use windows::Win32::{
50 Foundation::HANDLE,
51 System::{
52 LibraryLoader::FreeLibraryAndExitThread,
53 Threading::{
54 INFINITE, OpenProcess, PROCESS_SYNCHRONIZE, TerminateThread, WaitForSingleObject,
55 },
56 },
57};
58
59use crate::process::{Pid, module::Module};
60
61pub use dtor::declarative::dtor;
62
63/**
64Should clean up all the references to the DLL's code before,
65including hooks and threads.
66*/
67pub fn free_current_module_and_exit_thread(code: u32) -> ! {
68 unsafe { FreeLibraryAndExitThread(Module::current().0, code) }
69}
70
71/**
72Auto self unload, i.e. wait for the injector process and unload self.
73
74`teardown` should clean up all the references to the DLL's code,
75including hooks and threads.
76
77## Returns
78Actually `windows::core::Result<!>` but `!` is not stable yet.
79
80https://github.com/rust-lang/rust/issues/35121
81*/
82pub fn wait_and_free_current_module(
83 pid: Pid,
84 teardown: impl FnOnce() -> u32,
85) -> windows::core::Result<()> {
86 let process = unsafe { OpenProcess(PROCESS_SYNCHRONIZE, false, *pid) }?;
87 unsafe { WaitForSingleObject(process, INFINITE) };
88 free_current_module_and_exit_thread(teardown())
89}
90
91/**
92A guard that terminates the thread when drop.
93
94Unapply must clean up all threads.
95[`TerminateThread()`] has some [footguns](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-terminatethread#remarks),
96but better than crashing the entire process.
97
98Should be used with [`macro@dtor`], see [Pitfalls](super::dll#pitfalls).
99*/
100pub struct ThreadGuard(OwnedHandle);
101
102impl<T> From<thread::JoinHandle<T>> for ThreadGuard {
103 fn from(thread: thread::JoinHandle<T>) -> Self {
104 Self(thread.into())
105 }
106}
107
108impl ThreadGuard {
109 pub fn dismiss(self) -> OwnedHandle {
110 let t = mem::ManuallyDrop::new(self);
111 unsafe { std::ptr::read(&t.0) }
112 }
113}
114
115impl Drop for ThreadGuard {
116 fn drop(&mut self) {
117 _ = unsafe { TerminateThread(HANDLE(self.0.as_raw_handle()), 0) };
118 }
119}
120
121#[doc(hidden)]
122pub mod manual {
123 use super::*;
124
125 pub unsafe fn spawn_wait_and_free_current_module(
126 pid: Pid,
127 teardown: impl FnOnce() -> u32 + Send + 'static,
128 ) -> ThreadGuard {
129 thread::spawn(move || wait_and_free_current_module(pid, teardown)).into()
130 }
131
132 static mut WAIT_AND_FREE: OnceCell<ThreadGuard> = OnceCell::new();
133
134 /**
135 `teardown` should clean up all the references to the DLL's code,
136 including hooks and threads.
137 */
138 pub unsafe fn spawn_wait_and_free_current_module_once(
139 pid: Pid,
140 teardown: impl FnOnce() -> u32 + Send + 'static,
141 ) {
142 // Dismiss auto self unload thread guard
143 // free() may terminate FreeLibraryAndExitThread() and cause crash.
144 let teardown = || {
145 unsafe { &mut *&raw mut WAIT_AND_FREE }
146 .take()
147 .map(|w| w.dismiss());
148 teardown()
149 };
150 unsafe { &*&raw const WAIT_AND_FREE }
151 .get_or_init(move || unsafe { spawn_wait_and_free_current_module(pid, teardown) });
152 }
153
154 pub fn free() {
155 unsafe { &mut *&raw mut WAIT_AND_FREE }.take();
156 }
157}
158
159/**
160Auto self unload, i.e. wait for the injector process and unload self.
161
162- `pid`: [`Pid`]
163- `teardown`: `impl FnOnce() -> u32 + Send + 'static`
164
165 `teardown` should clean up all the references to the DLL's code,
166 including hooks and threads.
167
168Because using `#[dtor]` will cause some data and code always be compiled even not used,
169this is implemented as a macro instead of a function.
170*/
171#[macro_export]
172macro_rules! spawn_wait_and_free_current_module_once {
173 ($pid:expr, $teardown:expr) => {
174 $crate::inject::dll::dll::dtor! {
175 #[dtor(anonymous)]
176 fn free() {
177 $crate::inject::dll::dll::manual::free();
178 }
179 }
180
181 unsafe {
182 $crate::inject::dll::dll::manual::spawn_wait_and_free_current_module_once(
183 $pid, $teardown,
184 )
185 }
186 };
187}
188pub use spawn_wait_and_free_current_module_once;