Skip to main content

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;