"]
3 | description = "A cross-platform detour library written in Rust"
4 | documentation = "https://docs.rs/retour"
5 | homepage = "https://github.com/Hpmason/retour-rs"
6 | keywords = ["detour", "hook", "function", "api", "redirect"]
7 | license = "BSD-2-Clause"
8 | name = "retour"
9 | readme = "README.md"
10 | repository = "https://github.com/Hpmason/retour-rs"
11 | version = "0.4.0-alpha.3"
12 | edition = "2018"
13 | rust-version = "1.60.0"
14 |
15 | # [badges]
16 | # azure-devops = { project = "darfink/detour-rs", pipeline = "darfink.detour-rs" }
17 |
18 | [dependencies]
19 | cfg-if = "1.0.0"
20 | generic-array = "0.14.7"
21 | once_cell = "1.18.0"
22 | libc = "0.2.145"
23 | mmap = { package = "mmap-fixed-fixed", version = "0.1.3" }
24 | region = "3.0.0"
25 | slice-pool = {package = "slice-pool2", version = "0.4.3" }
26 |
27 | [dev-dependencies]
28 | matches = "0.1.10"
29 | ctor = "0.2.2"
30 |
31 | [features]
32 | default = []
33 | nightly = []
34 | thiscall-abi = ["nightly"]
35 | static-detour = ["nightly"]
36 | 28-args = []
37 | 42-args = ["28-args"]
38 |
39 | [[example]]
40 | name = "messageboxw_detour"
41 | required-features = ["static-detour"]
42 | crate-type = ["cdylib"]
43 |
44 | [[example]]
45 | name = "cat_detour"
46 | required-features = ["static-detour"]
47 | crate-type = ["cdylib"]
48 |
49 | [[example]]
50 | name = "com_dxgi_present"
51 | required-features = ["static-detour"]
52 | crate-type = ["cdylib"]
53 |
54 | [[example]]
55 | name = "kernel32_detour"
56 | crate-type = ["cdylib"]
57 |
58 | [target."cfg(any(target_arch = \"x86\", target_arch = \"x86_64\"))".dependencies.iced-x86]
59 | version = "1.20"
60 | default-features = false
61 | # https://github.com/icedland/iced/blob/master/src/rust/iced-x86/README.md#crate-feature-flags
62 | features = ["std", "decoder", "fast_fmt"]
63 |
64 | [target."cfg(windows)".dev-dependencies.windows]
65 | version = "0.48"
66 | features = ["Win32_Foundation",
67 | "Win32_System_Console", "Win32_System_LibraryLoader", "Win32_System_SystemServices",
68 | "Win32_Graphics_Gdi", "Win32_Graphics_Direct3D", "Win32_Graphics_Direct3D11",
69 | "Win32_Graphics_Dxgi", "Win32_Graphics_Dxgi_Common",
70 | "Win32_UI_WindowsAndMessaging",
71 | ]
72 |
73 | [package.metadata.docs.rs]
74 | all-features = true
75 | rustdoc-args = ["--cfg", "docsrs"]
76 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | detour-rs - A cross-platform detour library written in Rust
2 | Copyright (C) 2017 Elliott Linder.
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 |
29 | minhook-rs - A minimalist x86/x86-64 hooking library for Rust
30 | Copyright (C) 2015 Jascha Neutelings.
31 | All rights reserved.
32 |
33 | Redistribution and use in source and binary forms, with or without
34 | modification, are permitted provided that the following conditions
35 | are met:
36 |
37 | 1. Redistributions of source code must retain the above copyright
38 | notice, this list of conditions and the following disclaimer.
39 | 2. Redistributions in binary form must reproduce the above copyright
40 | notice, this list of conditions and the following disclaimer in the
41 | documentation and/or other materials provided with the distribution.
42 |
43 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
44 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
45 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
46 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
47 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
48 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
49 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
50 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
51 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
52 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
53 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # `retour` (a `detour` Fork)
4 |
5 | [![Crates.io][crates-badge]][crates-url]
6 | [![docs.rs][docs-badge]][docs-url]
7 | [![Lcense][license-badge]][license-url]
8 | [![Cargo Check/Tests][actions-badge]][actions-url]
9 |
10 |
11 | [crates-badge]: https://img.shields.io/crates/v/retour.svg
12 | [crates-url]: https://crates.io/crates/retour
13 |
14 | [docs-badge]: https://docs.rs/retour/badge.svg
15 | [docs-url]: https://docs.rs/retour
16 |
17 | [license-badge]: https://img.shields.io/crates/l/retour
18 | [license-url]: ./LICENSE
19 |
20 | [actions-badge]: https://github.com/Hpmason/retour-rs/actions/workflows/ci.yml/badge.svg
21 | [actions-url]: https://github.com/Hpmason/retour-rs/actions/workflows/ci.yml
22 |
23 | (Fork of original [detour-rs](https://github.com/darfink/detour-rs)
24 | that works on nightly after nightly-2022-11-07)
25 |
26 |
27 | This is a cross-platform detour library developed in Rust. Beyond the basic
28 | functionality, this library handles branch redirects, RIP-relative
29 | instructions, hot-patching, NOP-padded functions, and allows the original
30 | function to be called using a trampoline whilst hooked.
31 |
32 | This is one of few **cross-platform** detour libraries that exists, and to
33 | maintain this feature, not all desired functionality can be supported due to
34 | lack of cross-platform APIs. Therefore [EIP relocation](#appendix) is not
35 | supported.
36 |
37 | **NOTE**: Nightly is currently required for `static_detour!` and is enabled with the `static-detour` feature flag.
38 |
39 | ## Platforms
40 |
41 | This library provides CI for these targets:
42 |
43 | - Linux
44 | * `i686-unknown-linux-gnu`
45 | * `x86_64-unknown-linux-gnu`
46 | * `x86_64-unknown-linux-musl`
47 | - Windows
48 | * `i686-pc-windows-gnu`
49 | * `i686-pc-windows-msvc`
50 | * `x86_64-pc-windows-gnu`
51 | * `x86_64-pc-windows-msvc`
52 | - macOS
53 | * ~~`i686-apple-darwin`~~
54 | * `x86_64-apple-darwin`
55 |
56 | ## Installation
57 |
58 | Add this to your `Cargo.toml`:
59 |
60 | ```toml
61 | [dependencies]
62 | # `static-detour` feature requires nightly
63 | retour = { version = "0.3", features = ["static-detour"] }
64 | ```
65 |
66 | ## Supported Versions
67 | This crate, with default features, will support the MSRV in `Cargo.toml`
68 | (currently 1.60.0). Certain features may require newer versions of the compiler,
69 | which will be documented here and in the docs. Any features that require the
70 | nightly compiler will always target the newest version.
71 |
72 | Feature versions:
73 | - `static-detour`: nightly
74 | - `thiscall-abi`: 1.73.0 or newer
75 |
76 | ## Example
77 |
78 | - A static detour (one of *three* different detours):
79 |
80 | ```rust
81 | use std::error::Error;
82 | use retour::static_detour;
83 |
84 | static_detour! {
85 | static Test: /* extern "X" */ fn(i32) -> i32;
86 | }
87 |
88 | fn add5(val: i32) -> i32 {
89 | val + 5
90 | }
91 |
92 | fn add10(val: i32) -> i32 {
93 | val + 10
94 | }
95 |
96 | fn main() -> Result<(), Box> {
97 | // Reroute the 'add5' function to 'add10' (can also be a closure)
98 | unsafe { Test.initialize(add5, add10)? };
99 |
100 | assert_eq!(add5(1), 6);
101 | assert_eq!(Test.call(1), 6);
102 |
103 | // Hooks must be enabled to take effect
104 | unsafe { Test.enable()? };
105 |
106 | // The original function is detoured to 'add10'
107 | assert_eq!(add5(1), 11);
108 |
109 | // The original function can still be invoked using 'call'
110 | assert_eq!(Test.call(1), 6);
111 |
112 | // It is also possible to change the detour whilst hooked
113 | Test.set_detour(|val| val - 5);
114 | assert_eq!(add5(5), 0);
115 |
116 | unsafe { Test.disable()? };
117 |
118 | assert_eq!(add5(1), 6);
119 | Ok(())
120 | }
121 | ```
122 |
123 | - A Windows API hooking example is available [here](./examples/messageboxw_detour.rs); build it by running:
124 | ```
125 | $ cargo +nightly build --features="static-detour" --example messageboxw_detour
126 | ```
127 |
128 | - A non-nightly example using GenericDetour can be found [here](./examples/kernel32_detour.rs); build it by running:
129 | ```
130 | $ cargo build --example kernel32_detour
131 | ```
132 |
133 | ## Mentions
134 |
135 | This is fork of the original [detour-rs][detour-rs] creator
136 | [darfink][detour-rs-author] that put *so much* work into the original crate.
137 |
138 | Part of the library's external user interface was inspired by
139 | [minhook-rs][minhook], created by [Jascha-N][minhook-author], and it contains
140 | derivative code of his work.
141 |
142 | ## Injection Methods
143 | This crate provides the ability to hook functions in other applications, but does not provide the utilities necessary for injecting/attaching to another process. If you're looking for ways to inject your hooking library here are some approaches you can look into:
144 |
145 | - dll injection libraries such as [dll-syringe](https://crates.io/crates/dll-syringe)
146 | - LD_PRELOAD on *nix platforms
147 | - Various debuggers, including x64dbg and Cheat Engine
148 |
149 | ## Appendix
150 |
151 | - *EIP relocation*
152 |
153 | *Should be performed whenever a function's prolog instructions
154 | are being executed, simultaneously as the function itself is being
155 | detoured. This is done by halting all affected threads, copying the affected
156 | instructions and appending a `JMP` to return to the function. This is
157 | barely ever an issue, and never in single-threaded environments, but YMMV.*
158 |
159 | - *NOP-padding*
160 | ```c
161 | int function() { return 0; }
162 | // xor eax, eax
163 | // ret
164 | // nop
165 | // nop
166 | // ...
167 | ```
168 | *Functions such as this one, lacking a hot-patching area, and too small to
169 | be hooked with a 5-byte `jmp`, are supported thanks to the detection of
170 | code padding (`NOP/INT3` instructions). Therefore the required amount of
171 | trailing `NOP` instructions will be replaced, to make room for the detour.*
172 |
173 |
174 | [minhook-author]: https://github.com/Jascha-N
175 | [minhook]: https://github.com/Jascha-N/minhook-rs/
176 | [detour-rs]: https://github.com/darfink/detour-rs
177 | [detour-rs-author]: https://github.com/darfink
178 |
--------------------------------------------------------------------------------
/clippy.toml:
--------------------------------------------------------------------------------
1 | too-many-arguments-threshold = 15
2 |
--------------------------------------------------------------------------------
/examples/cat_detour.rs:
--------------------------------------------------------------------------------
1 | #![cfg(all(not(windows), feature = "static-detour"))]
2 |
3 | use retour::static_detour;
4 | use std::ffi::CString;
5 | use std::os::raw::c_char;
6 | use std::os::raw::c_int;
7 |
8 | extern "C" {
9 | fn open(pathname: *const c_char, flags: c_int) -> c_int;
10 | }
11 |
12 | static_detour! {
13 | static Opentour: unsafe extern "C" fn(*const c_char, c_int) -> c_int;
14 | }
15 |
16 | fn definitely_open(_: *const c_char, _: c_int) -> c_int {
17 | let cstring = CString::new("/etc/timezone").unwrap();
18 | let fd = unsafe { Opentour.call(cstring.as_ptr() as *const c_char, 0) };
19 | assert!(fd > 0);
20 | fd
21 | }
22 |
23 | #[ctor::ctor]
24 | fn main() {
25 | unsafe {
26 | Opentour.initialize(open, definitely_open).unwrap();
27 | Opentour.enable().unwrap();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/examples/com_dxgi_present.rs:
--------------------------------------------------------------------------------
1 | #![cfg(all(windows, feature = "static-detour"))]
2 | //! A `IDXGISwapChain::Present` detour example.
3 | //!
4 | //! Ensure the crate is compiled as a 'cdylib' library to allow C interop.
5 | use std::error::Error;
6 | use std::mem::size_of;
7 | use std::os::raw::c_void;
8 | use std::ptr::null;
9 |
10 | use windows::core::{Interface, HRESULT, HSTRING, PCWSTR};
11 | use windows::Win32::Foundation::{GetLastError, BOOL, HMODULE, HWND, LPARAM, LRESULT, WPARAM};
12 | use windows::Win32::Graphics::Direct3D::*;
13 | use windows::Win32::Graphics::Direct3D11::*;
14 | use windows::Win32::Graphics::Dxgi::Common::*;
15 | use windows::Win32::Graphics::Dxgi::*;
16 | use windows::Win32::Graphics::Gdi::HBRUSH;
17 | use windows::Win32::System::Console::AllocConsole;
18 | use windows::Win32::System::LibraryLoader::{DisableThreadLibraryCalls, GetModuleHandleW};
19 | use windows::Win32::System::SystemServices::DLL_PROCESS_ATTACH;
20 | use windows::Win32::UI::WindowsAndMessaging::*;
21 |
22 | use retour::static_detour;
23 |
24 | static_detour! {
25 | static PresentHook: unsafe extern "system" fn(*mut c_void, u32, u32) -> HRESULT;
26 | }
27 |
28 | #[allow(non_snake_case)]
29 | fn present(This: *mut c_void, SyncInterval: u32, Flags: u32) -> HRESULT {
30 | println!("present");
31 | unsafe { PresentHook.call(This, SyncInterval, Flags) }
32 | }
33 |
34 | #[no_mangle]
35 | #[allow(non_snake_case)]
36 | pub extern "system" fn DllMain(module: HMODULE, call_reason: u32, _reserved: *mut c_void) -> BOOL {
37 | unsafe {
38 | DisableThreadLibraryCalls(module);
39 | }
40 |
41 | if call_reason == DLL_PROCESS_ATTACH {
42 | unsafe {
43 | AllocConsole();
44 | }
45 |
46 | std::thread::spawn(|| unsafe {
47 | match crate::main() {
48 | Ok(()) => 0 as u32,
49 | Err(e) => {
50 | println!("Error occurred when injecting: {}", e);
51 | 1
52 | },
53 | }
54 | });
55 | }
56 | true.into()
57 | }
58 |
59 | unsafe extern "system" fn window_proc(
60 | hwnd: HWND,
61 | msg: u32,
62 | w_param: WPARAM,
63 | l_param: LPARAM,
64 | ) -> LRESULT {
65 | // workaround for https://github.com/microsoft/windows-rs/issues/2556
66 | DefWindowProcW(hwnd, msg, w_param, l_param)
67 | }
68 |
69 | unsafe fn main() -> Result<(), Box> {
70 | let vtable = get_d3d11_vtables().as_ref().unwrap();
71 | println!("Found Present Pointer at {:p}", vtable.Present as *const ());
72 | PresentHook.initialize(vtable.Present, present)?;
73 |
74 | PresentHook.enable()?;
75 |
76 | println!("Hook activated");
77 | Ok(())
78 | }
79 |
80 | unsafe fn get_render_window() -> (WNDCLASSEXW, HWND) {
81 | let window_class_name = HSTRING::from("DxHookWindowClass");
82 | let window_class = WNDCLASSEXW {
83 | cbSize: size_of::() as u32,
84 | style: CS_HREDRAW | CS_VREDRAW,
85 | lpfnWndProc: Some(window_proc),
86 | cbClsExtra: 0,
87 | cbWndExtra: 0,
88 | hInstance: GetModuleHandleW(None).unwrap(),
89 | hIcon: HICON::default(),
90 | hCursor: HCURSOR::default(),
91 | hbrBackground: HBRUSH::default(),
92 | lpszMenuName: PCWSTR(null()),
93 | lpszClassName: PCWSTR(window_class_name.as_wide().as_ptr()),
94 | hIconSm: HICON::default(),
95 | };
96 |
97 | RegisterClassExW(&window_class);
98 |
99 | let hwnd = CreateWindowExW(
100 | WINDOW_EX_STYLE::default(),
101 | window_class.lpszClassName,
102 | PCWSTR(HSTRING::from("DxHookWindowClass").as_wide().as_ptr()),
103 | WS_OVERLAPPEDWINDOW,
104 | 0,
105 | 0,
106 | 100,
107 | 100,
108 | HWND::default(),
109 | HMENU::default(),
110 | window_class.hInstance,
111 | None,
112 | );
113 |
114 | (window_class, hwnd)
115 | }
116 |
117 | unsafe fn get_d3d11_vtables() -> *const IDXGISwapChain_Vtbl {
118 | let (window_class, hwnd) = get_render_window();
119 | println!("made new hwnd {:?}", hwnd);
120 | let swapchain_desc = DXGI_SWAP_CHAIN_DESC {
121 | BufferDesc: DXGI_MODE_DESC {
122 | Width: 100,
123 | Height: 100,
124 | RefreshRate: DXGI_RATIONAL {
125 | Numerator: 60,
126 | Denominator: 1,
127 | },
128 | Format: DXGI_FORMAT_R8G8B8A8_UNORM,
129 | ScanlineOrdering: DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED,
130 | Scaling: DXGI_MODE_SCALING_UNSPECIFIED,
131 | },
132 | SampleDesc: DXGI_SAMPLE_DESC {
133 | Count: 1,
134 | Quality: 0,
135 | },
136 | BufferUsage: DXGI_USAGE_RENDER_TARGET_OUTPUT,
137 | BufferCount: 1,
138 | OutputWindow: hwnd,
139 | Windowed: true.into(),
140 | SwapEffect: DXGI_SWAP_EFFECT_DISCARD,
141 | Flags: DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH.0 as u32,
142 | };
143 |
144 | let feature_levels = [D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_11_1];
145 | let mut out_swapchain = None;
146 | let mut out_device = None;
147 | let mut out_context: Option = None;
148 | //
149 | D3D11CreateDeviceAndSwapChain(
150 | None,
151 | D3D_DRIVER_TYPE_HARDWARE,
152 | HMODULE::default(),
153 | D3D11_CREATE_DEVICE_FLAG::default(),
154 | Some(&feature_levels),
155 | D3D11_SDK_VERSION,
156 | Some(&swapchain_desc),
157 | Some(&mut out_swapchain),
158 | Some(&mut out_device),
159 | None,
160 | Some(&mut out_context),
161 | )
162 | .unwrap();
163 | println!("d3dhresult {:x?}", 0);
164 |
165 | let swapchain = out_swapchain.unwrap();
166 | let swapchain_vtbl: &IDXGISwapChain_Vtbl = swapchain.vtable();
167 |
168 | if !CloseWindow(hwnd).as_bool() {
169 | println!("Failed to close window. Error: {:?}", GetLastError());
170 | }
171 | if !DestroyWindow(hwnd).as_bool() {
172 | println!("Failed to destroy window. Error: {:?}", GetLastError());
173 | }
174 | // Needs fresh pointer to class name (else error 1411) + call DestroyWindow first (else error 1412)
175 | if !UnregisterClassW(PCWSTR(HSTRING::from("DxHookWindowClass").as_wide().as_ptr()), window_class.hInstance).as_bool() {
176 | println!("Failed to unregister window class. Error: {:?}", GetLastError());
177 | }
178 |
179 | swapchain_vtbl
180 | }
181 |
--------------------------------------------------------------------------------
/examples/kernel32_detour.rs:
--------------------------------------------------------------------------------
1 | #![cfg(windows)]
2 | #![allow(non_upper_case_globals, non_snake_case, non_camel_case_types)]
3 |
4 | use std::{ffi::CStr, os::raw::c_void};
5 |
6 | use once_cell::sync::Lazy;
7 | use retour::GenericDetour;
8 | use windows::{
9 | core::PCSTR,
10 | Win32::{
11 | Foundation::{BOOL, HANDLE, HMODULE},
12 | System::{
13 | LibraryLoader::{GetProcAddress, LoadLibraryA},
14 | SystemServices::{
15 | DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH, DLL_THREAD_ATTACH, DLL_THREAD_DETACH,
16 | },
17 | },
18 | },
19 | };
20 | type fn_LoadLibraryA = extern "system" fn(PCSTR) -> HMODULE;
21 |
22 | static hook_LoadLibraryA: Lazy> = Lazy::new(|| {
23 | let library_handle = unsafe { LoadLibraryA(PCSTR(b"kernel32.dll\0".as_ptr() as _)) }.unwrap();
24 | let address = unsafe { GetProcAddress(library_handle, PCSTR(b"LoadLibraryA\0".as_ptr() as _)) };
25 | let ori: fn_LoadLibraryA = unsafe { std::mem::transmute(address) };
26 | return unsafe { GenericDetour::new(ori, our_LoadLibraryA).unwrap() };
27 | });
28 |
29 | extern "system" fn our_LoadLibraryA(lpFileName: PCSTR) -> HMODULE {
30 | let file_name = unsafe { CStr::from_ptr(lpFileName.as_ptr() as _) };
31 | println!("our_LoadLibraryA lpFileName = {:?}", file_name);
32 | unsafe { hook_LoadLibraryA.disable().unwrap() };
33 | let ret_val = hook_LoadLibraryA.call(lpFileName);
34 | println!(
35 | "our_LoadLibraryA lpFileName = {:?} ret_val = {:#X}",
36 | file_name, ret_val.0
37 | );
38 | unsafe { hook_LoadLibraryA.enable().unwrap() };
39 | return ret_val;
40 | }
41 |
42 | #[no_mangle]
43 | unsafe extern "system" fn DllMain(_hinst: HANDLE, reason: u32, _reserved: *mut c_void) -> BOOL {
44 | match reason {
45 | DLL_PROCESS_ATTACH => {
46 | println!("attaching");
47 | unsafe {
48 | hook_LoadLibraryA.enable().unwrap();
49 | }
50 | },
51 | DLL_PROCESS_DETACH => {
52 | println!("detaching");
53 | },
54 | DLL_THREAD_ATTACH => {},
55 | DLL_THREAD_DETACH => {},
56 | _ => {},
57 | };
58 | return BOOL::from(true);
59 | }
60 |
--------------------------------------------------------------------------------
/examples/messageboxw_detour.rs:
--------------------------------------------------------------------------------
1 | #![cfg(all(windows, feature = "static-detour"))]
2 | //! A `MessageBoxW` detour example.
3 | //!
4 | //! Ensure the crate is compiled as a 'cdylib' library to allow C interop.
5 | use retour::static_detour;
6 | use std::error::Error;
7 | use std::ffi::c_int;
8 | use std::os::raw::c_void;
9 | use std::{ffi::CString, iter, mem};
10 | use windows::core::{PCSTR, PCWSTR};
11 | use windows::w;
12 | use windows::Win32::Foundation::{BOOL, HANDLE, HWND};
13 | use windows::Win32::System::LibraryLoader::{GetModuleHandleW, GetProcAddress};
14 | use windows::Win32::System::SystemServices::{
15 | DLL_PROCESS_ATTACH, DLL_PROCESS_DETACH, DLL_THREAD_ATTACH, DLL_THREAD_DETACH,
16 | };
17 |
18 | static_detour! {
19 | static MessageBoxWHook: unsafe extern "system" fn(HWND, PCWSTR, PCWSTR, u32) -> c_int;
20 | }
21 |
22 | // A type alias for `MessageBoxW` (makes the transmute easy on the eyes)
23 | type FnMessageBoxW = unsafe extern "system" fn(HWND, PCWSTR, PCWSTR, u32) -> c_int;
24 |
25 | /// Called when the DLL is attached to the process.
26 | unsafe fn main() -> Result<(), Box> {
27 | // Retrieve an absolute address of `MessageBoxW`. This is required for
28 | // libraries due to the import address table. If `MessageBoxW` would be
29 | // provided directly as the target, it would only hook this DLL's
30 | // `MessageBoxW`. Using the method below an absolute address is retrieved
31 | // instead, detouring all invocations of `MessageBoxW` in the active process.
32 | let address = get_module_symbol_address("user32.dll", "MessageBoxW")
33 | .expect("could not find 'MessageBoxW' address");
34 | let target: FnMessageBoxW = mem::transmute(address);
35 |
36 | // Initialize AND enable the detour (the 2nd parameter can also be a closure)
37 | MessageBoxWHook
38 | .initialize(target, messageboxw_detour)?
39 | .enable()?;
40 | Ok(())
41 | }
42 |
43 | /// Called whenever `MessageBoxW` is invoked in the process.
44 | fn messageboxw_detour(hwnd: HWND, text: PCWSTR, _caption: PCWSTR, msgbox_style: u32) -> c_int {
45 | // Call the original `MessageBoxW`, but replace the caption
46 | let replaced_caption = w!("Detoured!");
47 | unsafe { MessageBoxWHook.call(hwnd, text, replaced_caption, msgbox_style) }
48 | }
49 |
50 | /// Returns a module symbol's absolute address.
51 | fn get_module_symbol_address(module: &str, symbol: &str) -> Option {
52 | let module = module
53 | .encode_utf16()
54 | .chain(iter::once(0))
55 | .collect::>();
56 | let symbol = CString::new(symbol).unwrap();
57 | unsafe {
58 | let handle = GetModuleHandleW(PCWSTR(module.as_ptr() as _)).unwrap();
59 | match GetProcAddress(handle, PCSTR(symbol.as_ptr() as _)) {
60 | Some(func) => Some(func as usize),
61 | None => None,
62 | }
63 | }
64 | }
65 |
66 | #[no_mangle]
67 | unsafe extern "system" fn DllMain(_hinst: HANDLE, reason: u32, _reserved: *mut c_void) -> BOOL {
68 | match reason {
69 | DLL_PROCESS_ATTACH => {
70 | println!("attaching");
71 | unsafe { main().unwrap() }
72 | },
73 | DLL_PROCESS_DETACH => {
74 | println!("detaching");
75 | },
76 | DLL_THREAD_ATTACH => {},
77 | DLL_THREAD_DETACH => {},
78 | _ => {},
79 | };
80 | return BOOL::from(true);
81 | }
82 |
--------------------------------------------------------------------------------
/rustfmt.toml:
--------------------------------------------------------------------------------
1 | condense_wildcard_suffixes = true
2 | fn_single_line = false
3 | format_strings = true
4 | match_block_trailing_comma = true
5 | normalize_comments = true
6 | reorder_imports = true
7 | tab_spaces = 2
8 | use_field_init_shorthand = true
9 | use_try_shorthand = true
10 | wrap_comments = true
11 |
--------------------------------------------------------------------------------
/src/alloc/mod.rs:
--------------------------------------------------------------------------------
1 | use crate::error::Result;
2 | use std::ops::{Deref, DerefMut};
3 | use std::sync::{Arc, Mutex};
4 |
5 | mod proximity;
6 | mod search;
7 |
8 | /// A thread-safe memory pool for allocating chunks close to addresses.
9 | pub struct ThreadAllocator(Arc>);
10 |
11 | // TODO: Decrease use of mutexes
12 | impl ThreadAllocator {
13 | /// Creates a new proximity memory allocator.
14 | pub fn new(max_distance: usize) -> Self {
15 | ThreadAllocator(Arc::new(Mutex::new(proximity::ProximityAllocator {
16 | max_distance,
17 | pools: Vec::new(),
18 | })))
19 | }
20 |
21 | /// Allocates read-, write- & executable memory close to `origin`.
22 | pub fn allocate(&self, origin: *const (), size: usize) -> Result {
23 | let mut allocator = self.0.lock().unwrap();
24 | allocator
25 | .allocate(origin, size)
26 | .map(|data| ExecutableMemory {
27 | allocator: self.0.clone(),
28 | data,
29 | })
30 | }
31 | }
32 |
33 | /// A handle for allocated proximity memory.
34 | pub struct ExecutableMemory {
35 | allocator: Arc>,
36 | data: proximity::Allocation,
37 | }
38 |
39 | impl Drop for ExecutableMemory {
40 | fn drop(&mut self) {
41 | // Release the associated memory map (if unique)
42 | self.allocator.lock().unwrap().release(&self.data);
43 | }
44 | }
45 |
46 | impl Deref for ExecutableMemory {
47 | type Target = [u8];
48 |
49 | fn deref(&self) -> &Self::Target {
50 | self.data.deref()
51 | }
52 | }
53 |
54 | impl DerefMut for ExecutableMemory {
55 | fn deref_mut(&mut self) -> &mut [u8] {
56 | self.data.deref_mut()
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/alloc/proximity.rs:
--------------------------------------------------------------------------------
1 | use std::ops::Range;
2 | use std::slice;
3 |
4 | use slice_pool::sync::{SliceBox, SlicePool};
5 |
6 | use super::search as region_search;
7 | use crate::error::{Error, Result};
8 |
9 | /// Defines the allocation type.
10 | pub type Allocation = SliceBox;
11 |
12 | /// Shared instance containing all pools
13 | pub struct ProximityAllocator {
14 | pub max_distance: usize,
15 | pub pools: Vec>,
16 | }
17 |
18 | impl ProximityAllocator {
19 | /// Allocates a slice in an eligible memory map.
20 | pub fn allocate(&mut self, origin: *const (), size: usize) -> Result {
21 | let memory_range = ((origin as usize).saturating_sub(self.max_distance))
22 | ..((origin as usize).saturating_add(self.max_distance));
23 |
24 | // Check if an existing pool can handle the allocation request
25 | self.allocate_memory(&memory_range, size).or_else(|_| {
26 | // ... otherwise allocate a pool within the memory range
27 | self.allocate_pool(&memory_range, origin, size).map(|pool| {
28 | // Use the newly allocated pool for the request
29 | let allocation = pool.alloc(size).unwrap();
30 | self.pools.push(pool);
31 | allocation
32 | })
33 | })
34 | }
35 |
36 | /// Releases the memory pool associated with an allocation.
37 | pub fn release(&mut self, value: &Allocation) {
38 | // Find the associated memory pool
39 | let index = self
40 | .pools
41 | .iter()
42 | .position(|pool| {
43 | let lower = pool.as_ptr() as usize;
44 | let upper = lower + pool.len();
45 |
46 | // Determine if this is the associated memory pool
47 | (lower..upper).contains(&(value.as_ptr() as usize))
48 | })
49 | .expect("retrieving associated memory pool");
50 |
51 | // Release the pool if the associated allocation is unique
52 | if self.pools[index].len() == 1 {
53 | self.pools.remove(index);
54 | }
55 | }
56 |
57 | /// Allocates a chunk using any of the existing pools.
58 | fn allocate_memory(&mut self, range: &Range, size: usize) -> Result {
59 | // Returns true if the pool's memory is within the range
60 | let is_pool_in_range = |pool: &SlicePool| {
61 | let lower = pool.as_ptr() as usize;
62 | let upper = lower + pool.len();
63 | range.contains(&lower) && range.contains(&(upper - 1))
64 | };
65 |
66 | // Tries to allocate a slice within any eligible pool
67 | self
68 | .pools
69 | .iter_mut()
70 | .filter_map(|pool| {
71 | if is_pool_in_range(pool) {
72 | pool.alloc(size)
73 | } else {
74 | None
75 | }
76 | })
77 | .next()
78 | .ok_or(Error::OutOfMemory)
79 | }
80 |
81 | /// Allocates a new pool close to `origin`.
82 | fn allocate_pool(
83 | &mut self,
84 | range: &Range,
85 | origin: *const (),
86 | size: usize,
87 | ) -> Result> {
88 | let before = region_search::before(origin, Some(range.clone()));
89 | let after = region_search::after(origin, Some(range.clone()));
90 |
91 | // TODO: Part of the pool can be out of range
92 | // Try to allocate after the specified address first (mostly because
93 | // macOS cannot allocate memory before the process's address).
94 | after
95 | .chain(before)
96 | .filter_map(|result| match result {
97 | Ok(address) => Self::allocate_fixed_pool(address, size).map(Ok),
98 | Err(error) => Some(Err(error)),
99 | })
100 | .next()
101 | .unwrap_or(Err(Error::OutOfMemory))
102 | }
103 |
104 | /// Tries to allocate fixed memory at the specified address.
105 | fn allocate_fixed_pool(address: *const (), size: usize) -> Option> {
106 | // Try to allocate memory at the specified address
107 | mmap::MemoryMap::new(
108 | size,
109 | &[
110 | mmap::MapOption::MapReadable,
111 | mmap::MapOption::MapWritable,
112 | mmap::MapOption::MapExecutable,
113 | mmap::MapOption::MapAddr(address as *const _),
114 | ],
115 | )
116 | .ok()
117 | .map(SliceableMemoryMap)
118 | .map(SlicePool::new)
119 | }
120 | }
121 |
122 | // TODO: Use memmap-rs instead
123 | /// A wrapper for making a memory map compatible with `SlicePool`.
124 | struct SliceableMemoryMap(mmap::MemoryMap);
125 |
126 | impl SliceableMemoryMap {
127 | pub fn as_slice(&self) -> &[u8] {
128 | unsafe { slice::from_raw_parts(self.0.data(), self.0.len()) }
129 | }
130 |
131 | pub fn as_mut_slice(&mut self) -> &mut [u8] {
132 | unsafe { slice::from_raw_parts_mut(self.0.data(), self.0.len()) }
133 | }
134 | }
135 |
136 | impl AsRef<[u8]> for SliceableMemoryMap {
137 | fn as_ref(&self) -> &[u8] {
138 | self.as_slice()
139 | }
140 | }
141 |
142 | impl AsMut<[u8]> for SliceableMemoryMap {
143 | fn as_mut(&mut self) -> &mut [u8] {
144 | self.as_mut_slice()
145 | }
146 | }
147 |
148 | unsafe impl Send for SliceableMemoryMap {}
149 | unsafe impl Sync for SliceableMemoryMap {}
150 |
--------------------------------------------------------------------------------
/src/alloc/search.rs:
--------------------------------------------------------------------------------
1 | use crate::error::{Error, Result};
2 | use std::ops::Range;
3 |
4 | /// Returns an iterator for free after the specified address.
5 | pub fn after(
6 | origin: *const (),
7 | range: Option>,
8 | ) -> impl Iterator- > {
9 | FreeRegionIter::new(origin, range, SearchDirection::After)
10 | }
11 |
12 | /// Returns an iterator for free before the specified address.
13 | pub fn before(
14 | origin: *const (),
15 | range: Option>,
16 | ) -> impl Iterator
- > {
17 | FreeRegionIter::new(origin, range, SearchDirection::Before)
18 | }
19 |
20 | /// Direction for the region search.
21 | enum SearchDirection {
22 | Before,
23 | After,
24 | }
25 |
26 | /// An iterator searching for free regions.
27 | struct FreeRegionIter {
28 | range: Range,
29 | search: SearchDirection,
30 | current: usize,
31 | }
32 |
33 | impl FreeRegionIter {
34 | /// Creates a new iterator for free regions.
35 | fn new(origin: *const (), range: Option>, search: SearchDirection) -> Self {
36 | FreeRegionIter {
37 | range: range.unwrap_or(0..usize::max_value()),
38 | current: origin as usize,
39 | search,
40 | }
41 | }
42 | }
43 |
44 | impl Iterator for FreeRegionIter {
45 | type Item = Result<*const ()>;
46 |
47 | /// Returns the closest free region for the current address.
48 | fn next(&mut self) -> Option {
49 | let page_size = region::page::size();
50 |
51 | while self.current > 0 && self.range.contains(&self.current) {
52 | match region::query(self.current as *const usize) {
53 | Ok(region) => {
54 | let range = region.as_range();
55 | self.current = match self.search {
56 | SearchDirection::Before => range.start.saturating_sub(page_size),
57 | SearchDirection::After => range.end,
58 | }
59 | },
60 | Err(error) => {
61 | // Check whether the region is free, otherwise return the error
62 | let result = Some(match error {
63 | region::Error::UnmappedRegion => Ok(self.current as *const _),
64 | inner => Err(Error::RegionFailure(inner)),
65 | });
66 |
67 | // Adjust the offset for repeated calls.
68 | self.current = match self.search {
69 | SearchDirection::Before => self.current.saturating_sub(page_size),
70 | SearchDirection::After => self.current + page_size,
71 | };
72 |
73 | return result;
74 | },
75 | }
76 | }
77 |
78 | None
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/arch/detour.rs:
--------------------------------------------------------------------------------
1 | use super::memory;
2 | use crate::error::{Error, Result};
3 | use crate::{alloc, arch, util};
4 | use std::cell::UnsafeCell;
5 | use std::fmt;
6 | use std::sync::atomic::{AtomicBool, Ordering};
7 |
8 | /// An architecture-independent implementation of a base detour.
9 | ///
10 | /// This class is never instantiated by itself, it merely exposes an API
11 | /// available through it's descendants.
12 | pub struct Detour {
13 | #[allow(dead_code)]
14 | relay: Option,
15 | trampoline: alloc::ExecutableMemory,
16 | patcher: UnsafeCell,
17 | enabled: AtomicBool,
18 | }
19 |
20 | impl Detour {
21 | pub unsafe fn new(target: *const (), detour: *const ()) -> Result {
22 | if target == detour {
23 | Err(Error::SameAddress)?;
24 | }
25 |
26 | // Lock this so OS operations are not performed in parallell
27 | let mut pool = memory::POOL.lock().unwrap();
28 |
29 | if !util::is_executable_address(target)? || !util::is_executable_address(detour)? {
30 | Err(Error::NotExecutable)?;
31 | }
32 |
33 | // Create a trampoline generator for the target function
34 | let margin = arch::meta::prolog_margin(target);
35 | let trampoline = arch::Trampoline::new(target, margin)?;
36 |
37 | // A relay is used in case a normal branch cannot reach the destination
38 | let relay = if let Some(emitter) = arch::meta::relay_builder(target, detour)? {
39 | Some(memory::allocate_pic(&mut pool, &emitter, target)?)
40 | } else {
41 | None
42 | };
43 |
44 | // If a relay is supplied, use it instead of the detour address
45 | let detour = relay
46 | .as_ref()
47 | .map(|code| code.as_ptr() as *const ())
48 | .unwrap_or(detour);
49 |
50 | Ok(Detour {
51 | patcher: UnsafeCell::new(arch::Patcher::new(
52 | target,
53 | detour,
54 | trampoline.prolog_size(),
55 | )?),
56 | trampoline: memory::allocate_pic(&mut pool, trampoline.emitter(), target)?,
57 | enabled: AtomicBool::default(),
58 | relay,
59 | })
60 | }
61 |
62 | /// Enables the detour.
63 | pub unsafe fn enable(&self) -> Result<()> {
64 | self.toggle(true)
65 | }
66 |
67 | /// Disables the detour.
68 | pub unsafe fn disable(&self) -> Result<()> {
69 | self.toggle(false)
70 | }
71 |
72 | /// Returns whether the detour is enabled or not.
73 | pub fn is_enabled(&self) -> bool {
74 | self.enabled.load(Ordering::SeqCst)
75 | }
76 |
77 | /// Returns a reference to the generated trampoline.
78 | pub fn trampoline(&self) -> &() {
79 | unsafe {
80 | (self.trampoline.as_ptr() as *const ())
81 | .as_ref()
82 | .expect("trampoline should not be null")
83 | }
84 | }
85 |
86 | /// Enables or disables the detour.
87 | unsafe fn toggle(&self, enabled: bool) -> Result<()> {
88 | let _guard = memory::POOL.lock().unwrap();
89 |
90 | if self.enabled.load(Ordering::SeqCst) == enabled {
91 | return Ok(());
92 | }
93 |
94 | // Runtime code is by default only read-execute
95 | let _handle = {
96 | let area = (*self.patcher.get()).area();
97 | region::protect_with_handle(
98 | area.as_ptr(),
99 | area.len(),
100 | region::Protection::READ_WRITE_EXECUTE,
101 | )
102 | }?;
103 |
104 | // Copy either the detour or the original bytes of the function
105 | (*self.patcher.get()).toggle(enabled);
106 | self.enabled.store(enabled, Ordering::SeqCst);
107 | Ok(())
108 | }
109 | }
110 |
111 | impl Drop for Detour {
112 | /// Disables the detour, if enabled.
113 | fn drop(&mut self) {
114 | let did_succeed = unsafe { self.disable() }.is_ok();
115 | debug_assert!(did_succeed);
116 | }
117 | }
118 |
119 | impl fmt::Debug for Detour {
120 | /// Output whether the detour is enabled or not.
121 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
122 | write!(
123 | f,
124 | "Detour {{ enabled: {}, trampoline: {:?} }}",
125 | self.is_enabled(),
126 | self.trampoline()
127 | )
128 | }
129 | }
130 |
131 | unsafe impl Send for Detour {}
132 | unsafe impl Sync for Detour {}
133 |
--------------------------------------------------------------------------------
/src/arch/memory.rs:
--------------------------------------------------------------------------------
1 | use once_cell::sync::Lazy;
2 |
3 | use crate::{alloc, arch, error::Result, pic};
4 | use std::sync::Mutex;
5 |
6 | /// Shared allocator for all detours.
7 | pub static POOL: Lazy> = Lazy::new(|| {
8 | // Use a range of +/- 2 GB for seeking a memory block
9 | Mutex::new(alloc::ThreadAllocator::new(arch::meta::DETOUR_RANGE))
10 | });
11 |
12 | /// Allocates PIC code at the specified address.
13 | pub fn allocate_pic(
14 | pool: &mut alloc::ThreadAllocator,
15 | emitter: &pic::CodeEmitter,
16 | origin: *const (),
17 | ) -> Result {
18 | // Allocate memory close to the origin
19 | pool.allocate(origin, emitter.len()).map(|mut memory| {
20 | // Generate code for the obtained address
21 | let code = emitter.emit(memory.as_ptr() as *const _);
22 | memory.copy_from_slice(code.as_slice());
23 | memory
24 | })
25 | }
26 |
--------------------------------------------------------------------------------
/src/arch/mod.rs:
--------------------------------------------------------------------------------
1 | /// Architecture specific code
2 | ///
3 | /// The current implementation requires a module to expose some functionality:
4 | ///
5 | /// - A standalone `relay_builder` function.
6 | /// This function creates a relay for targets with large displacement, that
7 | /// requires special attention. An example would be detours further away than
8 | /// 2GB on x64. A relative jump is not enough, so the `relay_builder`
9 | /// generates an absolute jump that the relative jump can reach. If it's
10 | /// needless, `None` can be returned.
11 | ///
12 | /// - A `Patcher`, modifies a target in-memory.
13 | /// - A `Trampoline`, generates a callable address to the target.
14 | pub use self::detour::Detour;
15 |
16 | use cfg_if::cfg_if;
17 |
18 | // TODO: flush instruction cache? __clear_cache
19 | // See: https://github.com/llvm-mirror/compiler-rt/blob/master/lib/builtins/clear_cache.c
20 | cfg_if! {
21 | if #[cfg(any(target_arch = "x86", target_arch = "x86_64"))] {
22 | mod x86;
23 | use self::x86::{Patcher, Trampoline, meta};
24 | } else {
25 | // TODO: Implement ARM/AARCH64/MIPS support!
26 | }
27 | }
28 |
29 | mod detour;
30 | mod memory;
31 |
32 | /// Returns true if the displacement is within a certain range.
33 | pub fn is_within_range(displacement: isize) -> bool {
34 | let range = meta::DETOUR_RANGE as i64;
35 | (-range..range).contains(&(displacement as i64))
36 | }
37 |
--------------------------------------------------------------------------------
/src/arch/x86/meta.rs:
--------------------------------------------------------------------------------
1 | use super::thunk;
2 | use crate::{error::Result, pic};
3 | use std::mem;
4 |
5 | /// The furthest distance between a target and its detour (2 GiB).
6 | pub const DETOUR_RANGE: usize = 0x8000_0000;
7 |
8 | /// Returns the preferred prolog size for the target.
9 | pub fn prolog_margin(_target: *const ()) -> usize {
10 | mem::size_of::()
11 | }
12 |
13 | /// Creates a relay; required for destinations further away than 2GB (on x64).
14 | pub fn relay_builder(target: *const (), detour: *const ()) -> Result