substrate/utils.rs
1use crate::error::{Result, SubstrateError};
2use std::ffi::CStr;
3use std::fs::File;
4use std::io::{BufRead, BufReader};
5use std::os::raw::{c_char, c_void};
6
7/// Find the base address of a loaded library in the current process.
8///
9/// Searches through `/proc/self/maps` to locate the library and returns its base address.
10///
11/// # Arguments
12///
13/// * `library_name` - Name of the library to find (e.g., "libil2cpp.so")
14///
15/// # Returns
16///
17/// `Ok(usize)` containing the base address of the library.
18/// `Err(SubstrateError::LibraryNotFound)` if the library is not loaded.
19///
20/// # Examples
21///
22/// ```no_run
23/// use substrate::utils::find_library;
24///
25/// let base = find_library("libil2cpp.so").expect("Library not found");
26/// println!("libil2cpp.so base: 0x{:x}", base);
27/// ```
28pub fn find_library(library_name: &str) -> Result<usize> {
29 let maps_file = File::open("/proc/self/maps").map_err(|e| {
30 SubstrateError::FileNotFound(format!("Failed to open /proc/self/maps: {}", e))
31 })?;
32
33 let reader = BufReader::new(maps_file);
34
35 for line in reader.lines() {
36 let line = line.map_err(|e| {
37 SubstrateError::ParseError(format!("Failed to read line: {}", e))
38 })?;
39
40 if line.contains(library_name) {
41 let parts: Vec<&str> = line.split_whitespace().collect();
42 if !parts.is_empty() {
43 if let Some(addr_str) = parts[0].split('-').next() {
44 return usize::from_str_radix(addr_str, 16).map_err(|e| {
45 SubstrateError::ParseError(format!("Failed to parse address: {}", e))
46 });
47 }
48 }
49 }
50 }
51
52 Err(SubstrateError::LibraryNotFound(library_name.to_string()))
53}
54
55/// Convert a relative offset to an absolute address by adding the library base address.
56///
57/// This is the most commonly used function for hooking, combining library lookup and offset calculation.
58///
59/// # Arguments
60///
61/// * `library_name` - Name of the library (e.g., "libil2cpp.so")
62/// * `relative_addr` - Offset from the library base (e.g., 0x123456)
63///
64/// # Returns
65///
66/// `Ok(usize)` containing the absolute address.
67/// `Err(SubstrateError)` if the library cannot be found.
68///
69/// # Examples
70///
71/// ```no_run
72/// use substrate::utils::get_absolute_address;
73///
74/// let addr = get_absolute_address("libil2cpp.so", 0x123456)
75/// .expect("Failed to get address");
76/// println!("Absolute address: 0x{:x}", addr);
77/// ```
78pub fn get_absolute_address(library_name: &str, relative_addr: usize) -> Result<usize> {
79 let base = find_library(library_name)?;
80 Ok(base + relative_addr)
81}
82
83/// Check if a library is currently loaded in the process.
84///
85/// # Arguments
86///
87/// * `library_name` - Name of the library to check
88///
89/// # Returns
90///
91/// `true` if the library is loaded, `false` otherwise.
92///
93/// # Examples
94///
95/// ```no_run
96/// use substrate::utils::is_library_loaded;
97///
98/// if is_library_loaded("libil2cpp.so") {
99/// println!("IL2CPP is loaded!");
100/// }
101/// ```
102pub fn is_library_loaded(library_name: &str) -> bool {
103 if let Ok(file) = File::open("/proc/self/maps") {
104 let reader = BufReader::new(file);
105 for line in reader.lines().flatten() {
106 if line.contains(library_name) {
107 return true;
108 }
109 }
110 }
111 false
112}
113
114/// Parse a hexadecimal string to a numeric offset.
115///
116/// Accepts strings with or without "0x" prefix.
117///
118/// # Arguments
119///
120/// * `s` - String containing hexadecimal number (e.g., "0x123456" or "123456")
121///
122/// # Returns
123///
124/// `Ok(usize)` containing the parsed offset.
125/// `Err(SubstrateError::ParseError)` if parsing fails.
126///
127/// # Examples
128///
129/// ```no_run
130/// use substrate::utils::string_to_offset;
131///
132/// let offset = string_to_offset("0x123456").expect("Parse failed");
133/// assert_eq!(offset, 0x123456);
134/// ```
135pub fn string_to_offset(s: &str) -> Result<usize> {
136 let s = s.trim_start_matches("0x").trim_start_matches("0X");
137 usize::from_str_radix(s, 16).map_err(|e| {
138 SubstrateError::ParseError(format!("Failed to parse offset: {}", e))
139 })
140}
141
142/// C FFI: Find the base address of a loaded library.
143///
144/// # Safety
145///
146/// The `library` parameter must be a valid null-terminated C string.
147///
148/// # Returns
149///
150/// Base address of the library, or 0 if not found.
151#[no_mangle]
152pub unsafe extern "C" fn findLibrary(library: *const c_char) -> usize {
153 if library.is_null() {
154 return 0;
155 }
156
157 let library_name = match CStr::from_ptr(library).to_str() {
158 Ok(s) => s,
159 Err(_) => return 0,
160 };
161
162 find_library(library_name).unwrap_or(0)
163}
164
165/// C FFI: Convert a relative offset to an absolute address.
166///
167/// This is the most commonly used function from C/C++ for hooking.
168///
169/// # Safety
170///
171/// The `library` parameter must be a valid null-terminated C string.
172///
173/// # Returns
174///
175/// Absolute address, or 0 if the library cannot be found.
176///
177/// # Examples (C)
178///
179/// ```c
180/// uintptr_t addr = getAbsoluteAddress("libil2cpp.so", 0x123456);
181/// ```
182#[no_mangle]
183pub unsafe extern "C" fn getAbsoluteAddress(library: *const c_char, offset: usize) -> usize {
184 if library.is_null() {
185 return 0;
186 }
187
188 let library_name = match CStr::from_ptr(library).to_str() {
189 Ok(s) => s,
190 Err(_) => return 0,
191 };
192
193 get_absolute_address(library_name, offset).unwrap_or(0)
194}
195
196/// C FFI: Check if a library is loaded in the current process.
197///
198/// # Safety
199///
200/// The `library` parameter must be a valid null-terminated C string.
201///
202/// # Returns
203///
204/// `true` if loaded, `false` otherwise.
205#[no_mangle]
206pub unsafe extern "C" fn isLibraryLoaded(library: *const c_char) -> bool {
207 if library.is_null() {
208 return false;
209 }
210
211 let library_name = match CStr::from_ptr(library).to_str() {
212 Ok(s) => s,
213 Err(_) => return false,
214 };
215
216 is_library_loaded(library_name)
217}
218
219/// C FFI: Parse a hexadecimal string to a numeric offset.
220///
221/// # Safety
222///
223/// The `s` parameter must be a valid null-terminated C string.
224///
225/// # Returns
226///
227/// Parsed offset value, or 0 if parsing fails.
228#[no_mangle]
229pub unsafe extern "C" fn string2Offset(s: *const c_char) -> usize {
230 if s.is_null() {
231 return 0;
232 }
233
234 let string = match CStr::from_ptr(s).to_str() {
235 Ok(s) => s,
236 Err(_) => return 0,
237 };
238
239 string_to_offset(string).unwrap_or(0)
240}
241
242/// C FFI: Convenience function for hooking with automatic architecture detection.
243///
244/// This function automatically selects the appropriate hooking implementation
245/// based on the target architecture.
246///
247/// # Safety
248///
249/// Same safety requirements as `MSHookFunction`.
250#[no_mangle]
251pub unsafe extern "C" fn hook(
252 offset: *mut c_void,
253 ptr: *mut c_void,
254 orig: *mut *mut c_void,
255) {
256 #[cfg(target_arch = "aarch64")]
257 {
258 crate::A64HookFunction(offset, ptr, orig);
259 }
260
261 #[cfg(not(target_arch = "aarch64"))]
262 {
263 crate::MSHookFunction(offset, ptr, orig);
264 }
265}