| use crate::error; |
| use alloc::string::ToString; |
| use alloc::vec::Vec; |
| use scroll::Pread; |
| |
| use super::options; |
| use super::section_table; |
| |
| use crate::pe::data_directories::DataDirectory; |
| use core::cmp; |
| |
| use log::debug; |
| |
| pub fn is_in_range(rva: usize, r1: usize, r2: usize) -> bool { |
| r1 <= rva && rva < r2 |
| } |
| |
| // reference: Peter Ferrie. Reliable algorithm to extract overlay of a PE. https://bit.ly/2vBX2bR |
| #[inline] |
| fn aligned_pointer_to_raw_data(pointer_to_raw_data: usize) -> usize { |
| const PHYSICAL_ALIGN: usize = 0x1ff; |
| pointer_to_raw_data & !PHYSICAL_ALIGN |
| } |
| |
| #[inline] |
| fn section_read_size(section: §ion_table::SectionTable, file_alignment: u32) -> usize { |
| fn round_size(size: usize) -> usize { |
| const PAGE_MASK: usize = 0xfff; |
| (size + PAGE_MASK) & !PAGE_MASK |
| } |
| |
| // Paraphrased from https://reverseengineering.stackexchange.com/a/4326 (by Peter Ferrie). |
| // |
| // Handles the corner cases such as mis-aligned pointers (round down) and sizes (round up) |
| // Further rounding corner cases: |
| // - the physical pointer should be rounded down to a multiple of 512, regardless of the value in the header |
| // - the read size is rounded up by using a combination of the file alignment and 4kb |
| // - the virtual size is always rounded up to a multiple of 4kb, regardless of the value in the header. |
| // |
| // Reference C implementation: |
| // |
| // long pointerToRaw = section.get(POINTER_TO_RAW_DATA); |
| // long alignedpointerToRaw = pointerToRaw & ~0x1ff; |
| // long sizeOfRaw = section.get(SIZE_OF_RAW_DATA); |
| // long readsize = ((pointerToRaw + sizeOfRaw) + filealign - 1) & ~(filealign - 1)) - alignedpointerToRaw; |
| // readsize = min(readsize, (sizeOfRaw + 0xfff) & ~0xfff); |
| // long virtsize = section.get(VIRTUAL_SIZE); |
| // |
| // if (virtsize) |
| // { |
| // readsize = min(readsize, (virtsize + 0xfff) & ~0xfff); |
| // } |
| |
| let file_alignment = file_alignment as usize; |
| let size_of_raw_data = section.size_of_raw_data as usize; |
| let virtual_size = section.virtual_size as usize; |
| let read_size = { |
| let read_size = |
| ((section.pointer_to_raw_data as usize + size_of_raw_data + file_alignment - 1) |
| & !(file_alignment - 1)) |
| - aligned_pointer_to_raw_data(section.pointer_to_raw_data as usize); |
| cmp::min(read_size, round_size(size_of_raw_data)) |
| }; |
| |
| if virtual_size == 0 { |
| read_size |
| } else { |
| cmp::min(read_size, round_size(virtual_size)) |
| } |
| } |
| |
| fn rva2offset(rva: usize, section: §ion_table::SectionTable) -> usize { |
| (rva - section.virtual_address as usize) |
| + aligned_pointer_to_raw_data(section.pointer_to_raw_data as usize) |
| } |
| |
| fn is_in_section(rva: usize, section: §ion_table::SectionTable, file_alignment: u32) -> bool { |
| let section_rva = section.virtual_address as usize; |
| is_in_range( |
| rva, |
| section_rva, |
| section_rva + section_read_size(section, file_alignment), |
| ) |
| } |
| |
| pub fn find_offset( |
| rva: usize, |
| sections: &[section_table::SectionTable], |
| file_alignment: u32, |
| opts: &options::ParseOptions, |
| ) -> Option<usize> { |
| if opts.resolve_rva { |
| if file_alignment == 0 || file_alignment & (file_alignment - 1) != 0 { |
| return None; |
| } |
| for (i, section) in sections.iter().enumerate() { |
| debug!( |
| "Checking {} for {:#x} ∈ {:#x}..{:#x}", |
| section.name().unwrap_or(""), |
| rva, |
| section.virtual_address, |
| section.virtual_address + section.virtual_size |
| ); |
| if is_in_section(rva, §ion, file_alignment) { |
| let offset = rva2offset(rva, §ion); |
| debug!( |
| "Found in section {}({}), remapped into offset {:#x}", |
| section.name().unwrap_or(""), |
| i, |
| offset |
| ); |
| return Some(offset); |
| } |
| } |
| None |
| } else { |
| Some(rva) |
| } |
| } |
| |
| pub fn find_offset_or( |
| rva: usize, |
| sections: &[section_table::SectionTable], |
| file_alignment: u32, |
| opts: &options::ParseOptions, |
| msg: &str, |
| ) -> error::Result<usize> { |
| find_offset(rva, sections, file_alignment, opts) |
| .ok_or_else(|| error::Error::Malformed(msg.to_string())) |
| } |
| |
| pub fn try_name<'a>( |
| bytes: &'a [u8], |
| rva: usize, |
| sections: &[section_table::SectionTable], |
| file_alignment: u32, |
| opts: &options::ParseOptions, |
| ) -> error::Result<&'a str> { |
| match find_offset(rva, sections, file_alignment, opts) { |
| Some(offset) => Ok(bytes.pread::<&str>(offset)?), |
| None => Err(error::Error::Malformed(format!( |
| "Cannot find name from rva {:#x} in sections: {:?}", |
| rva, sections |
| ))), |
| } |
| } |
| |
| pub fn get_data<'a, T>( |
| bytes: &'a [u8], |
| sections: &[section_table::SectionTable], |
| directory: DataDirectory, |
| file_alignment: u32, |
| ) -> error::Result<T> |
| where |
| T: scroll::ctx::TryFromCtx<'a, scroll::Endian, Error = scroll::Error>, |
| { |
| get_data_with_opts( |
| bytes, |
| sections, |
| directory, |
| file_alignment, |
| &options::ParseOptions::default(), |
| ) |
| } |
| |
| pub fn get_data_with_opts<'a, T>( |
| bytes: &'a [u8], |
| sections: &[section_table::SectionTable], |
| directory: DataDirectory, |
| file_alignment: u32, |
| opts: &options::ParseOptions, |
| ) -> error::Result<T> |
| where |
| T: scroll::ctx::TryFromCtx<'a, scroll::Endian, Error = scroll::Error>, |
| { |
| let rva = directory.virtual_address as usize; |
| let offset = find_offset(rva, sections, file_alignment, opts) |
| .ok_or_else(|| error::Error::Malformed(directory.virtual_address.to_string()))?; |
| let result: T = bytes.pread_with(offset, scroll::LE)?; |
| Ok(result) |
| } |
| |
| pub(crate) fn pad(length: usize, alignment: Option<usize>) -> Option<Vec<u8>> { |
| match alignment { |
| Some(alignment) => { |
| let overhang = length % alignment; |
| if overhang != 0 { |
| let repeat = alignment - overhang; |
| Some(vec![0u8; repeat]) |
| } else { |
| None |
| } |
| } |
| None => None, |
| } |
| } |