specter/memory/info/
code_cave.rs1use crate::memory::info::{image, scan};
4#[cfg(feature = "dev_release")]
5use crate::utils::logger;
6use once_cell::sync::Lazy;
7use parking_lot::Mutex;
8use std::collections::HashMap;
9use thiserror::Error;
10
11const ARM64_NOP: u32 = 0x1F2003D5;
13
14#[derive(Error, Debug)]
15pub enum CodeCaveError {
17 #[error("No suitable cave found")]
19 NoCaveFound,
20 #[error("Cave already allocated at {0:#x}")]
22 AlreadyAllocated(usize),
23 #[error("Cave not found at {0:#x}")]
25 NotFound(usize),
26 #[error("Invalid size: {0}")]
28 InvalidSize(usize),
29 #[error("Image not found: {0}")]
31 ImageNotFound(#[from] image::ImageError),
32 #[error("Scan error: {0}")]
34 ScanError(#[from] scan::ScanError),
35 #[error("{0}")]
37 Custom(String),
38}
39
40#[derive(Debug, Clone)]
42pub struct CodeCave {
43 pub address: usize,
45 pub size: usize,
47 pub allocated: bool,
49 pub description: Option<String>,
51}
52
53impl CodeCave {
54 pub fn new(address: usize, size: usize) -> Self {
60 Self {
61 address,
62 size,
63 allocated: false,
64 description: None,
65 }
66 }
67
68 pub fn with_description(address: usize, size: usize, description: String) -> Self {
75 Self {
76 address,
77 size,
78 allocated: false,
79 description: Some(description),
80 }
81 }
82
83 pub fn allocate(&mut self) {
85 self.allocated = true;
86 }
87
88 pub fn free(&mut self) {
90 self.allocated = false;
91 }
92}
93
94struct CaveRegistry {
96 caves: HashMap<usize, CodeCave>,
97}
98
99impl CaveRegistry {
100 fn new() -> Self {
101 Self {
102 caves: HashMap::new(),
103 }
104 }
105
106 fn register(&mut self, cave: CodeCave) -> Result<(), CodeCaveError> {
107 if self.caves.contains_key(&cave.address) {
108 return Err(CodeCaveError::AlreadyAllocated(cave.address));
109 }
110 self.caves.insert(cave.address, cave);
111 Ok(())
112 }
113
114 fn unregister(&mut self, address: usize) -> Result<CodeCave, CodeCaveError> {
115 self.caves
116 .remove(&address)
117 .ok_or(CodeCaveError::NotFound(address))
118 }
119
120 fn get(&self, address: usize) -> Option<&CodeCave> {
121 self.caves.get(&address)
122 }
123
124 fn list_all(&self) -> Vec<CodeCave> {
125 self.caves.values().cloned().collect()
126 }
127
128 fn clear(&mut self) {
129 self.caves.clear();
130 }
131}
132
133static REGISTRY: Lazy<Mutex<CaveRegistry>> = Lazy::new(|| Mutex::new(CaveRegistry::new()));
134
135pub fn find_nop_sequences(
145 start: usize,
146 size: usize,
147 min_count: usize,
148) -> Result<Vec<CodeCave>, CodeCaveError> {
149 if min_count == 0 {
150 return Err(CodeCaveError::InvalidSize(min_count));
151 }
152
153 let min_size = min_count * 4;
154 let mut caves = Vec::new();
155
156 unsafe {
157 let mut current_addr = start;
158 let end_addr = start + size;
159
160 while current_addr < end_addr {
161 if let Ok(instr) = crate::memory::rw::read::<u32>(current_addr) {
162 if instr == ARM64_NOP {
163 let cave_start = current_addr;
164 let mut cave_size = 0;
165 let mut temp_addr = current_addr;
166
167 while temp_addr < end_addr {
168 if let Ok(i) = crate::memory::rw::read::<u32>(temp_addr) {
169 if i == ARM64_NOP {
170 cave_size += 4;
171 temp_addr += 4;
172 } else {
173 break;
174 }
175 } else {
176 break;
177 }
178 }
179
180 if cave_size >= min_size {
181 caves.push(CodeCave::with_description(
182 cave_start,
183 cave_size,
184 format!("NOP sequence ({} instructions)", cave_size / 4),
185 ));
186 }
187
188 current_addr = temp_addr;
189 } else {
190 current_addr += 4;
191 }
192 } else {
193 current_addr += 4;
194 }
195 }
196 }
197
198 Ok(caves)
199}
200
201pub fn find_alignment_padding(start: usize, size: usize) -> Result<Vec<CodeCave>, CodeCaveError> {
210 let mut caves = Vec::new();
211
212 unsafe {
213 let mut current_addr = start;
214 let end_addr = start + size;
215
216 while current_addr < end_addr {
217 if let Ok(byte) = crate::memory::rw::read::<u8>(current_addr) {
218 if byte == 0x00 {
219 let cave_start = current_addr;
220 let mut cave_size = 0;
221 let mut temp_addr = current_addr;
222
223 while temp_addr < end_addr {
224 if let Ok(b) = crate::memory::rw::read::<u8>(temp_addr) {
225 if b == 0x00 {
226 cave_size += 1;
227 temp_addr += 1;
228 } else {
229 break;
230 }
231 } else {
232 break;
233 }
234 }
235
236 if cave_size >= 16 {
237 caves.push(CodeCave::with_description(
238 cave_start,
239 cave_size,
240 format!("Padding ({} bytes)", cave_size),
241 ));
242 }
243
244 current_addr = temp_addr;
245 } else {
246 current_addr += 1;
247 }
248 } else {
249 current_addr += 1;
250 }
251 }
252 }
253
254 Ok(caves)
255}
256
257pub fn find_caves(
267 start: usize,
268 size: usize,
269 min_size: usize,
270) -> Result<Vec<CodeCave>, CodeCaveError> {
271 let mut all_caves = Vec::new();
272
273 let min_nops = min_size.div_ceil(4);
274 if let Ok(nop_caves) = find_nop_sequences(start, size, min_nops) {
275 all_caves.extend(nop_caves);
276 }
277
278 if let Ok(padding_caves) = find_alignment_padding(start, size) {
279 all_caves.extend(padding_caves.into_iter().filter(|c| c.size >= min_size));
280 }
281
282 all_caves.sort_by_key(|c| c.address);
283
284 Ok(all_caves)
285}
286
287pub fn find_caves_in_image(
296 image_name: &str,
297 min_size: usize,
298) -> Result<Vec<CodeCave>, CodeCaveError> {
299 let base = image::get_image_base(image_name)?;
300
301 let scan_size = 32 * 1024 * 1024; #[cfg(feature = "dev_release")]
304 logger::info(&format!(
305 "Scanning image '{}' at {:#x} for code caves (min size: {} bytes)",
306 image_name, base, min_size
307 ));
308
309 find_caves(base, scan_size, min_size)
310}
311
312pub fn allocate_cave(size: usize) -> Result<CodeCave, CodeCaveError> {
322 if size == 0 {
323 return Err(CodeCaveError::InvalidSize(size));
324 }
325
326 let image_name = crate::config::get_target_image_name()
327 .ok_or_else(|| image::ImageError::NotFound("call mem_init first".to_string()))?;
328 let caves = find_caves_in_image(&image_name, size)?;
329
330 for mut cave in caves {
331 let registry = REGISTRY.lock();
332 if registry.get(cave.address).is_some() {
333 continue;
334 }
335 drop(registry);
336
337 if cave.size >= size {
338 cave.allocate();
339 REGISTRY.lock().register(cave.clone())?;
340 #[cfg(feature = "dev_release")]
341 logger::info(&format!(
342 "Allocated code cave at {:#x} (size: {} bytes)",
343 cave.address, cave.size
344 ));
345 return Ok(cave);
346 }
347 }
348
349 Err(CodeCaveError::NoCaveFound)
350}
351
352pub fn allocate_cave_near(target: usize, size: usize) -> Result<CodeCave, CodeCaveError> {
363 if size == 0 {
364 return Err(CodeCaveError::InvalidSize(size));
365 }
366
367 const BRANCH_RANGE: usize = 128 * 1024 * 1024; let image_name = crate::config::get_target_image_name()
370 .ok_or_else(|| image::ImageError::NotFound("call mem_init first".to_string()))?;
371 let caves = find_caves_in_image(&image_name, size)?;
372
373 for mut cave in caves {
374 let distance = cave.address.abs_diff(target);
375
376 if distance <= BRANCH_RANGE && cave.size >= size {
377 let registry = REGISTRY.lock();
378 if registry.get(cave.address).is_some() {
379 continue;
380 }
381 drop(registry);
382
383 cave.allocate();
384 REGISTRY.lock().register(cave.clone())?;
385 #[cfg(feature = "dev_release")]
386 logger::info(&format!(
387 "Allocated code cave near {:#x} at {:#x} (size: {} bytes)",
388 target, cave.address, cave.size
389 ));
390 return Ok(cave);
391 }
392 }
393
394 Err(CodeCaveError::NoCaveFound)
395}
396
397pub fn free_cave(address: usize) -> Result<(), CodeCaveError> {
405 let mut cave = REGISTRY.lock().unregister(address)?;
406 cave.free();
407 #[cfg(feature = "dev_release")]
408 logger::info(&format!("Freed code cave at {:#x}", address));
409 Ok(())
410}
411
412pub fn is_cave_available(address: usize, size: usize) -> bool {
421 let registry = REGISTRY.lock();
422
423 if registry.get(address).is_some() {
424 return false;
425 }
426
427 unsafe {
428 for offset in (0..size).step_by(4) {
429 if let Ok(instr) = crate::memory::rw::read::<u32>(address + offset) {
430 if instr != ARM64_NOP && instr != 0 {
431 return false;
432 }
433 } else {
434 return false;
435 }
436 }
437 }
438
439 true
440}
441
442pub fn list_allocated_caves() -> Vec<CodeCave> {
447 REGISTRY.lock().list_all()
448}
449
450pub fn get_cave_stats() -> (usize, usize) {
455 let registry = REGISTRY.lock();
456 let caves = registry.list_all();
457 let total_size: usize = caves.iter().map(|c| c.size).sum();
458 (caves.len(), total_size)
459}
460
461pub fn clear_all_caves() {
463 REGISTRY.lock().clear();
464 #[cfg(feature = "dev_release")]
465 logger::info("Cleared all allocated code caves");
466}