├── .gitignore ├── .gitmodules ├── Cargo.toml ├── appveyor.yml ├── src ├── sync.rs ├── panic.rs ├── function.rs ├── error.rs ├── ffi.rs ├── macros.rs └── lib.rs ├── README.md └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | rustfmt.toml -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/minhook"] 2 | path = src/minhook 3 | url = https://github.com/TsudaKageyu/minhook 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minhook" 3 | description = "A function hooking library for the Rust programming language" 4 | version = "0.1.0" 5 | authors = ["Jascha Neutelings"] 6 | repository = "https://github.com/Jascha-N/minhook-rs" 7 | license = "BSD-2-Clause" 8 | readme = "README.md" 9 | links = "MinHook" 10 | build = "build.rs" 11 | 12 | [dependencies] 13 | kernel32-sys = "0.2" 14 | libc = "0.2" 15 | winapi = "0.2" 16 | 17 | [dependencies.clippy] 18 | version = "*" 19 | optional = true 20 | 21 | [build-dependencies] 22 | gcc = "0.3" 23 | 24 | [features] 25 | increased_arity = [] -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | branches: 2 | only: 3 | - master 4 | 5 | environment: 6 | matrix: 7 | - TARGET: i686-pc-windows-msvc 8 | MSVC_ARCH: x86 9 | - TARGET: x86_64-pc-windows-msvc 10 | MSVC_ARCH: amd64 11 | - TARGET: i686-pc-windows-gnu 12 | MSYS2_BITS: 32 13 | - TARGET: x86_64-pc-windows-gnu 14 | MSYS2_BITS: 64 15 | GH_TOKEN: 16 | secure: UHajdmalPacLa26ORMDaOwtjJP/U4KOWjlqlHOC/ZFd2wlGscrSq9f7hnpyWWHK0 17 | 18 | install: 19 | - git submodule update --init --recursive 20 | - set PATH=C:\Python27;C:\Python27\Scripts;%PATH%;%APPDATA%\Python\Scripts 21 | - pip install "travis-cargo<0.2" --user 22 | - appveyor DownloadFile "https://static.rust-lang.org/dist/rust-nightly-%TARGET%.exe" 23 | - rust-nightly-%TARGET%.exe /VERYSILENT /NORESTART /DIR="C:\Rust" 24 | - set PATH=%PATH%;C:\Rust\bin 25 | - if defined MSYS2_BITS set PATH=%PATH%;C:\msys64\mingw%MSYS2_BITS%\bin 26 | - if defined MSVC_ARCH call "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" %MSVC_ARCH% 27 | - rustc -V -v 28 | - cargo -V -v 29 | 30 | build_script: 31 | - cargo doc --no-deps 32 | - cargo package 33 | 34 | test_script: 35 | - cargo test 36 | 37 | deploy_script: 38 | - travis-cargo doc-upload -------------------------------------------------------------------------------- /src/sync.rs: -------------------------------------------------------------------------------- 1 | use std::{mem, ptr}; 2 | use std::cell::UnsafeCell; 3 | use std::sync::StaticRwLock; 4 | use std::sync::atomic::{AtomicPtr, Ordering}; 5 | 6 | 7 | 8 | #[doc(hidden)] 9 | pub struct AtomicInitCell(AtomicPtr); 10 | 11 | impl AtomicInitCell { 12 | #[doc(hidden)] 13 | pub const fn new() -> AtomicInitCell { 14 | AtomicInitCell(AtomicPtr::new(ptr::null_mut())) 15 | } 16 | 17 | #[doc(hidden)] 18 | pub fn initialize(&self, value: T) -> Result<(), ()> { 19 | let mut boxed = Box::new(value); 20 | if !self.0.compare_and_swap(ptr::null_mut(), &mut *boxed, Ordering::SeqCst).is_null() { 21 | return Err(()); 22 | } 23 | mem::forget(boxed); 24 | Ok(()) 25 | } 26 | 27 | #[doc(hidden)] 28 | pub fn get(&self) -> Option<&'static T> { 29 | let data = self.0.load(Ordering::SeqCst); 30 | if data.is_null() { 31 | return None; 32 | } 33 | unsafe { Some(&*data) } 34 | } 35 | } 36 | 37 | 38 | 39 | pub struct StaticRwCell { 40 | data: UnsafeCell, 41 | lock: StaticRwLock, 42 | } 43 | 44 | impl StaticRwCell { 45 | pub const fn new(value: T) -> StaticRwCell { 46 | StaticRwCell { 47 | data: UnsafeCell::new(value), 48 | lock: StaticRwLock::new() 49 | } 50 | } 51 | 52 | unsafe fn set_unsync(&self, value: T) { 53 | *self.data.get() = value 54 | } 55 | 56 | unsafe fn get_ref_unsync(&self) -> &T { 57 | &*self.data.get() 58 | } 59 | 60 | pub fn set(&'static self, value: T) { 61 | let _lock = self.lock.write(); 62 | 63 | unsafe { self.set_unsync(value); } 64 | } 65 | 66 | pub fn with(&'static self, f: F) -> R 67 | where F: FnOnce(&T) -> R { 68 | let _lock = self.lock.read(); 69 | 70 | unsafe { f(self.get_ref_unsync()) } 71 | } 72 | } 73 | 74 | impl StaticRwCell> { 75 | pub fn take(&'static self) -> Option { 76 | let _lock = self.lock.write(); 77 | 78 | let option = unsafe { &mut *self.data.get() }; 79 | option.take() 80 | } 81 | } 82 | 83 | unsafe impl Sync for StaticRwCell {} 84 | unsafe impl Send for StaticRwCell {} -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # minhook-rs 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/e7yg48n0835hy9b6?svg=true)](https://ci.appveyor.com/project/Jascha-N/minhook-rs) 4 | 5 | [Documentation](http://jascha-n.github.io/minhook-rs) 6 | 7 | A function hooking library for the Rust programming language. This library provides efficient and safe bindings to the 8 | [MinHook](https://github.com/TsudaKageyu/minhook) library. 9 | 10 | It currently supports the x86 and x86_64 architectures and the GCC (MinGW) and MSVC toolchains on Windows. Requires the Rust Nightly compiler. 11 | The supported target triples are: 12 | - `i686-pc-windows-msvc` 13 | - `x86_64-pc-windows-msvc` 14 | - `i686-pc-windows-gnu` 15 | - `x86_64-pc-windows-gnu` 16 | 17 | ## Usage 18 | First, add the following lines to your `Cargo.toml`: 19 | 20 | ```toml 21 | [dependencies] 22 | minhook = { git = "https://github.com/Jascha-N/minhook-rs" } 23 | ``` 24 | 25 | Next, add this to your crate root: 26 | 27 | ```rust 28 | #[macro_use] 29 | extern crate minhook; 30 | ``` 31 | 32 | ### Features 33 | The minhook-rs library has the following feature: 34 | - `increased_arity` - If there is a need to hook functions with an arity greater than 12, this will allow functions of up to 26 arguments to be hooked. 35 | 36 | ## Example 37 | 38 | Example using a static hook. 39 | 40 | `Cargo.toml:` 41 | 42 | ```toml 43 | [dependencies] 44 | minhook = { git = "https://github.com/Jascha-N/minhook-rs" } 45 | winapi = "0.2" 46 | user32-sys = "0.1" 47 | ``` 48 | 49 | `src/main.rs:` 50 | 51 | ```rust 52 | #![feature(const_fn, recover)] 53 | 54 | #[macro_use] 55 | extern crate minhook; 56 | extern crate winapi; 57 | extern crate user32; 58 | 59 | use std::ptr; 60 | 61 | use winapi::{HWND, LPCSTR, UINT, c_int}; 62 | 63 | static_hooks! { 64 | // Create a hook for user32::MessageBoxA. 65 | impl MessageBoxA for user32::MessageBoxA: unsafe extern "system" fn(HWND, LPCSTR, LPCSTR, UINT) -> c_int; 66 | } 67 | 68 | fn main() { 69 | // Create a detour closure. This closure can capture any Sync variables. 70 | let detour = |wnd, text, caption, flags| unsafe { MessageBoxA.call_real(wnd, caption, text, flags) }; 71 | 72 | // Install the hook. 73 | unsafe { MessageBoxA.initialize(detour).unwrap(); } 74 | 75 | let hello = b"Hello\0".as_ptr() as LPCSTR; 76 | let world = b"World\0".as_ptr() as LPCSTR; 77 | 78 | // Call the function. 79 | unsafe { user32::MessageBoxA(ptr::null_mut(), hello, world, winapi::MB_OK); } 80 | 81 | // Enable the hook. 82 | MessageBoxA.enable().unwrap(); 83 | 84 | // Call the - now hooked - function. 85 | unsafe { user32::MessageBoxA(ptr::null_mut(), hello, world, winapi::MB_OK); } 86 | } 87 | ``` 88 | -------------------------------------------------------------------------------- /src/panic.rs: -------------------------------------------------------------------------------- 1 | //! Panic handling for panics caught at foreign code boundaries in detour functions. 2 | 3 | use libc; 4 | use std::any::Any; 5 | use std::io::{self, Write}; 6 | use std::panic::{self, AssertRecoverSafe}; 7 | 8 | use sync::StaticRwCell; 9 | 10 | 11 | 12 | /// A struct providing information about a panic that happened inside of a guarded detour function. 13 | #[derive(Clone, Copy, Debug)] 14 | pub struct DetourPanicInfo<'a> { 15 | payload: &'a (Any + Send), 16 | detour: &'a str 17 | } 18 | 19 | impl<'a> DetourPanicInfo<'a> { 20 | /// Returns the payload associated with the panic. 21 | /// 22 | /// This will commonly, but not always, be a `&'static str` or `String`. 23 | pub fn payload(&self) -> &(Any + Send) { 24 | self.payload 25 | } 26 | 27 | /// Returns the name of the static hook for which the detour function 28 | /// panicked. 29 | pub fn detour(&self) -> &str { 30 | &self.detour 31 | } 32 | } 33 | 34 | 35 | 36 | static HANDLER: StaticRwCell>> = StaticRwCell::new(None); 37 | 38 | /// Registers a custom detour panic handler, replacing any that was previously 39 | /// registered. 40 | /// 41 | /// The panic handler is invoked when an extern detour function panics just before 42 | /// the code would unwind into foreign code. The default handler prints a message 43 | /// to standard error and aborts the process to prevent further unwinding, but this behavior 44 | /// can be customized with the `set_handler` and `take_handler` functions. 45 | /// 46 | /// The handler is provided with a `DetourPanicInfo` struct which contains information 47 | /// about the origin of the panic, including the payload passed to `panic!` and 48 | /// the name of the name of the associated hook. 49 | /// 50 | /// If the handler panics or returns normally, the process will be aborted. 51 | /// 52 | /// The panic handler is a global resource. 53 | pub fn set_handler(handler: F) 54 | where F: Fn(&DetourPanicInfo) + Sync + Send + 'static { 55 | HANDLER.set(Some(Box::new(handler))); 56 | } 57 | 58 | /// Unregisters the current panic handler, returning it. 59 | /// 60 | /// If no custom handler is registered, the default handler will be returned. 61 | pub fn take_handler() -> Box { 62 | HANDLER.take().unwrap_or_else(|| Box::new(default_handler)) 63 | } 64 | 65 | #[doc(hidden)] 66 | pub fn __handle(path: &'static str, name: &'static str, payload: Box) -> ! { 67 | let payload = AssertRecoverSafe(payload); 68 | 69 | let _ = panic::recover(move || { 70 | let full_path = format!("{}::{}", path, name); 71 | let info = DetourPanicInfo { 72 | payload: &**payload, 73 | detour: &full_path 74 | }; 75 | 76 | HANDLER.with(|handler| { 77 | if let Some(ref handler) = *handler { 78 | handler(&info); 79 | } else { 80 | default_handler(&info); 81 | } 82 | }); 83 | }); 84 | 85 | unsafe { libc::abort() } 86 | } 87 | 88 | fn default_handler(info: &DetourPanicInfo) { 89 | let mut stderr = io::stderr(); 90 | let _ = writeln!(stderr, "The detour function for '{}' panicked. Aborting.", info.detour); 91 | let _ = stderr.flush(); 92 | } -------------------------------------------------------------------------------- /src/function.rs: -------------------------------------------------------------------------------- 1 | //! Module containing information about hookable functions. 2 | //! 3 | //! The traits in this module are automatically implemented and should generally not be implemented 4 | //! by users of this library. 5 | 6 | use std::{fmt, mem}; 7 | use std::os::raw::c_void; 8 | 9 | use super::Hook; 10 | 11 | 12 | 13 | /// An untyped function pointer. 14 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 15 | pub struct FnPointer(*mut c_void); 16 | 17 | impl FnPointer { 18 | /// Creates a function pointer from a raw pointer. 19 | /// 20 | /// # Safety 21 | /// 22 | /// This function is unsafe because it can not check if the argument points to valid 23 | /// executable memory. 24 | pub unsafe fn from_raw(ptr: *mut c_void) -> FnPointer { FnPointer(ptr) } 25 | 26 | /// Returns function pointer as a raw pointer. 27 | pub fn to_raw(&self) -> *mut c_void { self.0 } 28 | } 29 | 30 | impl fmt::Pointer for FnPointer { 31 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 32 | write!(fmt, "{:p}", self.0) 33 | } 34 | } 35 | 36 | 37 | 38 | /// Trait representing a function that can be used as a target function or detour function for 39 | /// hooking. 40 | #[rustc_on_unimplemented = "The type `{Self}` is not an eligible target function or \ 41 | detour function."] 42 | pub unsafe trait Function: Sized + Copy + Sync + 'static { 43 | /// Unsafe version of this function. 44 | type Unsafe: UnsafeFunction; 45 | 46 | /// The argument types as a tuple. 47 | type Args; 48 | 49 | /// The return type. 50 | type Output; 51 | 52 | /// The function's arity (number of arguments). 53 | const ARITY: usize; 54 | 55 | /// Constructs a `Function` from an untyped function pointer. 56 | /// 57 | /// # Safety 58 | /// 59 | /// This function is unsafe because it can not check if the argument points to a function 60 | /// of the correct type. 61 | unsafe fn from_ptr(ptr: FnPointer) -> Self; 62 | 63 | /// Returns a untyped function pointer for this function. 64 | fn to_ptr(&self) -> FnPointer; 65 | 66 | /// Returns this function as its unsafe variant. 67 | fn to_unsafe(&self) -> Self::Unsafe; 68 | } 69 | 70 | 71 | 72 | /// Trait representing an unsafe function. 73 | pub unsafe trait UnsafeFunction: Function {} 74 | 75 | 76 | 77 | /// Marker trait indicating that the function `Self` can be hooked by the given function `D`. 78 | #[rustc_on_unimplemented = "The type `{D}` is not a suitable detour function type for a \ 79 | target function of type `{Self}`."] 80 | pub unsafe trait HookableWith: Function {} 81 | 82 | unsafe impl HookableWith for T {} 83 | 84 | 85 | 86 | #[cfg(not(feature = "increased_arity"))] 87 | impl_hookable! { 88 | __arg_0: A, __arg_1: B, __arg_2: C, __arg_3: D, __arg_4: E, __arg_5: F, __arg_6: G, 89 | __arg_7: H, __arg_8: I, __arg_9: J, __arg_10: K, __arg_11: L 90 | } 91 | 92 | #[cfg(feature = "increased_arity")] 93 | impl_hookable! { 94 | __arg_0: A, __arg_1: B, __arg_2: C, __arg_3: D, __arg_4: E, __arg_5: F, __arg_6: G, 95 | __arg_7: H, __arg_8: I, __arg_9: J, __arg_10: K, __arg_11: L, __arg_12: M, __arg_13: N, 96 | __arg_14: O, __arg_15: P, __arg_16: Q, __arg_17: R, __arg_18: S, __arg_19: T, __arg_20: U, 97 | __arg_21: V, __arg_22: W, __arg_23: X, __arg_24: Y, __arg_25: Z 98 | } 99 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::error; 2 | use std::fmt::{self, Display, Formatter}; 3 | 4 | use ffi::MH_STATUS; 5 | 6 | 7 | 8 | /// The error type for all hooking operations. 9 | /// 10 | /// MinHook error status codes map directly to this type. 11 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 12 | pub enum Error { 13 | /// MinHook is already initialized. 14 | AlreadyInitialized, 15 | /// MinHook is not initialized yet, or already uninitialized. 16 | NotInitialized, 17 | /// The hook for the specified target function is already created. 18 | AlreadyCreated, 19 | /// The hook for the specified target function is not created yet. 20 | NotCreated, 21 | /// The hook for the specified target function is already enabled. 22 | AlreadyEnabled, 23 | /// The hook for the specified target function is not enabled yet, or 24 | /// already disabled. 25 | Disabled, 26 | /// The specified pointer is invalid. It points the address of non-allocated 27 | /// and/or non-executable region. 28 | NotExecutable, 29 | /// The specified target function cannot be hooked. 30 | UnsupportedFunction, 31 | /// Failed to allocate memory. 32 | MemoryAlloc, 33 | /// Failed to change the memory protection. 34 | MemoryProtect, 35 | /// The specified module is not loaded. 36 | ModuleNotFound, 37 | /// The specified function is not found. 38 | FunctionNotFound, 39 | 40 | /// The specified module name is invalid. 41 | InvalidModuleName, 42 | /// The specified function name is invalid. 43 | InvalidFunctionName 44 | } 45 | 46 | impl Error { 47 | /// Constructs an `Error` from a MinHook status. 48 | pub fn from_status(status: MH_STATUS) -> Option { 49 | match status { 50 | MH_STATUS::MH_OK => None, 51 | MH_STATUS::MH_ERROR_ALREADY_INITIALIZED => Some(Error::AlreadyInitialized), 52 | MH_STATUS::MH_ERROR_NOT_INITIALIZED => Some(Error::NotInitialized), 53 | MH_STATUS::MH_ERROR_ALREADY_CREATED => Some(Error::AlreadyCreated), 54 | MH_STATUS::MH_ERROR_NOT_CREATED => Some(Error::NotCreated), 55 | MH_STATUS::MH_ERROR_ENABLED => Some(Error::AlreadyEnabled), 56 | MH_STATUS::MH_ERROR_DISABLED => Some(Error::Disabled), 57 | MH_STATUS::MH_ERROR_NOT_EXECUTABLE => Some(Error::NotExecutable), 58 | MH_STATUS::MH_ERROR_UNSUPPORTED_FUNCTION => Some(Error::UnsupportedFunction), 59 | MH_STATUS::MH_ERROR_MEMORY_ALLOC => Some(Error::MemoryAlloc), 60 | MH_STATUS::MH_ERROR_MEMORY_PROTECT => Some(Error::MemoryProtect), 61 | MH_STATUS::MH_ERROR_MODULE_NOT_FOUND => Some(Error::ModuleNotFound), 62 | MH_STATUS::MH_ERROR_FUNCTION_NOT_FOUND => Some(Error::FunctionNotFound), 63 | MH_STATUS::MH_UNKNOWN => unreachable!(), 64 | } 65 | } 66 | } 67 | 68 | impl error::Error for Error { 69 | fn description(&self) -> &str { 70 | match *self { 71 | Error::AlreadyInitialized => "library already initialized", 72 | Error::NotInitialized => "library not initialized", 73 | Error::AlreadyCreated => "hook already created", 74 | Error::NotCreated => "hook not created", 75 | Error::AlreadyEnabled => "hook already enabled", 76 | Error::Disabled => "hook not enabled", 77 | Error::NotExecutable => "invalid pointer", 78 | Error::UnsupportedFunction => "function cannot be hooked", 79 | Error::MemoryAlloc => "failed to allocate memory", 80 | Error::MemoryProtect => "failed to change the memory protection", 81 | Error::ModuleNotFound => "module not loaded", 82 | Error::FunctionNotFound => "function not found", 83 | 84 | Error::InvalidModuleName => "invalid module name", 85 | Error::InvalidFunctionName => "invalid function name" 86 | } 87 | } 88 | } 89 | 90 | impl Display for Error { 91 | fn fmt(&self, formatter: &mut Formatter) -> fmt::Result { 92 | write!(formatter, "{}", match *self { 93 | Error::AlreadyInitialized => "MinHook is already initialized", 94 | Error::NotInitialized => "MinHook is not initialized yet, or already uninitialized", 95 | Error::AlreadyCreated => "The hook for the specified target function is already \ 96 | created", 97 | Error::NotCreated => "The hook for the specified target function is not created yet", 98 | Error::AlreadyEnabled => "The hook for the specified target function is already \ 99 | enabled", 100 | Error::Disabled => "The hook for the specified target function is not enabled yet, or \ 101 | already disabled", 102 | Error::NotExecutable => "The specified pointer is invalid; it points the address of \ 103 | non-allocated and/or non-executable region", 104 | Error::UnsupportedFunction => "The specified target function cannot be hooked", 105 | Error::MemoryAlloc => "Failed to allocate memory", 106 | Error::MemoryProtect => "Failed to change the memory protection", 107 | Error::ModuleNotFound => "The specified module is not loaded", 108 | Error::FunctionNotFound => "The specified function is not found", 109 | 110 | Error::InvalidModuleName => "The specified module name is invalid", 111 | Error::InvalidFunctionName => "The specified function name is invalid" 112 | }) 113 | } 114 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | minhook-rs - A minimalist x86/x86-64 hooking library for Rust 2 | Copyright (C) 2015 Jascha Neutelings. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 17 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 18 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 19 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 22 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 23 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 24 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | ================================================================================ 28 | Portions of this software are Copyright (c) 2009-2015, Tsuda Kageyu. 29 | ================================================================================ 30 | MinHook - The Minimalistic API Hooking Library for x64/x86 31 | Copyright (C) 2009-2015 Tsuda Kageyu. 32 | All rights reserved. 33 | 34 | Redistribution and use in source and binary forms, with or without 35 | modification, are permitted provided that the following conditions 36 | are met: 37 | 38 | 1. Redistributions of source code must retain the above copyright 39 | notice, this list of conditions and the following disclaimer. 40 | 2. Redistributions in binary form must reproduce the above copyright 41 | notice, this list of conditions and the following disclaimer in the 42 | documentation and/or other materials provided with the distribution. 43 | 44 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 45 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 46 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 47 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER 48 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 49 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 50 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 51 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 52 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 53 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 54 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 55 | 56 | ================================================================================ 57 | Portions of this software are Copyright (c) 2008-2009, Vyacheslav Patkov. 58 | ================================================================================ 59 | Hacker Disassembler Engine 32 C 60 | Copyright (c) 2008-2009, Vyacheslav Patkov. 61 | All rights reserved. 62 | 63 | Redistribution and use in source and binary forms, with or without 64 | modification, are permitted provided that the following conditions 65 | are met: 66 | 67 | 1. Redistributions of source code must retain the above copyright 68 | notice, this list of conditions and the following disclaimer. 69 | 2. Redistributions in binary form must reproduce the above copyright 70 | notice, this list of conditions and the following disclaimer in the 71 | documentation and/or other materials provided with the distribution. 72 | 73 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 74 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 75 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 76 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR 77 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 78 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 79 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 80 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 81 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 82 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 83 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 84 | 85 | ------------------------------------------------------------------------------- 86 | Hacker Disassembler Engine 64 C 87 | Copyright (c) 2008-2009, Vyacheslav Patkov. 88 | All rights reserved. 89 | 90 | Redistribution and use in source and binary forms, with or without 91 | modification, are permitted provided that the following conditions 92 | are met: 93 | 94 | 1. Redistributions of source code must retain the above copyright 95 | notice, this list of conditions and the following disclaimer. 96 | 2. Redistributions in binary form must reproduce the above copyright 97 | notice, this list of conditions and the following disclaimer in the 98 | documentation and/or other materials provided with the distribution. 99 | 100 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 101 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 102 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 103 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR 104 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 105 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 106 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 107 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 108 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 109 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 110 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 111 | -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | //! Raw bindings for the MinHook library. 2 | //! 3 | //! The functions exposed in this module provide absolutely no guarantees with 4 | //! respect to type-safety of hooked functions. There should generally be no 5 | //! reason to use this module directly. 6 | #![allow(dead_code)] 7 | 8 | use std::ptr; 9 | 10 | pub use winapi::{LPCSTR, LPCWSTR, LPVOID}; 11 | 12 | 13 | 14 | /// MinHook Error Codes. 15 | #[must_use] 16 | #[repr(C)] 17 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 18 | pub enum MH_STATUS { 19 | /// Unknown error. Should not be returned. 20 | MH_UNKNOWN = -1, 21 | /// Successful. 22 | MH_OK = 0, 23 | /// MinHook is already initialized. 24 | MH_ERROR_ALREADY_INITIALIZED, 25 | /// MinHook is not initialized yet, or already uninitialized. 26 | MH_ERROR_NOT_INITIALIZED, 27 | /// The hook for the specified target function is already created. 28 | MH_ERROR_ALREADY_CREATED, 29 | /// The hook for the specified target function is not created yet. 30 | MH_ERROR_NOT_CREATED, 31 | /// The hook for the specified target function is already enabled. 32 | MH_ERROR_ENABLED, 33 | /// The hook for the specified target function is not enabled yet, or 34 | /// already disabled. 35 | MH_ERROR_DISABLED, 36 | /// The specified pointer is invalid. It points the address of non-allocated 37 | /// and/or non-executable region. 38 | MH_ERROR_NOT_EXECUTABLE, 39 | /// The specified target function cannot be hooked. 40 | MH_ERROR_UNSUPPORTED_FUNCTION, 41 | /// Failed to allocate memory. 42 | MH_ERROR_MEMORY_ALLOC, 43 | /// Failed to change the memory protection. 44 | MH_ERROR_MEMORY_PROTECT, 45 | /// The specified module is not loaded. 46 | MH_ERROR_MODULE_NOT_FOUND, 47 | /// The specified function is not found. 48 | MH_ERROR_FUNCTION_NOT_FOUND 49 | } 50 | 51 | 52 | 53 | /// Can be passed as a parameter to `MH_EnableHook`, `MH_DisableHook`, 54 | /// `MH_QueueEnableHook` or `MH_QueueDisableHook`. 55 | pub const MH_ALL_HOOKS: LPVOID = ptr::null_mut(); 56 | 57 | 58 | 59 | extern "system" { 60 | /// Initialize the MinHook library. 61 | /// 62 | /// You must call this function **exactly once** at the beginning of your 63 | /// program. 64 | pub fn MH_Initialize() -> MH_STATUS; 65 | 66 | /// Uninitialize the MinHook library. 67 | /// 68 | /// You must call this function **exactly once** at the end of your program. 69 | pub fn MH_Uninitialize() -> MH_STATUS; 70 | 71 | /// Creates a Hook for the specified target function, in disabled state. 72 | /// 73 | /// # Arguments 74 | /// * `pTarget` - A pointer to the target function, which will be 75 | /// overridden by the detour function. 76 | /// * `pDetour` - A pointer to the detour function, which will override 77 | /// the target function. 78 | /// * `ppOriginal` - A pointer to the trampoline function, which will be 79 | /// used to call the original target function. 80 | /// This parameter can be `null`. 81 | pub fn MH_CreateHook(pTarget: LPVOID, pDetour: LPVOID, ppOriginal: *mut LPVOID) -> MH_STATUS; 82 | 83 | /// Creates a Hook for the specified API function, in disabled state. 84 | /// 85 | /// # Arguments 86 | /// * `pszModule` - A pointer to the loaded module name which contains the 87 | /// target function. 88 | /// * `pszTarget` - A pointer to the target function name, which will be 89 | /// overridden by the detour function. 90 | /// * `pDetour` - A pointer to the detour function, which will override 91 | /// the target function. 92 | /// * `ppOriginal` - A pointer to the trampoline function, which will be 93 | /// used to call the original target function. 94 | /// This parameter can be `null`. 95 | pub fn MH_CreateHookApi(pszModule: LPCWSTR, pszProcName: LPCSTR, pDetour: LPVOID, 96 | ppOriginal: *mut LPVOID) -> MH_STATUS; 97 | 98 | /// Creates a Hook for the specified API function, in disabled state. 99 | /// 100 | /// # Arguments 101 | /// * `pszModule` - A pointer to the loaded module name which contains the 102 | /// target function. 103 | /// * `pszTarget` - A pointer to the target function name, which will be 104 | /// overridden by the detour function. 105 | /// * `pDetour` - A pointer to the detour function, which will override 106 | /// the target function. 107 | /// * `ppOriginal` - A pointer to the trampoline function, which will be 108 | /// used to call the original target function. 109 | /// This parameter can be `null`. 110 | /// * `ppTarget` - A pointer to the target function, which will be used 111 | /// with other functions. 112 | /// This parameter can be `null`. 113 | pub fn MH_CreateHookApiEx(pszModule: LPCWSTR, pszProcName: LPCSTR, pDetour: LPVOID, 114 | ppOriginal: *mut LPVOID, ppTarget: *mut LPVOID) -> MH_STATUS; 115 | 116 | /// Removes an already created hook. 117 | /// 118 | /// # Arguments 119 | /// * `pTarget` - A pointer to the target function. 120 | pub fn MH_RemoveHook(pTarget: LPVOID) -> MH_STATUS; 121 | 122 | /// Enables an already created hook. 123 | /// 124 | /// # Arguments 125 | /// * `pTarget` - A pointer to the target function. 126 | /// If this parameter is `MH_ALL_HOOKS`, all created hooks are 127 | /// enabled in one go. 128 | pub fn MH_EnableHook(pTarget: LPVOID) -> MH_STATUS; 129 | 130 | /// Disables an already created hook. 131 | /// 132 | /// # Arguments 133 | /// * `pTarget` - A pointer to the target function. 134 | /// If this parameter is `MH_ALL_HOOKS`, all created hooks are 135 | /// disabled in one go. 136 | pub fn MH_DisableHook(pTarget: LPVOID) -> MH_STATUS; 137 | 138 | /// Queues to enable an already created hook. 139 | /// 140 | /// # Arguments 141 | /// * `pTarget` - A pointer to the target function. 142 | /// If this parameter is `MH_ALL_HOOKS`, all created hooks are 143 | /// queued to be enabled. 144 | pub fn MH_QueueEnableHook(pTarget: LPVOID) -> MH_STATUS; 145 | 146 | /// Queues to disable an already created hook. 147 | /// 148 | /// # Arguments 149 | /// * `pTarget` - A pointer to the target function. 150 | /// If this parameter is `MH_ALL_HOOKS`, all created hooks are 151 | /// queued to be disabled. 152 | pub fn MH_QueueDisableHook(pTarget: LPVOID) -> MH_STATUS; 153 | 154 | /// Applies all queued changes in one go. 155 | pub fn MH_ApplyQueued() -> MH_STATUS; 156 | } 157 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | /// Defines one or more static hooks. 2 | /// 3 | /// A `static_hooks!` block can contain one or more hook definitions of the following forms: 4 | /// 5 | /// ```ignore 6 | /// // Creates a `StaticHookWithDefault` 7 | /// #[ATTR]* pub? impl HOOK_VAR_NAME for PATH::TO::TARGET: FN_TYPE = FN_EXPR; 8 | /// #[ATTR]* pub? impl HOOK_VAR_NAME for "FUNCTION" in "MODULE": FN_TYPE = FN_EXPR; 9 | /// 10 | /// // Creates a `StaticHook` 11 | /// #[ATTR]* pub? impl HOOK_VAR_NAME for PATH::TO::TARGET: FN_TYPE; 12 | /// #[ATTR]* pub? impl HOOK_VAR_NAME for "FUNCTION" in "MODULE": FN_TYPE; 13 | /// ``` 14 | /// 15 | /// All of the above definitions create a static variable with the specified name of 16 | /// type `StaticHook` or `StaticHookWithDefault` for a target function of the given 17 | /// type. If the function signature contains `extern`, any panics that happen inside of the 18 | /// detour `Fn` are automatically caught before they can propagate across foreign code boundaries. 19 | /// See the `panic` submodule for more information. 20 | /// 21 | /// The first two forms create a static hook with a default detour `Fn`. This is useful if 22 | /// the detour `Fn` is a closure that does not need to capture any local variables 23 | /// or if the detour `Fn` is just a normal function. See `StaticHookWithDefault`. 24 | /// 25 | /// The last two forms require a `Fn` to be supplied at the time of initialization of the 26 | /// static hook. In this case a closure that captures local variables can be supplied. 27 | /// See `StaticHook`. 28 | /// 29 | /// The first and third forms are used for hooking functions by their compile-time identifier. 30 | /// 31 | /// The second and fourth form will try to find the target function by name at initialization 32 | /// instead of at compile time. These forms require the exported function symbol name and 33 | /// its containing module's name to be supplied. 34 | /// 35 | /// The optional `pub` keyword can be used to give the resulting hook variable public 36 | /// visibility. Any attributes used on a hook definition will be applied to the resulting 37 | /// hook variable. 38 | #[macro_export] 39 | #[cfg_attr(rustfmt, rustfmt_skip)] 40 | macro_rules! static_hooks { 41 | // Step 1: parse attributes 42 | (@parse_attr ($($args:tt)*) 43 | | $(#[$var_attr:meta])* $next:tt $($rest:tt)*) => { 44 | static_hooks!(@parse_pub ($($args)* ($($var_attr)*)) | $next $($rest)*); 45 | }; 46 | 47 | // Step 2: parse optional pub modifier 48 | (@parse_pub ($($args:tt)*) 49 | | pub impl $($rest:tt)*) => 50 | { 51 | static_hooks!(@parse_mod ($($args)* (pub)) | $($rest)*); 52 | }; 53 | (@parse_pub ($($args:tt)*) 54 | | impl $($rest:tt)*) => 55 | { 56 | static_hooks!(@parse_mod ($($args)* ()) | $($rest)*); 57 | }; 58 | 59 | // Step 3: parse optional mut or const modifier 60 | // (@parse_mod ($($args:tt)*) 61 | // | mut $($rest:tt)*) => 62 | // { 63 | // static_hooks!(@parse_name_target ($($args)* (mut)) | $($rest)*); 64 | // }; 65 | // (@parse_mod ($($args:tt)*) 66 | // | const $($rest:tt)*) => 67 | // { 68 | // static_hooks!(@parse_name_target ($($args)* (const)) | $($rest)*); 69 | // }; 70 | (@parse_mod ($($args:tt)*) 71 | | $($rest:tt)*) => 72 | { 73 | static_hooks!(@parse_name_target ($($args)* ()) | $($rest)*); 74 | }; 75 | 76 | // Step 4: parse name and target 77 | (@parse_name_target ($($args:tt)*) 78 | | $var_name:ident for $target_fn_name:tt in $target_mod_name:tt : $($rest:tt)*) => 79 | { 80 | static_hooks!(@parse_fn_unsafe ($($args)* ($var_name) ($crate::__StaticHookTarget::Dynamic($target_mod_name, $target_fn_name))) | $($rest)*); 81 | }; 82 | (@parse_name_target ($($args:tt)*) 83 | | $var_name:ident for $target_path:path : $($rest:tt)*) => 84 | { 85 | static_hooks!(@parse_fn_unsafe ($($args)* ($var_name) ($crate::__StaticHookTarget::Static($target_path))) | $($rest)*); 86 | }; 87 | 88 | // Step 5a: parse optional unsafe modifier 89 | (@parse_fn_unsafe ($($args:tt)*) 90 | | unsafe $($rest:tt)*) => 91 | { 92 | static_hooks!(@parse_fn_linkage ($($args)*) (unsafe) | $($rest)*); 93 | }; 94 | (@parse_fn_unsafe ($($args:tt)*) 95 | | $($rest:tt)*) => { 96 | static_hooks!(@parse_fn_linkage ($($args)*) () | $($rest)*); 97 | }; 98 | 99 | // Step 5b: parse linkage 100 | (@parse_fn_linkage ($($args:tt)*) ($($fn_mod:tt)*) 101 | | extern $linkage:tt fn $($rest:tt)*) => 102 | { 103 | static_hooks!(@parse_fn_args ($($args)* ($($fn_mod)* extern $linkage) (GUARD)) | $($rest)*); 104 | }; 105 | (@parse_fn_linkage ($($args:tt)*) ($($fn_mod:tt)*) 106 | | extern fn $($rest:tt)*) => 107 | { 108 | static_hooks!(@parse_fn_args ($($args)* ($($fn_mod)* extern) (GUARD)) | $($rest)*); 109 | }; 110 | (@parse_fn_linkage ($($args:tt)*) ($($fn_mod:tt)*) 111 | | fn $($rest:tt)*) => 112 | { 113 | static_hooks!(@parse_fn_args ($($args)* ($($fn_mod)*) (NO_GUARD)) | $($rest)*); 114 | }; 115 | 116 | // Step 5c: parse argument types and return type 117 | // Requires explicit look-ahead to satisfy rule for tokens following ty fragment specifier 118 | (@parse_fn_args ($($args:tt)*) 119 | | ($($arg_type:ty),*) -> $return_type:ty = $($rest:tt)*) => 120 | { 121 | static_hooks!(@parse_fn_value ($($args)* ($($arg_type)*) ($return_type)) | = $($rest)*); 122 | }; 123 | (@parse_fn_args ($($args:tt)*) 124 | | ($($arg_type:ty),*) -> $return_type:ty ; $($rest:tt)*) => 125 | { 126 | static_hooks!(@parse_fn_value ($($args)* ($($arg_type)*) ($return_type)) | ; $($rest)*); 127 | }; 128 | 129 | (@parse_fn_args ($($args:tt)*) 130 | | ($($arg_type:ty),*) $($rest:tt)*) => 131 | { 132 | static_hooks!(@parse_fn_value ($($args)* ($($arg_type)*) (())) | $($rest)*); 133 | }; 134 | 135 | // Step 6: parse argument types and return type 136 | // Requires explicit look-ahead to satisfy rule for tokens following ty fragment specifier 137 | (@parse_fn_value ($($args:tt)*) 138 | | = $value:expr ; $($rest:tt)*) => 139 | { 140 | static_hooks!(@parse_rest ($($args)* ($value)) | $($rest)*); 141 | }; 142 | (@parse_fn_value ($($args:tt)*) 143 | | ; $($rest:tt)*) => 144 | { 145 | static_hooks!(@parse_rest ($($args)* (!)) | $($rest)*); 146 | }; 147 | 148 | // Step 6: parse rest and recurse 149 | (@parse_rest ($($args:tt)*) 150 | | $($rest:tt)+) => 151 | { 152 | static_hooks!(@make $($args)*); 153 | static_hooks!($($rest)*); 154 | }; 155 | (@parse_rest ($($args:tt)*) 156 | | ) => 157 | { 158 | static_hooks!(@make $($args)*); 159 | }; 160 | 161 | // Step 7: parse rest and recurse 162 | (@make ($($var_attr:meta)*) ($($var_mod:tt)*) ($($hook_mod:tt)*) ($var_name:ident) ($target:expr) 163 | ($($fn_mod:tt)*) ($guard:tt) ($($arg_type:ty)*) ($return_type:ty) ($value:tt)) => 164 | { 165 | static_hooks!(@gen_arg_names (make_hook_var) 166 | ( 167 | ($($var_attr)*) ($($var_mod)*) ($($hook_mod)*) ($var_name) ($target) 168 | ($($fn_mod)*) ($guard) ($($arg_type)*) ($return_type) ($value) 169 | ($($fn_mod)* fn ($($arg_type),*) -> $return_type) 170 | ) 171 | ($($arg_type)*)); 172 | }; 173 | 174 | (@make_hook_var ($($arg_name:ident)*) ($($var_attr:meta)*) ($($var_mod:tt)*) ($($hook_mod:tt)*) 175 | ($var_name:ident) ($target:expr) ($($fn_mod:tt)*) ($guard:tt) 176 | ($($arg_type:ty)*) ($return_type:ty) (!) ($fn_type:ty)) => 177 | { 178 | static_hooks!(@make_item 179 | #[allow(non_upper_case_globals)] 180 | $(#[$var_attr])* 181 | $($var_mod)* static $var_name: $crate::StaticHook<$fn_type> = { 182 | static __DATA: $crate::AtomicInitCell<$crate::__StaticHookInner<$fn_type>> = $crate::AtomicInitCell::new(); 183 | 184 | static_hooks!(@make_detour ($guard) ($var_name) ($($fn_mod)*) ($($arg_name)*) ($($arg_type)*) ($return_type)); 185 | 186 | $crate::StaticHook::<$fn_type>::__new(&__DATA, $target, __detour) 187 | }; 188 | ); 189 | }; 190 | 191 | (@make_hook_var ($($arg_name:ident)*) ($($var_attr:meta)*) ($($var_mod:tt)*) ($($hook_mod:tt)*) 192 | ($var_name:ident) ($target:expr) ($($fn_mod:tt)*) ($guard:tt) 193 | ($($arg_type:ty)*) ($return_type:ty) ($value:tt) ($fn_type:ty)) => 194 | { 195 | static_hooks!(@make_item 196 | #[allow(non_upper_case_globals)] 197 | $(#[$var_attr])* 198 | $($var_mod)* static $var_name: $crate::StaticHookWithDefault<$fn_type> = { 199 | static __DATA: $crate::AtomicInitCell<$crate::__StaticHookInner<$fn_type>> = $crate::AtomicInitCell::new(); 200 | 201 | static_hooks!(@make_detour ($guard) ($var_name) ($($fn_mod)*) ($($arg_name)*) ($($arg_type)*) ($return_type)); 202 | 203 | $crate::StaticHookWithDefault::<$fn_type>::__new( 204 | $crate::StaticHook::__new(&__DATA, $target, __detour), 205 | &$value) 206 | }; 207 | ); 208 | }; 209 | 210 | (@make_detour (GUARD) ($var_name:ident) ($($fn_mod:tt)*) ($($arg_name:ident)*) ($($arg_type:ty)*) ($return_type:ty)) => { 211 | static_hooks!(@make_item 212 | #[inline(never)] 213 | $($fn_mod)* fn __detour($($arg_name: $arg_type),*) -> $return_type { 214 | ::std::panic::recover(|| { 215 | let &$crate::__StaticHookInner(_, ref closure) = __DATA.get().unwrap(); 216 | closure($($arg_name),*) 217 | }).unwrap_or_else(|payload| $crate::panic::__handle(module_path!(), stringify!($var_name), payload)) 218 | } 219 | ); 220 | }; 221 | 222 | (@make_detour (NO_GUARD) ($var_name:ident) ($($fn_mod:tt)*) ($($arg_name:ident)*) ($($arg_type:ty)*) ($return_type:ty)) => { 223 | static_hooks!(@make_item 224 | #[inline(never)] 225 | $($fn_mod)* fn __detour($($arg_name: $arg_type),*) -> $return_type { 226 | let &$crate::__StaticHookInner(_, ref closure) = __DATA.get().unwrap(); 227 | closure($($arg_name),*) 228 | } 229 | ); 230 | }; 231 | 232 | 233 | 234 | // Makes sure items are interpreted correctly 235 | (@make_item $item:item) => { 236 | $item 237 | }; 238 | 239 | // Generates a list of idents for each given token and invokes the macro by the given label passing through arguments 240 | (@gen_arg_names ($label:ident) ($($args:tt)*) ($($token:tt)*)) => { 241 | static_hooks!(@gen_arg_names ($label) 242 | ($($args)*) 243 | ( 244 | __arg_0 __arg_1 __arg_2 __arg_3 __arg_4 __arg_5 __arg_6 __arg_7 245 | __arg_8 __arg_9 __arg_10 __arg_11 __arg_12 __arg_13 __arg_14 __arg_15 246 | __arg_16 __arg_17 __arg_18 __arg_19 __arg_20 __arg_21 __arg_22 __arg_23 247 | __arg_24 __arg_25 248 | ) 249 | ($($token)*) 250 | ()); 251 | }; 252 | (@gen_arg_names ($label:ident) ($($args:tt)*) ($hd_name:tt $($tl_name:tt)*) ($hd:tt $($tl:tt)*) ($($acc:tt)*) ) => { 253 | static_hooks!(@gen_arg_names ($label) ($($args)*) ($($tl_name)*) ($($tl)*) ($($acc)* $hd_name)); 254 | }; 255 | (@gen_arg_names ($label:ident) ($($args:tt)*) ($($name:tt)*) () ($($acc:tt)*)) => { 256 | static_hooks!(@$label ($($acc)*) $($args)*); 257 | }; 258 | 259 | // Step 0 260 | ($($t:tt)+) => { 261 | static_hooks!(@parse_attr () | $($t)+); 262 | }; 263 | } 264 | 265 | macro_rules! impl_hookable { 266 | (@recurse () ($($nm:ident : $ty:ident),*)) => { 267 | impl_hookable!(@impl_all ($($nm : $ty),*)); 268 | }; 269 | (@recurse ($hd_nm:ident : $hd_ty:ident $(, $tl_nm:ident : $tl_ty:ident)*) ($($nm:ident : $ty:ident),*)) => { 270 | impl_hookable!(@impl_all ($($nm : $ty),*)); 271 | impl_hookable!(@recurse ($($tl_nm : $tl_ty),*) ($($nm : $ty,)* $hd_nm : $hd_ty)); 272 | }; 273 | 274 | (@impl_all ($($nm:ident : $ty:ident),*)) => { 275 | impl_hookable!(@impl_pair ($($nm : $ty),*) ( fn($($ty),*) -> Ret)); 276 | impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "cdecl" fn($($ty),*) -> Ret)); 277 | impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "stdcall" fn($($ty),*) -> Ret)); 278 | impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "fastcall" fn($($ty),*) -> Ret)); 279 | impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "win64" fn($($ty),*) -> Ret)); 280 | impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "C" fn($($ty),*) -> Ret)); 281 | impl_hookable!(@impl_pair ($($nm : $ty),*) (extern "system" fn($($ty),*) -> Ret)); 282 | }; 283 | 284 | (@impl_pair ($($nm:ident : $ty:ident),*) ($($fn_t:tt)*)) => { 285 | impl_hookable!(@impl_fun ($($nm : $ty),*) ($($fn_t)*) (unsafe $($fn_t)*)); 286 | }; 287 | 288 | (@impl_fun ($($nm:ident : $ty:ident),*) ($safe_type:ty) ($unsafe_type:ty)) => { 289 | impl_hookable!(@impl_core ($($nm : $ty),*) ($safe_type) ($unsafe_type)); 290 | impl_hookable!(@impl_core ($($nm : $ty),*) ($unsafe_type) ($unsafe_type)); 291 | 292 | impl_hookable!(@impl_hookable_with ($($nm : $ty),*) ($unsafe_type) ($safe_type)); 293 | 294 | impl_hookable!(@impl_safe ($($nm : $ty),*) ($safe_type)); 295 | impl_hookable!(@impl_unsafe ($($nm : $ty),*) ($unsafe_type)); 296 | }; 297 | 298 | (@impl_hookable_with ($($nm:ident : $ty:ident),*) ($target:ty) ($detour:ty)) => { 299 | unsafe impl HookableWith<$detour> for $target {} 300 | }; 301 | 302 | (@impl_safe ($($nm:ident : $ty:ident),*) ($fn_type:ty)) => { 303 | impl Hook<$fn_type> { 304 | #[doc(hidden)] 305 | #[cfg_attr(feature = "clippy", allow(too_many_arguments))] 306 | pub fn call_real(&self, $($nm : $ty),*) -> Ret { 307 | (self.trampoline)($($nm),*) 308 | } 309 | } 310 | }; 311 | 312 | (@impl_unsafe ($($nm:ident : $ty:ident),*) ($fn_type:ty)) => { 313 | unsafe impl UnsafeFunction for $fn_type {} 314 | 315 | impl Hook<$fn_type> { 316 | #[doc(hidden)] 317 | #[cfg_attr(feature = "clippy", allow(too_many_arguments))] 318 | pub unsafe fn call_real(&self, $($nm : $ty),*) -> Ret { 319 | (self.trampoline)($($nm),*) 320 | } 321 | } 322 | }; 323 | 324 | (@impl_core ($($nm:ident : $ty:ident),*) ($fn_type:ty) ($unsafe_type:ty)) => { 325 | unsafe impl Function for $fn_type { 326 | type Args = ($($ty,)*); 327 | type Output = Ret; 328 | type Unsafe = $unsafe_type; 329 | 330 | const ARITY: usize = impl_hookable!(@count ($($ty)*)); 331 | 332 | unsafe fn from_ptr(ptr: FnPointer) -> Self { 333 | mem::transmute(ptr.to_raw()) 334 | } 335 | 336 | fn to_ptr(&self) -> FnPointer { 337 | unsafe { FnPointer::from_raw(*self as *mut c_void) } 338 | } 339 | 340 | #[cfg_attr(feature = "clippy", allow(useless_transmute))] 341 | fn to_unsafe(&self) -> Self::Unsafe { 342 | unsafe { mem::transmute(*self) } 343 | } 344 | } 345 | }; 346 | 347 | (@count ()) => { 348 | 0 349 | }; 350 | (@count ($hd:tt $($tl:tt)*)) => { 351 | 1 + impl_hookable!(@count ($($tl)*)) 352 | }; 353 | 354 | ($($nm:ident : $ty:ident),*) => { 355 | impl_hookable!(@recurse ($($nm : $ty),*) ()); 356 | }; 357 | } 358 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # The minhook-rs library 2 | //! This library provides function hooking support to Rust by providing a 3 | //! wrapper around the [MinHook][minhook] library. 4 | //! 5 | //! [minhook]: http://www.codeproject.com/KB/winsdk/LibMinHook.aspx 6 | #![feature(associated_consts, 7 | const_fn, 8 | on_unimplemented, 9 | recover, 10 | static_mutex, 11 | static_rwlock, 12 | std_panic, 13 | unboxed_closures)] 14 | #![cfg_attr(test, feature(static_recursion))] 15 | #![cfg_attr(feature = "clippy", feature(plugin))] 16 | #![cfg_attr(feature = "clippy", plugin(clippy))] 17 | #![warn(missing_docs)] 18 | 19 | extern crate libc; 20 | extern crate kernel32; 21 | extern crate winapi; 22 | 23 | use std::{mem, ptr, result}; 24 | use std::ffi::OsStr; 25 | use std::ops::Deref; 26 | use std::os::windows::ffi::OsStrExt; 27 | use std::sync::StaticMutex; 28 | 29 | use function::{Function, FnPointer, HookableWith}; 30 | 31 | pub use error::Error; 32 | pub use sync::AtomicInitCell; 33 | 34 | mod error; 35 | mod ffi; 36 | #[macro_use] mod macros; 37 | mod sync; 38 | 39 | pub mod function; 40 | pub mod panic; 41 | 42 | 43 | 44 | /// Result type for most functions and methods in this module. 45 | pub type Result = result::Result; 46 | 47 | 48 | 49 | /// A queue of hook changes to be applied at once. 50 | #[derive(Debug)] 51 | pub struct HookQueue(Vec<(FnPointer, bool)>); 52 | 53 | impl HookQueue { 54 | /// Create a new empty queue. 55 | #[cfg_attr(feature = "clippy", allow(new_without_default))] 56 | pub fn new() -> HookQueue { 57 | HookQueue(Vec::new()) 58 | } 59 | 60 | /// Queue the given hook to be enabled. 61 | pub fn enable(&mut self, hook: &Hook) -> &mut HookQueue { 62 | self.0.push((hook.target, true)); 63 | self 64 | } 65 | 66 | /// Queue the given hook to be disabled. 67 | pub fn disable(&mut self, hook: &Hook) -> &mut HookQueue { 68 | self.0.push((hook.target, false)); 69 | self 70 | } 71 | 72 | /// Applies all the changes in this queue at once. 73 | pub fn apply(&mut self) -> Result<()> { 74 | static LOCK: StaticMutex = StaticMutex::new(); 75 | 76 | try!(initialize()); 77 | let _lock = LOCK.lock().unwrap(); 78 | 79 | for &(target, enabled) in &*self.0 { 80 | // Any failure at this point is a bug. 81 | if enabled { 82 | unsafe { s2r(ffi::MH_QueueEnableHook(target.to_raw())).unwrap() }; 83 | } else { 84 | unsafe { s2r(ffi::MH_QueueDisableHook(target.to_raw())).unwrap() }; 85 | } 86 | } 87 | unsafe { s2r(ffi::MH_ApplyQueued()) } 88 | } 89 | } 90 | 91 | 92 | 93 | /// A hook that is destroyed when it goes out of scope. 94 | #[derive(Debug)] 95 | pub struct Hook { 96 | target: FnPointer, 97 | trampoline: T 98 | } 99 | 100 | impl Hook { 101 | /// Create a new hook given a target function and a compatible detour function. 102 | /// 103 | /// The hook is disabled by default. Even when this function is succesful, there is no 104 | /// guaranteee that the detour function will actually get called when the target function gets 105 | /// called. An invocation of the target function might for example get inlined in which case 106 | /// it is impossible to hook at runtime. 107 | /// 108 | /// # Safety 109 | /// 110 | /// The given target function type must uniquely match the actual target function. This 111 | /// means two things: the given target function type has to be correct, but also there 112 | /// can not be two function pointers with different signatures pointing to the same 113 | /// code location. This last situation can for example happen when the Rust compiler 114 | /// or LLVM decide to merge multiple functions with the same code into one. 115 | pub unsafe fn create(target: T, detour: D) -> Result> 116 | where T: HookableWith, D: Function { 117 | try!(initialize()); 118 | 119 | let target = target.to_ptr(); 120 | let detour = detour.to_ptr(); 121 | let mut trampoline = mem::uninitialized(); 122 | try!(s2r(ffi::MH_CreateHook(target.to_raw(), detour.to_raw(), &mut trampoline))); 123 | 124 | Ok(Hook { 125 | target: target, 126 | trampoline: T::from_ptr(FnPointer::from_raw(trampoline)), 127 | }) 128 | } 129 | 130 | /// Create a new hook given the name of the module, the name of the function symbol and a 131 | /// compatible detour function. 132 | /// 133 | /// The module has to be loaded before this function is called. This function does not 134 | /// attempt to load the module first. The hook is disabled by default. 135 | /// 136 | /// # Safety 137 | /// 138 | /// The target module must remain loaded in memory for the entire duration of the hook. 139 | /// 140 | /// See `create()` for more safety requirements. 141 | pub unsafe fn create_api(target_module: M, target_function: FunctionId, detour: D) -> Result> 142 | where M: AsRef, T: HookableWith, D: Function { 143 | fn str_to_wstring(string: &OsStr) -> Option> { 144 | let mut wide = string.encode_wide().collect::>(); 145 | if wide.contains(&0) { 146 | return None; 147 | } 148 | wide.push(0); 149 | Some(wide) 150 | } 151 | 152 | try!(initialize()); 153 | 154 | let module_name = try!(str_to_wstring(target_module.as_ref()).ok_or(Error::InvalidModuleName)); 155 | 156 | let (function_name, _data) = match target_function { 157 | FunctionId::Ordinal(ord) => (ord as winapi::LPCSTR, Vec::new()), 158 | FunctionId::Name(name) => { 159 | let symbol_name_wide = try!(str_to_wstring(name).ok_or(Error::InvalidFunctionName)); 160 | 161 | let size = kernel32::WideCharToMultiByte(winapi::CP_ACP, 0, symbol_name_wide.as_ptr(), -1, ptr::null_mut(), 0, ptr::null(), ptr::null_mut()); 162 | if size == 0 { 163 | return Err(Error::InvalidFunctionName); 164 | } 165 | 166 | let mut buffer = Vec::with_capacity(size as usize); 167 | buffer.set_len(size as usize); 168 | 169 | let size = kernel32::WideCharToMultiByte(winapi::CP_ACP, 0, symbol_name_wide.as_ptr(), -1, buffer.as_mut_ptr(), size, ptr::null(), ptr::null_mut()); 170 | if size == 0 { 171 | return Err(Error::InvalidFunctionName); 172 | } 173 | 174 | (buffer.as_ptr(), buffer) 175 | } 176 | }; 177 | 178 | let detour = detour.to_ptr(); 179 | let mut trampoline = mem::uninitialized(); 180 | let mut target = mem::uninitialized(); 181 | 182 | try!(s2r(ffi::MH_CreateHookApiEx(module_name.as_ptr(), function_name, detour.to_raw(), &mut trampoline, &mut target))); 183 | 184 | Ok(Hook { 185 | target: FnPointer::from_raw(target), 186 | trampoline: T::from_ptr(FnPointer::from_raw(trampoline)), 187 | }) 188 | } 189 | 190 | /// Returns a pointer to the trampoline function. 191 | /// 192 | /// Calling the returned function is unsafe because it will point to invalid memory after the 193 | /// hook is destroyed. 194 | pub fn trampoline(&self) -> T::Unsafe { 195 | self.trampoline.to_unsafe() 196 | } 197 | 198 | /// Enables this hook. 199 | /// 200 | /// Consider using a `HookQueue` if you want to enable/disable a large amount of hooks at once. 201 | pub fn enable(&self) -> Result<()> { 202 | unsafe { s2r(ffi::MH_EnableHook(self.target.to_raw())) } 203 | } 204 | 205 | /// Disables this hook. 206 | /// 207 | /// Consider using a `HookQueue` if you want to enable/disable a large amount of hooks at once. 208 | pub fn disable(&self) -> Result<()> { 209 | unsafe { s2r(ffi::MH_DisableHook(self.target.to_raw())) } 210 | } 211 | } 212 | 213 | impl Drop for Hook { 214 | fn drop(&mut self) { 215 | let _ = unsafe { s2r(ffi::MH_RemoveHook(self.target.to_raw())) }; 216 | } 217 | } 218 | 219 | // Synchronization is done in the MinHook library. 220 | unsafe impl Sync for Hook {} 221 | unsafe impl Send for Hook {} 222 | 223 | 224 | 225 | /// A function identifier used for dynamically looking up a function. 226 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 227 | pub enum FunctionId<'a> { 228 | /// The function's ordinal value. 229 | Ordinal(u16), 230 | /// The function's name. 231 | Name(&'a OsStr) 232 | } 233 | 234 | impl<'a> FunctionId<'a> { 235 | /// Create a function identifier given it's ordinal value. 236 | pub fn ordinal(value: u16) -> FunctionId<'static> { 237 | FunctionId::Ordinal(value) 238 | } 239 | 240 | /// Create a function identifier given it's string name. 241 | pub fn name + 'a>(name: &'a N) -> FunctionId<'a> { 242 | FunctionId::Name(name.as_ref()) 243 | } 244 | } 245 | 246 | 247 | /// A hook with a static lifetime. 248 | /// 249 | /// This hook can only be constructed using the `static_hooks!` macro. It has one of the 250 | /// following forms: 251 | /// 252 | /// ```ignore 253 | /// #[ATTR]* pub? impl HOOK_VAR_NAME for PATH::TO::TARGET: FN_TYPE; 254 | /// #[ATTR]* pub? impl HOOK_VAR_NAME for "FUNCTION" in "MODULE": FN_TYPE; 255 | /// ``` 256 | /// 257 | /// Before accessing this hook it is **required** to call `initialize()` **once**. Accessing the 258 | /// hook before initializing or trying to initialize the hook twice (even after the first attempt 259 | /// failed) will result in a panic. 260 | pub struct StaticHook { 261 | hook: &'static AtomicInitCell<__StaticHookInner>, 262 | target: __StaticHookTarget, 263 | detour: T 264 | } 265 | 266 | impl StaticHook { 267 | #[doc(hidden)] 268 | pub const fn __new(hook: &'static AtomicInitCell<__StaticHookInner>, target: __StaticHookTarget, detour: T) -> StaticHook { 269 | StaticHook { 270 | hook: hook, 271 | target: target, 272 | detour: detour 273 | } 274 | } 275 | 276 | /// Returns a reference to the trampoline function. 277 | pub fn trampoline(&self) -> T { 278 | self.inner().trampoline 279 | } 280 | 281 | unsafe fn initialize_ref(&self, closure: &'static (Fn + Sync)) -> Result<()> { 282 | let hook = match self.target { 283 | __StaticHookTarget::Static(target) => try!(Hook::create(target, self.detour)), 284 | __StaticHookTarget::Dynamic(module_name, function_name) => 285 | try!(Hook::create_api(module_name, FunctionId::name(function_name), self.detour)) 286 | }; 287 | 288 | Ok(self.hook.initialize(__StaticHookInner(hook, closure)).expect("static hook already initialized")) 289 | } 290 | 291 | unsafe fn initialize_box(&self, closure: Box + Sync>) -> Result<()> { 292 | try!(self.initialize_ref(&*(&*closure as *const _))); 293 | mem::forget(closure); 294 | Ok(()) 295 | } 296 | 297 | /// Initialize and install the underlying hook using a detour closure. 298 | /// 299 | /// # Panics 300 | /// 301 | /// Panics if the hook was already initialized. 302 | /// 303 | /// # Safety 304 | /// 305 | /// See documentation for [`Hook::create()`](struct.Hook.html#method.create) and 306 | /// [`Hook::create_api()`](struct.Hook.html#method.create_api) 307 | pub unsafe fn initialize(&self, closure: F) -> Result<()> 308 | where F: Fn + Sync + 'static { 309 | self.initialize_box(Box::new(closure)) 310 | } 311 | 312 | fn inner(&self) -> &'static Hook { 313 | let &__StaticHookInner(ref hook, _) = self.hook.get().expect("attempt to access uninitialized static hook"); 314 | hook 315 | } 316 | } 317 | 318 | impl Deref for StaticHook { 319 | type Target = Hook; 320 | 321 | fn deref(&self) -> &Hook { 322 | self.inner() 323 | } 324 | } 325 | 326 | 327 | 328 | /// A hook with a static lifetime and a default detour closure. 329 | /// 330 | /// This hook can only be constructed using the `static_hooks!` macro. It has one of the 331 | /// following forms: 332 | /// 333 | /// ```ignore 334 | /// #[ATTR]* pub? impl HOOK_VAR_NAME for PATH::TO::TARGET: FN_TYPE = CLOSURE_EXPR; 335 | /// #[ATTR]* pub? impl HOOK_VAR_NAME for "FUNCTION" in "MODULE": FN_TYPE = CLOSURE_EXPR; 336 | /// ``` 337 | /// 338 | /// Before accessing this hook it is **required** to call `initialize()` **once**. Accessing the 339 | /// hook before initializing or trying to initialize the hook twice (even after the first attempt 340 | /// failed) will result in a panic. 341 | pub struct StaticHookWithDefault { 342 | inner: StaticHook, 343 | default: &'static (Fn + Sync), 344 | } 345 | 346 | impl StaticHookWithDefault { 347 | #[doc(hidden)] 348 | pub const fn __new(hook: StaticHook, default: &'static (Fn + Sync)) -> StaticHookWithDefault { 349 | StaticHookWithDefault { 350 | inner: hook, 351 | default: default 352 | } 353 | } 354 | 355 | /// Initialize and install the underlying hook. 356 | /// 357 | /// # Panics 358 | /// 359 | /// Panics if the hook was already initialized. 360 | /// 361 | /// # Safety 362 | /// 363 | /// See documentation for [`Hook::create()`](struct.Hook.html#method.create) and 364 | /// [`Hook::create_api()`](struct.Hook.html#method.create_api) 365 | pub unsafe fn initialize(&self) -> Result<()> { 366 | self.inner.initialize_ref(self.default) 367 | } 368 | } 369 | 370 | impl Deref for StaticHookWithDefault { 371 | type Target = StaticHook; 372 | 373 | fn deref(&self) -> &StaticHook { 374 | &self.inner 375 | } 376 | } 377 | 378 | 379 | 380 | fn initialize() -> Result<()> { 381 | // Clean-up is *required* in DLLs. If a DLL gets unloaded while static hooks are installed 382 | // the hook instructions will point to detour functions that are already unloaded. 383 | extern "C" fn cleanup() { 384 | let _ = unsafe { ffi::MH_Uninitialize() }; 385 | } 386 | 387 | unsafe { 388 | s2r(ffi::MH_Initialize()).map(|_| { 389 | libc::atexit(cleanup); 390 | }).or_else(|error| match error { 391 | Error::AlreadyInitialized => Ok(()), 392 | error => Err(error) 393 | }) 394 | } 395 | } 396 | 397 | fn s2r(status: ffi::MH_STATUS) -> Result<()> { 398 | Error::from_status(status).map_or(Ok(()), Err) 399 | } 400 | 401 | 402 | 403 | #[doc(hidden)] 404 | pub struct __StaticHookInner(Hook, pub &'static (Fn + Sync)); 405 | 406 | #[doc(hidden)] 407 | pub enum __StaticHookTarget { 408 | Static(T), 409 | Dynamic(&'static str, &'static str) 410 | } 411 | 412 | 413 | 414 | 415 | #[cfg(test)] 416 | mod tests { 417 | use std::mem; 418 | use std::sync::Mutex; 419 | use std::ffi::OsStr; 420 | use std::os::windows::ffi::OsStrExt; 421 | use std::os::raw::c_int; 422 | 423 | use {winapi, kernel32}; 424 | 425 | use super::*; 426 | 427 | #[test] 428 | fn local() { 429 | fn f(x: i32) -> i32 { x * 2 } 430 | fn d(x: i32) -> i32 { x * 3 } 431 | 432 | assert_eq!(f(5), 10); 433 | let h = unsafe { Hook:: i32>::create(f, d).unwrap() }; 434 | assert_eq!(f(5), 10); 435 | h.enable().unwrap(); 436 | assert_eq!(f(5), 15); 437 | h.disable().unwrap(); 438 | assert_eq!(f(5), 10); 439 | h.enable().unwrap(); 440 | assert_eq!(f(5), 15); 441 | mem::drop(h); 442 | assert_eq!(f(5), 10); 443 | } 444 | 445 | #[test] 446 | fn local_dynamic() { 447 | extern "system" fn lstrlen_w_detour(_string: winapi::LPCWSTR) -> c_int { 448 | -42 449 | } 450 | 451 | let foo = OsStr::new("foo").encode_wide().chain(Some(0)).collect::>(); 452 | unsafe { 453 | assert_eq!(kernel32::lstrlenW(foo.as_ptr()), 3); 454 | let h = Hook:: c_int>::create_api( 455 | "kernel32.dll", 456 | FunctionId::name("lstrlenW"), 457 | lstrlen_w_detour).unwrap(); 458 | assert_eq!(kernel32::lstrlenW(foo.as_ptr()), 3); 459 | h.enable().unwrap(); 460 | assert_eq!(kernel32::lstrlenW(foo.as_ptr()), -42); 461 | h.disable().unwrap(); 462 | assert_eq!(kernel32::lstrlenW(foo.as_ptr()), 3); 463 | } 464 | } 465 | 466 | #[test] 467 | fn static_with_default() { 468 | fn f(x: i32, y: i32) -> i32 { x + y } 469 | 470 | static_hooks! { 471 | impl h for f: fn(i32, i32) -> i32 = |x, y| x * y; 472 | } 473 | 474 | assert_eq!(f(3, 6), 9); 475 | unsafe { h.initialize().unwrap(); } 476 | assert_eq!(f(3, 6), 9); 477 | h.enable().unwrap(); 478 | assert_eq!(f(3, 6), 18); 479 | h.disable().unwrap(); 480 | assert_eq!(f(3, 6), 9); 481 | } 482 | 483 | #[test] 484 | fn static_no_default() { 485 | fn f(x: i32, y: i32) -> i32 { x + y } 486 | 487 | static_hooks! { 488 | impl h for f: fn(i32, i32) -> i32; 489 | } 490 | 491 | let y = Mutex::new(2); 492 | let d = move |x, _| { 493 | let mut y = y.lock().unwrap(); 494 | let r = h.call_real(x, *y); 495 | *y += 1; 496 | r 497 | }; 498 | 499 | assert_eq!(f(3, 6), 9); 500 | unsafe { h.initialize(d).unwrap(); } 501 | assert_eq!(f(3, 6), 9); 502 | h.enable().unwrap(); 503 | assert_eq!(f(3, 6), 5); 504 | assert_eq!(f(3, 6), 6); 505 | assert_eq!(f(3, 66), 7); 506 | h.disable().unwrap(); 507 | assert_eq!(f(3, 6), 9); 508 | } 509 | 510 | #[test] 511 | fn static_dynamic() { 512 | static_hooks! { 513 | impl h for "lstrlenA" in "kernel32.dll": extern "system" fn(winapi::LPCSTR) -> c_int = |s| -h.call_real(s); 514 | } 515 | 516 | let foobar = b"foobar\0".as_ptr() as winapi::LPCSTR; 517 | unsafe { 518 | assert_eq!(kernel32::lstrlenA(foobar), 6); 519 | h.initialize().unwrap(); 520 | assert_eq!(kernel32::lstrlenA(foobar), 6); 521 | h.enable().unwrap(); 522 | assert_eq!(kernel32::lstrlenA(foobar), -6); 523 | h.disable().unwrap(); 524 | assert_eq!(kernel32::lstrlenA(foobar), 6); 525 | } 526 | } 527 | 528 | #[test] 529 | #[should_panic] 530 | fn static_use_before_init() { 531 | fn f() {} 532 | 533 | static_hooks! { 534 | impl h for f: fn() = || (); 535 | } 536 | 537 | h.enable().unwrap(); 538 | } 539 | 540 | #[test] 541 | fn queue() { 542 | fn f1(x: &str) -> &str { x } 543 | fn d1(_x: &str) -> &str { "bar" } 544 | 545 | fn f2(x: i32) -> i32 { x * 2 } 546 | fn d2(x: i32) -> i32 { x + 2 } 547 | 548 | fn f3(x: i32) -> Option { if x < 0 { None } else { Some(x as u32) } } 549 | fn d3(x: i32) -> Option { Some(x.abs() as u32) } 550 | 551 | let (h1, h2, h3) = unsafe { ( 552 | Hook:: &'static str>::create(f1, d1).unwrap(), 553 | Hook:: i32>::create(f2, d2).unwrap(), 554 | Hook:: Option>::create(f3, d3).unwrap() 555 | ) }; 556 | 557 | HookQueue::new() 558 | .enable(&h1) 559 | .disable(&h2) 560 | .enable(&h3) 561 | .disable(&h3) 562 | .apply() 563 | .unwrap(); 564 | 565 | assert_eq!(f1("foo"), "bar"); 566 | assert_eq!(f2(42), 84); 567 | assert_eq!(f3(-10), None); 568 | } 569 | } --------------------------------------------------------------------------------