use std::error::Error;
#[cfg(unix)]
pub mod unix;
#[cfg(windows)]
pub mod windows;
pub unsafe fn find_pattern(
start_offset: usize,
end_offset: usize,
mask: &'static str,
bytes: &'static [u8],
) -> Option<*const u8> {
(start_offset..=end_offset)
.map(|addr| addr as *const u8)
.find(|addr| unsafe { compare_mask(*addr, mask, bytes) })
}
pub unsafe fn compare_mask(addr: *const u8, mask: &'static str, bytes: &'static [u8]) -> bool {
mask.chars()
.enumerate()
.zip(bytes.iter().copied())
.all(|((offset, mask), op)| mask == '?' || unsafe { *addr.add(offset) } == op)
}
pub struct MopConfig<P> {
pub pointer: *const P,
pub size: usize,
pub align_to: Option<usize>,
pub prot_before: MaybeProt,
pub prot_after: MaybeProt,
}
pub enum MaybeProt {
DoNotChange,
ChangeTo(Prot),
RestorePrevious,
}
pub enum Prot {
None,
Read,
ReadWrite,
ReadWriteExecute,
Custom(u32),
}
impl std::fmt::Display for Prot {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let s = match self {
Prot::None => "none",
Prot::Read => "read",
Prot::ReadWrite => "read-write",
Prot::ReadWriteExecute => "read-write-execute",
Prot::Custom(v) => &format!("custom ({v})"),
};
write!(f, "{s}")
}
}
#[inline]
pub unsafe fn mop<F, P>(config: MopConfig<P>, op_func: F) -> Result<(), Box<dyn Error>>
where
F: FnOnce(*mut P) -> Result<(), std::io::Error>,
{
let mut protect_ptr: *mut P = config.pointer.cast_mut();
let mut chunk_size = config.size;
if let Some(align_bits) = config.align_to {
let adjustment = align_to(protect_ptr, align_bits, config.size);
protect_ptr = adjustment.new_ptr;
chunk_size = adjustment.size_to_modify;
}
let mut orig_prot: Option<Prot> = None;
match config.prot_before {
MaybeProt::RestorePrevious => {
return Err(format!(
"prot_before cannot be set to MaybeProt::RestorePrevious"
))?;
}
MaybeProt::ChangeTo(new_prot) => {
match protect(protect_ptr, chunk_size, new_prot, None) {
Ok(i) => {
if let Some(old) = i.old {
orig_prot = Some(Prot::Custom(old));
}
}
Err(err) => {
return Err(format!(
"failed to protect memory region 0x{:x?} (orig: 0x{:x?}) size 0x{:x?} (orig: 0x{:x?}) - {}",
protect_ptr.addr(),
config.pointer.addr(),
chunk_size,
config.size,
err
))?;
}
};
}
MaybeProt::DoNotChange => {}
};
let func_result = op_func(config.pointer.cast_mut());
let prot_after_result: Result<ProtectResult, Box<dyn Error>> = match config.prot_after {
MaybeProt::RestorePrevious => {
if let Some(orig) = orig_prot {
protect(protect_ptr, chunk_size, orig, None)
} else {
Ok(ProtectResult { old: None })
}
}
MaybeProt::ChangeTo(new_prot) => protect(protect_ptr, chunk_size, new_prot, None),
MaybeProt::DoNotChange => Ok(ProtectResult { old: None }),
};
if let Err(err) = func_result {
return Err(format!("op_func failed - {err}"))?;
}
match prot_after_result {
Ok(_) => Ok(()),
Err(err) => {
return Err(format!(
"failed to restore memory region protection at {:p} (orig: {:p}) size {:#x} (orig: {:#x}) - {}",
protect_ptr, config.pointer, chunk_size, config.size, err
))?;
}
}
}
pub fn protect<P>(
pointer: *mut P,
size: usize,
prot: Prot,
align_with: Option<usize>,
) -> Result<ProtectResult, Box<dyn Error>> {
let mut target_ptr = pointer;
let mut chunk_size = size;
if let Some(align_bits) = align_with {
let adjustment = align_to(target_ptr.cast(), align_bits, chunk_size);
target_ptr = adjustment.new_ptr;
chunk_size = adjustment.size_to_modify;
}
#[cfg(unix)]
let result = unix::protect(target_ptr, chunk_size, prot);
#[cfg(windows)]
let result = windows::protect(target_ptr, chunk_size, prot);
result
}
pub struct ProtectResult {
pub old: Option<u32>,
}
pub enum AllocFlags {
Default,
Custom(u64),
}
pub fn alloc<P>(
addr: Option<*mut P>,
size: usize,
prot: Prot,
flags: AllocFlags,
) -> Result<*mut P, Box<dyn Error>> {
#[cfg(unix)]
let result = unix::alloc(addr, size, prot, flags);
#[cfg(windows)]
let result = windows::alloc(addr, size, prot, flags);
result
}
fn align_to<P>(pointer: *mut P, bits: usize, chunk_size: usize) -> AlignToOutput<P> {
let current_addr = pointer.addr();
let new_addr = current_addr & !(bits - 1);
let diff: usize;
match new_addr {
new_addr if current_addr == new_addr => {
return AlignToOutput {
new_ptr: pointer,
size_to_modify: chunk_size,
};
}
new_addr if current_addr > new_addr => diff = current_addr - new_addr,
_ => diff = new_addr - current_addr,
};
AlignToOutput {
new_ptr: new_addr as *mut P,
size_to_modify: diff + chunk_size,
}
}
struct AlignToOutput<P> {
new_ptr: *mut P,
size_to_modify: usize,
}
fn last_error(prefix: &str) -> std::io::Error {
std::io::Error::new(
std::io::ErrorKind::Other,
format!("{prefix} - {err}", err = std::io::Error::last_os_error()),
)
}