Expand description
§Substrate - Powerful Code Injection Platform
A complete Rust rewrite of Cydia Substrate and And64InlineHook, providing powerful function hooking capabilities for Android and Linux platforms across multiple architectures.
§Features
-
✅ Multi-Architecture Support
- x86/x86-64 (Intel/AMD 32-bit and 64-bit)
- ARMv7 (32-bit ARM with Thumb/Thumb-2)
- ARM64/AArch64 (64-bit ARM)
-
✅ Complete Function Hooking
- Inline function hooking with automatic trampoline generation
- PC-relative instruction relocation
- Symbol resolution from ELF binaries
- Library base address lookup
-
✅ Dual API
- C-compatible FFI for use in C/C++ projects
- Safe, idiomatic Rust API
- Drop-in replacement for Cydia Substrate
-
✅ Production Ready
- Zero unsafe behavior leaks
- Comprehensive error handling
- Memory-safe by default
- Thoroughly tested across architectures
§Installation
Add this to your Cargo.toml:
[dependencies]
substrate-rs = "0.1.1"Or for C/C++ projects, download the prebuilt library from releases.
§Quick Start
§Rust Usage
use substrate::{hook_function, utils};
// Define your hook function
unsafe extern "C" fn my_hooked_function(arg: i32) -> i32 {
println!("Hooked! arg = {}", arg);
// Call original if needed
arg + 100
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
unsafe {
// Find the library base address
let lib_base = utils::find_library("libexample.so")?;
// Calculate absolute address
let target_addr = lib_base + 0x1234;
// Hook the function
let original = hook_function(
target_addr as *mut u8,
my_hooked_function as *mut u8
)?;
println!("Hook installed! Original at: {:p}", original);
}
Ok(())
}§C/C++ Usage
#include <substrate.h>
// Original function pointer
void (*old_FixedUpdate)(void *instance);
// Hook function
void hooked_FixedUpdate(void *instance) {
// Your code here
printf("FixedUpdate hooked!\n");
// Call original
if (old_FixedUpdate) {
old_FixedUpdate(instance);
}
}
int main() {
// Hook using offset
MSHookFunction(
(void *)getAbsoluteAddress("libil2cpp.so", 0x123456),
(void *)hooked_FixedUpdate,
(void **)&old_FixedUpdate
);
return 0;
}§Complete Examples
§Example 1: Hooking with Symbol Name
use substrate::{hook_function, utils};
unsafe extern "C" fn my_malloc(size: usize) -> *mut u8 {
println!("Allocating {} bytes", size);
// Call original malloc through dlsym or saved pointer
std::ptr::null_mut()
}
fn main() {
unsafe {
// Find malloc in libc
let malloc_addr = libc::dlsym(
libc::RTLD_DEFAULT,
b"malloc\0".as_ptr() as *const i8
);
if !malloc_addr.is_null() {
let original = hook_function(
malloc_addr as *mut u8,
my_malloc as *mut u8
).expect("Failed to hook malloc");
println!("malloc hooked! Original: {:p}", original);
}
}
}§Example 2: Hooking Game Functions (Android/IL2CPP)
use substrate::utils::{get_absolute_address, is_library_loaded};
use substrate::MSHookFunction;
use std::ffi::c_void;
static mut OLD_UPDATE: *mut c_void = std::ptr::null_mut();
unsafe extern "C" fn hooked_update(instance: *mut c_void) {
println!("Game Update() called!");
// Modify game behavior here
// Call original
if !OLD_UPDATE.is_null() {
let original: extern "C" fn(*mut c_void) =
std::mem::transmute(OLD_UPDATE);
original(instance);
}
}
fn main() {
unsafe {
// Wait for library to load
while !is_library_loaded("libil2cpp.so") {
std::thread::sleep(std::time::Duration::from_millis(100));
}
// Hook Update function at offset 0x123456
let update_addr = get_absolute_address("libil2cpp.so", 0x123456)
.expect("Failed to find libil2cpp.so");
MSHookFunction(
update_addr as *mut c_void,
hooked_update as *mut c_void,
&mut OLD_UPDATE
);
println!("Update() hooked successfully!");
}
}§Example 3: Using Helper Macros (C-style)
use substrate::utils::*;
// Convert hex string to offset
fn hook_with_string_offset() {
unsafe {
let offset = string_to_offset("0x123456")
.expect("Invalid offset");
let addr = get_absolute_address("libgame.so", offset)
.expect("Library not found");
println!("Target address: 0x{:x}", addr);
}
}§Example 4: Architecture-Specific Hooking
use substrate;
fn hook_based_on_arch() {
#[cfg(target_arch = "aarch64")]
unsafe {
// ARM64-specific hooking
substrate::A64HookFunction(
target as *mut std::ffi::c_void,
hook as *mut std::ffi::c_void,
&mut original as *mut *mut std::ffi::c_void
);
}
#[cfg(target_arch = "arm")]
unsafe {
// ARMv7-specific hooking
substrate::MSHookFunction(
target as *mut std::ffi::c_void,
hook as *mut std::ffi::c_void,
&mut original as *mut *mut std::ffi::c_void
);
}
}§API Reference
§Core Functions
§MSHookFunction
void MSHookFunction(void *symbol, void *replace, void **result);Hook a function at the given address. Compatible with original Cydia Substrate.
Parameters:
symbol: Target function address to hookreplace: Your hook function addressresult: Pointer to store original function (trampoline), can be NULL
§A64HookFunction
void A64HookFunction(void *symbol, void *replace, void **result);ARM64-specific hook function (alias to MSHookFunction on ARM64).
§Utility Functions
§findLibrary
uintptr_t findLibrary(const char *library);Find the base address of a loaded library.
Example:
uintptr_t base = findLibrary("libil2cpp.so");§getAbsoluteAddress
uintptr_t getAbsoluteAddress(const char *library, uintptr_t offset);Calculate absolute address from library name and offset.
Example:
uintptr_t addr = getAbsoluteAddress("libil2cpp.so", 0x123456);§isLibraryLoaded
bool isLibraryLoaded(const char *library);Check if a library is currently loaded.
Example:
if (isLibraryLoaded("libunity.so")) {
// Library is loaded
}§string2Offset
uintptr_t string2Offset(const char *str);Convert hex string (e.g., “0x123456”) to numeric offset.
Example:
uintptr_t offset = string2Offset("0x123456");§hook
void hook(void *offset, void *ptr, void **orig);Universal hook function that dispatches to the correct implementation.
§Rust API
// Safe wrapper for hooking
pub unsafe fn hook_function<T>(
symbol: *mut T,
replace: *mut T
) -> Result<*mut T>
// Utility functions
pub fn find_library(library_name: &str) -> Result<usize>
pub fn get_absolute_address(library_name: &str, offset: usize) -> Result<usize>
pub fn is_library_loaded(library_name: &str) -> bool
pub fn string_to_offset(s: &str) -> Result<usize>
// Symbol resolution
pub fn find_symbol_in_process(
pid: libc::pid_t,
library: &str,
symbol: &str
) -> Result<*mut c_void>§Platform Support
| Platform | x86 | x86-64 | ARMv7 | ARM64 | Status |
|---|---|---|---|---|---|
| Linux | ✅ | ✅ | ✅ | ✅ | Full |
| Android | ✅ | ✅ | ✅ | ✅ | Full |
| macOS | ❌ | ⚠️ | ❌ | ⚠️ | Untested |
§Building
# Debug build
cargo build
# Release build (optimized)
cargo build --release
# With debug logging
cargo build --release --features debug
# Cross-compile for Android ARM64
cargo build --release --target aarch64-linux-android
# Cross-compile for Android ARMv7
cargo build --release --target armv7-linux-androideabi§Advanced Usage
§Custom Error Handling
use substrate::error::SubstrateError;
match hook_function(target, hook) {
Ok(original) => println!("Success! Original: {:p}", original),
Err(SubstrateError::NullPointer) => eprintln!("Null pointer!"),
Err(SubstrateError::HookFailed(msg)) => eprintln!("Hook failed: {}", msg),
Err(e) => eprintln!("Error: {}", e),
}§Enable Debug Logging
use substrate::{set_debug, is_debug};
// Enable debug output
set_debug(true);
// Check if debug is enabled
if is_debug() {
println!("Debug mode active");
}§Memory Safety
The library uses RAII and safe abstractions internally, but the public API requires unsafe due to the nature of function hooking:
unsafe {
// All hooking must be in unsafe blocks
let orig = hook_function(target, replacement)?;
// Safe to use the trampoline
let original_fn: extern "C" fn() = std::mem::transmute(orig);
original_fn();
}§Technical Details
§Architecture-Specific Implementation
- x86/x86-64: Full instruction decoder (HDE64), handles RIP-relative addressing
- ARMv7: Separate ARM and Thumb mode handlers, PC-relative instruction relocation
- ARM64: Complete instruction fixing for all PC-relative types (B, BL, CBZ, LDR, ADR, ADRP, etc.)
§Trampoline Generation
The library automatically:
- Disassembles instructions at the target
- Relocates PC-relative instructions
- Creates a trampoline with original code
- Installs jump to hook function
- Returns pointer to trampoline (original function)
§Memory Protection
Automatically handles:
- Memory page protection (mprotect)
- Instruction cache clearing
- Alignment requirements
- Permission restoration
§License
This project is licensed under the GNU Lesser General Public License v3.0 (LGPL-3.0).
Original Cydia Substrate: Copyright (C) 2008-2011 Jay Freeman (saurik) And64InlineHook: Copyright (C) 2018 Rprop (MIT License) Rust Implementation: 2024
§Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
§Acknowledgments
- Jay Freeman (saurik) - Original Cydia Substrate
- Rprop - And64InlineHook implementation
- Rodroid Mods
§FAQ
Q: Is this compatible with the original Cydia Substrate? A: Yes! It’s a drop-in replacement with the same C API.
Q: Can I use this for game modding? A: Yes, it’s commonly used for Android game modding (IL2CPP, Unity, Unreal Engine).
Q: Does it work on rooted devices only? A: No, it works on non-rooted devices within your own app’s process.
Q: What about anti-cheat detection? A: This is a hooking library. Detection avoidance is your responsibility.
Q: Performance impact? A: Minimal - only affects hooked functions, optimized trampolines.
§Support
Modules§
Statics§
Functions§
- A64Hook
Function ⚠ - ARM64-specific hook function (alias for MSHookFunction).
- MSFind
Symbol ⚠ - Find a symbol by name within a loaded image.
- MSGet
Image ⚠ByName - Get a reference to a loaded image (library) by filename.
- MSHook
Function ⚠ - Hook a function at runtime by redirecting its execution to a replacement function.
- MSHook
Process ⚠ - Hook into another process by injecting a library.
- find_
symbol_ in_ process - Find a symbol address in a specific process.
- hook_
function ⚠ - Type-safe Rust wrapper for hooking functions.
- is_
debug - set_
debug