Skip to main content

specter/memory/info/
scan.rs

1//! Memory scanning and pattern matching utilities
2
3use 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)]
13/// Errors that can occur during memory scanning
14pub enum ScanError {
15    /// The pattern string (IDA style or mask) is invalid
16    #[error("Invalid pattern format: {0}")]
17    InvalidPattern(String),
18    /// The specified pattern was not found in the target range
19    #[error("Pattern not found")]
20    NotFound,
21    /// The scan attempted to access invalid or protected memory
22    #[error("Memory access violation at {0:#x}")]
23    MemoryAccessViolation(usize),
24    /// The memory region definition is invalid
25    #[error("Invalid memory region")]
26    InvalidRegion,
27    /// Image lookup failed
28    #[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
35/// Parses an IDA-style pattern string (e.g., "A1 ?? B2") into bytes and mask
36///
37/// # Arguments
38/// * `pattern` - The pattern string (e.g., "DE AD BE EF" or "DE ?? BE EF")
39///
40/// # Returns
41/// * `Result<(Vec<u8>, String), ScanError>` - A tuple containing the byte vector and the mask string
42pub 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
69/// Scans for a pattern within a memory range, returning all matches
70///
71/// # Arguments
72/// * `start` - The start address of the scan
73/// * `size` - The size of the memory range to scan
74/// * `pattern` - The byte sequence to find
75/// * `mask` - The mask string ('x' for match, '?' for wildcard)
76///
77/// # Returns
78/// * `Result<Vec<usize>, ScanError>` - A list of addresses where the pattern matches
79pub 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
106/// Scans for an IDA-style pattern within a memory range, returning all matches
107///
108/// # Arguments
109/// * `start` - The start address
110/// * `size` - The size of the range
111/// * `ida_pattern` - The pattern string (e.g., "DE ?? BE EF")
112///
113/// # Returns
114/// * `Result<Vec<usize>, ScanError>` - A list of addresses or an error
115pub 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
124/// Scans an entire image for an IDA-style pattern, returning all matches
125///
126/// # Arguments
127/// * `image_name` - The name of the image to scan
128/// * `ida_pattern` - The pattern string
129///
130/// # Returns
131/// * `Result<Vec<usize>, ScanError>` - A list of addresses or an error
132pub 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
148/// Scans for an IDA-style pattern with caching support
149///
150/// Subsequent calls with the same parameters will return cached results.
151///
152/// # Arguments
153/// * `start` - The start address
154/// * `size` - The scan size
155/// * `ida_pattern` - The pattern string
156///
157/// # Returns
158/// * `Result<Vec<usize>, ScanError>` - A list of addresses or an error
159pub 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
180/// Clears the scan cache
181pub fn clear_cache() {
182    SCAN_CACHE.lock().clear();
183    #[cfg(feature = "dev_release")]
184    logger::info("Scan cache cleared");
185}
186
187/// Checks if a memory region is readable
188fn 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
201/// Retrieves the readable sections of a loaded image
202fn 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/// Checks if a pattern matches at a specific address
232#[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}