├── .gitignore ├── codecov.yml ├── .gitmodules ├── tests ├── anyhow.rs ├── initialize_and_uninitialize.rs ├── enable_and_disable_hook.rs ├── remove_hook.rs ├── trampoline_enable_and_disable_hook.rs ├── create_hook_api_ex.rs ├── create_hook_api.rs └── hook_queue.rs ├── .github └── workflows │ ├── test.yml │ ├── coverage.yml │ └── docs.yml ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── ffi.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: off 4 | patch: off -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "minhook"] 2 | path = minhook 3 | url = https://github.com/TsudaKageyu/minhook.git 4 | -------------------------------------------------------------------------------- /tests/anyhow.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use minhook::MinHook; 3 | 4 | #[test] 5 | fn test_anyhow() -> Result<()> { 6 | unsafe { 7 | MinHook::enable_all_hooks()?; 8 | } 9 | 10 | Ok(()) 11 | } 12 | -------------------------------------------------------------------------------- /tests/initialize_and_uninitialize.rs: -------------------------------------------------------------------------------- 1 | use minhook::MinHook; 2 | 3 | #[test] 4 | fn test_hook() { 5 | // Minhook will automatically be initialized, so there is no need to ever call initialize(). 6 | // However, we can call uninitialize() when we are done with MinHook. 7 | // It is not unsafe to call uninitialize(), even multiple times, but it will only uninitialize MinHook once. 8 | MinHook::uninitialize(); 9 | } 10 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build: 6 | runs-on: windows-latest 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v4 10 | with: 11 | submodules: true 12 | 13 | - name: Install Rust toolchain 14 | run: rustup toolchain install stable --profile minimal -c rustfmt,clippy 15 | 16 | - name: Format 17 | run: cargo fmt --all -- --check 18 | 19 | - name: Clippy 20 | run: cargo clippy --all-targets -- -D clippy::all 21 | 22 | - name: Test 23 | run: cargo test --all-targets -------------------------------------------------------------------------------- /tests/enable_and_disable_hook.rs: -------------------------------------------------------------------------------- 1 | use minhook::MinHook; 2 | 3 | #[test] 4 | fn test_hook() { 5 | unsafe { 6 | MinHook::create_hook(test_fn as _, test_fn_hook as _).unwrap(); 7 | 8 | // Test that the hook is enabled. 9 | MinHook::enable_hook(test_fn as _).unwrap(); 10 | assert_eq!(test_fn(), 1); 11 | 12 | // Test that the hook is disabled. 13 | MinHook::disable_hook(test_fn as _).unwrap(); 14 | assert_eq!(test_fn(), 0); 15 | } 16 | 17 | fn test_fn() -> i32 { 18 | 0 19 | } 20 | 21 | fn test_fn_hook() -> i32 { 22 | 1 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/remove_hook.rs: -------------------------------------------------------------------------------- 1 | use minhook::MinHook; 2 | 3 | #[test] 4 | fn test_remove_hook() { 5 | unsafe { 6 | MinHook::create_hook(test_fn as _, test_fn_hook as _).unwrap(); 7 | 8 | // Remove the hook. 9 | MinHook::remove_hook(test_fn as _).unwrap(); 10 | 11 | // Now enable all hooks 12 | MinHook::enable_all_hooks().unwrap(); 13 | 14 | // Test that the hook is removed and never got enabled. 15 | 16 | assert_eq!(test_fn(), 0); 17 | assert_eq!(test_fn_hook(), 1); 18 | } 19 | 20 | fn test_fn() -> i32 { 21 | 0 22 | } 23 | 24 | fn test_fn_hook() -> i32 { 25 | 1 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "minhook" 3 | version = "0.9.0" 4 | rust-version = "1.85.0" 5 | edition = "2024" 6 | description = "A Rust wrapper for MinHook, a minimalistic x86/x64 API hooking library for Windows." 7 | homepage = "https://github.com/Jakobzs/minhook" 8 | repository = "https://github.com/Jakobzs/minhook" 9 | documentation = "https://jakobzs.github.io/minhook/minhook" 10 | license = "MIT" 11 | readme = "README.md" 12 | keywords = ["minhook", "hooking", "hook", "windows", "detour"] 13 | authors = ["Jakobzs <31919330+Jakobzs@users.noreply.github.com>"] 14 | 15 | [package.metadata.docs.rs] 16 | default-target = "x86_64-pc-windows-gnu" 17 | targets = [ 18 | "x86_64-pc-windows-msvc", 19 | "i686-pc-windows-msvc", 20 | "x86_64-pc-windows-gnu", 21 | "i686-pc-windows-gnu", 22 | ] 23 | 24 | [dependencies] 25 | tracing = { version = "0.1", features = ["log"] } 26 | 27 | [dev-dependencies] 28 | once_cell = "1" 29 | anyhow = "1" 30 | 31 | [build-dependencies] 32 | cc = "1" 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Jakobzs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 2 | on: [push] 3 | 4 | jobs: 5 | coverage: 6 | name: Code coverage 7 | runs-on: windows-latest 8 | env: 9 | RUST_BACKTRACE: 1 10 | steps: 11 | - name: Check out code 12 | uses: actions/checkout@v4 13 | with: 14 | submodules: true 15 | 16 | - name: Install Rust toolchain 17 | run: rustup toolchain install stable --profile minimal 18 | 19 | - name: Install llvm-cov 20 | run: cargo install cargo-llvm-cov --locked 21 | 22 | - name: Generate default features 23 | run: cargo llvm-cov --no-report 24 | 25 | - name: Generate all features 26 | run: cargo llvm-cov --all-features --no-report 27 | 28 | - name: Generate no features 29 | run: cargo llvm-cov --no-default-features --no-report 30 | 31 | - name: Show coverage results in CI 32 | run: cargo llvm-cov report 33 | 34 | - name: Generate coverage file 35 | run: cargo llvm-cov report --lcov --output-path lcov.info 36 | 37 | - name: Upload to codecov.io 38 | uses: codecov/codecov-action@v5 39 | with: 40 | files: lcov.info 41 | token: ${{ secrets.CODECOV_TOKEN }} -------------------------------------------------------------------------------- /tests/trampoline_enable_and_disable_hook.rs: -------------------------------------------------------------------------------- 1 | use minhook::MinHook; 2 | use once_cell::sync::OnceCell; 3 | use std::mem; 4 | 5 | #[test] 6 | fn test_hook_trampoline_enable_and_disable_hook() { 7 | unsafe { 8 | // Create a hook for `test_fn_trampoline_orig` 9 | let trampoline = 10 | MinHook::create_hook(test_fn_trampoline_orig as _, test_fn_trampoline_hook as _) 11 | .unwrap(); 12 | 13 | // Store the trampoline function. 14 | TRAMPOLINE.get_or_init(|| mem::transmute(trampoline)); 15 | 16 | // Enable the hook. 17 | MinHook::enable_hook(test_fn_trampoline_orig as _).unwrap(); 18 | 19 | assert_eq!(test_fn_trampoline_orig(69), 42); 20 | 21 | // Disable the hook. 22 | MinHook::disable_hook(test_fn_trampoline_orig as _).unwrap(); 23 | 24 | assert_eq!(test_fn_trampoline_orig(69), 69); 25 | } 26 | 27 | type FnType = fn(i32) -> i32; 28 | static TRAMPOLINE: OnceCell = OnceCell::new(); 29 | 30 | fn test_fn_trampoline_orig(x: i32) -> i32 { 31 | x 32 | } 33 | 34 | fn test_fn_trampoline_hook(_x: i32) -> i32 { 35 | // Set a value that we want to return for the test. 36 | let val = 42; 37 | 38 | // Call the trampoline function with the new value. 39 | TRAMPOLINE.get().unwrap()(val) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/create_hook_api_ex.rs: -------------------------------------------------------------------------------- 1 | use minhook::MinHook; 2 | 3 | #[test] 4 | fn test_create_hook_api_ex() { 5 | // Hook get process id function 6 | unsafe { 7 | // Create a hook for the `GetCurrentProcessId` function using the extended API 8 | let (original, target) = MinHook::create_hook_api_ex( 9 | "kernel32.dll", 10 | "GetCurrentProcessId", 11 | get_current_process_id_hook as _, 12 | ) 13 | .unwrap(); 14 | 15 | // Grab the current process id 16 | let original_pid = std::process::id(); 17 | 18 | // Enable the hook 19 | MinHook::enable_hook(target as _).unwrap(); 20 | 21 | // Call the Rust std library function to get the current process id 22 | // It should return the value we set in the hook `get_current_process_id_hook` 23 | assert_eq!(std::process::id(), 42); 24 | 25 | // Transmute the original function address to a function pointer to make it callable 26 | let original_fn: fn() -> u32 = std::mem::transmute(original); 27 | 28 | // Call the original function using the original function pointer 29 | assert_eq!(original_fn(), original_pid); 30 | 31 | // Disable the hook 32 | MinHook::disable_hook(target as _).unwrap(); 33 | } 34 | 35 | fn get_current_process_id_hook() -> u32 { 36 | 42 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy documentation 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["master"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow one concurrent deployment 19 | concurrency: 20 | group: "pages" 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | # Single deploy job since we're just deploying 25 | deploy: 26 | runs-on: windows-latest 27 | environment: 28 | name: github-pages 29 | url: ${{ steps.deployment.outputs.page_url }} 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v4 33 | with: 34 | submodules: 'recursive' 35 | 36 | - name: Install Rust toolchain 37 | run: | 38 | rustup toolchain install nightly --profile minimal 39 | rustup default nightly 40 | 41 | - name: Build documentation 42 | run: cargo doc --all --no-deps 43 | 44 | - name: Setup Pages 45 | uses: actions/configure-pages@v2 46 | 47 | - name: Upload artifact 48 | uses: actions/upload-pages-artifact@v3 49 | with: 50 | path: 'target/doc' 51 | 52 | - name: Deploy to GitHub Pages 53 | id: deployment 54 | uses: actions/deploy-pages@v4 55 | 56 | -------------------------------------------------------------------------------- /tests/create_hook_api.rs: -------------------------------------------------------------------------------- 1 | use minhook::MinHook; 2 | 3 | #[test] 4 | fn test_create_hook_api() { 5 | // Hook get process id function 6 | unsafe { 7 | // Create a hook for the `GetCurrentProcessId` function using the extended API 8 | let original = MinHook::create_hook_api( 9 | "kernel32.dll", 10 | "GetCurrentProcessId", 11 | get_current_process_id_hook as _, 12 | ) 13 | .unwrap(); 14 | 15 | // Grab the current process id 16 | let original_pid = std::process::id(); 17 | 18 | // Enable all hooks (this is necessary since we do not have a handle to the GetCurrentProcessId function) 19 | MinHook::enable_all_hooks().unwrap(); 20 | 21 | // Call the Rust std library function to get the current process id 22 | // It should return the value we set in the hook `get_current_process_id_hook` 23 | assert_eq!(std::process::id(), 42); 24 | 25 | // Transmute the original function address to a function pointer to make it callable 26 | let original_fn: fn() -> u32 = std::mem::transmute(original); 27 | 28 | // Call the original function using the original function pointer 29 | assert_eq!(original_fn(), original_pid); 30 | 31 | // Disable the hook 32 | MinHook::disable_all_hooks().unwrap(); 33 | 34 | // Ensure the original function is restored 35 | assert_eq!(std::process::id(), original_pid); 36 | } 37 | 38 | fn get_current_process_id_hook() -> u32 { 39 | 42 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/hook_queue.rs: -------------------------------------------------------------------------------- 1 | use minhook::MinHook; 2 | use std::ffi::c_void; 3 | 4 | #[test] 5 | fn test_hooks_queue() { 6 | unsafe { 7 | MinHook::create_hook( 8 | test_fn1 as FnType1 as *mut c_void, 9 | test_fn1_hook as FnType1 as *mut c_void, 10 | ) 11 | .unwrap(); 12 | MinHook::create_hook( 13 | test_fn2 as FnType2 as *mut c_void, 14 | test_fn2_hook as FnType2 as *mut c_void, 15 | ) 16 | .unwrap(); 17 | 18 | // Queue to enable the hooks, then apply them. 19 | MinHook::queue_enable_hook(test_fn1 as FnType1 as *mut c_void).unwrap(); 20 | MinHook::queue_enable_hook(test_fn2 as FnType2 as *mut c_void).unwrap(); 21 | MinHook::apply_queued().unwrap(); 22 | 23 | // Test that the hooks are enabled. 24 | assert_eq!(test_fn1(), 1); 25 | assert_eq!(test_fn2(1), 2); 26 | 27 | // Queue to disable the hooks, then apply them. 28 | MinHook::queue_disable_hook(test_fn1 as FnType1 as *mut c_void).unwrap(); 29 | MinHook::queue_disable_hook(test_fn2 as FnType2 as *mut c_void).unwrap(); 30 | MinHook::apply_queued().unwrap(); 31 | 32 | // Test that the hooks are disabled. 33 | assert_eq!(test_fn1(), 0); 34 | assert_eq!(test_fn2(1), 1); 35 | } 36 | 37 | type FnType1 = fn() -> i32; 38 | type FnType2 = fn(i32) -> i32; 39 | 40 | fn test_fn1() -> i32 { 41 | 0 42 | } 43 | 44 | fn test_fn1_hook() -> i32 { 45 | 1 46 | } 47 | 48 | fn test_fn2(x: i32) -> i32 { 49 | x 50 | } 51 | 52 | fn test_fn2_hook(x: i32) -> i32 { 53 | x + 1 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # minhook 2 | 3 | [![CI](https://github.com/Jakobzs/minhook/actions/workflows/test.yml/badge.svg)](https://github.com/Jakobzs/minhook/actions/workflows/test.yml) 4 | [![Crates.io](https://img.shields.io/crates/v/minhook)](https://crates.io/crates/minhook) 5 | [![rustdoc](https://img.shields.io/badge/docs-rustdoc-brightgreen)](https://jakobzs.github.io/minhook/minhook) 6 | [![codecov](https://codecov.io/github/Jakobzs/minhook/graph/badge.svg?token=PGVBSDVD83)](https://codecov.io/github/Jakobzs/minhook) 7 | ![MSRV](https://img.shields.io/badge/rust-1.85+-brightgreen.svg?&logo=rust) 8 | 9 | A Rust wrapper for the [MinHook](https://github.com/TsudaKageyu/minhook) library. 10 | 11 | ## Usage 12 | 13 | Add `minhook` by using the following command: 14 | 15 | ```bash 16 | cargo add minhook 17 | ``` 18 | 19 | Or if you prefer to add it manually, you can do so by editing your `Cargo.toml` file: 20 | 21 | ```toml 22 | [dependencies] 23 | minhook = "0.9.0" 24 | ``` 25 | 26 | ## Example 27 | 28 | This example shows how to create a hook for a function, and also call the original function. 29 | 30 | ```rust 31 | use minhook::{MinHook, MH_STATUS}; 32 | 33 | fn main() -> Result<(), MH_STATUS> { 34 | // Create a hook for the return_0 function, detouring it to return_1 35 | let return_0_address = unsafe { MinHook::create_hook(return_0 as _, return_1 as _)? }; 36 | 37 | // Enable the hook 38 | unsafe { MinHook::enable_all_hooks()? }; 39 | 40 | // Call the detoured return_0 function, it should return 1 41 | assert_eq!(return_0(), 1); 42 | 43 | // Transmute the original return_0 function address to a function pointer 44 | let return_0_original = unsafe { std::mem::transmute::<_, fn() -> i32>(return_0_address) }; 45 | 46 | // Call the original return_0 function 47 | assert_eq!(return_0_original(), 0); 48 | 49 | Ok(()) 50 | } 51 | 52 | fn return_0() -> i32 { 53 | 0 54 | } 55 | 56 | fn return_1() -> i32 { 57 | 1 58 | } 59 | ``` 60 | 61 | ## License 62 | 63 | This project is licensed under the MIT license ([LICENSE](LICENSE) or http://opensource.org/licenses/MIT). 64 | 65 | ## Contribution 66 | 67 | Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the MIT license, shall be licensed as above, without any additional terms or conditions. 68 | -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | use crate::MH_STATUS; 2 | use std::ffi::c_void; 3 | 4 | unsafe extern "system" { 5 | /// Initializes the MinHook library. You must call this function in the 6 | /// beginning of your program. 7 | pub fn MH_Initialize() -> MH_STATUS; 8 | 9 | /// Uninitialize the MinHook library. You must call this function EXACTLY 10 | /// ONCE at the end of your program. 11 | pub fn MH_Uninitialize() -> MH_STATUS; 12 | 13 | /// Creates a hook for the specified target function, in disabled state. 14 | /// 15 | /// # Arguments 16 | /// 17 | /// * `pTarget` \[in\] - A pointer to the target function, which will be overridden by the detour function. 18 | /// * `pDetour` \[in\] - A pointer to the detour function, which will override the target function. 19 | /// * `ppOriginal` \[out\] - A pointer to the trampoline function, which will be used to call the original target function. This parameter can be NULL. 20 | pub fn MH_CreateHook( 21 | pTarget: *mut c_void, 22 | pDetour: *mut c_void, 23 | ppOriginal: *mut *mut c_void, 24 | ) -> MH_STATUS; 25 | 26 | /// Creates a hook for the specified API function, in disabled state. 27 | /// 28 | /// # Arguments 29 | /// 30 | /// * `pszModule` \[in\] - A pointer to the loaded module name which contains the target function. 31 | /// * `pszProcName` \[in\] - A pointer to the target function name, which will be overridden by the detour function. 32 | /// * `pDetour` \[in\] - A pointer to the detour function, which will override the target function. 33 | /// * `ppOriginal` \[out\] - A pointer to the trampoline function, which will be used to call the original target function. This parameter can be NULL. 34 | pub fn MH_CreateHookApi( 35 | pszModule: *const u8, 36 | pszProcName: *const u8, 37 | pDetour: *mut c_void, 38 | ppOriginal: *mut *mut c_void, 39 | ) -> MH_STATUS; 40 | 41 | /// Creates a hook for the specified API function, in disabled state. 42 | /// 43 | /// # Arguments 44 | /// 45 | /// * `pszModule` \[in\] - A pointer to the loaded module name which contains the target function. 46 | /// * `pszProcName` \[in\] - A pointer to the target function name, which will be overridden by the detour function. 47 | /// * `pDetour` \[in\] - A pointer to the detour function, which will override the target function. 48 | /// * `ppOriginal` \[out\] - A pointer to the trampoline function, which will be used to call the original target function. This parameter can be NULL. 49 | /// * `ppTarget` \[out\] - A pointer to the target function, which will be overridden by the detour function. This parameter can be NULL. 50 | pub fn MH_CreateHookApiEx( 51 | pszModule: *const u8, 52 | pszProcName: *const u8, 53 | pDetour: *mut c_void, 54 | ppOriginal: *mut *mut c_void, 55 | ppTarget: *mut *mut c_void, 56 | ) -> MH_STATUS; 57 | 58 | /// Removes an already created hook. 59 | /// 60 | /// # Arguments 61 | /// 62 | /// * `pTarget` \[in\] - A pointer to the target function. 63 | pub fn MH_RemoveHook(pTarget: *mut c_void) -> MH_STATUS; 64 | 65 | /// Enables an already created hook. 66 | /// 67 | /// # Arguments 68 | /// 69 | /// * `pTarget` \[in\] - A pointer to the target function. 70 | pub fn MH_EnableHook(pTarget: *mut c_void) -> MH_STATUS; 71 | 72 | /// Disables an already created hook. 73 | /// 74 | /// # Arguments 75 | /// 76 | /// * `pTarget` \[in\] - A pointer to the target function. 77 | pub fn MH_DisableHook(pTarget: *mut c_void) -> MH_STATUS; 78 | 79 | /// Queues to enable an already created hook. 80 | /// 81 | /// # Arguments 82 | /// 83 | /// * `pTarget` \[in\] - A pointer to the target function. 84 | pub fn MH_QueueEnableHook(pTarget: *mut c_void) -> MH_STATUS; 85 | 86 | /// Queues to disable an already created hook. 87 | /// 88 | /// # Arguments 89 | /// 90 | /// * `pTarget` \[in\] - A pointer to the target function. 91 | pub fn MH_QueueDisableHook(pTarget: *mut c_void) -> MH_STATUS; 92 | 93 | /// Applies all queued changes in one go. 94 | pub fn MH_ApplyQueued() -> MH_STATUS; 95 | } 96 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! [MinHook](https://github.com/TsudaKageyu/minhook) is a Windows API hooking library that allows you to intercept calls to functions in programs. 2 | //! 3 | //! This crate is a wrapper around the MinHook library. Most of the API is unsafe because it is not possible to guarantee safety of the hooks. 4 | //! 5 | //! # Example 6 | //! 7 | //! This example shows how to create a hook for a function, and also call the original function. 8 | //! 9 | //! ```rust 10 | //! use minhook::{MinHook, MH_STATUS}; 11 | //! 12 | //! fn main() -> Result<(), MH_STATUS> { 13 | //! // Create a hook for the return_0 function, detouring it to return_1 14 | //! let return_0_address = unsafe { MinHook::create_hook(return_0 as _, return_1 as _)? }; 15 | //! 16 | //! // Enable the hook 17 | //! unsafe { MinHook::enable_all_hooks()? }; 18 | //! 19 | //! // Call the detoured return_0 function, it should return 1 20 | //! assert_eq!(return_0(), 1); 21 | //! 22 | //! // Transmute the original return_0 function address to a function pointer 23 | //! let return_0_original = unsafe { std::mem::transmute::<_, fn() -> i32>(return_0_address) }; 24 | //! 25 | //! // Call the original return_0 function 26 | //! assert_eq!(return_0_original(), 0); 27 | //! 28 | //! Ok(()) 29 | //! } 30 | //! 31 | //! fn return_0() -> i32 { 32 | //! 0 33 | //! } 34 | //! 35 | //! fn return_1() -> i32 { 36 | //! 1 37 | //! } 38 | //! ``` 39 | 40 | use ffi::{ 41 | MH_ApplyQueued, MH_CreateHook, MH_CreateHookApi, MH_CreateHookApiEx, MH_DisableHook, 42 | MH_EnableHook, MH_Initialize, MH_QueueDisableHook, MH_QueueEnableHook, MH_RemoveHook, 43 | MH_Uninitialize, 44 | }; 45 | use std::{ 46 | ffi::{CString, c_void}, 47 | fmt, 48 | ptr::null_mut, 49 | sync::Once, 50 | }; 51 | use tracing::debug; 52 | 53 | mod ffi; 54 | 55 | const MH_ALL_HOOKS: *const i32 = std::ptr::null(); 56 | 57 | static MINHOOK_INIT: Once = Once::new(); 58 | static MINHOOK_UNINIT: Once = Once::new(); 59 | 60 | /// A struct to access the MinHook API. 61 | pub struct MinHook {} 62 | 63 | impl MinHook { 64 | // Initialize MinHook 65 | fn initialize() { 66 | MINHOOK_INIT.call_once(|| { 67 | let status = unsafe { MH_Initialize() }; 68 | debug!("MH_Initialize: {:?}", status); 69 | 70 | match status.ok() { 71 | Ok(_) => (), // Initialization successful, do nothing 72 | Err(MH_STATUS::MH_ERROR_ALREADY_INITIALIZED) => (), // Ignore if already initialized 73 | Err(e) => panic!("Could not initialize MinHook, error: {e:?}"), 74 | } 75 | }); 76 | } 77 | 78 | /// Uninitializes MinHook. This function is only possible to call once. If you want to reinitialize MinHook, you need to restart the program. 79 | /// 80 | /// # Safety 81 | pub fn uninitialize() { 82 | // Make sure we are initialized before we uninitialize 83 | Self::initialize(); 84 | 85 | MINHOOK_UNINIT.call_once(|| { 86 | let status = unsafe { MH_Uninitialize() }; 87 | debug!("MH_Uninitialize: {:?}", status); 88 | 89 | status.ok().expect("Could not uninitialize MinHook"); 90 | }); 91 | } 92 | 93 | /// Creates a hook for the target function and detours it to the detour function. This function returns the original function pointer. 94 | /// 95 | /// # Safety 96 | pub unsafe fn create_hook( 97 | target: *mut c_void, 98 | detour: *mut c_void, 99 | ) -> Result<*mut c_void, MH_STATUS> { 100 | Self::initialize(); 101 | 102 | let mut pp_original: *mut c_void = null_mut(); 103 | let status = unsafe { MH_CreateHook(target, detour, &mut pp_original) }; 104 | debug!("MH_CreateHook: {:?}", status); 105 | match status { 106 | MH_STATUS::MH_OK => Ok(pp_original), 107 | _ => Err(status), 108 | } 109 | } 110 | 111 | /// Creates a hook for the targeted API function and detours it to the detour function. This function returns the original function pointer. 112 | /// 113 | /// # Safety 114 | pub unsafe fn create_hook_api>( 115 | module_name: T, 116 | proc_name: T, 117 | detour: *mut c_void, 118 | ) -> Result<*mut c_void, MH_STATUS> { 119 | Self::initialize(); 120 | 121 | let mut module_name = module_name.as_ref().encode_utf16().collect::>(); 122 | module_name.push(0); 123 | 124 | let proc_name = CString::new(proc_name.as_ref()).unwrap(); 125 | let mut pp_original: *mut c_void = null_mut(); 126 | let status = unsafe { 127 | MH_CreateHookApi( 128 | module_name.as_ptr() as *const _, 129 | proc_name.as_ptr() as *const _, 130 | detour, 131 | &mut pp_original, 132 | ) 133 | }; 134 | debug!("MH_CreateHookApi: {:?}", status); 135 | match status { 136 | MH_STATUS::MH_OK => Ok(pp_original), 137 | _ => Err(status), 138 | } 139 | } 140 | 141 | /// Extended function for creating a hook for the targeted API function and detours it to the detour function. This function returns the original function pointer as well as a pointer to the target function. 142 | /// # Safety 143 | /// 144 | /// TOOO: Revise if this is correct 145 | pub unsafe fn create_hook_api_ex>( 146 | module_name: T, 147 | proc_name: T, 148 | detour: *mut c_void, 149 | ) -> Result<(*mut c_void, *mut *mut c_void), MH_STATUS> { 150 | Self::initialize(); 151 | 152 | let mut module_name = module_name.as_ref().encode_utf16().collect::>(); 153 | module_name.push(0); 154 | 155 | let proc_name = CString::new(proc_name.as_ref()).unwrap(); 156 | let mut pp_original: *mut c_void = null_mut(); 157 | let pp_target: *mut *mut c_void = null_mut(); 158 | let status = unsafe { 159 | MH_CreateHookApiEx( 160 | module_name.as_ptr() as *const _, 161 | proc_name.as_ptr() as *const _, 162 | detour, 163 | &mut pp_original, 164 | pp_target, 165 | ) 166 | }; 167 | debug!("MH_CreateHookApiEx: {:?}", status); 168 | match status { 169 | MH_STATUS::MH_OK => Ok((pp_original, pp_target)), 170 | _ => Err(status), 171 | } 172 | } 173 | 174 | /// Enables a hook for the target function. 175 | /// 176 | /// # Safety 177 | pub unsafe fn enable_hook(target: *mut c_void) -> Result<(), MH_STATUS> { 178 | Self::initialize(); 179 | 180 | let status = unsafe { MH_EnableHook(target) }; 181 | debug!("MH_EnableHook: {:?}", status); 182 | match status { 183 | MH_STATUS::MH_OK => Ok(()), 184 | _ => Err(status), 185 | } 186 | } 187 | 188 | /// Enables all hooks. 189 | /// 190 | /// # Safety 191 | pub unsafe fn enable_all_hooks() -> Result<(), MH_STATUS> { 192 | unsafe { Self::enable_hook(MH_ALL_HOOKS as *mut _) } 193 | } 194 | 195 | /// Disables a hook for the target function. 196 | /// 197 | /// # Safety 198 | pub unsafe fn disable_hook(target: *mut c_void) -> Result<(), MH_STATUS> { 199 | Self::initialize(); 200 | 201 | let status = unsafe { MH_DisableHook(target) }; 202 | debug!("MH_DisableHook: {:?}", status); 203 | match status { 204 | MH_STATUS::MH_OK => Ok(()), 205 | _ => Err(status), 206 | } 207 | } 208 | 209 | /// Disables all hooks. 210 | /// 211 | /// # Safety 212 | pub unsafe fn disable_all_hooks() -> Result<(), MH_STATUS> { 213 | unsafe { Self::disable_hook(MH_ALL_HOOKS as *mut _) } 214 | } 215 | 216 | /// Removes a hook for the target function. 217 | /// 218 | /// # Safety 219 | pub unsafe fn remove_hook(target: *mut c_void) -> Result<(), MH_STATUS> { 220 | Self::initialize(); 221 | 222 | let status = unsafe { MH_RemoveHook(target) }; 223 | debug!("MH_RemoveHook: {:?}", status); 224 | match status { 225 | MH_STATUS::MH_OK => Ok(()), 226 | _ => Err(status), 227 | } 228 | } 229 | 230 | /// Queues a hook for enabling. 231 | /// 232 | /// # Safety 233 | pub unsafe fn queue_enable_hook(target: *mut c_void) -> Result<(), MH_STATUS> { 234 | Self::initialize(); 235 | 236 | let status = unsafe { MH_QueueEnableHook(target) }; 237 | debug!("MH_QueueEnableHook: {:?}", status); 238 | match status { 239 | MH_STATUS::MH_OK => Ok(()), 240 | _ => Err(status), 241 | } 242 | } 243 | 244 | /// Queues a hook for disabling. 245 | /// 246 | /// # Safety 247 | pub unsafe fn queue_disable_hook(target: *mut c_void) -> Result<(), MH_STATUS> { 248 | Self::initialize(); 249 | 250 | let status = unsafe { MH_QueueDisableHook(target) }; 251 | debug!("MH_QueueDisableHook: {:?}", status); 252 | match status { 253 | MH_STATUS::MH_OK => Ok(()), 254 | _ => Err(status), 255 | } 256 | } 257 | 258 | /// Applies all queued hooks. 259 | /// 260 | /// # Safety 261 | pub unsafe fn apply_queued() -> Result<(), MH_STATUS> { 262 | Self::initialize(); 263 | 264 | let status = unsafe { MH_ApplyQueued() }; 265 | debug!("MH_ApplyQueued: {:?}", status); 266 | match status { 267 | MH_STATUS::MH_OK => Ok(()), 268 | _ => Err(status), 269 | } 270 | } 271 | } 272 | 273 | /// MinHook status codes. 274 | #[allow(non_camel_case_types)] 275 | #[must_use] 276 | #[repr(C)] 277 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 278 | pub enum MH_STATUS { 279 | /// Unknown error. Should not be returned. 280 | MH_UNKNOWN = -1, 281 | /// Successful. 282 | MH_OK = 0, 283 | /// MinHook is already initialized. 284 | MH_ERROR_ALREADY_INITIALIZED, 285 | /// MinHook is not initialized yet, or already uninitialized. 286 | MH_ERROR_NOT_INITIALIZED, 287 | /// The hook for the specified target function is already created. 288 | MH_ERROR_ALREADY_CREATED, 289 | /// The hook for the specified target function is not created yet. 290 | MH_ERROR_NOT_CREATED, 291 | /// The hook for the specified target function is already enabled. 292 | MH_ERROR_ENABLED, 293 | /// The hook for the specified target function is not enabled yet, or 294 | /// already disabled. 295 | MH_ERROR_DISABLED, 296 | /// The specified pointer is invalid. It points the address of non-allocated 297 | /// and/or non-executable region. 298 | MH_ERROR_NOT_EXECUTABLE, 299 | /// The specified target function cannot be hooked. 300 | MH_ERROR_UNSUPPORTED_FUNCTION, 301 | /// Failed to allocate memory. 302 | MH_ERROR_MEMORY_ALLOC, 303 | /// Failed to change the memory protection. 304 | MH_ERROR_MEMORY_PROTECT, 305 | /// The specified module is not loaded. 306 | MH_ERROR_MODULE_NOT_FOUND, 307 | /// The specified function is not found. 308 | MH_ERROR_FUNCTION_NOT_FOUND, 309 | } 310 | 311 | impl MH_STATUS { 312 | pub fn ok(self) -> Result<(), MH_STATUS> { 313 | if self == MH_STATUS::MH_OK { 314 | Ok(()) 315 | } else { 316 | Err(self) 317 | } 318 | } 319 | } 320 | 321 | impl fmt::Display for MH_STATUS { 322 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 323 | let message = match self { 324 | MH_STATUS::MH_UNKNOWN => "Unknown error. Should not be returned.", 325 | MH_STATUS::MH_OK => "Successful.", 326 | MH_STATUS::MH_ERROR_ALREADY_INITIALIZED => "MinHook is already initialized.", 327 | MH_STATUS::MH_ERROR_NOT_INITIALIZED => { 328 | "MinHook is not initialized yet, or already uninitialized." 329 | } 330 | MH_STATUS::MH_ERROR_ALREADY_CREATED => { 331 | "The hook for the specified target function is already created." 332 | } 333 | MH_STATUS::MH_ERROR_NOT_CREATED => { 334 | "The hook for the specified target function is not created yet." 335 | } 336 | MH_STATUS::MH_ERROR_ENABLED => { 337 | "The hook for the specified target function is already enabled." 338 | } 339 | MH_STATUS::MH_ERROR_DISABLED => { 340 | "The hook for the specified target function is not enabled yet, or already disabled." 341 | } 342 | MH_STATUS::MH_ERROR_NOT_EXECUTABLE => { 343 | "The specified pointer is invalid. It points the address of non-allocated and/or non-executable region." 344 | } 345 | MH_STATUS::MH_ERROR_UNSUPPORTED_FUNCTION => { 346 | "The specified target function cannot be hooked." 347 | } 348 | MH_STATUS::MH_ERROR_MEMORY_ALLOC => "Failed to allocate memory.", 349 | MH_STATUS::MH_ERROR_MEMORY_PROTECT => "Failed to change the memory protection.", 350 | MH_STATUS::MH_ERROR_MODULE_NOT_FOUND => "The specified module is not loaded.", 351 | MH_STATUS::MH_ERROR_FUNCTION_NOT_FOUND => "The specified function is not found.", 352 | }; 353 | 354 | write!(f, "{message}") 355 | } 356 | } 357 | 358 | impl std::error::Error for MH_STATUS {} 359 | --------------------------------------------------------------------------------