use std::collections::HashSet;
use crate::errors::FileParseError;
use crate::field::Field;
use crate::utils::{extract_u16, extract_u32};
pub struct ExportDirectory {
pub characteristics: Field<u32>,
pub time_date_stamp: Field<u32>,
pub major_version: Field<u16>,
pub minor_version: Field<u16>,
pub name: Field<u32>,
pub base: Field<u32>,
pub number_of_functions: Field<u32>,
pub number_of_names: Field<u32>,
pub address_of_functions: Field<u32>,
pub address_of_names: Field<u32>,
pub address_of_name_ordinals: Field<u32>,
}
pub enum FunctionExport {
Local {
ordinal: u16,
index: usize,
function_rva: Field<u32>,
},
Forwarder {
ordinal: u16,
index: usize,
function_rva: Field<u32>,
forwarder: String,
},
}
pub struct NamedExport {
pub name: String,
pub ordinal: u16,
pub function_rva: Field<u32>,
pub name_rva: Field<u32>,
pub name_ordinal_index: Field<u16>,
}
pub struct Exports {
pub directory: ExportDirectory,
pub functions: Vec<FunctionExport>,
pub named: Vec<NamedExport>,
}
impl ExportDirectory {
pub fn parse(buffer: &[u8], offset: usize) -> Result<Self, FileParseError> {
if buffer.len() < offset + 40 {
return Err(FileParseError::BufferOverflow);
}
Ok(ExportDirectory {
characteristics: Field::new(extract_u32(buffer, offset)?, offset, 4),
time_date_stamp: Field::new(extract_u32(buffer, offset + 4)?, offset + 4, 4),
major_version: Field::new(extract_u16(buffer, offset + 8)?, offset + 8, 2),
minor_version: Field::new(extract_u16(buffer, offset + 10)?, offset + 10, 2),
name: Field::new(extract_u32(buffer, offset + 12)?, offset + 12, 4),
base: Field::new(extract_u32(buffer, offset + 16)?, offset + 16, 4),
number_of_functions: Field::new(extract_u32(buffer, offset + 20)?, offset + 20, 4),
number_of_names: Field::new(extract_u32(buffer, offset + 24)?, offset + 24, 4),
address_of_functions: Field::new(extract_u32(buffer, offset + 28)?, offset + 28, 4),
address_of_names: Field::new(extract_u32(buffer, offset + 32)?, offset + 32, 4),
address_of_name_ordinals: Field::new(extract_u32(buffer, offset + 36)?, offset + 36, 4),
})
}
pub fn dll_name(
&self,
buffer: &[u8],
rva_to_offset: impl Fn(u32) -> Result<usize, FileParseError>,
) -> Result<String, FileParseError> {
read_c_string(buffer, rva_to_offset(self.name.value)?)
}
pub fn is_forwarder_rva(
&self,
export_dir_rva: u32,
export_dir_size: u32,
function_rva: u32,
) -> bool {
function_rva >= export_dir_rva
&& function_rva < export_dir_rva.saturating_add(export_dir_size)
}
}
impl Exports {
pub fn parse(
buffer: &[u8],
directory: &ExportDirectory,
export_dir_rva: u32,
export_dir_size: u32,
rva_to_offset: impl Fn(u32) -> Result<usize, FileParseError>,
) -> Result<Self, FileParseError> {
let named = Self::parse_named(buffer, directory, &rva_to_offset)?;
let functions = Self::parse_functions(
buffer,
directory,
export_dir_rva,
export_dir_size,
&rva_to_offset,
)?;
Ok(Exports {
directory: ExportDirectory {
characteristics: directory.characteristics.clone(),
time_date_stamp: directory.time_date_stamp.clone(),
major_version: directory.major_version.clone(),
minor_version: directory.minor_version.clone(),
name: directory.name.clone(),
base: directory.base.clone(),
number_of_functions: directory.number_of_functions.clone(),
number_of_names: directory.number_of_names.clone(),
address_of_functions: directory.address_of_functions.clone(),
address_of_names: directory.address_of_names.clone(),
address_of_name_ordinals: directory.address_of_name_ordinals.clone(),
},
functions,
named,
})
}
pub fn parse_named(
buffer: &[u8],
directory: &ExportDirectory,
rva_to_offset: impl Fn(u32) -> Result<usize, FileParseError>,
) -> Result<Vec<NamedExport>, FileParseError> {
let count = directory.number_of_names.value as usize;
if count == 0 {
return Ok(Vec::new());
}
let names_table = rva_to_offset(directory.address_of_names.value)?;
let ordinals_table = rva_to_offset(directory.address_of_name_ordinals.value)?;
let functions_table = rva_to_offset(directory.address_of_functions.value)?;
let ordinal_base = directory.base.value;
let mut named = Vec::with_capacity(count);
for i in 0..count {
let name_rva_off = names_table + i * 4;
let name_rva = Field::new(extract_u32(buffer, name_rva_off)?, name_rva_off, 4);
let ord_off = ordinals_table + i * 2;
let name_ordinal_index = Field::new(extract_u16(buffer, ord_off)?, ord_off, 2);
let func_off = functions_table + name_ordinal_index.value as usize * 4;
let function_rva = Field::new(extract_u32(buffer, func_off)?, func_off, 4);
let name = read_c_string(buffer, rva_to_offset(name_rva.value)?)?;
let ordinal = ordinal_base
.checked_add(name_ordinal_index.value as u32)
.and_then(|v| u16::try_from(v).ok())
.ok_or(FileParseError::ValueTooLarge)?;
named.push(NamedExport {
name,
ordinal,
function_rva,
name_rva,
name_ordinal_index,
});
}
Ok(named)
}
pub fn parse_functions(
buffer: &[u8],
directory: &ExportDirectory,
export_dir_rva: u32,
export_dir_size: u32,
rva_to_offset: impl Fn(u32) -> Result<usize, FileParseError>,
) -> Result<Vec<FunctionExport>, FileParseError> {
let count = directory.number_of_functions.value as usize;
if count == 0 {
return Ok(Vec::new());
}
let functions_table = rva_to_offset(directory.address_of_functions.value)?;
let ordinal_base = directory.base.value;
let mut functions = Vec::with_capacity(count);
for i in 0..count {
let func_off = functions_table + i * 4;
let function_rva = Field::new(extract_u32(buffer, func_off)?, func_off, 4);
let ordinal = ordinal_base
.checked_add(i as u32)
.and_then(|v| u16::try_from(v).ok())
.ok_or(FileParseError::ValueTooLarge)?;
if directory.is_forwarder_rva(export_dir_rva, export_dir_size, function_rva.value) {
let forwarder = read_c_string(buffer, rva_to_offset(function_rva.value)?)?;
functions.push(FunctionExport::Forwarder {
ordinal,
index: i,
function_rva,
forwarder,
});
} else {
functions.push(FunctionExport::Local {
ordinal,
index: i,
function_rva,
});
}
}
Ok(functions)
}
pub fn ordinal_only_exports(&self) -> Vec<&FunctionExport> {
let named_indices: HashSet<usize> = self
.named
.iter()
.map(|entry| entry.name_ordinal_index.value as usize)
.collect();
self.functions
.iter()
.filter(|entry| match entry {
FunctionExport::Local { index, .. } | FunctionExport::Forwarder { index, .. } => {
!named_indices.contains(index)
}
})
.collect()
}
}
fn read_c_string(buffer: &[u8], offset: usize) -> Result<String, FileParseError> {
let tail = buffer.get(offset..).ok_or(FileParseError::BufferOverflow)?;
let end = tail
.iter()
.position(|&b| b == 0)
.ok_or(FileParseError::InvalidFileFormat)?;
std::str::from_utf8(&tail[..end])
.map(|s| s.to_owned())
.map_err(|_| FileParseError::InvalidFileFormat)
}