use crate::error::Result;
use crate::navigation::Module;
use crate::structures::pe::{DataDirectoryType, ExportDirectory};
use std::collections::HashMap;
pub struct IntegrityChecker {
checksums: HashMap<usize, u64>,
prologue_size: usize,
}
impl IntegrityChecker {
pub fn new(prologue_size: usize) -> Self {
Self {
checksums: HashMap::new(),
prologue_size,
}
}
pub fn with_default_size() -> Self {
Self::new(32)
}
pub fn prologue_size(&self) -> usize {
self.prologue_size
}
pub fn recorded_count(&self) -> usize {
self.checksums.len()
}
pub fn record(&mut self, addr: usize) {
let checksum = self.compute_checksum(addr);
self.checksums.insert(addr, checksum);
}
pub fn record_addresses(&mut self, addresses: &[usize]) {
for &addr in addresses {
self.record(addr);
}
}
pub fn record_module(&mut self, module: &Module) -> Result<usize> {
let nt = module.nt_headers()?;
let export_dir = match nt.data_directory(DataDirectoryType::Export.index()) {
Some(dir) if dir.is_present() => dir,
_ => return Ok(0),
};
let base = module.base();
let exports = unsafe {
&*((base + export_dir.virtual_address as usize) as *const ExportDirectory)
};
let num_funcs = exports.number_of_functions as usize;
let functions_va = base + exports.address_of_functions as usize;
let mut count = 0;
for i in 0..num_funcs {
let func_rva = unsafe { *((functions_va + i * 4) as *const u32) };
if func_rva != 0 {
if func_rva >= export_dir.virtual_address
&& func_rva < export_dir.virtual_address + export_dir.size
{
continue;
}
let func_addr = base + func_rva as usize;
self.record(func_addr);
count += 1;
}
}
Ok(count)
}
pub fn record_exports(&mut self, module: &Module, names: &[&str]) -> Result<usize> {
let mut count = 0;
for name in names {
if let Ok(addr) = module.get_export(name) {
self.record(addr);
count += 1;
}
}
Ok(count)
}
pub fn verify(&self, addr: usize) -> bool {
match self.checksums.get(&addr) {
Some(&expected) => {
let current = self.compute_checksum(addr);
current == expected
}
None => true, }
}
pub fn verify_all(&self) -> Vec<usize> {
self.checksums
.keys()
.filter(|&&addr| !self.verify(addr))
.copied()
.collect()
}
pub fn get_modified(&self) -> Vec<usize> {
self.verify_all()
}
pub fn is_recorded(&self, addr: usize) -> bool {
self.checksums.contains_key(&addr)
}
pub fn unrecord(&mut self, addr: usize) -> bool {
self.checksums.remove(&addr).is_some()
}
pub fn clear(&mut self) {
self.checksums.clear();
}
fn compute_checksum(&self, addr: usize) -> u64 {
let bytes = unsafe { core::slice::from_raw_parts(addr as *const u8, self.prologue_size) };
const FNV_OFFSET: u64 = 0xcbf29ce484222325;
const FNV_PRIME: u64 = 0x100000001b3;
let mut hash = FNV_OFFSET;
for &byte in bytes {
hash ^= byte as u64;
hash = hash.wrapping_mul(FNV_PRIME);
}
hash
}
}
impl Default for IntegrityChecker {
fn default() -> Self {
Self::with_default_size()
}
}
pub struct IntegrityMonitor {
checker: IntegrityChecker,
module_name: String,
}
impl IntegrityMonitor {
pub fn for_module(module: &Module) -> Result<Self> {
let mut checker = IntegrityChecker::with_default_size();
checker.record_module(module)?;
Ok(Self {
checker,
module_name: module.name(),
})
}
pub fn for_exports(module: &Module, exports: &[&str]) -> Result<Self> {
let mut checker = IntegrityChecker::with_default_size();
checker.record_exports(module, exports)?;
Ok(Self {
checker,
module_name: module.name(),
})
}
pub fn check(&self) -> Vec<usize> {
self.checker.get_modified()
}
pub fn module_name(&self) -> &str {
&self.module_name
}
pub fn monitored_count(&self) -> usize {
self.checker.recorded_count()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fnv1a_consistency() {
let checker = IntegrityChecker::new(8);
let data = [0x90u8; 8]; let addr = data.as_ptr() as usize;
let hash1 = checker.compute_checksum(addr);
let hash2 = checker.compute_checksum(addr);
assert_eq!(hash1, hash2);
}
}