substrate/
lib.rs

1#![doc = include_str!("../README.md")]
2#![allow(unsafe_op_in_unsafe_fn)]
3
4pub mod arch;
5pub mod debug;
6pub mod disasm;
7pub mod error;
8pub mod hook;
9pub mod symbol;
10pub mod utils;
11
12use error::{Result, SubstrateError};
13use std::ffi::CStr;
14use std::os::raw::{c_char, c_int, c_void};
15use std::ptr;
16use std::sync::atomic::{AtomicBool, Ordering};
17
18pub type MSImageRef = *const c_void;
19
20static MS_DEBUG: AtomicBool = AtomicBool::new(false);
21
22#[unsafe(no_mangle)]
23pub static mut MSDebug: bool = false;
24
25pub fn set_debug(enabled: bool) {
26    MS_DEBUG.store(enabled, Ordering::Relaxed);
27    unsafe { MSDebug = enabled; }
28}
29
30pub fn is_debug() -> bool {
31    MS_DEBUG.load(Ordering::Relaxed)
32}
33
34/// Hook a function at runtime by redirecting its execution to a replacement function.
35///
36/// This is the primary C-compatible hooking function that works across all supported architectures
37/// (x86-64, ARMv7, ARM64). It installs an inline hook by modifying the target function's prologue
38/// to jump to your replacement function, while preserving the original instructions in a trampoline.
39///
40/// # Arguments
41///
42/// * `symbol` - Pointer to the function to hook (must not be null)
43/// * `replace` - Pointer to your replacement function (must not be null)
44/// * `result` - Output pointer that receives the trampoline address to call the original function.
45///              Pass null if you don't need to call the original function.
46///
47/// # Safety
48///
49/// This function is unsafe because it:
50/// - Modifies executable code at runtime
51/// - Requires valid function pointers
52/// - Changes memory protection flags
53/// - Can cause undefined behavior if pointers are invalid
54///
55/// # Examples
56///
57/// ```no_run
58/// use substrate::MSHookFunction;
59/// use std::os::raw::c_void;
60///
61/// static mut ORIGINAL: *mut c_void = std::ptr::null_mut();
62///
63/// unsafe extern "C" fn my_replacement() {
64///     println!("Hooked!");
65///     if !ORIGINAL.is_null() {
66///         let orig: extern "C" fn() = std::mem::transmute(ORIGINAL);
67///         orig();
68///     }
69/// }
70///
71/// unsafe {
72///     let target = 0x12345678 as *mut c_void;
73///     MSHookFunction(target, my_replacement as *mut c_void, &mut ORIGINAL);
74/// }
75/// ```
76#[unsafe(no_mangle)]
77pub unsafe extern "C" fn MSHookFunction(
78    symbol: *mut c_void,
79    replace: *mut c_void,
80    result: *mut *mut c_void,
81) { unsafe {
82    if symbol.is_null() {
83        return;
84    }
85
86    let result_ptr = if result.is_null() {
87        ptr::null_mut()
88    } else {
89        result as *mut *mut u8
90    };
91
92    #[cfg(target_arch = "x86_64")]
93    {
94        let _ = arch::x86_64::hook_function_x86_64(
95            symbol as *mut u8,
96            replace as *mut u8,
97            result_ptr,
98        );
99    }
100
101    #[cfg(target_arch = "arm")]
102    {
103        let symbol_addr = symbol as usize;
104        if (symbol_addr & 0x1) == 0 {
105            let _ = arch::arm::hook_function_arm(
106                symbol as *mut u8,
107                replace as *mut u8,
108                result_ptr,
109            );
110        } else {
111            let _ = arch::thumb::hook_function_thumb(
112                (symbol_addr & !0x1) as *mut u8,
113                replace as *mut u8,
114                result_ptr,
115            );
116        }
117    }
118
119    #[cfg(target_arch = "aarch64")]
120    {
121        let _ = arch::aarch64::hook_function_aarch64(
122            symbol as *mut u8,
123            replace as *mut u8,
124            result_ptr,
125        );
126    }
127}}
128
129/// ARM64-specific hook function (alias for MSHookFunction).
130///
131/// This function is provided for compatibility with And64InlineHook API.
132/// On ARM64 platforms, it behaves identically to `MSHookFunction`.
133///
134/// # Safety
135///
136/// Same safety requirements as `MSHookFunction`. See [`MSHookFunction`] for details.
137#[unsafe(no_mangle)]
138pub unsafe extern "C" fn A64HookFunction(
139    symbol: *mut c_void,
140    replace: *mut c_void,
141    result: *mut *mut c_void,
142) { unsafe {
143    MSHookFunction(symbol, replace, result);
144}}
145
146/// Find a symbol by name within a loaded image.
147///
148/// # Arguments
149///
150/// * `_image` - Reference to the loaded image (currently unused)
151/// * `name` - C string containing the symbol name to find
152///
153/// # Returns
154///
155/// Pointer to the symbol if found, null pointer otherwise.
156///
157/// # Safety
158///
159/// The `name` parameter must be a valid null-terminated C string.
160#[unsafe(no_mangle)]
161pub unsafe extern "C" fn MSFindSymbol(_image: MSImageRef, name: *const c_char) -> *mut c_void { unsafe {
162    if name.is_null() {
163        return ptr::null_mut();
164    }
165
166    let _symbol_name = match CStr::from_ptr(name).to_str() {
167        Ok(s) => s,
168        Err(_) => return ptr::null_mut(),
169    };
170
171    ptr::null_mut()
172}}
173
174/// Get a reference to a loaded image (library) by filename.
175///
176/// # Arguments
177///
178/// * `_file` - C string containing the library filename
179///
180/// # Returns
181///
182/// Reference to the loaded image if found, null otherwise.
183///
184/// # Safety
185///
186/// The `_file` parameter must be a valid null-terminated C string.
187#[unsafe(no_mangle)]
188pub unsafe extern "C" fn MSGetImageByName(_file: *const c_char) -> MSImageRef {
189    ptr::null()
190}
191
192/// Hook into another process by injecting a library.
193///
194/// # Arguments
195///
196/// * `_pid` - Process ID to hook into
197/// * `_library` - C string containing the library path to inject
198///
199/// # Returns
200///
201/// `true` if successful, `false` otherwise.
202///
203/// # Safety
204///
205/// This function requires appropriate permissions and the library path must be valid.
206#[unsafe(no_mangle)]
207pub unsafe extern "C" fn MSHookProcess(_pid: c_int, _library: *const c_char) -> bool {
208    false
209}
210
211/// Type-safe Rust wrapper for hooking functions.
212///
213/// This is a generic wrapper around the C API that provides type safety and Result-based
214/// error handling. It's the recommended way to use the hooking functionality from Rust code.
215///
216/// # Type Parameters
217///
218/// * `T` - The function type to hook (typically a function pointer)
219///
220/// # Arguments
221///
222/// * `symbol` - Pointer to the function to hook
223/// * `replace` - Pointer to your replacement function
224///
225/// # Returns
226///
227/// `Ok(*mut T)` containing the trampoline pointer to call the original function.
228/// `Err(SubstrateError)` if the hook installation fails.
229///
230/// # Safety
231///
232/// This function is unsafe because it modifies executable code at runtime.
233/// Both pointers must be valid function pointers of the correct type.
234///
235/// # Examples
236///
237/// ```no_run
238/// use substrate::hook_function;
239///
240/// extern "C" fn original_func(x: i32) -> i32 { x }
241/// extern "C" fn hooked_func(x: i32) -> i32 { x + 1 }
242///
243/// unsafe {
244///     let trampoline = hook_function(
245///         original_func as *mut _,
246///         hooked_func as *mut _
247///     ).expect("Hook failed");
248/// }
249/// ```
250pub unsafe fn hook_function<T>(symbol: *mut T, replace: *mut T) -> Result<*mut T> { unsafe {
251    if symbol.is_null() || replace.is_null() {
252        return Err(SubstrateError::NullPointer);
253    }
254
255    let mut result: *mut T = ptr::null_mut();
256
257    #[cfg(target_arch = "x86_64")]
258    {
259        arch::x86_64::hook_function_x86_64(
260            symbol as *mut u8,
261            replace as *mut u8,
262            &mut result as *mut *mut T as *mut *mut u8,
263        )?;
264    }
265
266    #[cfg(target_arch = "arm")]
267    {
268        let symbol_addr = symbol as usize;
269        if (symbol_addr & 0x1) == 0 {
270            arch::arm::hook_function_arm(
271                symbol as *mut u8,
272                replace as *mut u8,
273                &mut result as *mut *mut T as *mut *mut u8,
274            )?;
275        } else {
276            arch::thumb::hook_function_thumb(
277                (symbol_addr & !0x1) as *mut u8,
278                replace as *mut u8,
279                &mut result as *mut *mut T as *mut *mut u8,
280            )?;
281        }
282    }
283
284    #[cfg(target_arch = "aarch64")]
285    {
286        arch::aarch64::hook_function_aarch64(
287            symbol as *mut u8,
288            replace as *mut u8,
289            &mut result as *mut *mut T as *mut *mut u8,
290        )?;
291    }
292
293    #[cfg(not(any(target_arch = "x86_64", target_arch = "arm", target_arch = "aarch64")))]
294    {
295        return Err(SubstrateError::HookFailed("Architecture not implemented".to_string()));
296    }
297
298    Ok(result)
299}}
300
301/// Find a symbol address in a specific process.
302///
303/// This function looks up a symbol by name within a specific library loaded in the target process.
304/// It parses the process memory maps and ELF symbol tables to resolve the symbol address.
305///
306/// # Arguments
307///
308/// * `pid` - Process ID to search in
309/// * `library` - Name of the library containing the symbol
310/// * `symbol` - Symbol name to find
311///
312/// # Returns
313///
314/// `Ok(*mut c_void)` containing the symbol address.
315/// `Err(SubstrateError)` if the symbol or library cannot be found.
316///
317/// # Examples
318///
319/// ```no_run
320/// use substrate::find_symbol_in_process;
321///
322/// let addr = find_symbol_in_process(
323///     std::process::id() as i32,
324///     "libil2cpp.so",
325///     "il2cpp_init"
326/// ).expect("Symbol not found");
327/// ```
328pub fn find_symbol_in_process(
329    pid: libc::pid_t,
330    library: &str,
331    symbol: &str,
332) -> Result<*mut c_void> {
333    let addr = symbol::finder::find_symbol_address(pid, symbol, library)?;
334    Ok(addr as *mut c_void)
335}