use super::driver::DriverHandle;
use super::process::ProcessOps;
use super::{ClientError, ClientResult};
pub mod MemoryProtection {
pub const NOACCESS: u32 = 0x01;
pub const READONLY: u32 = 0x02;
pub const READWRITE: u32 = 0x04;
pub const WRITECOPY: u32 = 0x08;
pub const EXECUTE: u32 = 0x10;
pub const EXECUTE_READ: u32 = 0x20;
pub const EXECUTE_READWRITE: u32 = 0x40;
pub const EXECUTE_WRITECOPY: u32 = 0x80;
pub const GUARD: u32 = 0x100;
pub const NOCACHE: u32 = 0x200;
}
pub struct RemoteMemory<'a> {
process: &'a ProcessOps<'a>,
address: u64,
size: u64,
}
impl<'a> RemoteMemory<'a> {
pub fn allocate(
process: &'a ProcessOps<'a>,
size: u64,
protection: u32,
) -> ClientResult<Self> {
let address = process.allocate(size, protection)?;
Ok(Self {
process,
address,
size,
})
}
pub fn allocate_at(
process: &'a ProcessOps<'a>,
address: u64,
size: u64,
protection: u32,
) -> ClientResult<Self> {
let address = process.allocate_at(address, size, protection)?;
Ok(Self {
process,
address,
size,
})
}
pub fn address(&self) -> u64 {
self.address
}
pub fn size(&self) -> u64 {
self.size
}
pub fn write(&self, data: &[u8]) -> ClientResult<()> {
if data.len() as u64 > self.size {
return Err(ClientError::BufferTooSmall {
required: data.len(),
provided: self.size as usize,
});
}
self.process.write_bytes(self.address, data)
}
pub fn write_value<T: Copy>(&self, value: &T) -> ClientResult<()> {
if std::mem::size_of::<T>() as u64 > self.size {
return Err(ClientError::BufferTooSmall {
required: std::mem::size_of::<T>(),
provided: self.size as usize,
});
}
self.process.write(self.address, value)
}
pub fn write_at(&self, offset: u64, data: &[u8]) -> ClientResult<()> {
if offset + data.len() as u64 > self.size {
return Err(ClientError::BufferTooSmall {
required: (offset + data.len() as u64) as usize,
provided: self.size as usize,
});
}
self.process.write_bytes(self.address + offset, data)
}
pub fn read(&self, size: usize) -> ClientResult<Vec<u8>> {
if size as u64 > self.size {
return Err(ClientError::BufferTooSmall {
required: size,
provided: self.size as usize,
});
}
self.process.read_bytes(self.address, size)
}
pub fn read_value<T: Copy>(&self) -> ClientResult<T> {
if std::mem::size_of::<T>() as u64 > self.size {
return Err(ClientError::BufferTooSmall {
required: std::mem::size_of::<T>(),
provided: self.size as usize,
});
}
self.process.read(self.address)
}
pub fn read_at(&self, offset: u64, size: usize) -> ClientResult<Vec<u8>> {
if offset + size as u64 > self.size {
return Err(ClientError::BufferTooSmall {
required: (offset + size as u64) as usize,
provided: self.size as usize,
});
}
self.process.read_bytes(self.address + offset, size)
}
pub fn protect(&self, protection: u32) -> ClientResult<u32> {
self.process.protect(self.address, self.size, protection)
}
pub fn leak(self) -> u64 {
let addr = self.address;
std::mem::forget(self);
addr
}
}
impl<'a> Drop for RemoteMemory<'a> {
fn drop(&mut self) {
let _ = self.process.free(self.address);
}
}
pub struct MemoryScanner<'a> {
process: &'a ProcessOps<'a>,
}
impl<'a> MemoryScanner<'a> {
pub fn new(process: &'a ProcessOps<'a>) -> Self {
Self { process }
}
pub fn scan_range(
&self,
start: u64,
size: usize,
pattern: &[u8],
mask: &[u8],
) -> ClientResult<Vec<u64>> {
let mut results = Vec::new();
const CHUNK_SIZE: usize = 0x10000;
let mut offset = 0;
while offset < size {
let chunk_size = std::cmp::min(CHUNK_SIZE, size - offset);
let chunk = self.process.read_bytes(start + offset as u64, chunk_size)?;
for i in 0..chunk.len().saturating_sub(pattern.len()) {
let mut matched = true;
for (j, (&p, &m)) in pattern.iter().zip(mask.iter()).enumerate() {
if m != 0 && chunk[i + j] != p {
matched = false;
break;
}
}
if matched {
results.push(start + offset as u64 + i as u64);
}
}
offset += chunk_size - pattern.len(); }
Ok(results)
}
pub fn scan_bytes(&self, start: u64, size: usize, bytes: &[u8]) -> ClientResult<Vec<u64>> {
let mask = vec![0xFF; bytes.len()];
self.scan_range(start, size, bytes, &mask)
}
pub fn scan_ida_pattern(&self, start: u64, size: usize, pattern: &str) -> ClientResult<Vec<u64>> {
let (bytes, mask) = parse_ida_pattern(pattern)?;
self.scan_range(start, size, &bytes, &mask)
}
}
fn parse_ida_pattern(pattern: &str) -> ClientResult<(Vec<u8>, Vec<u8>)> {
let parts: Vec<&str> = pattern.split_whitespace().collect();
let mut bytes = Vec::with_capacity(parts.len());
let mut mask = Vec::with_capacity(parts.len());
for part in parts {
if part == "?" || part == "??" {
bytes.push(0);
mask.push(0);
} else {
let byte = u8::from_str_radix(part, 16)
.map_err(|_| ClientError::MemoryError {
address: 0,
reason: format!("invalid pattern byte: {}", part),
})?;
bytes.push(byte);
mask.push(0xFF);
}
}
Ok((bytes, mask))
}
pub struct ProtectionGuard<'a> {
process: &'a ProcessOps<'a>,
address: u64,
size: u64,
old_protection: u32,
}
impl<'a> ProtectionGuard<'a> {
pub fn new(
process: &'a ProcessOps<'a>,
address: u64,
size: u64,
new_protection: u32,
) -> ClientResult<Self> {
let old_protection = process.protect(address, size, new_protection)?;
Ok(Self {
process,
address,
size,
old_protection,
})
}
}
impl<'a> Drop for ProtectionGuard<'a> {
fn drop(&mut self) {
let _ = self.process.protect(self.address, self.size, self.old_protection);
}
}