scanmem alternative with concurrent memory scanning
1use anyhow::Result;
2use log::{debug, info};
3use rayon::prelude::*;
4use std::sync::{Arc, Mutex};
5
6use crate::memory::MemoryReader;
7use crate::process::{MemoryRegion, Process};
8
9#[derive(Clone, Debug)]
10pub enum ScanValueType {
11 #[allow(dead_code)]
12 U8(u8),
13 #[allow(dead_code)]
14 U16(u16),
15 #[allow(dead_code)]
16 U32(u32),
17 #[allow(dead_code)]
18 U64(u64),
19 #[allow(dead_code)]
20 I8(i8),
21 #[allow(dead_code)]
22 I16(i16),
23 I32(i32),
24 #[allow(dead_code)]
25 I64(i64),
26 #[allow(dead_code)]
27 F32(f32),
28 #[allow(dead_code)]
29 F64(f64),
30}
31
32#[derive(Clone, Debug)]
33pub struct MemoryMatch {
34 pub address: usize,
35 #[allow(dead_code)]
36 pub value_type: ScanValueType,
37}
38
39pub struct MemoryScanner {
40 reader: MemoryReader,
41 regions: Vec<MemoryRegion>,
42 matches: Vec<MemoryMatch>,
43}
44
45impl MemoryScanner {
46 pub fn new(process: Process) -> Result<Self> {
47 let regions = process.get_memory_maps()?;
48 let reader = MemoryReader::new(process);
49
50 println!(
51 "Successfully attached to process. Found {} readable memory regions.",
52 regions.len()
53 );
54
55 Ok(Self {
56 reader,
57 regions,
58 matches: Vec::new(),
59 })
60 }
61
62 pub fn scan_for_value(&mut self, value: ScanValueType) -> Result<()> {
63 let matches = Arc::new(Mutex::new(Vec::new()));
64
65 self.regions
66 .par_iter()
67 .enumerate()
68 .for_each(|(idx, region)| {
69 println!(
70 "{:02}/{:03} searching {:x} - {:x}...",
71 idx + 1,
72 self.regions.len(),
73 region.start_addr,
74 region.end_addr
75 );
76
77 match self.scan_region(region, &value) {
78 Ok(region_matches) => {
79 let mut matches_guard = matches.lock().unwrap();
80 matches_guard.extend(region_matches);
81 }
82 Err(e) => {
83 debug!(
84 "Error scanning region {:x}-{:x}: {}",
85 region.start_addr, region.end_addr, e
86 );
87 }
88 }
89
90 print!("........ok\n");
91 });
92
93 self.matches = Arc::try_unwrap(matches)
94 .expect("Failed to unwrap Arc")
95 .into_inner()
96 .expect("Failed to unlock Mutex");
97
98 info!("we currently have {} matches.", self.matches.len());
99 Ok(())
100 }
101
102 pub fn get_matches_count(&self) -> usize {
103 self.matches.len()
104 }
105
106 fn scan_region(
107 &self,
108 region: &MemoryRegion,
109 value: &ScanValueType,
110 ) -> Result<Vec<MemoryMatch>> {
111 // Basic implementation that scans a single region for matches
112 let mut region_matches = Vec::new();
113
114 match self.reader.read_region(region) {
115 Ok(memory) => {
116 // Simple implementation searching for i32 values
117 if let ScanValueType::I32(target) = value {
118 let target_bytes = target.to_ne_bytes();
119
120 for i in (0..memory.len().saturating_sub(4)).step_by(4) {
121 let mut matches = true;
122 for j in 0..4 {
123 if memory[i + j] != target_bytes[j] {
124 matches = false;
125 break;
126 }
127 }
128
129 if matches {
130 region_matches.push(MemoryMatch {
131 address: region.start_addr + i,
132 value_type: value.clone(),
133 });
134 }
135 }
136 }
137 // TODO missing handlers for all other types
138 }
139 Err(e) => {
140 println!(
141 "Error reading region {:x}-{:x}: {}",
142 region.start_addr, region.end_addr, e
143 );
144 }
145 }
146
147 Ok(region_matches)
148 }
149 pub fn filter_matches(&mut self, value: ScanValueType) -> Result<()> {
150 let old_matches = std::mem::take(&mut self.matches);
151
152 for m in old_matches {
153 let addr = m.address;
154
155 let data = match &value {
156 ScanValueType::I32(_) => {
157 match self.reader.read_memory(addr, 4) {
158 Ok(data) => data,
159 Err(_) => continue, // Skip if we can't read
160 }
161 }
162 _ => continue,
163 };
164
165 let matched = match &value {
166 ScanValueType::I32(target) => {
167 if data.len() >= 4 {
168 let value_bytes = target.to_ne_bytes();
169 data[0] == value_bytes[0]
170 && data[1] == value_bytes[1]
171 && data[2] == value_bytes[2]
172 && data[3] == value_bytes[3]
173 } else {
174 false
175 }
176 }
177 _ => false,
178 };
179
180 if matched {
181 self.matches.push(MemoryMatch {
182 address: addr,
183 value_type: value.clone(),
184 });
185 }
186 }
187
188 println!("..........ok");
189 info!("we currently have {} matches.", self.matches.len());
190 Ok(())
191 }
192
193 pub fn set_value(&self, index: usize, value: i32) -> Result<()> {
194 if index >= self.matches.len() {
195 anyhow::bail!(
196 "Index {} out of range (have {} matches)",
197 index,
198 self.matches.len()
199 );
200 }
201
202 let address = self.matches[index].address;
203 let data = value.to_ne_bytes();
204
205 self.reader.write_memory(address, &data)
206 }
207}