specter/memory/manipulation/
checksum.rs1use once_cell::sync::Lazy;
7use parking_lot::Mutex;
8use std::collections::HashMap;
9use std::sync::Arc;
10use std::sync::atomic::{AtomicBool, Ordering};
11use std::thread::{self, JoinHandle};
12use std::time::Duration;
13
14#[cfg(feature = "dev_release")]
15use crate::utils::logger;
16
17use crate::memory::info::protection;
18
19const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
21const FNV_PRIME: u64 = 0x100000001b3;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum IntegrityError {
27 InvalidAddress,
28 InvalidLength,
29 MonitorAlreadyRunning,
30 MonitorNotRunning,
31}
32
33#[derive(Clone, Debug)]
35pub struct HookChecksum {
36 pub target: usize,
38 pub expected_hash: u64,
40 pub length: usize,
42}
43
44impl HookChecksum {
45 #[inline]
54 pub unsafe fn new_unchecked(target: usize, len: usize) -> Self {
55 unsafe {
56 let hash = Self::compute_unchecked(target, len);
57 Self {
58 target,
59 expected_hash: hash,
60 length: len,
61 }
62 }
63 }
64
65 pub fn new(target: usize, len: usize) -> Result<Self, IntegrityError> {
67 if target == 0 || len == 0 {
68 return Err(IntegrityError::InvalidAddress);
69 }
70 if len > 4096 {
71 return Err(IntegrityError::InvalidLength);
72 }
73
74 if !Self::is_readable(target, len) {
76 return Err(IntegrityError::InvalidAddress);
77 }
78
79 Ok(unsafe { Self::new_unchecked(target, len) })
80 }
81
82 fn is_readable(addr: usize, len: usize) -> bool {
84 match protection::get_region_info(addr) {
85 Ok(info) => {
86 let is_accessible = info.protection.is_readable();
87 let end_addr = addr + len;
89 let region_end = info.address + info.size;
90 let in_range = end_addr <= region_end;
91
92 is_accessible && in_range
93 }
94 Err(_) => false,
95 }
96 }
97
98 #[inline]
103 unsafe fn compute_unchecked(target: usize, len: usize) -> u64 {
104 unsafe {
105 let mut hash = FNV_OFFSET_BASIS;
106 let ptr = target as *const u8;
107
108 let slice = std::slice::from_raw_parts(ptr, len);
110 for &byte in slice {
111 hash ^= byte as u64;
112 hash = hash.wrapping_mul(FNV_PRIME);
113 }
114
115 hash
116 }
117 }
118
119 #[inline]
124 pub fn verify(&self) -> bool {
125 unsafe {
126 let current = Self::compute_unchecked(self.target, self.length);
127 current == self.expected_hash
128 }
129 }
130
131 #[inline]
133 pub fn target(&self) -> usize {
134 self.target
135 }
136
137 pub fn update(&mut self) {
139 unsafe {
140 self.expected_hash = Self::compute_unchecked(self.target, self.length);
141 }
142 }
143}
144
145static CHECKSUMS: Lazy<Mutex<HashMap<usize, HookChecksum>>> =
147 Lazy::new(|| Mutex::new(HashMap::new()));
148
149pub fn register(target: usize, len: usize) -> Result<(), IntegrityError> {
157 let checksum = HookChecksum::new(target, len)?;
158
159 let mut checksums = CHECKSUMS.lock();
160 let was_empty = checksums.is_empty();
161 checksums.insert(target, checksum);
162 drop(checksums); if was_empty && !is_monitor_running() {
165 let _ = start_monitor(5000, Some(default_tamper_callback));
166 }
167
168 Ok(())
169}
170
171pub fn unregister(target: usize) -> bool {
176 CHECKSUMS.lock().remove(&target).is_some()
177}
178
179pub fn verify(target: usize) -> Option<bool> {
187 CHECKSUMS.lock().get(&target).map(|c| c.verify())
188}
189
190pub fn verify_all() -> Vec<usize> {
195 CHECKSUMS
196 .lock()
197 .values()
198 .filter(|c| !c.verify())
199 .map(|c| c.target)
200 .collect()
201}
202
203pub fn count() -> usize {
205 CHECKSUMS.lock().len()
206}
207
208pub fn clear() {
210 CHECKSUMS.lock().clear();
211}
212
213#[derive(Debug, Clone)]
215pub struct IntegrityReport {
216 pub total: usize,
218 pub intact: usize,
220 pub tampered: Vec<usize>,
222}
223
224impl IntegrityReport {
225 #[inline]
227 pub fn is_clean(&self) -> bool {
228 self.tampered.is_empty()
229 }
230
231 pub fn integrity_percentage(&self) -> f64 {
233 if self.total == 0 {
234 return 100.0;
235 }
236 (self.intact as f64 / self.total as f64) * 100.0
237 }
238}
239
240pub fn scan() -> IntegrityReport {
245 let checksums = CHECKSUMS.lock();
246 let total = checksums.len();
247 let tampered: Vec<usize> = checksums
248 .values()
249 .filter(|c| !c.verify())
250 .map(|c| c.target)
251 .collect();
252 let intact = total - tampered.len();
253
254 IntegrityReport {
255 total,
256 intact,
257 tampered,
258 }
259}
260
261struct MonitorState {
263 running: AtomicBool,
264 handle: Mutex<Option<JoinHandle<()>>>,
265}
266
267static MONITOR_STATE: Lazy<Arc<MonitorState>> = Lazy::new(|| {
268 Arc::new(MonitorState {
269 running: AtomicBool::new(false),
270 handle: Mutex::new(None),
271 })
272});
273
274pub type TamperCallback = fn(tampered: &[usize]);
276
277fn default_tamper_callback(tampered: &[usize]) {
284 use super::hook::restore_hook_bytes;
285
286 for &addr in tampered {
287 #[cfg(feature = "dev_release")]
288 logger::warning(&format!("Hook tampered at {:#x}, restoring...", addr));
289
290 if restore_hook_bytes(addr) {
291 let mut checksums = CHECKSUMS.lock();
293 if let Some(checksum) = checksums.get_mut(&addr) {
294 checksum.update();
295 }
296 drop(checksums);
297
298 #[cfg(feature = "dev_release")]
299 logger::info(&format!("Hook restored at {:#x}", addr));
300 } else {
301 #[cfg(feature = "dev_release")]
302 logger::error(&format!("Failed to restore hook at {:#x}", addr));
303 }
304 }
305}
306
307pub fn start_monitor(
316 interval_ms: u64,
317 on_tamper: Option<TamperCallback>,
318) -> Result<(), IntegrityError> {
319 let state = Arc::clone(&MONITOR_STATE);
320
321 if state.running.swap(true, Ordering::SeqCst) {
322 return Err(IntegrityError::MonitorAlreadyRunning);
323 }
324
325 let callback = on_tamper.unwrap_or(default_tamper_callback);
326 let interval = Duration::from_millis(interval_ms);
327 let state_clone = Arc::clone(&state);
328
329 let handle = thread::spawn(move || {
330 #[cfg(feature = "dev_release")]
331 logger::info("Integrity monitor started");
332
333 while state_clone.running.load(Ordering::Relaxed) {
334 thread::sleep(interval);
335
336 if count() == 0 {
337 continue;
338 }
339
340 let tampered = verify_all();
341 if !tampered.is_empty() {
342 callback(&tampered);
343 }
344 }
345
346 #[cfg(feature = "dev_release")]
347 logger::info("Integrity monitor stopped");
348 });
349
350 *state.handle.lock() = Some(handle);
351 Ok(())
352}
353
354pub fn stop_monitor() -> Result<(), IntegrityError> {
359 let state = Arc::clone(&MONITOR_STATE);
360
361 if !state.running.swap(false, Ordering::SeqCst) {
362 return Err(IntegrityError::MonitorNotRunning);
363 }
364
365 if let Some(handle) = state.handle.lock().take() {
367 let _ = handle.join();
368 }
369
370 Ok(())
371}
372
373#[inline]
375pub fn is_monitor_running() -> bool {
376 MONITOR_STATE.running.load(Ordering::Relaxed)
377}