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}