use super::{
binary::{
cli::{Header, Metadata, RVASize},
heap::Reader,
metadata, method,
},
resolution::{read, Resolution},
};
use object::{
endian::{LittleEndian, U32Bytes},
pe::{self, ImageDataDirectory},
read::{
pe::{PeFile32, PeFile64, SectionTable},
Error as ObjectReadError, FileKind,
},
};
use scroll::{Error as ScrollError, Pread};
use thiserror::Error;
use DLLError::*;
#[derive(Debug)]
pub struct DLL<'a> {
buffer: &'a [u8],
pub cli: Header,
sections: SectionTable<'a>,
}
#[derive(Debug, Error)]
pub enum DLLError {
#[error("PE parsing: {0}")]
PERead(#[from] ObjectReadError),
#[error("CLI metadata: {0}")]
CLI(#[from] ScrollError),
#[error("Other parsing: {0}")]
Other(&'static str),
}
pub type Result<T> = std::result::Result<T, DLLError>;
impl<'a> DLL<'a> {
pub fn parse(bytes: &'a [u8]) -> Result<DLL<'a>> {
let (sections, dir) = match FileKind::parse(bytes)? {
FileKind::Pe32 => {
let file = PeFile32::parse(bytes)?;
(
file.section_table(),
file.data_directory(pe::IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR),
)
}
FileKind::Pe64 => {
let file = PeFile64::parse(bytes)?;
(
file.section_table(),
file.data_directory(pe::IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR),
)
}
_ => return Err(Other("invalid object type, must be PE32 or PE64")),
};
let cli_b = dir
.ok_or(Other("missing CLI metadata data directory in PE image"))?
.data(bytes, §ions)?;
Ok(DLL {
buffer: bytes,
cli: cli_b.pread_with(0, scroll::LE)?,
sections,
})
}
pub fn at_rva(&self, rva: &RVASize) -> Result<&'a [u8]> {
let dir = ImageDataDirectory {
virtual_address: U32Bytes::new(LittleEndian, rva.rva),
size: U32Bytes::new(LittleEndian, rva.size),
};
dir.data(self.buffer, &self.sections).map_err(PERead)
}
pub(crate) fn raw_rva(&self, rva: u32) -> Result<&'a [u8]> {
self.sections
.pe_data_at(self.buffer, rva)
.ok_or(Other("bad stream offset"))
}
fn get_stream(&self, name: &'static str) -> Result<Option<&'a [u8]>> {
let meta = self.get_cli_metadata()?;
let Some(header) = meta.stream_headers.iter().find(|h| h.name == name) else {
return Ok(None);
};
let data = self.raw_rva(self.cli.metadata.rva + header.offset)?;
Ok(Some(&data[..header.size as usize]))
}
pub fn get_heap<T: Reader<'a>>(&self) -> Result<T> {
Ok(T::new(self.get_stream(T::NAME)?.unwrap_or(&[])))
}
pub fn get_cli_metadata(&self) -> Result<Metadata<'a>> {
self.at_rva(&self.cli.metadata)?.pread(0).map_err(CLI)
}
pub fn get_logical_metadata(&self) -> Result<metadata::header::Header> {
self.get_stream("#~")?
.ok_or(Other("unable to find metadata stream"))?
.pread(0)
.map_err(CLI)
}
#[allow(clippy::nonminimal_bool)]
pub fn get_method(&self, def: &metadata::table::MethodDef) -> Result<method::Method> {
let bytes = self.raw_rva(def.rva)?;
let mut offset = 0;
if !check_bitmask!(bytes[0], 0x2) {
offset = 4 - (def.rva as usize % 4);
}
bytes.pread(offset).map_err(CLI)
}
pub fn resolve(&self, opts: read::Options) -> Result<Resolution<'a>> {
read::read_impl(self, opts)
}
}