specter/memory/info/
scan.rs1use crate::memory::image;
4use crate::memory::info::protection;
5#[cfg(feature = "dev_release")]
6use crate::utils::logger;
7use once_cell::sync::Lazy;
8use parking_lot::Mutex;
9use std::collections::HashMap;
10use thiserror::Error;
11
12#[derive(Error, Debug)]
13pub enum ScanError {
15 #[error("Invalid pattern format: {0}")]
17 InvalidPattern(String),
18 #[error("Pattern not found")]
20 NotFound,
21 #[error("Memory access violation at {0:#x}")]
23 MemoryAccessViolation(usize),
24 #[error("Invalid memory region")]
26 InvalidRegion,
27 #[error("Image not found: {0}")]
29 ImageNotFound(#[from] super::image::ImageError),
30}
31
32static SCAN_CACHE: Lazy<Mutex<HashMap<String, Vec<usize>>>> =
33 Lazy::new(|| Mutex::new(HashMap::new()));
34
35pub fn parse_ida_pattern(pattern: &str) -> Result<(Vec<u8>, String), ScanError> {
43 let parts: Vec<&str> = pattern.split_whitespace().collect();
44 let mut bytes = Vec::new();
45 let mut mask = String::new();
46 for part in parts {
47 if part == "??" {
48 bytes.push(0);
49 mask.push('?');
50 } else if part.len() == 2 {
51 bytes.push(
52 u8::from_str_radix(part, 16)
53 .map_err(|_| ScanError::InvalidPattern(format!("Invalid hex: {}", part)))?,
54 );
55 mask.push('x');
56 } else {
57 return Err(ScanError::InvalidPattern(format!(
58 "Invalid pattern part: {}",
59 part
60 )));
61 }
62 }
63 if bytes.is_empty() {
64 return Err(ScanError::InvalidPattern("Empty pattern".to_string()));
65 }
66 Ok((bytes, mask))
67}
68
69pub fn scan_pattern(
80 start: usize,
81 size: usize,
82 pattern: &[u8],
83 mask: &str,
84) -> Result<Vec<usize>, ScanError> {
85 if pattern.is_empty() || pattern.len() != mask.len() {
86 return Err(ScanError::InvalidPattern(
87 "Pattern and mask length mismatch".to_string(),
88 ));
89 }
90 if !is_readable_memory(start, size) {
91 return Err(ScanError::MemoryAccessViolation(start));
92 }
93 let mut results = Vec::new();
94 let end = start + size - pattern.len();
95 for addr in start..=end {
96 if pattern_match(addr, pattern, mask) {
97 results.push(addr);
98 }
99 }
100 if results.is_empty() {
101 return Err(ScanError::NotFound);
102 }
103 Ok(results)
104}
105
106pub fn scan_ida_pattern(
116 start: usize,
117 size: usize,
118 ida_pattern: &str,
119) -> Result<Vec<usize>, ScanError> {
120 let (bytes, mask) = parse_ida_pattern(ida_pattern)?;
121 scan_pattern(start, size, &bytes, &mask)
122}
123
124pub fn scan_image(image_name: &str, ida_pattern: &str) -> Result<Vec<usize>, ScanError> {
133 let base = image::get_image_base(image_name)?;
134 let sections = get_image_sections(base)?;
135 let (bytes, mask) = parse_ida_pattern(ida_pattern)?;
136 let mut all_results = Vec::new();
137 for (section_start, section_size) in sections {
138 if let Ok(mut results) = scan_pattern(section_start, section_size, &bytes, &mask) {
139 all_results.append(&mut results);
140 }
141 }
142 if all_results.is_empty() {
143 return Err(ScanError::NotFound);
144 }
145 Ok(all_results)
146}
147
148pub fn scan_pattern_cached(
160 start: usize,
161 size: usize,
162 ida_pattern: &str,
163) -> Result<Vec<usize>, ScanError> {
164 let cache_key = format!("{:#x}_{:#x}_{}", start, size, ida_pattern);
165 {
166 let cache = SCAN_CACHE.lock();
167 if let Some(cached) = cache.get(&cache_key) {
168 #[cfg(feature = "dev_release")]
169 logger::info(&format!("Cache hit for pattern: {}", ida_pattern));
170 return Ok(cached.clone());
171 }
172 }
173 let results = scan_ida_pattern(start, size, ida_pattern)?;
174 {
175 SCAN_CACHE.lock().insert(cache_key, results.clone());
176 }
177 Ok(results)
178}
179
180pub fn clear_cache() {
182 SCAN_CACHE.lock().clear();
183 #[cfg(feature = "dev_release")]
184 logger::info("Scan cache cleared");
185}
186
187fn is_readable_memory(addr: usize, size: usize) -> bool {
189 match protection::get_region_info(addr) {
190 Ok(info) => {
191 let region_end = info.address + info.size;
192 if (addr + size) > region_end {
193 return false;
194 }
195 info.protection.is_readable()
196 }
197 Err(_) => false,
198 }
199}
200
201fn get_image_sections(base: usize) -> Result<Vec<(usize, usize)>, ScanError> {
203 let mut sections = Vec::new();
204 let mut address = base;
205 let end_address = address + 0x10000000;
206
207 while address < end_address {
208 match protection::find_region(address) {
209 Ok(info) => {
210 if info.address >= end_address {
211 break;
212 }
213 if info.protection.is_readable() {
214 sections.push((info.address, info.size));
215 }
216 let next = info.address + info.size;
217 if next <= address {
218 break;
219 }
220 address = next;
221 }
222 Err(_) => break,
223 }
224 }
225 if sections.is_empty() {
226 return Err(ScanError::InvalidRegion);
227 }
228 Ok(sections)
229}
230
231#[inline]
233fn pattern_match(addr: usize, pattern: &[u8], mask: &str) -> bool {
234 unsafe {
235 let ptr = addr as *const u8;
236 for (i, &byte) in pattern.iter().enumerate() {
237 if mask.as_bytes()[i] == b'x' && *ptr.add(i) != byte {
238 return false;
239 }
240 }
241 true
242 }
243}