use super::{
binary::{
cli::{Header, Metadata, RVASize},
heap::{BlobReader, GUIDReader, Reader, StringsReader, UserStringReader},
metadata, method,
},
resolution::{Resolution, read},
};
use DLLError::*;
use object::{
endian::{LittleEndian, U32Bytes},
pe::{self, ImageDataDirectory},
read::{
Error as ObjectReadError, FileKind,
pe::{PeFile32, PeFile64, SectionTable},
},
};
use scroll::{Error as ScrollError, Pread};
use thiserror::Error;
#[derive(Debug)]
pub struct DLL<'a> {
buffer: &'a [u8],
pub cli: Header,
sections: SectionTable<'a>,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ParseError {
Truncated { offset: usize },
BadElementType { tag: u8 },
UnknownTableBit { bit: u8 },
BadTokenTag { tag: u8 },
BadSignatureKind { tag: u8, context: &'static str },
BadNativeIntrinsic { tag: u8 },
BadStructure(&'static str),
HeapOutOfRange { heap: &'static str, offset: usize },
BadCompressedInt { offset: usize },
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ValidityError {
BadExceptionClauseFlags { flags: u32 },
BadVarianceFlags { flags: u16 },
BadCustomAttributeType { tag: u8 },
BadFlags { context: &'static str, flags: u32 },
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResolveError {
IndexOutOfRange {
kind: &'static str,
index: usize,
max: usize,
},
BadTokenTarget { context: &'static str },
LazyLookupFailed(&'static str),
IndexArithmetic { context: &'static str },
MissingRow { table: &'static str, index: usize },
GenericArityMismatch { expected: usize, got: usize },
}
#[derive(Debug, Error)]
pub enum DLLError {
#[error("PE parsing: {0}")]
PERead(#[from] ObjectReadError),
#[error("Parse error: {0}")]
Parse(ParseError),
#[error("Validity error: {0}")]
Validity(ValidityError),
#[error("Resolution error: {0}")]
Resolve(ResolveError),
#[error("Decode error: {0}")]
Decode(#[from] ScrollError),
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Truncated { offset } => write!(f, "truncated data at byte offset {offset}"),
Self::BadElementType { tag } => write!(f, "invalid element type tag 0x{tag:02X}"),
Self::UnknownTableBit { bit } => write!(f, "unknown metadata table bit {bit}"),
Self::BadTokenTag { tag } => write!(f, "invalid metadata token tag 0x{tag:02X}"),
Self::BadSignatureKind { tag, context } => {
write!(f, "invalid signature kind 0x{tag:02X} ({context})")
}
Self::BadNativeIntrinsic { tag } => {
write!(f, "invalid native intrinsic tag 0x{tag:02X}")
}
Self::BadStructure(msg) => write!(f, "invalid metadata structure: {msg}"),
Self::HeapOutOfRange { heap, offset } => {
write!(f, "{heap} heap offset {offset} is out of range")
}
Self::BadCompressedInt { offset } => {
write!(f, "invalid compressed integer at byte offset {offset}")
}
}
}
}
impl std::error::Error for ParseError {}
impl std::fmt::Display for ValidityError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BadExceptionClauseFlags { flags } => {
write!(f, "invalid exception clause flags 0x{flags:08X}")
}
Self::BadVarianceFlags { flags } => {
write!(f, "invalid generic variance/constraint flags 0x{flags:04X}")
}
Self::BadCustomAttributeType { tag } => {
write!(f, "invalid custom attribute type tag {tag}")
}
Self::BadFlags { context, flags } => {
write!(f, "invalid metadata flags 0x{flags:X} ({context})")
}
}
}
}
impl std::error::Error for ValidityError {}
impl std::fmt::Display for ResolveError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::IndexOutOfRange { kind, index, max } => {
write!(f, "{kind} index {index} is out of range (max {max})")
}
Self::BadTokenTarget { context } => {
write!(f, "token or coded index points to an invalid table ({context})")
}
Self::LazyLookupFailed(msg) => write!(f, "lazy metadata lookup failed: {msg}"),
Self::IndexArithmetic { context } => {
write!(f, "metadata index arithmetic failed ({context})")
}
Self::MissingRow { table, index } => {
write!(f, "missing required {table} row at index {index}")
}
Self::GenericArityMismatch { expected, got } => {
write!(f, "generic arity mismatch: expected {expected}, got {got}")
}
}
}
}
impl std::error::Error for ResolveError {}
impl From<ParseError> for DLLError {
fn from(value: ParseError) -> Self {
Self::Parse(value)
}
}
impl From<ValidityError> for DLLError {
fn from(value: ValidityError) -> Self {
Self::Validity(value)
}
}
impl From<ResolveError> for DLLError {
fn from(value: ResolveError) -> Self {
Self::Resolve(value)
}
}
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(ParseError::BadStructure("invalid object type, must be PE32 or PE64").into())
}
};
let cli_b = dir
.ok_or(ParseError::BadStructure(
"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(ParseError::BadStructure("bad stream offset").into())
}
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_all_streams(
&self,
) -> Result<(
StringsReader<'a>,
BlobReader<'a>,
GUIDReader<'a>,
UserStringReader<'a>,
metadata::header::Header,
)> {
let meta = self.get_cli_metadata()?;
let mut strings = None;
let mut blobs = None;
let mut guids = None;
let mut userstrings = None;
let mut logical = None;
for header in &meta.stream_headers {
let data = self.raw_rva(self.cli.metadata.rva + header.offset)?;
let stream = &data[..header.size as usize];
if header.name == <StringsReader<'a> as Reader<'a>>::NAME && strings.is_none() {
strings = Some(stream);
} else if header.name == <BlobReader<'a> as Reader<'a>>::NAME && blobs.is_none() {
blobs = Some(stream);
} else if header.name == <GUIDReader<'a> as Reader<'a>>::NAME && guids.is_none() {
guids = Some(stream);
} else if header.name == <UserStringReader<'a> as Reader<'a>>::NAME && userstrings.is_none() {
userstrings = Some(stream);
} else if header.name == "#~" && logical.is_none() {
logical = Some(stream);
}
}
Ok((
StringsReader::new(strings.unwrap_or(&[])),
BlobReader::new(blobs.unwrap_or(&[])),
GUIDReader::new(guids.unwrap_or(&[])),
UserStringReader::new(userstrings.unwrap_or(&[])),
logical
.ok_or(ParseError::BadStructure("unable to find metadata stream"))?
.pread(0)?,
))
}
pub fn get_cli_metadata(&self) -> Result<Metadata<'a>> {
self.at_rva(&self.cli.metadata)?.pread(0).map_err(Decode)
}
pub fn get_logical_metadata(&self) -> Result<metadata::header::Header> {
self.get_stream("#~")?
.ok_or(ParseError::BadStructure("unable to find metadata stream"))?
.pread(0)
}
#[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(Decode)
}
pub(crate) fn method_bytes(&self, def: &metadata::table::MethodDef) -> Result<(&'a [u8], usize)> {
let bytes = self.raw_rva(def.rva)?;
let offset = if !check_bitmask!(bytes[0], 0x2) {
4 - (def.rva as usize % 4)
} else {
0
};
Ok((bytes, offset))
}
pub fn resolve(&self, opts: read::Options) -> Result<Resolution<'a>> {
read::read_impl(self, opts)
}
}