Skip to content

Latest commit

 

History

History
414 lines (284 loc) · 8.69 KB

File metadata and controls

414 lines (284 loc) · 8.69 KB

Rust API

Specter can be used directly as a Rust crate without going through the C FFI layer.

[dependencies]
specter-mem = "1.0.4"

Platform note: The crate builds for both iOS (aarch64-apple-ios) and macOS (aarch64-apple-darwin). Use make ios or make macos to build a single target.

Note: The crate type is staticlib by default. To use it as a Rust dependency you may need to add "lib" to crate-type in the downstream Cargo.toml, or consume the modules directly in-tree.

Initialization

Before using RVA-based APIs, set the target image name:

use specter::config;

config::set_target_image_name("MyApp");

Inline Hooking

Hook by RVA

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();
}

Hook by Symbol

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
}

Hook at Absolute Address

use specter::memory::manipulation::hook;

unsafe {
    let trampoline = hook::install_at_address(0x100004000, replacement_fn as usize)?;
}

Stealth Hook (Code Cave)

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)?;
}

Hook Utilities

use specter::memory::manipulation::hook;

let count = hook::hook_count();
let targets = hook::list_hooks();
let active = hook::is_hooked(0x100004000);

Memory Patching

Hex Patch at RVA

use specter::memory::manipulation::patch;

let p = patch::apply(0x1234, "1F 20 03 D5")?; // NOP

// Revert to original bytes
p.revert();

Assembly Patch at RVA

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()
})?;

Code-Cave Patches

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()
})?;

Patch at Absolute Address

use specter::memory::manipulation::patch;

let p = patch::apply_at_address(0x100004000, &[0x1F, 0x20, 0x03, 0xD5])?;

Memory Read / Write

Typed Read/Write

use specter::memory::manipulation::rw;

unsafe {
    let value: u32 = rw::read::<u32>(0x100004000)?;
    rw::write::<u32>(0x100004000, 0xDEADBEEF)?;
}

RVA-based Read/Write

use specter::memory::manipulation::rw;

unsafe {
    let value: u64 = rw::read_at_rva::<u64>(0x1234)?;
    rw::write_at_rva::<u32>(0x1234, 0)?;
}

Pointer Chain

use specter::memory::manipulation::rw;

unsafe {
    // Follows: *(*(base + 0x10) + 0x20) + 0x30
    let addr = rw::read_pointer_chain(base, &[0x10, 0x20, 0x30])?;
}

Write to Code Memory

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])?;
}

Symbol Resolution

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();

Image Lookup

use specter::memory::info::image;

let base = image::get_image_base("MyApp")?;

Image Enumeration

use specter::memory::info::image;

// List all loaded images
let images = image::get_all_images();
for img in &images {
    println!("[{}] {:#x} {}", img.index, img.base, img.name);
}

// Get count
let count = image::image_count();

// Get name by dyld index
if let Some(name) = image::get_image_name(0) {
    println!("Image 0: {}", name);
}

Pattern Scanning

IDA-Style Pattern Scan

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")?;

Raw Pattern Scan

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)?;

Hardware Breakpoints

Limited to 6 concurrent breakpoints. Does not modify code — uses ARM64 debug registers and Mach exception handling.

Install / Remove

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()?;
}

Breakpoint Info

use specter::memory::platform::breakpoint;

let active = breakpoint::active_count();
let max = breakpoint::max_breakpoints(); // typically 6

Shellcode Loading

Simple Load

use 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 drop

Builder with Symbol Relocations

use 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();

Load from ARM64 Instructions

use specter::memory::allocation::shellcode::ShellcodeBuilder;

let instructions: &[u32] = &[
    0xD2800000, // MOV X0, #0
    0xD65F03C0, // RET
];

let loaded = ShellcodeBuilder::from_instructions(instructions).load()?;

Memory Protection

Query and modify page protection flags.

use specter::memory::info::protection;

// Query protection
let prot = protection::get_protection(0x100004000)?;
println!("readable={} writable={} executable={}",
         prot.is_readable(), prot.is_writable(), prot.is_executable());

// Quick boolean checks
let readable = protection::is_readable(0x100004000);

// Full region info
let info = protection::get_region_info(0x100004000)?;
println!("region: {:#x} size: {:#x}", info.address, info.size);

// Find region containing or after an address
let region = protection::find_region(0x100004000)?;

// Change protection
protection::protect(addr, size, protection::PageProtection::read_write())?;

// Enumerate all readable regions
let regions = protection::get_all_regions()?;

Mach-O Segments & Sections

Query Mach-O segments and sections of loaded images.

use specter::memory::info::macho;

// Get a segment
let text = macho::get_segment("MyApp", "__TEXT")?;
println!("__TEXT: {:#x} - {:#x} ({} bytes)", text.start, text.end, text.size);

// Get a section within a segment
let text_text = macho::get_section("MyApp", "__TEXT", "__text")?;

Patch Introspection

Query details about applied patches.

use specter::memory::manipulation::patch;

// After applying a patch:
let p = patch::apply(0x1234, "1F 20 03 D5")?;

// Query patch info
let size = p.size();
let orig = p.original_bytes();
let patched = p.patch_bytes();
let current = p.current_bytes();

Memory Backup

Standalone backup and restore, independent of the patch system.

use specter::memory::manipulation::backup::MemoryBackup;

// Create a backup
let backup = MemoryBackup::create(0x100004000, 64)?;

// Inspect
let addr = backup.address();
let size = backup.size();
let orig = backup.original_bytes();
let curr = backup.current_bytes();

// Restore original bytes
backup.restore()?;