substrate/
lib.rs

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