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}