retour/lib.rs
1#![recursion_limit = "1024"]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![cfg_attr(
4 feature = "static-detour",
5 feature(unboxed_closures, tuple_trait)
6)]
7#![cfg_attr(
8 all(feature = "static-detour", test),
9 feature(naked_functions)
10)]
11#![cfg_attr(
12 feature = "thiscall-abi",
13 feature(abi_thiscall)
14)]
15
16//! A cross-platform detour library written in Rust.
17//!
18//! ## Intro
19//!
20//! This library provides a thread-safe, inline detouring functionality by
21//! disassembling and patching functions during runtime, using assembly opcodes
22//! allocated within executable memory. It modifies the target functions and
23//! replaces their prolog with an unconditional jump.
24//!
25//! Beyond the basic functionality this library handles several different edge
26//! cases:
27//!
28//! - Relative branches.
29//! - RIP relative operands.
30//! - Detects NOP-padding.
31//! - Relay for large offsets (>2GB).
32//! - Supports hot patching.
33//!
34//! ## Detours
35//!
36//! Three different types of detours are provided:
37//!
38//! - [Static](./struct.StaticDetour.html): A static & type-safe interface.
39//! Thanks to its static nature it can accept a closure as its detour, but is
40//! required to be statically defined at compile time.
41//!
42//! - [Generic](./struct.GenericDetour.html): A type-safe interface — the same
43//! prototype is enforced for both the target and the detour. It is also
44//! enforced when invoking the original target.
45//!
46//! - [Raw](./struct.RawDetour.html): The underlying building block that the
47//! others types abstract upon. It has no type-safety and interacts with raw
48//! pointers. It should be avoided unless any types are references, or not
49//! known until runtime.
50//!
51//! ## Features
52//!
53//! - **static-detour**: Required for static detours, due to usage
54//! of *unboxed_closures* and *tuple_trait*. The feature also enables a more
55//! extensive test suite. *Requires nightly compiler*
56//! - **thiscall-abi**: Required for hooking functions that use the "thiscall" ABI, which is
57//! nightly only. *Requires nightly compiler*
58//!
59//! ## Platforms
60//!
61//! - Both `x86` & `x86-64` are supported.
62//!
63//! ## Procedure
64//!
65//! To illustrate a detour on an x86 platform:
66//!
67//! ```c
68//! 0 int return_five() {
69//! 1 return 5;
70//! 00400020 [b8 05 00 00 00] mov eax, 5
71//! 00400025 [c3] ret
72//! 2 }
73//! 3
74//! 4 int detour_function() {
75//! 5 return 10;
76//! 00400040 [b8 0A 00 00 00] mov eax, 10
77//! 00400045 [c3] ret
78//! 6 }
79//! ```
80//!
81//! To detour `return_five` the library by default tries to replace five bytes
82//! with a relative jump (the optimal scenario), which works in this case.
83//! Executable memory will be allocated for the instruction and the function's
84//! prolog will be replaced.
85//!
86//! ```c
87//! 0 int return_five() {
88//! 1 return detour_function();
89//! 00400020 [e9 16 00 00 00] jmp 1b <detour_function>
90//! 00400025 [c3] ret
91//! 2 }
92//! 3
93//! 4 int detour_function() {
94//! 5 return 10;
95//! 00400040 [b8 0a 00 00 00] mov eax, 10
96//! 00400045 [c3] ret
97//! 6 }
98//! ```
99//!
100//! Beyond what is shown here, a trampoline is also generated so the original
101//! function can be called regardless whether the function is hooked or not.
102//!
103//! For various injection methods, see the [README in the GitHub repo](https://github.com/Hpmason/retour-rs)
104
105// Re-exports
106pub use detours::*;
107pub use error::{Error, Result};
108pub use traits::{Function, HookableWith};
109
110#[macro_use]
111mod macros;
112
113// Modules
114mod alloc;
115mod arch;
116mod detours;
117mod error;
118mod pic;
119mod traits;
120mod util;
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125 use crate::Result;
126 use matches::assert_matches;
127
128 #[test]
129 fn detours_share_target() -> Result<()> {
130 #[inline(never)]
131 extern "C" fn add(x: i32, y: i32) -> i32 {
132 unsafe { std::ptr::read_volatile(&x as *const i32) + y }
133 }
134
135 let hook1 = unsafe {
136 extern "C" fn sub(x: i32, y: i32) -> i32 {
137 x - y
138 }
139 GenericDetour::<extern "C" fn(i32, i32) -> i32>::new(add, sub)?
140 };
141
142 unsafe { hook1.enable()? };
143 assert_eq!(add(5, 5), 0);
144
145 let hook2 = unsafe {
146 extern "C" fn div(x: i32, y: i32) -> i32 {
147 x / y
148 }
149 GenericDetour::<extern "C" fn(i32, i32) -> i32>::new(add, div)?
150 };
151
152 unsafe { hook2.enable()? };
153
154 // This will call the previous hook's detour
155 assert_eq!(hook2.call(5, 5), 0);
156 assert_eq!(add(10, 5), 2);
157 Ok(())
158 }
159
160 #[test]
161 fn same_detour_and_target() {
162 #[inline(never)]
163 extern "C" fn add(x: i32, y: i32) -> i32 {
164 unsafe { std::ptr::read_volatile(&x as *const i32) + y }
165 }
166
167 let err = unsafe { RawDetour::new(add as *const (), add as *const ()).unwrap_err() };
168 assert_matches!(err, Error::SameAddress);
169 }
170}