wraith/manipulation/hooks/
integrity.rs1use crate::error::Result;
8use crate::navigation::Module;
9use crate::structures::pe::{DataDirectoryType, ExportDirectory};
10use std::collections::HashMap;
11
12pub struct IntegrityChecker {
14 checksums: HashMap<usize, u64>,
15 prologue_size: usize,
16}
17
18impl IntegrityChecker {
19 pub fn new(prologue_size: usize) -> Self {
21 Self {
22 checksums: HashMap::new(),
23 prologue_size,
24 }
25 }
26
27 pub fn with_default_size() -> Self {
29 Self::new(32)
30 }
31
32 pub fn prologue_size(&self) -> usize {
34 self.prologue_size
35 }
36
37 pub fn recorded_count(&self) -> usize {
39 self.checksums.len()
40 }
41
42 pub fn record(&mut self, addr: usize) {
44 let checksum = self.compute_checksum(addr);
45 self.checksums.insert(addr, checksum);
46 }
47
48 pub fn record_addresses(&mut self, addresses: &[usize]) {
50 for &addr in addresses {
51 self.record(addr);
52 }
53 }
54
55 pub fn record_module(&mut self, module: &Module) -> Result<usize> {
57 let nt = module.nt_headers()?;
58 let export_dir = match nt.data_directory(DataDirectoryType::Export.index()) {
59 Some(dir) if dir.is_present() => dir,
60 _ => return Ok(0),
61 };
62
63 let base = module.base();
64 let exports = unsafe {
66 &*((base + export_dir.virtual_address as usize) as *const ExportDirectory)
67 };
68
69 let num_funcs = exports.number_of_functions as usize;
70 let functions_va = base + exports.address_of_functions as usize;
71 let mut count = 0;
72
73 for i in 0..num_funcs {
74 let func_rva = unsafe { *((functions_va + i * 4) as *const u32) };
76 if func_rva != 0 {
77 if func_rva >= export_dir.virtual_address
79 && func_rva < export_dir.virtual_address + export_dir.size
80 {
81 continue;
82 }
83
84 let func_addr = base + func_rva as usize;
85 self.record(func_addr);
86 count += 1;
87 }
88 }
89
90 Ok(count)
91 }
92
93 pub fn record_exports(&mut self, module: &Module, names: &[&str]) -> Result<usize> {
95 let mut count = 0;
96 for name in names {
97 if let Ok(addr) = module.get_export(name) {
98 self.record(addr);
99 count += 1;
100 }
101 }
102 Ok(count)
103 }
104
105 pub fn verify(&self, addr: usize) -> bool {
107 match self.checksums.get(&addr) {
108 Some(&expected) => {
109 let current = self.compute_checksum(addr);
110 current == expected
111 }
112 None => true, }
114 }
115
116 pub fn verify_all(&self) -> Vec<usize> {
118 self.checksums
119 .keys()
120 .filter(|&&addr| !self.verify(addr))
121 .copied()
122 .collect()
123 }
124
125 pub fn get_modified(&self) -> Vec<usize> {
127 self.verify_all()
128 }
129
130 pub fn is_recorded(&self, addr: usize) -> bool {
132 self.checksums.contains_key(&addr)
133 }
134
135 pub fn unrecord(&mut self, addr: usize) -> bool {
137 self.checksums.remove(&addr).is_some()
138 }
139
140 pub fn clear(&mut self) {
142 self.checksums.clear();
143 }
144
145 fn compute_checksum(&self, addr: usize) -> u64 {
147 let bytes = unsafe { core::slice::from_raw_parts(addr as *const u8, self.prologue_size) };
149
150 const FNV_OFFSET: u64 = 0xcbf29ce484222325;
152 const FNV_PRIME: u64 = 0x100000001b3;
153
154 let mut hash = FNV_OFFSET;
155 for &byte in bytes {
156 hash ^= byte as u64;
157 hash = hash.wrapping_mul(FNV_PRIME);
158 }
159 hash
160 }
161}
162
163impl Default for IntegrityChecker {
164 fn default() -> Self {
165 Self::with_default_size()
166 }
167}
168
169pub struct IntegrityMonitor {
171 checker: IntegrityChecker,
172 module_name: String,
173}
174
175impl IntegrityMonitor {
176 pub fn for_module(module: &Module) -> Result<Self> {
178 let mut checker = IntegrityChecker::with_default_size();
179 checker.record_module(module)?;
180
181 Ok(Self {
182 checker,
183 module_name: module.name(),
184 })
185 }
186
187 pub fn for_exports(module: &Module, exports: &[&str]) -> Result<Self> {
189 let mut checker = IntegrityChecker::with_default_size();
190 checker.record_exports(module, exports)?;
191
192 Ok(Self {
193 checker,
194 module_name: module.name(),
195 })
196 }
197
198 pub fn check(&self) -> Vec<usize> {
200 self.checker.get_modified()
201 }
202
203 pub fn module_name(&self) -> &str {
205 &self.module_name
206 }
207
208 pub fn monitored_count(&self) -> usize {
210 self.checker.recorded_count()
211 }
212}
213
214#[cfg(test)]
215mod tests {
216 use super::*;
217
218 #[test]
219 fn test_fnv1a_consistency() {
220 let checker = IntegrityChecker::new(8);
221
222 let data = [0x90u8; 8]; let addr = data.as_ptr() as usize;
225
226 let hash1 = checker.compute_checksum(addr);
227 let hash2 = checker.compute_checksum(addr);
228
229 assert_eq!(hash1, hash2);
230 }
231}