Specter can be used directly as a Rust crate without going through the C FFI layer.
[dependencies]
specter-mem = "1.0.2"Platform note: The crate builds for both iOS (
aarch64-apple-ios) and macOS (aarch64-apple-darwin). Usemake iosormake macosto build a single target.Note: The crate type is
staticlibby default. To use it as a Rust dependency you may need to add"lib"tocrate-typein the downstreamCargo.toml, or consume the modules directly in-tree.
Before using RVA-based APIs, set the target image name:
use specter::config;
config::set_target_image_name("MyApp");use specter::memory::manipulation::hook;
unsafe {
let h = hook::install(0x1234, replacement_fn as usize)?;
// Call the original function through the trampoline
let result: i32 = h.call_original(|orig: extern "C" fn(i32) -> i32| orig(42));
// Check integrity
assert!(h.verify_integrity());
// Remove the hook (restores original bytes)
h.remove();
}use specter::memory::manipulation::hook;
unsafe {
let h = hook::hook_symbol("_objc_msgSend", replacement_fn as usize)?;
// use h.call_original(...) to invoke the original
}use specter::memory::manipulation::hook;
unsafe {
let trampoline = hook::install_at_address(0x100004000, replacement_fn as usize)?;
}Installs the trampoline in a NOP code cave instead of a new allocation, making it harder to detect:
use specter::memory::manipulation::hook;
unsafe {
let h = hook::install_in_cave(0x1234, replacement_fn as usize)?;
}use specter::memory::manipulation::hook;
let count = hook::hook_count();
let targets = hook::list_hooks();
let active = hook::is_hooked(0x100004000);use specter::memory::manipulation::patch;
let p = patch::apply(0x1234, "1F 20 03 D5")?; // NOP
// Revert to original bytes
p.revert();Uses the jit-assembler crate to build ARM64 instructions inline:
use specter::memory::manipulation::patch;
let p = patch::apply_asm(0x1234, |asm| {
asm.nop().ret()
})?;Write patch payload into a nearby code cave and branch to it:
use specter::memory::manipulation::patch;
// Hex in cave
let p = patch::apply_in_cave(0x1234, "1F 20 03 D5 C0 03 5F D6")?;
// Assembly in cave
let p = patch::apply_asm_in_cave(0x1234, |asm| {
asm.nop().ret()
})?;use specter::memory::manipulation::patch;
let p = patch::apply_at_address(0x100004000, &[0x1F, 0x20, 0x03, 0xD5])?;use specter::memory::manipulation::rw;
unsafe {
let value: u32 = rw::read::<u32>(0x100004000)?;
rw::write::<u32>(0x100004000, 0xDEADBEEF)?;
}use specter::memory::manipulation::rw;
unsafe {
let value: u64 = rw::read_at_rva::<u64>(0x1234)?;
rw::write_at_rva::<u32>(0x1234, 0)?;
}use specter::memory::manipulation::rw;
unsafe {
// Follows: *(*(base + 0x10) + 0x20) + 0x30
let addr = rw::read_pointer_chain(base, &[0x10, 0x20, 0x30])?;
}Writes through mach_vm_remap (stealth) and invalidates the instruction cache:
use specter::memory::manipulation::rw;
unsafe {
rw::write_code::<u32>(0x100004000, 0xD503201F)?; // NOP
rw::write_bytes(0x100004000, &[0x1F, 0x20, 0x03, 0xD5])?;
}use specter::memory::info::symbol;
let addr = symbol::resolve_symbol("_objc_msgSend")?;
// Manually cache a known address
symbol::cache_symbol("_my_func", 0x100004000);
// Clear the cache
symbol::clear_cache();use specter::memory::info::image;
let base = image::get_image_base("MyApp")?;use specter::memory::info::scan;
// Scan a range
let results = scan::scan_ida_pattern(start, size, "DE AD ?? EF")?;
// Scan an entire image
let results = scan::scan_image("MyApp", "48 8B ?? ?? 48 89")?;
// Cached scan (subsequent calls return cached results)
let results = scan::scan_pattern_cached(start, size, "DE AD ?? EF")?;use specter::memory::info::scan;
let pattern = vec![0xDE, 0xAD, 0x00, 0xEF];
let mask = "xx?x";
let results = scan::scan_pattern(start, size, &pattern, mask)?;Limited to 6 concurrent breakpoints. Does not modify code — uses ARM64 debug registers and Mach exception handling.
use specter::memory::platform::breakpoint;
unsafe {
let bp = breakpoint::install(0x1234, replacement_fn as usize)?;
// Call the original by temporarily disabling the breakpoint
let result: i32 = bp.call_original(|orig: extern "C" fn(i32) -> i32| orig(42));
bp.remove()?;
}use specter::memory::platform::breakpoint;
let active = breakpoint::active_count();
let max = breakpoint::max_breakpoints(); // typically 6use specter::memory::allocation::shellcode;
let code: &[u8] = &[0xC0, 0x03, 0x5F, 0xD6]; // RET
let loaded = shellcode::load(code)?;
unsafe {
let result = loaded.execute();
}
// Memory is freed on dropuse specter::memory::allocation::shellcode::ShellcodeBuilder;
let loaded = ShellcodeBuilder::new(&raw_bytes)
.with_symbol(0x20, "_printf") // resolve and write _printf address at offset 0x20
.with_symbol(0x28, "_objc_msgSend") // same for _objc_msgSend at offset 0x28
.near_address(0x100004000) // allocate near this address
.no_auto_free() // persist after drop
.load()?;
unsafe {
// Execute with a custom signature
loaded.execute_as(|f: extern "C" fn(i32, i32) -> i32| f(1, 2));
// Or get a raw function pointer
let f: extern "C" fn() -> usize = loaded.as_function();
}
// Manual cleanup (since auto_free is off)
loaded.free();use specter::memory::allocation::shellcode::ShellcodeBuilder;
let instructions: &[u32] = &[
0xD2800000, // MOV X0, #0
0xD65F03C0, // RET
];
let loaded = ShellcodeBuilder::from_instructions(instructions).load()?;