#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
struct Segment {
start: u64,
bytes: Vec<u8>,
executable: bool,
#[serde(default)]
writable: bool,
}
impl Segment {
fn end(&self) -> u64 {
self.start + self.bytes.len() as u64
}
fn contains(&self, addr: u64) -> bool {
addr >= self.start && addr < self.end()
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct MemoryImage {
segments: Vec<Segment>,
#[serde(default)]
protections_known: bool,
}
impl MemoryImage {
pub fn add_segment(&mut self, start: u64, bytes: Vec<u8>, executable: bool, writable: bool) {
if bytes.is_empty() {
return;
}
let seg = Segment {
start,
bytes,
executable,
writable,
};
let pos = self.segments.partition_point(|s| s.start < seg.start);
self.segments.insert(pos, seg);
}
fn segment_at(&self, addr: u64) -> Option<&Segment> {
let pos = self.segments.partition_point(|s| s.start <= addr);
self.segments
.get(pos.checked_sub(1)?)
.filter(|s| s.contains(addr))
}
pub fn read_bytes(&self, addr: u64, n: usize) -> Option<Vec<u8>> {
let seg = self.segment_at(addr)?;
let off = (addr - seg.start) as usize;
let end = off.checked_add(n)?;
seg.bytes.get(off..end).map(|s| s.to_vec())
}
pub fn read_uint(&self, addr: u64, size: usize) -> Option<u64> {
if size == 0 || size > 8 {
return None;
}
let bytes = self.read_bytes(addr, size)?;
let mut value = 0u64;
for (i, &b) in bytes.iter().enumerate() {
value |= (b as u64) << (i * 8);
}
Some(value)
}
pub fn is_executable(&self, addr: u64) -> bool {
self.segment_at(addr).is_some_and(|s| s.executable)
}
pub fn is_known_writable(&self, addr: u64) -> bool {
self.protections_known && self.segment_at(addr).is_some_and(|s| s.writable)
}
pub fn contains(&self, addr: u64) -> bool {
self.segment_at(addr).is_some()
}
pub fn segment_bounds(&self, addr: u64) -> Option<(u64, u64)> {
self.segment_at(addr).map(|s| (s.start, s.end()))
}
pub fn protections_known(&self) -> bool {
self.protections_known
}
pub fn mark_protections_known(&mut self) {
self.protections_known = true;
}
pub fn is_empty(&self) -> bool {
self.segments.is_empty()
}
}
impl wazabin_binary::BinaryFormat for MemoryImage {
fn load_address(&self) -> u64 {
self.segments.first().map(|s| s.start).unwrap_or(0)
}
fn byte_at(&self, addr: u64) -> Option<u8> {
let seg = self.segment_at(addr)?;
seg.bytes.get((addr - seg.start) as usize).copied()
}
fn bytes_at(&self, addr: u64) -> Option<&[u8]> {
let seg = self.segment_at(addr)?;
seg.bytes.get((addr - seg.start) as usize..)
}
fn entry_points(&self) -> Vec<u64> {
Vec::new()
}
fn architecture(&self) -> wazabin_binary::Arch {
wazabin_binary::Arch::X86_64
}
fn segment_bounds(&self, addr: u64) -> Option<(u64, u64)> {
MemoryImage::segment_bounds(self, addr)
}
fn is_executable(&self, addr: u64) -> bool {
MemoryImage::is_executable(self, addr)
}
fn is_known_writable(&self, addr: u64) -> bool {
self.segment_at(addr).is_some_and(|s| s.writable)
}
fn is_known_read_only(&self, addr: u64) -> bool {
self.segment_at(addr).is_some_and(|s| !s.writable)
}
fn mapped_regions(&self) -> Vec<(u64, Vec<u8>, bool, bool)> {
self.segments
.iter()
.map(|s| (s.start, s.bytes.clone(), s.executable, s.writable))
.collect()
}
fn read_bytes(&self, addr: u64, n: usize) -> Option<Vec<u8>> {
MemoryImage::read_bytes(self, addr, n)
}
fn read_uint(&self, addr: u64, size: usize) -> Option<u64> {
MemoryImage::read_uint(self, addr, size)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn image() -> MemoryImage {
let mut img = MemoryImage::default();
img.add_segment(0x2000, vec![0xaa, 0xbb, 0xcc, 0xdd], true, false);
img.add_segment(0x1000, vec![0x01, 0x02, 0x03, 0x04], false, false);
img
}
#[test]
fn read_uint_little_endian() {
let img = image();
assert_eq!(img.read_uint(0x1000, 4), Some(0x04030201));
assert_eq!(img.read_uint(0x1000, 2), Some(0x0201));
assert_eq!(img.read_uint(0x1001, 1), Some(0x02));
}
#[test]
fn read_bytes_within_segment() {
let img = image();
assert_eq!(img.read_bytes(0x2001, 2), Some(vec![0xbb, 0xcc]));
}
#[test]
fn unmapped_and_straddling_are_none() {
let img = image();
assert_eq!(img.read_uint(0x500, 1), None);
assert_eq!(img.read_bytes(0x1003, 2), None);
assert_eq!(img.read_uint(0x1004, 1), None);
}
#[test]
fn executability() {
let img = image();
assert!(img.is_executable(0x2002));
assert!(!img.is_executable(0x1002));
assert!(!img.is_executable(0x9999));
}
#[test]
fn protections_known_is_off_by_default() {
let mut img = image();
assert!(!img.protections_known());
img.mark_protections_known();
assert!(img.protections_known());
}
#[test]
fn known_writable_requires_established_protections() {
let mut img = MemoryImage::default();
img.add_segment(0x1000, vec![0u8; 4], false, true); img.add_segment(0x2000, vec![0u8; 4], false, false);
assert!(!img.is_known_writable(0x1000));
assert!(!img.is_known_writable(0x2000));
img.mark_protections_known();
assert!(img.is_known_writable(0x1000), "writable segment now known");
assert!(!img.is_known_writable(0x2000), "read-only stays read-only");
assert!(!img.is_known_writable(0x9999), "unmapped is not writable");
}
}