use crate::error::Error;
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct SectionEntry {
pub virtual_address: u32,
pub virtual_size: u32,
pub raw_data_offset: u32,
pub raw_data_size: u32,
}
impl SectionEntry {
#[inline]
fn rva_end(self) -> u32 {
self.virtual_address.saturating_add(self.virtual_size)
}
#[inline]
fn raw_end(self) -> u32 {
self.raw_data_offset.saturating_add(self.raw_data_size)
}
}
#[derive(Debug, Clone)]
pub struct AddressMap<'a> {
file: &'a [u8],
image_base: u32,
sections: Vec<SectionEntry>,
}
impl<'a> AddressMap<'a> {
pub fn from_goblin(file: &'a [u8], pe: &goblin::pe::PE<'_>) -> Result<Self, Error> {
let oh = pe.header.optional_header.as_ref().ok_or(Error::TooShort {
expected: 1,
actual: 0,
context: "PE optional header",
})?;
if pe.is_64 {
return Err(Error::Not32Bit { magic: 0x020B });
}
let image_base = oh.windows_fields.image_base as u32;
let sections = pe
.sections
.iter()
.map(|s| SectionEntry {
virtual_address: s.virtual_address,
virtual_size: s.virtual_size,
raw_data_offset: s.pointer_to_raw_data,
raw_data_size: s.size_of_raw_data,
})
.collect();
Ok(Self {
file,
image_base,
sections,
})
}
#[inline]
pub fn image_base(&self) -> u32 {
self.image_base
}
#[inline]
pub fn file(&self) -> &'a [u8] {
self.file
}
#[cfg(test)]
pub(crate) fn from_parts(file: &'a [u8], image_base: u32, sections: Vec<SectionEntry>) -> Self {
Self {
file,
image_base,
sections,
}
}
pub fn rva_to_offset(&self, rva: u32) -> Result<usize, Error> {
for s in &self.sections {
if rva >= s.virtual_address && rva < s.rva_end() {
let offset_within = rva.wrapping_sub(s.virtual_address);
if offset_within >= s.raw_data_size {
return Err(Error::RvaInBssRegion { rva });
}
let raw = s.raw_data_offset.checked_add(offset_within).ok_or(
Error::ArithmeticOverflow {
context: "rva_to_offset raw_data_offset + offset_within",
},
)?;
return Ok(raw as usize);
}
}
Err(Error::RvaNotMapped { rva })
}
pub fn va_to_offset(&self, va: u32) -> Result<usize, Error> {
let rva = va
.checked_sub(self.image_base)
.ok_or(Error::VaBelowImageBase {
va,
image_base: self.image_base,
})?;
self.rva_to_offset(rva)
}
pub fn offset_to_va(&self, offset: usize) -> Option<u32> {
let offset = u32::try_from(offset).ok()?;
for s in &self.sections {
if offset >= s.raw_data_offset && offset < s.raw_end() {
let within = offset.checked_sub(s.raw_data_offset)?;
let rva = s.virtual_address.checked_add(within)?;
return self.image_base.checked_add(rva);
}
}
None
}
#[inline]
pub fn is_va_in_image(&self, va: u32) -> bool {
self.va_to_offset(va).is_ok()
}
pub fn slice_from_va(&self, va: u32, min_len: usize) -> Result<&'a [u8], Error> {
let offset = self.va_to_offset(va)?;
let remaining = self.file.len().saturating_sub(offset);
if remaining < min_len {
return Err(Error::TooShort {
expected: min_len,
actual: remaining,
context: "slice_from_va",
});
}
self.file.get(offset..).ok_or(Error::TooShort {
expected: min_len,
actual: remaining,
context: "slice_from_va",
})
}
pub fn slice_from_rva(&self, rva: u32, min_len: usize) -> Result<&'a [u8], Error> {
let offset = self.rva_to_offset(rva)?;
let remaining = self.file.len().saturating_sub(offset);
if remaining < min_len {
return Err(Error::TooShort {
expected: min_len,
actual: remaining,
context: "slice_from_rva",
});
}
self.file.get(offset..).ok_or(Error::TooShort {
expected: min_len,
actual: remaining,
context: "slice_from_rva",
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_map(file: &[u8], image_base: u32, sections: Vec<SectionEntry>) -> AddressMap<'_> {
AddressMap {
file,
image_base,
sections,
}
}
fn text_section() -> SectionEntry {
SectionEntry {
virtual_address: 0x1000,
virtual_size: 0x2000,
raw_data_offset: 0x200,
raw_data_size: 0x2000,
}
}
fn bss_section() -> SectionEntry {
SectionEntry {
virtual_address: 0x5000,
virtual_size: 0x1000,
raw_data_offset: 0x3000,
raw_data_size: 0x100, }
}
#[test]
fn test_rva_to_offset_basic() {
let file = vec![0u8; 0x5000];
let map = make_map(&file, 0x00400000, vec![text_section()]);
assert_eq!(map.rva_to_offset(0x1000).unwrap(), 0x200);
assert_eq!(map.rva_to_offset(0x1100).unwrap(), 0x300);
}
#[test]
fn test_rva_to_offset_end_of_section() {
let file = vec![0u8; 0x5000];
let map = make_map(&file, 0x00400000, vec![text_section()]);
assert_eq!(map.rva_to_offset(0x2FFF).unwrap(), 0x200 + 0x1FFF);
}
#[test]
fn test_rva_not_mapped() {
let file = vec![0u8; 0x5000];
let map = make_map(&file, 0x00400000, vec![text_section()]);
assert_eq!(
map.rva_to_offset(0x4000),
Err(Error::RvaNotMapped { rva: 0x4000 })
);
}
#[test]
fn test_rva_in_bss() {
let file = vec![0u8; 0x5000];
let map = make_map(&file, 0x00400000, vec![bss_section()]);
assert_eq!(
map.rva_to_offset(0x5200),
Err(Error::RvaInBssRegion { rva: 0x5200 })
);
}
#[test]
fn test_va_to_offset() {
let file = vec![0u8; 0x5000];
let map = make_map(&file, 0x00400000, vec![text_section()]);
assert_eq!(map.va_to_offset(0x00401000).unwrap(), 0x200);
}
#[test]
fn test_va_below_image_base() {
let file = vec![0u8; 0x5000];
let map = make_map(&file, 0x00400000, vec![text_section()]);
assert_eq!(
map.va_to_offset(0x100),
Err(Error::VaBelowImageBase {
va: 0x100,
image_base: 0x00400000,
})
);
}
#[test]
fn test_slice_from_va() {
let mut file = vec![0u8; 0x5000];
file[0x200] = 0x56;
file[0x201] = 0x42;
file[0x202] = 0x35;
file[0x203] = 0x21;
let map = make_map(&file, 0x00400000, vec![text_section()]);
let slice = map.slice_from_va(0x00401000, 4).unwrap();
assert_eq!(&slice[..4], b"VB5!");
}
#[test]
fn test_slice_from_va_too_short() {
let file = vec![0u8; 0x201]; let map = make_map(&file, 0x00400000, vec![text_section()]);
assert!(matches!(
map.slice_from_va(0x00401000, 4),
Err(Error::TooShort { .. })
));
}
#[test]
fn test_slice_from_rva() {
let mut file = vec![0u8; 0x5000];
file[0x200] = 0xAA;
let map = make_map(&file, 0x00400000, vec![text_section()]);
let slice = map.slice_from_rva(0x1000, 1).unwrap();
assert_eq!(slice[0], 0xAA);
}
#[test]
fn test_image_base() {
let file = vec![0u8; 1];
let map = make_map(&file, 0x00400000, vec![]);
assert_eq!(map.image_base(), 0x00400000);
}
#[test]
fn test_file_accessor() {
let file = vec![1, 2, 3];
let map = make_map(&file, 0, vec![]);
assert_eq!(map.file(), &[1, 2, 3]);
}
#[test]
fn test_multiple_sections() {
let file = vec![0u8; 0x8000];
let sec1 = SectionEntry {
virtual_address: 0x1000,
virtual_size: 0x1000,
raw_data_offset: 0x200,
raw_data_size: 0x1000,
};
let sec2 = SectionEntry {
virtual_address: 0x3000,
virtual_size: 0x2000,
raw_data_offset: 0x2000,
raw_data_size: 0x2000,
};
let map = make_map(&file, 0x00400000, vec![sec1, sec2]);
assert_eq!(map.rva_to_offset(0x1500).unwrap(), 0x200 + 0x500);
assert_eq!(map.rva_to_offset(0x3100).unwrap(), 0x2000 + 0x100);
assert!(map.rva_to_offset(0x2500).is_err());
}
}