use anyhow::Result;
use byteorder::ByteOrder;
use log::debug;
use crate::{aspace::AddressSpace, loader::pe::PE, module::Permissions, VA};
pub fn find_pe_nonrelocated_executable_pointers(pe: &PE) -> Result<Vec<VA>> {
let mut candidates: Vec<(VA, VA)> = vec![];
let min_addr = pe.module.address_space.base_address;
let max_addr = pe
.module
.sections
.iter()
.map(|section| section.virtual_range.end)
.max()
.unwrap();
for section in pe.module.sections.iter() {
let vstart: VA = section.virtual_range.start;
let vsize = (section.virtual_range.end - section.virtual_range.start) as usize;
let sec_buf = pe.module.address_space.read_bytes(vstart, vsize)?;
debug!(
"pointers: scanning section {:#x}-{:#x}",
section.virtual_range.start, section.virtual_range.end
);
if let crate::arch::Arch::X64 = pe.module.arch {
candidates.extend(
sec_buf
.windows(8)
.map(|b| byteorder::LittleEndian::read_u64(b) as VA)
.enumerate()
.filter(|&(_, va)| va >= min_addr && va < max_addr)
.filter(|&(_, va)| pe.module.probe_va(va, Permissions::X))
.map(|(i, va)| (vstart + (i as u64), va)),
)
} else {
candidates.extend(
sec_buf
.windows(4)
.map(|b| byteorder::LittleEndian::read_u32(b) as VA)
.enumerate()
.filter(|&(_, va)| va >= min_addr && va < max_addr)
.filter(|&(_, va)| pe.module.probe_va(va, Permissions::X))
.map(|(i, va)| (vstart + (i as u64), va)),
)
}
}
const CC: u8 = 0xCC;
const NOP: u8 = 0x90;
const RET: u8 = 0xC3;
const RET_FAR: u8 = 0xCB;
const RETN: u8 = 0xC2;
Ok(candidates
.into_iter()
.filter(|&(src, dst)| {
let mut buf = [0u8; 3];
if pe.module.address_space.read_into(dst - 3, &mut buf).is_ok() {
if buf[0] == RETN {
return true;
}
match buf[2] {
CC => return true,
NOP => return true,
RET => return true,
RET_FAR => return true,
_ => (),
}
debug!(
"pointers: candidate pointer {:#x}: pointed-to content at {:#x} does not follow ret/filler byte",
src, dst
);
false
} else {
true
}
})
.filter(|&(src, dst)| {
if let Ok(ptr) = pe.module.read_va_at_va(dst) {
if pe.module.probe_va(ptr, Permissions::R) {
debug!("pointers: candidate pointer {:#x}: valid pointer to {:#x}", src, dst);
false
} else {
true
}
} else {
true
}
})
.filter(|&(src, dst)| {
if pe.module.address_space.read_ascii(dst, 4).is_ok() {
debug!(
"pointers: candidate pointer {:#x}: points to a string at {:#x}",
src, dst
);
false
} else {
true
}
})
.map(|(src, dst)| {
debug!(
"pointers: candidate pointer: {:#x} points to valid content at {:#x}",
src, dst
);
dst
})
.collect())
}
#[cfg(test)]
mod tests {
use crate::rsrc::*;
use anyhow::Result;
#[test]
fn mimi() -> Result<()> {
let buf = get_buf(Rsrc::MIMI);
let pe = crate::loader::pe::PE::from_bytes(&buf)?;
let functions = super::find_pe_nonrelocated_executable_pointers(&pe)?;
assert!(functions.iter().any(|&function| function == 0x45CC62));
assert!(functions.iter().any(|&function| function == 0x45D028));
assert!(functions.iter().any(|&function| function == 0x45D16A));
Ok(())
}
}