Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support reading PE data directly from memory (sections are page aligned) #548

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Support reading PE data directly from memory (sections are page aligned)
  • Loading branch information
valaphee committed May 19, 2023
commit 60e78aacfd511bf49e40b1e5e05b5e136c09cbb9
4 changes: 2 additions & 2 deletions src/read/any.rs
Original file line number Diff line number Diff line change
@@ -253,9 +253,9 @@ impl<'data, R: ReadRef<'data>> File<'data, R> {
#[cfg(feature = "wasm")]
FileKind::Wasm => FileInternal::Wasm(wasm::WasmFile::parse(data)?),
#[cfg(feature = "pe")]
FileKind::Pe32 => FileInternal::Pe32(pe::PeFile32::parse(data)?),
FileKind::Pe32 => FileInternal::Pe32(pe::PeFile32::parse(data, false)?),
#[cfg(feature = "pe")]
FileKind::Pe64 => FileInternal::Pe64(pe::PeFile64::parse(data)?),
FileKind::Pe64 => FileInternal::Pe64(pe::PeFile64::parse(data, false)?),
#[cfg(feature = "coff")]
FileKind::Coff => FileInternal::Coff(coff::CoffFile::parse(data)?),
#[cfg(feature = "coff")]
2 changes: 1 addition & 1 deletion src/read/coff/file.rs
Original file line number Diff line number Diff line change
@@ -259,7 +259,7 @@ pub trait CoffHeader: Debug + Pod {
data: R,
offset: u64,
) -> read::Result<SectionTable<'data>> {
SectionTable::parse(self, data, offset)
SectionTable::parse(self, data, offset, false)
}

/// Read the symbol table and string table.
4 changes: 3 additions & 1 deletion src/read/coff/section.rs
Original file line number Diff line number Diff line change
@@ -15,6 +15,7 @@ use super::{CoffFile, CoffHeader, CoffRelocationIterator};
#[derive(Debug, Default, Clone, Copy)]
pub struct SectionTable<'data> {
sections: &'data [pe::ImageSectionHeader],
pub(crate) va_space: bool,
}

impl<'data> SectionTable<'data> {
@@ -26,11 +27,12 @@ impl<'data> SectionTable<'data> {
header: &Coff,
data: R,
offset: u64,
va_space: bool,
) -> Result<Self> {
let sections = data
.read_slice_at(offset, header.number_of_sections() as usize)
.read_error("Invalid COFF/PE section headers")?;
Ok(SectionTable { sections })
Ok(SectionTable { sections, va_space })
}

/// Iterate over the section headers.
6 changes: 3 additions & 3 deletions src/read/pe/data_directory.rs
Original file line number Diff line number Diff line change
@@ -170,17 +170,17 @@ impl pe::ImageDataDirectory {
(self.virtual_address.get(LE), self.size.get(LE))
}

/// Return the file offset and size of this directory entry.
/// Return the offset and size of this directory entry.
///
/// This function has some limitations:
/// - It requires that the data is contained in a single section.
/// - It uses the size field of the directory entry, which is
/// not desirable for all data directories.
/// - It uses the `virtual_address` of the directory entry as an address,
/// which is not valid for `IMAGE_DIRECTORY_ENTRY_SECURITY`.
pub fn file_range(&self, sections: &SectionTable<'_>) -> Result<(u32, u32)> {
pub fn range(&self, sections: &SectionTable<'_>) -> Result<(u32, u32)> {
let (offset, section_size) = sections
.pe_file_range_at(self.virtual_address.get(LE))
.pe_range_at(self.virtual_address.get(LE))
.read_error("Invalid data dir virtual address")?;
let size = self.size.get(LE);
if size > section_size {
9 changes: 5 additions & 4 deletions src/read/pe/file.rs
Original file line number Diff line number Diff line change
@@ -40,12 +40,12 @@ where
Pe: ImageNtHeaders,
R: ReadRef<'data>,
{
/// Parse the raw PE file data.
pub fn parse(data: R) -> Result<Self> {
/// Parse the raw PE data.
pub fn parse(data: R, va_space: bool) -> Result<Self> {
let dos_header = pe::ImageDosHeader::parse(data)?;
let mut offset = dos_header.nt_headers_offset().into();
let (nt_headers, data_directories) = Pe::parse(data, &mut offset)?;
let sections = nt_headers.sections(data, offset)?;
let sections = nt_headers.sections(data, offset, va_space)?;
let coff_symbols = nt_headers.symbols(data);
let image_base = nt_headers.optional_header().image_base();

@@ -613,8 +613,9 @@ pub trait ImageNtHeaders: Debug + Pod {
&self,
data: R,
offset: u64,
va_space: bool,
) -> read::Result<SectionTable<'data>> {
SectionTable::parse(self.file_header(), data, offset)
SectionTable::parse(self.file_header(), data, offset, va_space)
}

/// Read the COFF symbol table and string table.
50 changes: 25 additions & 25 deletions src/read/pe/section.rs
Original file line number Diff line number Diff line change
@@ -91,12 +91,12 @@ where

#[inline]
fn file_range(&self) -> (u64, u64) {
let (offset, size) = self.section.pe_file_range();
let (offset, size) = self.section.pe_range(self.file.common.sections.va_space);
(u64::from(offset), u64::from(size))
}

fn data(&self) -> Result<&'data [u8]> {
self.section.pe_data(self.file.data)
self.section.pe_data(self.file.data, self.file.common.sections.va_space)
}

fn data_range(&self, address: u64, size: u64) -> Result<Option<&'data [u8]>> {
@@ -221,7 +221,7 @@ where

#[inline]
fn file_range(&self) -> Option<(u64, u64)> {
let (offset, size) = self.section.pe_file_range();
let (offset, size) = self.section.pe_range(false);
if size == 0 {
None
} else {
@@ -230,7 +230,7 @@ where
}

fn data(&self) -> Result<&'data [u8]> {
self.section.pe_data(self.file.data)
self.section.pe_data(self.file.data, false)
}

fn data_range(&self, address: u64, size: u64) -> Result<Option<&'data [u8]>> {
@@ -292,12 +292,12 @@ where
}

impl<'data> SectionTable<'data> {
/// Return the file offset of the given virtual address, and the size up
/// Return the offset of the given virtual address, and the size up
/// to the end of the section containing it.
///
/// Returns `None` if no section contains the address.
pub fn pe_file_range_at(&self, va: u32) -> Option<(u32, u32)> {
self.iter().find_map(|section| section.pe_file_range_at(va))
pub fn pe_range_at(&self, va: u32) -> Option<(u32, u32)> {
self.iter().find_map(|section| section.pe_range_at(va, self.va_space))
}

/// Return the data starting at the given virtual address, up to the end of the
@@ -307,7 +307,7 @@ impl<'data> SectionTable<'data> {
///
/// Returns `None` if no section contains the address.
pub fn pe_data_at<R: ReadRef<'data>>(&self, data: R, va: u32) -> Option<&'data [u8]> {
self.iter().find_map(|section| section.pe_data_at(data, va))
self.iter().find_map(|section| section.pe_data_at(data, va, self.va_space))
}

/// Return the data of the section that contains the given virtual address in a PE file.
@@ -321,7 +321,7 @@ impl<'data> SectionTable<'data> {
va: u32,
) -> Option<(&'data [u8], u32)> {
self.iter()
.find_map(|section| section.pe_data_containing(data, va))
.find_map(|section| section.pe_data_containing(data, va, self.va_space))
}

/// Return the section that contains a given virtual address.
@@ -331,24 +331,28 @@ impl<'data> SectionTable<'data> {
}

impl pe::ImageSectionHeader {
/// Return the offset and size of the section in a PE file.
/// Return the offset and size of the section
///
/// The size of the range will be the minimum of the file size and virtual size.
pub fn pe_file_range(&self) -> (u32, u32) {
pub fn pe_range(&self, va_space: bool) -> (u32, u32) {
// Pointer and size will be zero for uninitialized data; we don't need to validate this.
let offset = self.pointer_to_raw_data.get(LE);
let offset = if va_space {
self.virtual_address.get(LE)
} else {
self.pointer_to_raw_data.get(LE)
};
let size = cmp::min(self.virtual_size.get(LE), self.size_of_raw_data.get(LE));
(offset, size)
}

/// Return the file offset of the given virtual address, and the remaining size up
/// Return the offset of the given virtual address, and the remaining size up
/// to the end of the section.
///
/// Returns `None` if the section does not contain the address.
pub fn pe_file_range_at(&self, va: u32) -> Option<(u32, u32)> {
pub fn pe_range_at(&self, va: u32, va_space: bool) -> Option<(u32, u32)> {
let section_va = self.virtual_address.get(LE);
let offset = va.checked_sub(section_va)?;
let (section_offset, section_size) = self.pe_file_range();
let (section_offset, section_size) = self.pe_range(va_space);
// Address must be within section (and not at its end).
if offset < section_size {
Some((section_offset.checked_add(offset)?, section_size - offset))
@@ -357,16 +361,11 @@ impl pe::ImageSectionHeader {
}
}

/// Return the virtual address and size of the section.
pub fn pe_address_range(&self) -> (u32, u32) {
(self.virtual_address.get(LE), self.virtual_size.get(LE))
}

/// Return the section data in a PE file.
///
/// The length of the data will be the minimum of the file size and virtual size.
pub fn pe_data<'data, R: ReadRef<'data>>(&self, data: R) -> Result<&'data [u8]> {
let (offset, size) = self.pe_file_range();
pub fn pe_data<'data, R: ReadRef<'data>>(&self, data: R, va_space: bool) -> Result<&'data [u8]> {
let (offset, size) = self.pe_range(va_space);
data.read_bytes_at(offset.into(), size.into())
.read_error("Invalid PE section offset or size")
}
@@ -377,8 +376,8 @@ impl pe::ImageSectionHeader {
/// Ignores sections with invalid data.
///
/// Returns `None` if the section does not contain the address.
pub fn pe_data_at<'data, R: ReadRef<'data>>(&self, data: R, va: u32) -> Option<&'data [u8]> {
let (offset, size) = self.pe_file_range_at(va)?;
pub fn pe_data_at<'data, R: ReadRef<'data>>(&self, data: R, va: u32, va_space: bool) -> Option<&'data [u8]> {
let (offset, size) = self.pe_range_at(va, va_space)?;
data.read_bytes_at(offset.into(), size.into()).ok()
}

@@ -403,10 +402,11 @@ impl pe::ImageSectionHeader {
&self,
data: R,
va: u32,
va_space: bool,
) -> Option<(&'data [u8], u32)> {
let section_va = self.virtual_address.get(LE);
let offset = va.checked_sub(section_va)?;
let (section_offset, section_size) = self.pe_file_range();
let (section_offset, section_size) = self.pe_range(va_space);
// Address must be within section (and not at its end).
if offset < section_size {
let section_data = data