use hashbrown::HashMap;
use super::consts::{
IMPORT_CODE, IMPORT_CONST, IMPORT_DATA, IMPORT_NAME_EXPORTAS, IMPORT_NAME_NOPREFIX,
IMPORT_NAME_UNDECORATE, IMPORT_ORDINAL, machine_architecture,
};
use super::object::CoffObject;
use super::source::{Source, c_string, subslice, u16_at, u32_at, u64_at};
use super::symbol::SectionNumber;
use crate::error::Result;
use crate::target::Architecture;
pub const IMPORT_OBJECT_HEADER_SIZE: usize = 20;
pub const IMP_PREFIX: &[u8] = b"__imp_";
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ImportName<'a> {
Ordinal(u16),
Name {
hint: u16,
name: &'a [u8],
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ShortImport<'a> {
pub version: u16,
pub machine: u16,
pub time_date_stamp: u32,
pub size_of_data: u32,
pub ordinal_or_hint: u16,
pub import_type: u8,
pub name_type: u8,
pub symbol_name: &'a [u8],
pub dll_name: &'a [u8],
pub export_as: Option<&'a [u8]>,
}
impl<'a> ShortImport<'a> {
pub fn parse(data: &'a [u8], source: Source<'a>) -> Result<Self> {
let field16 = |offset| {
u16_at(data, offset)
.ok_or_else(|| source.malformed(0, "import object header (truncated)"))
};
let field32 = |offset| {
u32_at(data, offset)
.ok_or_else(|| source.malformed(0, "import object header (truncated)"))
};
if field16(0)? != 0 || field16(2)? != 0xffff {
return Err(source.malformed(0, "import object signature"));
}
let version = field16(4)?;
if version != 0 {
return Err(source.malformed(4, "import object version"));
}
let size_of_data = field32(12)?;
let strings = subslice(data, 20, u64::from(size_of_data))
.ok_or_else(|| source.malformed(12, "import object data size (out of bounds)"))?;
let type_info = field16(18)?;
let import_type = u8::try_from(type_info & 0x3).unwrap_or(0);
let name_type = u8::try_from((type_info >> 2) & 0x7).unwrap_or(0);
let symbol_name = c_string(strings)
.ok_or_else(|| source.malformed(20, "import object symbol name (unterminated)"))?;
let rest = strings
.get(symbol_name.len().saturating_add(1)..)
.unwrap_or_default();
let dll_name = c_string(rest)
.ok_or_else(|| source.malformed(20, "import object DLL name (unterminated)"))?;
let export_as = if name_type == IMPORT_NAME_EXPORTAS {
let rest = rest
.get(dll_name.len().saturating_add(1)..)
.unwrap_or_default();
Some(
c_string(rest)
.ok_or_else(|| source.malformed(20, "import object EXPORTAS name"))?,
)
} else {
None
};
if symbol_name.is_empty() {
return Err(source.malformed(20, "import object symbol name (empty)"));
}
Ok(Self {
version,
machine: field16(6)?,
time_date_stamp: field32(8)?,
size_of_data,
ordinal_or_hint: field16(16)?,
import_type,
name_type,
symbol_name,
dll_name,
export_as,
})
}
#[must_use]
pub fn architecture(&self) -> Option<Architecture> {
machine_architecture(self.machine)
}
#[must_use]
pub fn is_code(&self) -> bool {
self.import_type == IMPORT_CODE
}
#[must_use]
pub fn is_data(&self) -> bool {
self.import_type == IMPORT_DATA
}
#[must_use]
pub fn is_const(&self) -> bool {
self.import_type == IMPORT_CONST
}
#[must_use]
pub fn defines_symbol_name(&self) -> bool {
self.import_type != IMPORT_DATA
}
#[must_use]
pub fn import_name(&self) -> ImportName<'a> {
let hint = self.ordinal_or_hint;
let strip_prefix = |name: &'a [u8]| match name.first() {
Some(b'?' | b'@' | b'_') => name.get(1..).unwrap_or_default(),
_ => name,
};
let name = match self.name_type {
IMPORT_ORDINAL => return ImportName::Ordinal(hint),
IMPORT_NAME_NOPREFIX => strip_prefix(self.symbol_name),
IMPORT_NAME_UNDECORATE => {
let name = strip_prefix(self.symbol_name);
match name.iter().position(|&c| c == b'@') {
Some(at) => name.get(..at).unwrap_or(name),
None => name,
}
}
IMPORT_NAME_EXPORTAS => self.export_as.unwrap_or(self.symbol_name),
_ => self.symbol_name,
};
ImportName::Name { hint, name }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LongImportMember<'a> {
Head {
head_symbol: &'a [u8],
iname_symbol: Option<&'a [u8]>,
},
Tail {
iname_symbol: &'a [u8],
dll_name: &'a [u8],
},
Symbol(LongImportSymbol<'a>),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LongImportSymbol<'a> {
pub head_symbol: &'a [u8],
pub imp_symbol: &'a [u8],
pub thunk_symbol: Option<&'a [u8]>,
pub import: ImportName<'a>,
}
impl<'a> LongImportSymbol<'a> {
#[must_use]
pub fn symbol_name(&self) -> &'a [u8] {
self.thunk_symbol.unwrap_or_else(|| {
self.imp_symbol
.strip_prefix(IMP_PREFIX)
.unwrap_or(self.imp_symbol)
})
}
}
#[derive(Clone, Copy, Debug, Default)]
struct IdataSections {
idata2: Option<u32>,
idata5: Option<u32>,
idata6: Option<u32>,
idata7: Option<u32>,
}
pub fn classify_long_import<'a>(object: &CoffObject<'a>) -> Result<Option<LongImportMember<'a>>> {
let mut idata = IdataSections::default();
let mut code_sections = 0u64;
for section in object.sections() {
let section = section?;
let slot = match section.name {
b".idata$2" => &mut idata.idata2,
b".idata$5" => &mut idata.idata5,
b".idata$6" => &mut idata.idata6,
b".idata$7" => &mut idata.idata7,
_ => {
if section.header.is_code() && section.number < 64 {
code_sections |= 1u64 << section.number;
}
continue;
}
};
slot.get_or_insert(section.number);
}
if idata.idata2.is_none() && idata.idata5.is_none() && idata.idata7.is_none() {
return Ok(None);
}
let defined_in = |number: Option<u32>, filter: &dyn Fn(&[u8]) -> bool| -> Result<_> {
let Some(number) = number else {
return Ok(None);
};
for symbol in object.symbols().iter() {
let symbol = symbol?;
if symbol.is_external()
&& symbol.section() == SectionNumber::Section(number)
&& filter(symbol.name)
{
return Ok(Some(symbol.name));
}
}
Ok(None)
};
if let Some(idata2) = idata.idata2 {
let Some(head_symbol) = defined_in(Some(idata2), &|_| true)? else {
return Ok(None);
};
let iname_symbol = relocation_target(object, idata2, |offset| offset == 12)?;
return Ok(Some(LongImportMember::Head {
head_symbol,
iname_symbol,
}));
}
if let Some(iname_symbol) = defined_in(idata.idata7, &|_| true)? {
let Some(number) = idata.idata7 else {
return Ok(None);
};
let section = object.section(number)?;
let data = object.section_data(§ion.header)?;
let dll_name = c_string(data).unwrap_or(data);
return Ok(Some(LongImportMember::Tail {
iname_symbol,
dll_name,
}));
}
let Some(imp_symbol) = defined_in(idata.idata5, &|name| name.starts_with(IMP_PREFIX))? else {
return Ok(None);
};
let Some(head_symbol) = (match idata.idata7 {
Some(number) => relocation_target(object, number, |_| true)?,
None => None,
}) else {
return Ok(None);
};
let ordinal = match idata.idata5 {
Some(number) => {
let section = object.section(number)?;
let data = object.section_data(§ion.header)?;
match data.len() {
8 => u64_at(data, 0)
.filter(|entry| entry >> 63 != 0)
.map(|entry| entry & 0xffff),
4 => u32_at(data, 0)
.filter(|entry| entry >> 31 != 0)
.map(|entry| u64::from(entry & 0xffff)),
_ => None,
}
}
None => None,
};
let import = match (ordinal.and_then(|o| u16::try_from(o).ok()), idata.idata6) {
(Some(ordinal), _) => ImportName::Ordinal(ordinal),
(None, Some(number)) => {
let section = object.section(number)?;
let data = object.section_data(§ion.header)?;
let hint = u16_at(data, 0).unwrap_or(0);
let tail = data.get(2..).unwrap_or_default();
ImportName::Name {
hint,
name: c_string(tail).unwrap_or(tail),
}
}
(None, None) => return Ok(None),
};
let thunk_symbol = defined_in_code(object, code_sections)?;
Ok(Some(LongImportMember::Symbol(LongImportSymbol {
head_symbol,
imp_symbol,
thunk_symbol,
import,
})))
}
fn defined_in_code<'a>(object: &CoffObject<'a>, code_sections: u64) -> Result<Option<&'a [u8]>> {
if code_sections == 0 {
return Ok(None);
}
for symbol in object.symbols().iter() {
let symbol = symbol?;
if let SectionNumber::Section(number) = symbol.section()
&& symbol.is_external()
&& number < 64
&& code_sections & (1u64 << number) != 0
{
return Ok(Some(symbol.name));
}
}
Ok(None)
}
fn relocation_target<'a>(
object: &CoffObject<'a>,
number: u32,
at: impl Fn(u32) -> bool,
) -> Result<Option<&'a [u8]>> {
let section = object.section(number)?;
for reloc in object.relocations(§ion.header)?.iter() {
if !at(reloc.virtual_address) {
continue;
}
let symbol = object.symbol(reloc.symbol_table_index)?;
if symbol.is_undefined() {
return Ok(Some(symbol.name));
}
}
Ok(None)
}
#[derive(Clone, Debug, Default)]
pub struct LongImportDlls<'a> {
heads: HashMap<&'a [u8], &'a [u8], foldhash::fast::FixedState>,
tails: HashMap<&'a [u8], &'a [u8], foldhash::fast::FixedState>,
}
impl<'a> LongImportDlls<'a> {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, member: &LongImportMember<'a>) {
match *member {
LongImportMember::Head {
head_symbol,
iname_symbol: Some(iname),
} => {
self.heads.insert(head_symbol, iname);
}
LongImportMember::Tail {
iname_symbol,
dll_name,
} => {
self.tails.insert(iname_symbol, dll_name);
}
LongImportMember::Head { .. } | LongImportMember::Symbol(_) => {}
}
}
#[must_use]
pub fn dll_name(&self, head_symbol: &[u8]) -> Option<&'a [u8]> {
let iname = self.heads.get(head_symbol)?;
self.tails.get(iname).copied()
}
}
#[cfg(test)]
mod tests {
use std::path::Path;
use super::*;
fn short(type_info: u16, strings: &[u8]) -> Vec<u8> {
let mut data = vec![0, 0, 0xff, 0xff, 0, 0, 0x64, 0x86, 0, 0, 0, 0];
data.extend(u32::try_from(strings.len()).unwrap().to_le_bytes());
data.extend(7u16.to_le_bytes());
data.extend(type_info.to_le_bytes());
data.extend(strings);
data
}
#[test]
fn short_imports() {
let source = Source::new(Path::new("x.lib"));
let data = short(1 << 2, b"GetLastError\0KERNEL32.dll\0");
let import = ShortImport::parse(&data, source).unwrap();
assert_eq!(import.symbol_name, b"GetLastError");
assert_eq!(import.dll_name, b"KERNEL32.dll");
assert!(import.is_code());
assert_eq!(
import.import_name(),
ImportName::Name {
hint: 7,
name: b"GetLastError"
}
);
let data = short(1 | (3 << 2), b"_foo@4\0a.dll\0");
let import = ShortImport::parse(&data, source).unwrap();
assert!(import.is_data());
assert_eq!(
import.import_name(),
ImportName::Name {
hint: 7,
name: b"foo"
}
);
let data = short(2 << 2, b"?bar\0a.dll\0");
let import = ShortImport::parse(&data, source).unwrap();
assert_eq!(
import.import_name(),
ImportName::Name {
hint: 7,
name: b"bar"
}
);
let data = short(0, b"baz\0a.dll\0");
let import = ShortImport::parse(&data, source).unwrap();
assert_eq!(import.import_name(), ImportName::Ordinal(7));
let data = short(4 << 2, b"sym\0a.dll\0real\0");
let import = ShortImport::parse(&data, source).unwrap();
assert_eq!(
import.import_name(),
ImportName::Name {
hint: 7,
name: b"real"
}
);
for bad in [
&short(4 << 2, b"sym\0a.dll\0real")[..],
&short(0, b"sym\0a.dll")[..],
&short(0, b"\0a.dll\0")[..],
&data[..data.len() - 1],
&data[..10],
] {
assert!(ShortImport::parse(bad, source).is_err());
}
}
}