use crate::DriverError;
use crate::DriverResult;
use serde::{Deserialize, Serialize};
use sysinfo::{Pid, ProcessRefreshKind, System};
use tracing::debug;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryReadResult {
pub address: usize,
pub size: usize,
pub data: Vec<u8>,
pub data_type: String,
pub value: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryScanResult {
pub address: usize,
pub matched_bytes: Vec<u8>,
pub module_name: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleInfo {
pub name: String,
pub base_address: usize,
pub size: usize,
pub path: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MemoryRegion {
pub start_address: usize,
pub end_address: usize,
pub size: usize,
pub permissions: String,
pub module_name: Option<String>,
}
#[derive(Debug, Clone)]
pub struct Pattern {
pub bytes: Vec<u8>,
pub masks: Vec<bool>,
}
impl Pattern {
pub fn from_hex(hex_str: &str) -> Result<Self, String> {
let parts: Vec<&str> = hex_str.split_whitespace().collect();
if parts.is_empty() {
return Err("Empty pattern".to_string());
}
let mut bytes = Vec::new();
let mut masks = Vec::new();
for part in parts {
if part == "?" || part == "??" {
bytes.push(0);
masks.push(false);
} else if part.len() == 2 {
let byte = u8::from_str_radix(part, 16).map_err(|_| format!("Invalid hex byte: {}", part))?;
bytes.push(byte);
masks.push(true);
} else {
return Err(format!("Invalid pattern part: {}", part));
}
}
return Ok(Pattern { bytes, masks });
}
pub fn matches(&self, data: &[u8]) -> bool {
if data.len() < self.bytes.len() {
return false;
}
for i in 0..self.bytes.len() {
if self.masks[i] && data[i] != self.bytes[i] {
return false;
}
}
return true;
}
pub fn len(&self) -> usize {
return self.bytes.len();
}
pub fn is_empty(&self) -> bool {
return self.bytes.is_empty();
}
}
pub struct ProcessMemory {
pid: u32,
#[cfg(target_os = "windows")]
handle: windows_sys::Win32::Foundation::HANDLE,
}
impl ProcessMemory {
pub fn open(pid: u32, read_only: bool) -> DriverResult<Self> {
#[cfg(target_os = "windows")]
{
use windows_sys::Win32::Foundation::HANDLE;
use windows_sys::Win32::System::Threading::{
OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_READ, PROCESS_VM_WRITE,
};
let desired_access = if read_only {
PROCESS_VM_READ | PROCESS_QUERY_INFORMATION
} else {
PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_QUERY_INFORMATION
};
let handle = unsafe { OpenProcess(desired_access, 0, pid) };
if handle == std::ptr::null_mut() {
return Err(DriverError::execution(format!("Failed to open process with PID: {}", pid)));
}
return Ok(ProcessMemory { pid, handle });
}
#[cfg(not(target_os = "windows"))]
{
return Ok(ProcessMemory { pid });
}
}
pub fn read_memory(&mut self, address: usize, buffer: &mut [u8]) -> DriverResult<usize> {
#[cfg(target_os = "windows")]
{
use windows_sys::Win32::System::Diagnostics::Debug::ReadProcessMemory;
let mut bytes_read = 0usize;
let result = unsafe {
ReadProcessMemory(
self.handle,
address as *const std::ffi::c_void,
buffer.as_mut_ptr() as *mut std::ffi::c_void,
buffer.len(),
&mut bytes_read,
)
};
if result == 0 {
return Err(DriverError::execution("Failed to read process memory".to_string()));
}
return Ok(bytes_read);
}
#[cfg(not(target_os = "windows"))]
{
#[cfg(target_os = "linux")]
{
use libc::{iovec, pid_t, process_vm_readv};
let pid_t = self.pid as pid_t;
let mut local_iov = iovec { iov_base: buffer.as_mut_ptr() as *mut std::ffi::c_void, iov_len: buffer.len() };
let remote_iov = iovec { iov_base: address as *mut std::ffi::c_void, iov_len: buffer.len() };
let result = unsafe { process_vm_readv(pid_t, &mut local_iov as *mut iovec, 1, &remote_iov as *const iovec, 1, 0) };
if result < 0 {
return Err(DriverError::execution(format!("Failed to read process memory: {}", std::io::Error::last_os_error())));
}
return Ok(result as usize);
}
#[cfg(not(target_os = "linux"))]
{
return Err(DriverError::execution("Memory reading not supported on this platform".to_string()));
}
}
}
pub fn read_u8(&mut self, address: usize) -> DriverResult<u8> {
let mut buffer = [0u8; 1];
self.read_memory(address, &mut buffer)?;
return Ok(buffer[0]);
}
pub fn read_u16(&mut self, address: usize) -> DriverResult<u16> {
let mut buffer = [0u8; 2];
self.read_memory(address, &mut buffer)?;
return Ok(u16::from_le_bytes(buffer));
}
pub fn read_u32(&mut self, address: usize) -> DriverResult<u32> {
let mut buffer = [0u8; 4];
self.read_memory(address, &mut buffer)?;
return Ok(u32::from_le_bytes(buffer));
}
pub fn read_u64(&mut self, address: usize) -> DriverResult<u64> {
let mut buffer = [0u8; 8];
self.read_memory(address, &mut buffer)?;
return Ok(u64::from_le_bytes(buffer));
}
pub fn read_f32(&mut self, address: usize) -> DriverResult<f32> {
let mut buffer = [0u8; 4];
self.read_memory(address, &mut buffer)?;
return Ok(f32::from_le_bytes(buffer));
}
pub fn read_f64(&mut self, address: usize) -> DriverResult<f64> {
let mut buffer = [0u8; 8];
self.read_memory(address, &mut buffer)?;
return Ok(f64::from_le_bytes(buffer));
}
pub fn read_string(&mut self, address: usize, max_len: usize) -> DriverResult<String> {
let mut buffer = vec![0u8; max_len];
let bytes_read = self.read_memory(address, &mut buffer)?;
let end = buffer[..bytes_read].iter().position(|&b| b == 0).unwrap_or(bytes_read);
String::from_utf8(buffer[..end].to_vec()).map_err(|e| DriverError::execution(format!("Invalid UTF-8: {}", e)))
}
pub fn get_module_base(&self, module_name: &str) -> DriverResult<usize> {
let modules = self.get_modules()?;
let module_lower = module_name.to_lowercase();
for module in modules {
if module.name.to_lowercase().contains(&module_lower) {
return Ok(module.base_address);
}
}
return Err(DriverError::execution(format!("Module not found: {}", module_name)));
}
pub fn get_modules(&self) -> DriverResult<Vec<ModuleInfo>> {
let mut system = System::new();
system.refresh_processes_specifics(sysinfo::ProcessesToUpdate::All, true, ProcessRefreshKind::everything());
let pid = Pid::from_u32(self.pid);
if let Some(process) = system.process(pid) {
let mut modules = Vec::new();
#[cfg(target_os = "linux")]
{
let path = format!("/proc/{}/maps", self.pid);
if let Ok(content) = std::fs::read_to_string(&path) {
let mut seen_paths = std::collections::HashSet::new();
for line in content.lines() {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 6 {
let addr_parts: Vec<&str> = parts[0].split('-').collect();
if addr_parts.len() == 2 {
let base = usize::from_str_radix(addr_parts[0], 16).unwrap_or(0);
let end = usize::from_str_radix(addr_parts[1], 16).unwrap_or(0);
let path = parts[5];
if path != "[vdso]" && path != "[vsyscall]" && path != "[heap]" && path != "[stack]" && !path.is_empty() {
let name = path.split('/').last().unwrap_or(path);
if !seen_paths.contains(name) && !name.is_empty() {
seen_paths.insert(name.to_string());
modules.push(ModuleInfo {
name: name.to_string(),
base_address: base,
size: end - base,
path: Some(path.to_string()),
});
}
}
}
}
}
}
}
#[cfg(target_os = "windows")]
{
if let Some(exe_path) = process.exe() {
if let Some(name) = exe_path.file_name() {
if let Some(name_str) = name.to_str() {
modules.push(ModuleInfo {
name: name_str.to_string(),
base_address: 0,
size: 0,
path: Some(exe_path.to_string_lossy().to_string()),
});
}
}
}
}
return Ok(modules);
} else {
return Err(DriverError::execution(format!("Process with PID {} not found", self.pid)));
}
}
pub fn list_processes() -> DriverResult<Vec<ProcessInfo>> {
let mut system = System::new();
system.refresh_processes_specifics(sysinfo::ProcessesToUpdate::All, true, ProcessRefreshKind::everything());
let mut processes = Vec::new();
for (pid, process) in system.processes() {
processes.push(ProcessInfo {
pid: pid.as_u32(),
name: process.name().to_string_lossy().to_string(),
parent_pid: process.parent().map(|p| p.as_u32()),
});
}
return Ok(processes);
}
pub fn scan(&mut self, start_address: usize, size: usize, pattern: &Pattern) -> DriverResult<Vec<usize>> {
debug!("Scanning memory from 0x{:X} size {} for pattern", start_address, size);
let mut results = Vec::new();
let chunk_size = 4096;
let mut buffer = vec![0u8; chunk_size + pattern.len()];
let mut current_addr = start_address;
let end_addr = start_address + size;
while current_addr < end_addr {
let remaining = end_addr - current_addr;
let read_size = std::cmp::min(chunk_size + pattern.len(), remaining);
let read_result = self.read_memory(current_addr, &mut buffer[..read_size]);
if let Ok(bytes_read) = read_result {
if bytes_read == 0 {
break;
}
let search_end = bytes_read - pattern.len() + 1;
for i in 0..search_end {
if pattern.matches(&buffer[i..i + pattern.len()]) {
results.push(current_addr + i);
}
}
current_addr += bytes_read - pattern.len() + 1;
} else {
current_addr = (current_addr + 4096) & !4095;
}
if results.len() >= 1000 {
break;
}
}
debug!("Scan complete, found {} matches", results.len());
return Ok(results);
}
}
impl Drop for ProcessMemory {
#[cfg(target_os = "windows")]
fn drop(&mut self) {
use windows_sys::Win32::Foundation::CloseHandle;
unsafe {
CloseHandle(self.handle);
}
}
#[cfg(not(target_os = "windows"))]
fn drop(&mut self) {}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProcessInfo {
pub pid: u32,
pub name: String,
pub parent_pid: Option<u32>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pattern_from_hex() {
let pattern = Pattern::from_hex("48 8B 05 ? ? ? ?").unwrap();
assert_eq!(pattern.bytes.len(), 7);
assert_eq!(pattern.masks.len(), 7);
assert!(pattern.masks[0]);
assert!(!pattern.masks[3]);
}
#[test]
fn test_pattern_matches() {
let pattern = Pattern::from_hex("48 8B ? ? ? ? ?").unwrap();
let data = vec![0x48, 0x8B, 0x05, 0x00, 0x00, 0x00, 0x00];
assert!(pattern.matches(&data));
}
#[test]
fn test_process_info_creation() {
let info = ProcessInfo { pid: 1234, name: "test".to_string(), parent_pid: Some(5678) };
assert_eq!(info.pid, 1234);
assert_eq!(info.name, "test");
assert_eq!(info.parent_pid, Some(5678));
}
}