Expand description
§Substrate - Powerful Code Injection Platform
§Note
Version 0.1.5 is the official stable release; earlier versions are not stable.
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.5"Or for C/C++ projects, download the prebuilt library from releases.
§Quick Start
§Rust Usage
use core::ffi::c_void;
use substrate::{MSHookFunction, utils::getAbsoluteAddress};
static mut LEVEL_BONUS: bool = false;
static mut ORIG_RACE_UPDATE: *mut c_void = core::ptr::null_mut();
unsafe extern "C" fn hook_RaceUpdate(raceManager: *mut c_void, deltaTime: f32) {
if LEVEL_BONUS && !raceManager.is_null() {
*((raceManager as *mut u8).add(0x4EC) as *mut f32) = 999.0;
}
if !ORIG_RACE_UPDATE.is_null() {
let orig: extern "C" fn(*mut c_void, f32) = core::mem::transmute(ORIG_RACE_UPDATE);
orig(raceManager, deltaTime);
}
}
fn main() {
unsafe {
let target = getAbsoluteAddress(b"libil2cpp.so\0".as_ptr() as *const i8, 0x19ADD4);
MSHookFunction(
target as *mut c_void,
hook_RaceUpdate as *mut c_void,
core::ptr::addr_of_mut!(ORIG_RACE_UPDATE),
);
}
}§C/C++ Usage
#include <substrate.h>
#include <stdint.h>
#include <stdbool.h>
bool levelbonus = false;
void (*orig_RaceUpdate)(void* raceManager, float deltaTime);
void hook_RaceUpdate(void* raceManager, float deltaTime) {
if (levelbonus && raceManager != NULL) {
*(float*)((uintptr_t)raceManager + 0x4EC) = 999.0f;
}
orig_RaceUpdate(raceManager, deltaTime);
}
void (*old_update)(void *instance);
void* (*GetCurrentCH)(void*);
void update(void *instance) {
if (instance != NULL) {
bool isBlabla = true;
if (isBlabla) {
void* getClass = GetCurrentCH(instance);
if (getClass != NULL) {
*(bool*)((uintptr_t)getClass + 0xB4) = true;
}
}
}
old_update(instance);
}
__attribute__((constructor))
static void init_hooks() {
GetCurrentCH = (void* (*)(void*))getAbsoluteAddress("libil2cpp.so", 0xC10738);
MSHookFunction(
(void*)getAbsoluteAddress("libil2cpp.so", 0x19ADD4),
(void*)hook_RaceUpdate,
(void**)&orig_RaceUpdate
);
MSHookFunction(
(void*)getAbsoluteAddress("libil2cpp.so", 0xC1123C),
(void*)update,
(void**)&old_update
);
}§Complete Examples
§Example 1: Hooking with Symbol Name
use core::ffi::c_void;
use substrate::{MSHookFunction, utils::getAbsoluteAddress};
static mut ORIG_MALLOC: *mut c_void = core::ptr::null_mut();
unsafe extern "C" fn hook_malloc(size: usize) -> *mut c_void {
if !ORIG_MALLOC.is_null() {
let orig: extern "C" fn(usize) -> *mut c_void = core::mem::transmute(ORIG_MALLOC);
return orig(size);
}
core::ptr::null_mut()
}
fn main() {
unsafe {
let malloc_addr = libc::dlsym(libc::RTLD_DEFAULT, b"malloc\0".as_ptr() as *const i8);
if malloc_addr.is_null() {
return;
}
MSHookFunction(
malloc_addr as *mut c_void,
hook_malloc as *mut c_void,
core::ptr::addr_of_mut!(ORIG_MALLOC),
);
}
}§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