Skip to main content

specter/
ffi.rs

1//! # C FFI Layer
2//!
3//! Exposes the memory manipulation library to C/C++ callers via a stable ABI.
4//! All functions follow these conventions:
5//!
6//! - Return `MEM_OK` (0) on success, or a negative `MEM_ERR_*` code on failure.
7//! - Output parameters are written only when the function succeeds, unless otherwise noted.
8//! - Pointer out-parameters that are documented as "optional" may be null; the function
9//!   silently skips writing them in that case.
10//! - Pointer out-parameters that are documented as "required" return `MEM_ERR_NULL` when null.
11//! - All string parameters are expected to be valid, null-terminated UTF-8 C strings.
12
13use once_cell::sync::Lazy;
14use parking_lot::Mutex;
15use std::collections::HashMap;
16use std::ffi::{CStr, c_char};
17use std::sync::atomic::{AtomicU64, Ordering};
18
19// Error code constants
20pub const MEM_OK: i32 = 0;
21pub const MEM_ERR_GENERIC: i32 = -1;
22pub const MEM_ERR_NULL: i32 = -2;
23pub const MEM_ERR_NOT_FOUND: i32 = -3;
24pub const MEM_ERR_EXISTS: i32 = -4;
25pub const MEM_ERR_ALLOC: i32 = -5;
26pub const MEM_ERR_PROTECT: i32 = -6;
27pub const MEM_ERR_PATCH: i32 = -7;
28pub const MEM_ERR_RELOC: i32 = -8;
29pub const MEM_ERR_THREAD: i32 = -9;
30pub const MEM_ERR_SYMBOL: i32 = -10;
31pub const MEM_ERR_RANGE: i32 = -11;
32pub const MEM_ERR_EMPTY: i32 = -12;
33pub const MEM_ERR_HW_LIMIT: i32 = -13;
34
35// Private error-mapping helpers
36
37fn hook_err(e: &crate::memory::manipulation::hook::HookError) -> i32 {
38    use crate::memory::manipulation::hook::HookError;
39    match e {
40        HookError::AlreadyExists(_) => MEM_ERR_EXISTS,
41        HookError::ImageBaseNotFound(_) => MEM_ERR_NOT_FOUND,
42        HookError::AllocationFailed => MEM_ERR_ALLOC,
43        HookError::ProtectionFailed(_) => MEM_ERR_PROTECT,
44        HookError::PatchFailed => MEM_ERR_PATCH,
45        HookError::RelocationFailed => MEM_ERR_RELOC,
46        HookError::ThreadError(_) => MEM_ERR_THREAD,
47        HookError::SymbolError(_) => MEM_ERR_SYMBOL,
48    }
49}
50
51fn patch_err(e: &crate::memory::manipulation::patch::PatchError) -> i32 {
52    use crate::memory::manipulation::patch::PatchError;
53    match e {
54        PatchError::InvalidHex(_) => MEM_ERR_GENERIC,
55        PatchError::ImageBaseNotFound(_) => MEM_ERR_NOT_FOUND,
56        PatchError::ProtectionFailed(_) => MEM_ERR_PROTECT,
57        PatchError::ThreadError(_) => MEM_ERR_THREAD,
58        PatchError::EmptyInstructions => MEM_ERR_EMPTY,
59        PatchError::CaveError(_) => MEM_ERR_ALLOC,
60        PatchError::BranchOutOfRange => MEM_ERR_RANGE,
61        PatchError::VerificationFailed => MEM_ERR_PATCH,
62    }
63}
64
65fn rw_err(e: &crate::memory::manipulation::rw::RwError) -> i32 {
66    use crate::memory::manipulation::rw::RwError;
67    match e {
68        RwError::NullPointer => MEM_ERR_NULL,
69        RwError::ImageBaseNotFound(_) => MEM_ERR_NOT_FOUND,
70        RwError::ProtectionFailed(_) => MEM_ERR_PROTECT,
71        RwError::ThreadError(_) => MEM_ERR_THREAD,
72    }
73}
74
75#[cfg(target_os = "ios")]
76fn brk_err(e: &crate::memory::platform::breakpoint::BrkHookError) -> i32 {
77    use crate::memory::platform::breakpoint::BrkHookError;
78    match e {
79        BrkHookError::TooManyHooks => MEM_ERR_HW_LIMIT,
80        BrkHookError::AlreadyExists(_) => MEM_ERR_EXISTS,
81        BrkHookError::ExceedsHwBreakpoints(_) => MEM_ERR_HW_LIMIT,
82        BrkHookError::SetStateFailed => MEM_ERR_PROTECT,
83        BrkHookError::NotFound(_) => MEM_ERR_NOT_FOUND,
84        BrkHookError::InitFailed => MEM_ERR_GENERIC,
85    }
86}
87
88fn loader_err(e: &crate::memory::allocation::shellcode::LoaderError) -> i32 {
89    use crate::memory::allocation::shellcode::LoaderError;
90    match e {
91        LoaderError::AllocationFailed(_) => MEM_ERR_ALLOC,
92        LoaderError::SymbolNotFound(_) => MEM_ERR_SYMBOL,
93        LoaderError::WriteFailed(_) => MEM_ERR_PATCH,
94        LoaderError::InvalidSize(_) => MEM_ERR_EMPTY,
95        LoaderError::RelocationFailed(_) => MEM_ERR_RELOC,
96    }
97}
98
99// Handle slabs (global registries)
100
101static HOOK_REGISTRY: Lazy<Mutex<HashMap<u64, crate::memory::manipulation::hook::Hook>>> =
102    Lazy::new(|| Mutex::new(HashMap::new()));
103
104static PATCH_REGISTRY: Lazy<Mutex<HashMap<usize, crate::memory::manipulation::patch::Patch>>> =
105    Lazy::new(|| Mutex::new(HashMap::new()));
106
107#[cfg(target_os = "ios")]
108static BRK_REGISTRY: Lazy<Mutex<HashMap<u64, crate::memory::platform::breakpoint::Breakpoint>>> =
109    Lazy::new(|| Mutex::new(HashMap::new()));
110
111static SHELLCODE_REGISTRY: Lazy<
112    Mutex<HashMap<usize, crate::memory::allocation::shellcode::LoadedShellcode>>,
113> = Lazy::new(|| Mutex::new(HashMap::new()));
114
115static HANDLE_COUNTER: AtomicU64 = AtomicU64::new(1);
116
117fn next_handle() -> u64 {
118    HANDLE_COUNTER.fetch_add(1, Ordering::Relaxed)
119}
120
121// Helpers for safe C-string conversion
122
123/// Converts a raw C string pointer to a `&str`.
124/// Returns `Err(MEM_ERR_NULL)` if the pointer is null, or `Err(MEM_ERR_GENERIC)` if it is not
125/// valid UTF-8.
126unsafe fn cstr_to_str<'a>(ptr: *const c_char) -> Result<&'a str, i32> {
127    if ptr.is_null() {
128        return Err(MEM_ERR_NULL);
129    }
130    unsafe { CStr::from_ptr(ptr).to_str().map_err(|_| MEM_ERR_GENERIC) }
131}
132
133// Init API
134
135/// Initializes the library by storing the target image name for all subsequent RVA-based
136/// operations (hooks, patches, reads/writes relative to image base).
137///
138/// Must be called before any function that takes an RVA (relative virtual address).
139///
140/// `base_out` is optional: when non-null, receives the resolved load address of the image
141/// (ASLR slide applied).  Returns `MEM_ERR_NOT_FOUND` if the image is not currently loaded.
142#[unsafe(no_mangle)]
143pub unsafe extern "C" fn mem_init(image_name: *const c_char, base_out: *mut usize) -> i32 {
144    let name = unsafe {
145        match cstr_to_str(image_name) {
146            Ok(s) => s,
147            Err(e) => return e,
148        }
149    };
150    crate::config::set_target_image_name(name);
151    if !base_out.is_null() {
152        match crate::memory::info::image::get_image_base(name) {
153            Ok(base) => unsafe { *base_out = base },
154            Err(_) => return MEM_ERR_NOT_FOUND,
155        }
156    }
157    MEM_OK
158}
159
160// Hook API
161
162/// Installs an inline hook at `rva` (relative to the target image base).
163///
164/// `trampoline_out` receives the trampoline address; it may be null if the caller
165/// does not need it.  `handle_out` is required (must be non-null) because the
166/// returned handle is the only way to remove the hook via `mem_hook_remove`.
167#[unsafe(no_mangle)]
168pub unsafe extern "C" fn mem_hook_install(
169    rva: usize,
170    replacement: usize,
171    trampoline_out: *mut usize,
172    handle_out: *mut u64,
173) -> i32 {
174    if handle_out.is_null() {
175        return MEM_ERR_NULL;
176    }
177    let hook = unsafe {
178        match crate::memory::manipulation::hook::install(rva, replacement) {
179            Ok(h) => h,
180            Err(ref e) => return hook_err(e),
181        }
182    };
183    let trampoline = hook.trampoline();
184    let handle = next_handle();
185    HOOK_REGISTRY.lock().insert(handle, hook);
186    if !trampoline_out.is_null() {
187        unsafe { *trampoline_out = trampoline };
188    }
189    unsafe { *handle_out = handle };
190    MEM_OK
191}
192
193/// Installs an inline hook on the symbol named by `symbol_name`.
194///
195/// `trampoline_out` is optional; `handle_out` is required.
196#[unsafe(no_mangle)]
197pub unsafe extern "C" fn mem_hook_symbol(
198    symbol_name: *const c_char,
199    replacement: usize,
200    trampoline_out: *mut usize,
201    handle_out: *mut u64,
202) -> i32 {
203    if handle_out.is_null() {
204        return MEM_ERR_NULL;
205    }
206    let name = unsafe {
207        match cstr_to_str(symbol_name) {
208            Ok(s) => s,
209            Err(e) => return e,
210        }
211    };
212    let hook = unsafe {
213        match crate::memory::manipulation::hook::hook_symbol(name, replacement) {
214            Ok(h) => h,
215            Err(ref e) => return hook_err(e),
216        }
217    };
218    let trampoline = hook.trampoline();
219    let handle = next_handle();
220    HOOK_REGISTRY.lock().insert(handle, hook);
221    if !trampoline_out.is_null() {
222        unsafe { *trampoline_out = trampoline };
223    }
224    unsafe { *handle_out = handle };
225    MEM_OK
226}
227
228/// Installs an inline hook at an absolute address.
229///
230/// No registry entry is created; the hook is removed via `mem_hook_remove_at`.
231/// `trampoline_out` receives the trampoline address and is optional.
232#[unsafe(no_mangle)]
233pub unsafe extern "C" fn mem_hook_install_at(
234    target: usize,
235    replacement: usize,
236    trampoline_out: *mut usize,
237) -> i32 {
238    let trampoline = unsafe {
239        match crate::memory::manipulation::hook::install_at_address(target, replacement) {
240            Ok(t) => t,
241            Err(ref e) => return hook_err(e),
242        }
243    };
244    if !trampoline_out.is_null() {
245        unsafe { *trampoline_out = trampoline };
246    }
247    MEM_OK
248}
249
250/// Installs a code-cave hook at `rva` (relative to the target image base).
251///
252/// `trampoline_out` is optional; `handle_out` is required.
253#[unsafe(no_mangle)]
254pub unsafe extern "C" fn mem_hook_install_cave(
255    rva: usize,
256    replacement: usize,
257    trampoline_out: *mut usize,
258    handle_out: *mut u64,
259) -> i32 {
260    if handle_out.is_null() {
261        return MEM_ERR_NULL;
262    }
263    let hook = unsafe {
264        match crate::memory::manipulation::hook::install_in_cave(rva, replacement) {
265            Ok(h) => h,
266            Err(ref e) => return hook_err(e),
267        }
268    };
269    let trampoline = hook.trampoline();
270    let handle = next_handle();
271    HOOK_REGISTRY.lock().insert(handle, hook);
272    if !trampoline_out.is_null() {
273        unsafe { *trampoline_out = trampoline };
274    }
275    unsafe { *handle_out = handle };
276    MEM_OK
277}
278
279/// Installs a code-cave hook at an absolute address.
280///
281/// `trampoline_out` is optional; `handle_out` is required.
282#[unsafe(no_mangle)]
283pub unsafe extern "C" fn mem_hook_install_cave_at(
284    target: usize,
285    replacement: usize,
286    trampoline_out: *mut usize,
287    handle_out: *mut u64,
288) -> i32 {
289    if handle_out.is_null() {
290        return MEM_ERR_NULL;
291    }
292    let hook = unsafe {
293        match crate::memory::manipulation::hook::install_in_cave_at_address(target, replacement) {
294            Ok(h) => h,
295            Err(ref e) => return hook_err(e),
296        }
297    };
298    let trampoline = hook.trampoline();
299    let handle = next_handle();
300    HOOK_REGISTRY.lock().insert(handle, hook);
301    if !trampoline_out.is_null() {
302        unsafe { *trampoline_out = trampoline };
303    }
304    unsafe { *handle_out = handle };
305    MEM_OK
306}
307
308/// Removes a hook by its handle (installed via any `mem_hook_install*` function that
309/// produces a handle).
310#[unsafe(no_mangle)]
311pub unsafe extern "C" fn mem_hook_remove(handle: u64) -> i32 {
312    let hook = match HOOK_REGISTRY.lock().remove(&handle) {
313        Some(h) => h,
314        None => return MEM_ERR_NOT_FOUND,
315    };
316    hook.remove();
317    MEM_OK
318}
319
320/// Removes a hook that was installed at an absolute address via `mem_hook_install_at`.
321#[unsafe(no_mangle)]
322pub unsafe extern "C" fn mem_hook_remove_at(target: usize) -> i32 {
323    let removed = unsafe { crate::memory::manipulation::hook::remove_at_address(target) };
324    if removed { MEM_OK } else { MEM_ERR_NOT_FOUND }
325}
326
327/// Returns the number of hooks currently managed by the internal registry inside
328/// the hook module.  Note: hooks installed via `mem_hook_install_at` (no-handle
329/// variant) are still counted by the underlying registry.
330#[unsafe(no_mangle)]
331pub unsafe extern "C" fn mem_hook_count() -> usize {
332    crate::memory::manipulation::hook::hook_count()
333}
334
335/// Returns 1 if there is an active hook at `target`, 0 otherwise.
336#[unsafe(no_mangle)]
337pub unsafe extern "C" fn mem_hook_is_hooked(target: usize) -> i32 {
338    if crate::memory::manipulation::hook::is_hooked(target) {
339        1
340    } else {
341        0
342    }
343}
344
345/// Fills `buf` with up to `cap` hook target addresses from the internal hook registry.
346/// Writes the total number of active hooks to `*count_out` (which may be larger than
347/// `cap` if the buffer was too small).  `count_out` must be non-null.
348#[unsafe(no_mangle)]
349pub unsafe extern "C" fn mem_hook_list(buf: *mut usize, cap: usize, count_out: *mut usize) -> i32 {
350    if count_out.is_null() {
351        return MEM_ERR_NULL;
352    }
353    let hooks = crate::memory::manipulation::hook::list_hooks();
354    let total = hooks.len();
355    unsafe { *count_out = total };
356    if !buf.is_null() {
357        let to_copy = total.min(cap);
358        for (i, &addr) in hooks.iter().take(to_copy).enumerate() {
359            unsafe { *buf.add(i) = addr };
360        }
361    }
362    MEM_OK
363}
364
365// Patch API
366
367/// Applies a hex-string patch at `rva` (relative to the target image base).
368///
369/// Stores the resulting `Patch` in the registry keyed by its address.
370/// `address_out` receives the absolute address of the patch and is optional.
371#[unsafe(no_mangle)]
372pub unsafe extern "C" fn mem_patch_apply(
373    rva: usize,
374    hex_str: *const c_char,
375    address_out: *mut usize,
376) -> i32 {
377    let hex = unsafe {
378        match cstr_to_str(hex_str) {
379            Ok(s) => s,
380            Err(e) => return e,
381        }
382    };
383    let patch = match crate::memory::manipulation::patch::apply(rva, hex) {
384        Ok(p) => p,
385        Err(ref e) => return patch_err(e),
386    };
387    let addr = patch.address();
388    if !address_out.is_null() {
389        unsafe { *address_out = addr };
390    }
391    PATCH_REGISTRY.lock().insert(addr, patch);
392    MEM_OK
393}
394
395/// Applies a raw-bytes patch at an absolute address.
396///
397/// `data` and `len` form a byte slice.  `address_out` is optional.
398#[unsafe(no_mangle)]
399pub unsafe extern "C" fn mem_patch_apply_at(
400    address: usize,
401    data: *const u8,
402    len: usize,
403    address_out: *mut usize,
404) -> i32 {
405    if data.is_null() {
406        return MEM_ERR_NULL;
407    }
408    if len == 0 {
409        return MEM_ERR_EMPTY;
410    }
411    let slice = unsafe { std::slice::from_raw_parts(data, len) };
412    let patch = match crate::memory::manipulation::patch::apply_at_address(address, slice) {
413        Ok(p) => p,
414        Err(ref e) => return patch_err(e),
415    };
416    let addr = patch.address();
417    if !address_out.is_null() {
418        unsafe { *address_out = addr };
419    }
420    PATCH_REGISTRY.lock().insert(addr, patch);
421    MEM_OK
422}
423
424/// Applies a hex-string patch via a code cave at `rva`.
425///
426/// `address_out` is optional.
427#[unsafe(no_mangle)]
428pub unsafe extern "C" fn mem_patch_apply_cave(
429    rva: usize,
430    hex_str: *const c_char,
431    address_out: *mut usize,
432) -> i32 {
433    let hex = unsafe {
434        match cstr_to_str(hex_str) {
435            Ok(s) => s,
436            Err(e) => return e,
437        }
438    };
439    let patch = match crate::memory::manipulation::patch::apply_in_cave(rva, hex) {
440        Ok(p) => p,
441        Err(ref e) => return patch_err(e),
442    };
443    let addr = patch.address();
444    if !address_out.is_null() {
445        unsafe { *address_out = addr };
446    }
447    PATCH_REGISTRY.lock().insert(addr, patch);
448    MEM_OK
449}
450
451/// Reverts the patch that was applied at `address`, restoring the original bytes.
452/// The patch is removed from the registry.
453#[unsafe(no_mangle)]
454pub unsafe extern "C" fn mem_patch_revert(address: usize) -> i32 {
455    let patch = match PATCH_REGISTRY.lock().remove(&address) {
456        Some(p) => p,
457        None => return MEM_ERR_NOT_FOUND,
458    };
459    patch.revert();
460    MEM_OK
461}
462
463// Read / Write API
464
465/// Reads `size` raw bytes from `address` into `out`.
466///
467/// Works for any scalar type — pass `sizeof(T)` as size.
468/// In C++ prefer the typed template wrapper `mem_read<T>(address, &value)`.
469#[unsafe(no_mangle)]
470pub unsafe extern "C" fn mem_read(address: usize, out: *mut u8, size: usize) -> i32 {
471    if out.is_null() {
472        return MEM_ERR_NULL;
473    }
474    if address == 0 {
475        return MEM_ERR_NULL;
476    }
477    if size == 0 {
478        return MEM_OK;
479    }
480    unsafe { std::ptr::copy_nonoverlapping(address as *const u8, out, size) };
481    MEM_OK
482}
483
484/// Reads `size` raw bytes from `rva` (relative to the target image base) into `out`.
485#[unsafe(no_mangle)]
486pub unsafe extern "C" fn mem_read_rva(rva: usize, out: *mut u8, size: usize) -> i32 {
487    if out.is_null() {
488        return MEM_ERR_NULL;
489    }
490    if size == 0 {
491        return MEM_OK;
492    }
493    let image_name = match crate::config::get_target_image_name() {
494        Some(n) => n,
495        None => return MEM_ERR_NOT_FOUND,
496    };
497    let base = match crate::memory::info::image::get_image_base(&image_name) {
498        Ok(b) => b,
499        Err(_) => return MEM_ERR_NOT_FOUND,
500    };
501    unsafe { std::ptr::copy_nonoverlapping((base + rva) as *const u8, out, size) };
502    MEM_OK
503}
504
505/// Follows a pointer chain starting at `base` through `offset_count` offsets.
506/// `result_out` must be non-null.
507#[unsafe(no_mangle)]
508pub unsafe extern "C" fn mem_read_pointer_chain(
509    base: usize,
510    offsets: *const usize,
511    offset_count: usize,
512    result_out: *mut usize,
513) -> i32 {
514    if result_out.is_null() {
515        return MEM_ERR_NULL;
516    }
517    if offsets.is_null() && offset_count > 0 {
518        return MEM_ERR_NULL;
519    }
520    let slice = if offset_count == 0 {
521        &[] as &[usize]
522    } else {
523        unsafe { std::slice::from_raw_parts(offsets, offset_count) }
524    };
525    match unsafe { crate::memory::manipulation::rw::read_pointer_chain(base, slice) } {
526        Ok(v) => {
527            unsafe { *result_out = v };
528            MEM_OK
529        }
530        Err(ref e) => rw_err(e),
531    }
532}
533
534/// Writes `size` raw bytes from `value` to `address` (data memory, direct write).
535///
536/// For patching executable code use `mem_write_bytes` (stealth mach_vm_remap path).
537/// In C++ prefer the typed template wrapper `mem_write<T>(address, value)`.
538#[unsafe(no_mangle)]
539pub unsafe extern "C" fn mem_write(address: usize, value: *const u8, size: usize) -> i32 {
540    if value.is_null() {
541        return MEM_ERR_NULL;
542    }
543    if address == 0 {
544        return MEM_ERR_NULL;
545    }
546    if size == 0 {
547        return MEM_OK;
548    }
549    unsafe { std::ptr::copy_nonoverlapping(value, address as *mut u8, size) };
550    MEM_OK
551}
552
553/// Writes `size` raw bytes from `value` to `rva` (relative to the target image base).
554#[unsafe(no_mangle)]
555pub unsafe extern "C" fn mem_write_rva(rva: usize, value: *const u8, size: usize) -> i32 {
556    if value.is_null() {
557        return MEM_ERR_NULL;
558    }
559    if size == 0 {
560        return MEM_OK;
561    }
562    let image_name = match crate::config::get_target_image_name() {
563        Some(n) => n,
564        None => return MEM_ERR_NOT_FOUND,
565    };
566    let base = match crate::memory::info::image::get_image_base(&image_name) {
567        Ok(b) => b,
568        Err(_) => return MEM_ERR_NOT_FOUND,
569    };
570    unsafe { std::ptr::copy_nonoverlapping(value, (base + rva) as *mut u8, size) };
571    MEM_OK
572}
573
574/// Writes `len` bytes from `data` to `address` via the stealth write path
575/// (mach_vm_remap + icache flush). Use this for patching executable code pages.
576#[unsafe(no_mangle)]
577pub unsafe extern "C" fn mem_write_bytes(address: usize, data: *const u8, len: usize) -> i32 {
578    if data.is_null() {
579        return MEM_ERR_NULL;
580    }
581    if len == 0 {
582        return MEM_OK;
583    }
584    let slice = unsafe { std::slice::from_raw_parts(data, len) };
585    match unsafe { crate::memory::manipulation::rw::write_bytes(address, slice) } {
586        Ok(()) => MEM_OK,
587        Err(ref e) => rw_err(e),
588    }
589}
590
591// Image / Symbol API
592
593/// Retrieves the base address of the loaded image named `image_name`.
594/// `base_out` must be non-null.
595#[unsafe(no_mangle)]
596pub unsafe extern "C" fn mem_get_image_base(
597    image_name: *const c_char,
598    base_out: *mut usize,
599) -> i32 {
600    if base_out.is_null() {
601        return MEM_ERR_NULL;
602    }
603    let name = unsafe {
604        match cstr_to_str(image_name) {
605            Ok(s) => s,
606            Err(e) => return e,
607        }
608    };
609    match crate::memory::info::image::get_image_base(name) {
610        Ok(base) => {
611            unsafe { *base_out = base };
612            MEM_OK
613        }
614        Err(_) => MEM_ERR_NOT_FOUND,
615    }
616}
617
618/// Resolves the address of `symbol_name` via `dlsym` (with caching).
619/// `address_out` must be non-null.
620#[unsafe(no_mangle)]
621pub unsafe extern "C" fn mem_resolve_symbol(
622    symbol_name: *const c_char,
623    address_out: *mut usize,
624) -> i32 {
625    if address_out.is_null() {
626        return MEM_ERR_NULL;
627    }
628    let name = unsafe {
629        match cstr_to_str(symbol_name) {
630            Ok(s) => s,
631            Err(e) => return e,
632        }
633    };
634    match crate::memory::info::symbol::resolve_symbol(name) {
635        Ok(addr) => {
636            unsafe { *address_out = addr };
637            MEM_OK
638        }
639        Err(_) => MEM_ERR_SYMBOL,
640    }
641}
642
643/// Manually inserts `(symbol_name, address)` into the symbol cache.
644#[unsafe(no_mangle)]
645pub unsafe extern "C" fn mem_cache_symbol(symbol_name: *const c_char, address: usize) {
646    let name = unsafe {
647        match cstr_to_str(symbol_name) {
648            Ok(s) => s,
649            Err(_) => return,
650        }
651    };
652    crate::memory::info::symbol::cache_symbol(name, address);
653}
654
655/// Clears all entries from the symbol cache.
656#[unsafe(no_mangle)]
657pub unsafe extern "C" fn mem_clear_symbol_cache() {
658    crate::memory::info::symbol::clear_cache();
659}
660
661// Breakpoint API
662
663/// Installs a hardware breakpoint hook at `rva` (relative to the target image base).
664/// `handle_out` must be non-null.
665#[cfg(target_os = "ios")]
666#[unsafe(no_mangle)]
667pub unsafe extern "C" fn mem_brk_install(
668    rva: usize,
669    replacement: usize,
670    handle_out: *mut u64,
671) -> i32 {
672    if handle_out.is_null() {
673        return MEM_ERR_NULL;
674    }
675    let bp = unsafe {
676        match crate::memory::platform::breakpoint::install(rva, replacement) {
677            Ok(b) => b,
678            Err(ref e) => return brk_err(e),
679        }
680    };
681    let handle = next_handle();
682    BRK_REGISTRY.lock().insert(handle, bp);
683    unsafe { *handle_out = handle };
684    MEM_OK
685}
686
687/// Installs a hardware breakpoint hook at an absolute address.
688/// `handle_out` must be non-null.
689#[cfg(target_os = "ios")]
690#[unsafe(no_mangle)]
691pub unsafe extern "C" fn mem_brk_install_at(
692    target: usize,
693    replacement: usize,
694    handle_out: *mut u64,
695) -> i32 {
696    if handle_out.is_null() {
697        return MEM_ERR_NULL;
698    }
699    let bp = unsafe {
700        match crate::memory::platform::breakpoint::install_at_address(target, replacement) {
701            Ok(b) => b,
702            Err(ref e) => return brk_err(e),
703        }
704    };
705    let handle = next_handle();
706    BRK_REGISTRY.lock().insert(handle, bp);
707    unsafe { *handle_out = handle };
708    MEM_OK
709}
710
711/// Removes a breakpoint by handle.
712#[cfg(target_os = "ios")]
713#[unsafe(no_mangle)]
714pub unsafe extern "C" fn mem_brk_remove(handle: u64) -> i32 {
715    let bp = match BRK_REGISTRY.lock().remove(&handle) {
716        Some(b) => b,
717        None => return MEM_ERR_NOT_FOUND,
718    };
719    match bp.remove() {
720        Ok(()) => MEM_OK,
721        Err(ref e) => brk_err(e),
722    }
723}
724
725/// Removes the breakpoint registered for `target` by iterating the registry.
726#[cfg(target_os = "ios")]
727#[unsafe(no_mangle)]
728pub unsafe extern "C" fn mem_brk_remove_at(target: usize) -> i32 {
729    let mut registry = BRK_REGISTRY.lock();
730    // Find the handle whose Breakpoint watches `target`.
731    let handle = match registry.iter().find_map(
732        |(&h, bp)| {
733            if bp.target() == target { Some(h) } else { None }
734        },
735    ) {
736        Some(h) => h,
737        None => return MEM_ERR_NOT_FOUND,
738    };
739    let bp = registry.remove(&handle).expect("handle was just found");
740    drop(registry);
741    match bp.remove() {
742        Ok(()) => MEM_OK,
743        Err(ref e) => brk_err(e),
744    }
745}
746
747/// Returns the number of currently active hardware breakpoints.
748#[cfg(target_os = "ios")]
749#[unsafe(no_mangle)]
750pub unsafe extern "C" fn mem_brk_active_count() -> i32 {
751    crate::memory::platform::breakpoint::active_count() as i32
752}
753
754/// Returns the maximum number of hardware breakpoints supported by this device.
755#[cfg(target_os = "ios")]
756#[unsafe(no_mangle)]
757pub unsafe extern "C" fn mem_brk_max_breakpoints() -> i32 {
758    crate::memory::platform::breakpoint::max_breakpoints()
759}
760
761// Shellcode API
762
763/// Loads shellcode into a code cave and returns its address.
764///
765/// Parameters:
766/// - `code`          : pointer to the raw machine code bytes (required, non-null)
767/// - `code_len`      : number of bytes at `code` (must be > 0)
768/// - `reloc_offsets` : parallel array of byte offsets for symbol relocations
769/// - `reloc_symbols` : parallel array of null-terminated C strings naming the symbols
770/// - `reloc_count`   : length of both relocation arrays (0 = no relocations)
771/// - `near_address`  : hint for cave allocation; pass 0 for no preference
772/// - `auto_free`     : ignored by this implementation — shellcode is always stored in the
773///   registry so `mem_shellcode_free` controls the lifetime
774/// - `address_out`   : receives the address of the loaded shellcode (required, non-null)
775#[unsafe(no_mangle)]
776pub unsafe extern "C" fn mem_shellcode_load(
777    code: *const u8,
778    code_len: usize,
779    reloc_offsets: *const usize,
780    reloc_symbols: *const *const c_char,
781    reloc_count: usize,
782    near_address: usize,
783    _auto_free: i32,
784    address_out: *mut usize,
785) -> i32 {
786    if code.is_null() {
787        return MEM_ERR_NULL;
788    }
789    if code_len == 0 {
790        return MEM_ERR_EMPTY;
791    }
792    if address_out.is_null() {
793        return MEM_ERR_NULL;
794    }
795
796    let code_slice = unsafe { std::slice::from_raw_parts(code, code_len) };
797
798    let mut builder = crate::memory::allocation::shellcode::ShellcodeBuilder::new(code_slice);
799
800    if near_address != 0 {
801        builder = builder.near_address(near_address);
802    }
803
804    if reloc_count > 0 {
805        if reloc_offsets.is_null() || reloc_symbols.is_null() {
806            return MEM_ERR_NULL;
807        }
808        let offsets = unsafe { std::slice::from_raw_parts(reloc_offsets, reloc_count) };
809        let sym_ptrs = unsafe { std::slice::from_raw_parts(reloc_symbols, reloc_count) };
810        for i in 0..reloc_count {
811            let sym_name = unsafe {
812                match cstr_to_str(sym_ptrs[i]) {
813                    Ok(s) => s,
814                    Err(e) => return e,
815                }
816            };
817            builder = builder.with_symbol(offsets[i], sym_name);
818        }
819    }
820
821    // Always manage the shellcode lifetime through the registry — disable
822    // auto-free on the Rust side so Drop does not free the cave while the
823    // value is still live in the registry.
824    builder = builder.no_auto_free();
825
826    let loaded = match builder.load() {
827        Ok(l) => l,
828        Err(ref e) => return loader_err(e),
829    };
830
831    let addr = loaded.address;
832    unsafe { *address_out = addr };
833    SHELLCODE_REGISTRY.lock().insert(addr, loaded);
834    MEM_OK
835}
836
837/// Frees shellcode that was previously loaded with `mem_shellcode_load`.
838/// Removes the entry from the registry (triggering `Drop` / `free_cave`).
839#[unsafe(no_mangle)]
840pub unsafe extern "C" fn mem_shellcode_free(address: usize) -> i32 {
841    match SHELLCODE_REGISTRY.lock().remove(&address) {
842        Some(loaded) => {
843            // Re-enable auto_free so that Drop actually calls free_cave.
844            // We do this by calling `free()` explicitly which does the same thing
845            // regardless of the auto_free flag.
846            loaded.free();
847            MEM_OK
848        }
849        None => MEM_ERR_NOT_FOUND,
850    }
851}