use crate::memory::info::{image, scan};
#[cfg(debug_assertions)]
use crate::utils::logger;
use once_cell::sync::Lazy;
use parking_lot::Mutex;
use std::collections::HashMap;
use thiserror::Error;
const ARM64_NOP: u32 = 0x1F2003D5;
#[derive(Error, Debug)]
pub enum CodeCaveError {
#[error("No suitable cave found")]
NoCaveFound,
#[error("Cave already allocated at {0:#x}")]
AlreadyAllocated(usize),
#[error("Cave not found at {0:#x}")]
NotFound(usize),
#[error("Invalid size: {0}")]
InvalidSize(usize),
#[error("Image not found: {0}")]
ImageNotFound(#[from] image::ImageError),
#[error("Scan error: {0}")]
ScanError(#[from] scan::ScanError),
#[error("{0}")]
Custom(String),
}
#[derive(Debug, Clone)]
pub struct CodeCave {
pub address: usize,
pub size: usize,
pub allocated: bool,
pub description: Option<String>,
}
impl CodeCave {
pub fn new(address: usize, size: usize) -> Self {
Self {
address,
size,
allocated: false,
description: None,
}
}
pub fn with_description(address: usize, size: usize, description: String) -> Self {
Self {
address,
size,
allocated: false,
description: Some(description),
}
}
pub fn allocate(&mut self) {
self.allocated = true;
}
pub fn free(&mut self) {
self.allocated = false;
}
}
struct CaveRegistry {
caves: HashMap<usize, CodeCave>,
}
impl CaveRegistry {
fn new() -> Self {
Self {
caves: HashMap::new(),
}
}
fn register(&mut self, cave: CodeCave) -> Result<(), CodeCaveError> {
if self.caves.contains_key(&cave.address) {
return Err(CodeCaveError::AlreadyAllocated(cave.address));
}
self.caves.insert(cave.address, cave);
Ok(())
}
fn unregister(&mut self, address: usize) -> Result<CodeCave, CodeCaveError> {
self.caves
.remove(&address)
.ok_or(CodeCaveError::NotFound(address))
}
fn get(&self, address: usize) -> Option<&CodeCave> {
self.caves.get(&address)
}
fn list_all(&self) -> Vec<CodeCave> {
self.caves.values().cloned().collect()
}
fn clear(&mut self) {
self.caves.clear();
}
}
static REGISTRY: Lazy<Mutex<CaveRegistry>> = Lazy::new(|| Mutex::new(CaveRegistry::new()));
pub fn find_nop_sequences(
start: usize,
size: usize,
min_count: usize,
) -> Result<Vec<CodeCave>, CodeCaveError> {
if min_count == 0 {
return Err(CodeCaveError::InvalidSize(min_count));
}
let min_size = min_count * 4;
let mut caves = Vec::new();
unsafe {
let mut current_addr = start;
let end_addr = start + size;
while current_addr < end_addr {
if let Ok(instr) = crate::memory::rw::read::<u32>(current_addr) {
if instr == ARM64_NOP {
let cave_start = current_addr;
let mut cave_size = 0;
let mut temp_addr = current_addr;
while temp_addr < end_addr {
if let Ok(i) = crate::memory::rw::read::<u32>(temp_addr) {
if i == ARM64_NOP {
cave_size += 4;
temp_addr += 4;
} else {
break;
}
} else {
break;
}
}
if cave_size >= min_size {
caves.push(CodeCave::with_description(
cave_start,
cave_size,
format!("NOP sequence ({} instructions)", cave_size / 4),
));
}
current_addr = temp_addr;
} else {
current_addr += 4;
}
} else {
current_addr += 4;
}
}
}
Ok(caves)
}
pub fn find_alignment_padding(start: usize, size: usize) -> Result<Vec<CodeCave>, CodeCaveError> {
let mut caves = Vec::new();
unsafe {
let mut current_addr = start;
let end_addr = start + size;
while current_addr < end_addr {
if let Ok(byte) = crate::memory::rw::read::<u8>(current_addr) {
if byte == 0x00 {
let cave_start = current_addr;
let mut cave_size = 0;
let mut temp_addr = current_addr;
while temp_addr < end_addr {
if let Ok(b) = crate::memory::rw::read::<u8>(temp_addr) {
if b == 0x00 {
cave_size += 1;
temp_addr += 1;
} else {
break;
}
} else {
break;
}
}
if cave_size >= 16 {
caves.push(CodeCave::with_description(
cave_start,
cave_size,
format!("Padding ({} bytes)", cave_size),
));
}
current_addr = temp_addr;
} else {
current_addr += 1;
}
} else {
current_addr += 1;
}
}
}
Ok(caves)
}
pub fn find_caves(
start: usize,
size: usize,
min_size: usize,
) -> Result<Vec<CodeCave>, CodeCaveError> {
let mut all_caves = Vec::new();
let min_nops = min_size.div_ceil(4);
if let Ok(nop_caves) = find_nop_sequences(start, size, min_nops) {
all_caves.extend(nop_caves);
}
if let Ok(padding_caves) = find_alignment_padding(start, size) {
all_caves.extend(padding_caves.into_iter().filter(|c| c.size >= min_size));
}
all_caves.sort_by_key(|c| c.address);
Ok(all_caves)
}
pub fn find_caves_in_image(
image_name: &str,
min_size: usize,
) -> Result<Vec<CodeCave>, CodeCaveError> {
let base = image::get_image_base(image_name)?;
let scan_size = 32 * 1024 * 1024;
#[cfg(debug_assertions)]
logger::info(&format!(
"Scanning image '{}' at {:#x} for code caves (min size: {} bytes)",
image_name, base, min_size
));
find_caves(base, scan_size, min_size)
}
pub fn allocate_cave(size: usize) -> Result<CodeCave, CodeCaveError> {
if size == 0 {
return Err(CodeCaveError::InvalidSize(size));
}
let image_name = crate::config::get_target_image_name()
.ok_or_else(|| image::ImageError::NotFound("call mem_init first".to_string()))?;
let caves = find_caves_in_image(&image_name, size)?;
for mut cave in caves {
let registry = REGISTRY.lock();
if registry.get(cave.address).is_some() {
continue;
}
drop(registry);
if cave.size >= size {
cave.allocate();
REGISTRY.lock().register(cave.clone())?;
#[cfg(debug_assertions)]
logger::info(&format!(
"Allocated code cave at {:#x} (size: {} bytes)",
cave.address, cave.size
));
return Ok(cave);
}
}
Err(CodeCaveError::NoCaveFound)
}
pub fn allocate_cave_near(target: usize, size: usize) -> Result<CodeCave, CodeCaveError> {
if size == 0 {
return Err(CodeCaveError::InvalidSize(size));
}
const BRANCH_RANGE: usize = 128 * 1024 * 1024;
let image_name = crate::config::get_target_image_name()
.ok_or_else(|| image::ImageError::NotFound("call mem_init first".to_string()))?;
let caves = find_caves_in_image(&image_name, size)?;
for mut cave in caves {
let distance = cave.address.abs_diff(target);
if distance <= BRANCH_RANGE && cave.size >= size {
let registry = REGISTRY.lock();
if registry.get(cave.address).is_some() {
continue;
}
drop(registry);
cave.allocate();
REGISTRY.lock().register(cave.clone())?;
#[cfg(debug_assertions)]
logger::info(&format!(
"Allocated code cave near {:#x} at {:#x} (size: {} bytes)",
target, cave.address, cave.size
));
return Ok(cave);
}
}
Err(CodeCaveError::NoCaveFound)
}
pub fn free_cave(address: usize) -> Result<(), CodeCaveError> {
let mut cave = REGISTRY.lock().unregister(address)?;
cave.free();
#[cfg(debug_assertions)]
logger::info(&format!("Freed code cave at {:#x}", address));
Ok(())
}
pub fn is_cave_available(address: usize, size: usize) -> bool {
let registry = REGISTRY.lock();
if registry.get(address).is_some() {
return false;
}
unsafe {
for offset in (0..size).step_by(4) {
if let Ok(instr) = crate::memory::rw::read::<u32>(address + offset) {
if instr != ARM64_NOP && instr != 0 {
return false;
}
} else {
return false;
}
}
}
true
}
pub fn list_allocated_caves() -> Vec<CodeCave> {
REGISTRY.lock().list_all()
}
pub fn get_cave_stats() -> (usize, usize) {
let registry = REGISTRY.lock();
let caves = registry.list_all();
let total_size: usize = caves.iter().map(|c| c.size).sum();
(caves.len(), total_size)
}
pub fn clear_all_caves() {
REGISTRY.lock().clear();
#[cfg(debug_assertions)]
logger::info("Cleared all allocated code caves");
}