Crate substrate

Crate substrate 

Source
Expand description

§Substrate - Powerful Code Injection Platform

§Note

Version 0.1.5 is the official stable release; earlier versions are not stable.

Crates.io Documentation License: LGPL v3

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 hook
  • replace: Your hook function address
  • result: 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

Platformx86x86-64ARMv7ARM64Status
LinuxFull
AndroidFull
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:

  1. Disassembles instructions at the target
  2. Relocates PC-relative instructions
  3. Creates a trampoline with original code
  4. Installs jump to hook function
  5. 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§

arch
debug
disasm
error
hook
symbol
utils

Statics§

MSDebug

Functions§

A64HookFunction
ARM64-specific hook function (alias for MSHookFunction).
MSFindSymbol
Find a symbol by name within a loaded image.
MSGetImageByName
Get a reference to a loaded image (library) by filename.
MSHookFunction
Hook a function at runtime by redirecting its execution to a replacement function.
MSHookProcess
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

Type Aliases§

MSImageRef