Skip to main content

pers/pe/
export.rs

1use alloc::borrow::Cow;
2use alloc::vec::Vec;
3
4use zerocopy::little_endian::{U16, U32};
5
6use super::{ImageDataDirectory, SectionTable};
7use crate::address::Rva;
8use crate::bytes::Bytes;
9use crate::error::{Result, malformed};
10
11pe_struct! {
12    /// The IMAGE_EXPORT_DIRECTORY, which describes the functions exported by the file.
13    pub struct ImageExportDirectory {
14        characteristics: U32 => u32,
15        time_date_stamp: U32 => u32,
16        major_version: U16 => u16,
17        minor_version: U16 => u16,
18        /// RVA of the module name.
19        name: U32 => Rva,
20        /// Ordinal of the first exported function.
21        base: U32 => u32,
22        number_of_functions: U32 => u32,
23        number_of_names: U32 => u32,
24        address_of_functions: U32 => Rva,
25        address_of_names: U32 => Rva,
26        address_of_name_ordinals: U32 => Rva,
27    }
28}
29
30/// A function exported by the file.
31#[derive(Debug, Clone, PartialEq, Eq)]
32#[non_exhaustive]
33pub struct ExportFunction<'a> {
34    pub name: Option<Cow<'a, str>>,
35    pub address: Rva,
36    pub ordinal: u16,
37    /// Target of a forwarded export, e.g. `NTDLL.RtlEnterCriticalSection`.
38    pub forward_name: Option<Cow<'a, str>>,
39}
40
41impl ExportFunction<'_> {
42    pub fn is_forwarded(&self) -> bool {
43        self.forward_name.as_deref().is_some_and(|n| !n.is_empty())
44    }
45}
46
47/// Parse the exported functions. Names are associated through the name ordinal table; an
48/// address inside the export directory marks a forwarded export.
49pub(crate) fn parse_export_functions<'a>(
50    data: &'a [u8],
51    directory: &ImageExportDirectory,
52    sections: &SectionTable<'a>,
53    export_data_directory: &ImageDataDirectory,
54) -> Result<Vec<ExportFunction<'a>>> {
55    let bytes = Bytes(data);
56    let function_table = sections
57        .rva_to_offset(directory.address_of_functions())?
58        .usize();
59    let addresses: &[U32] =
60        bytes.read_slice(function_table, directory.number_of_functions() as usize)?;
61    let mut functions: Vec<_> = addresses
62        .iter()
63        .zip(0u32..)
64        .map(|(address, i)| ExportFunction {
65            name: None,
66            address: Rva(address.get()),
67            ordinal: i.wrapping_add(directory.base()) as u16,
68            forward_name: None,
69        })
70        .collect();
71    if directory.number_of_names() == 0 {
72        return Ok(functions);
73    }
74
75    let count = directory.number_of_names() as usize;
76    let names: &[U32] = bytes.read_slice(
77        sections
78            .rva_to_offset(directory.address_of_names())?
79            .usize(),
80        count,
81    )?;
82    let ordinals: &[U16] = bytes.read_slice(
83        sections
84            .rva_to_offset(directory.address_of_name_ordinals())?
85            .usize(),
86        count,
87    )?;
88    let start = export_data_directory.virtual_address().get();
89    let end = start.wrapping_add(export_data_directory.size());
90    for (name, ordinal) in names.iter().zip(ordinals) {
91        let function = functions
92            .get_mut(ordinal.get() as usize)
93            .ok_or(malformed("export directory", "name ordinal out of range"))?;
94        if (start..end).contains(&function.address.get()) {
95            let forward = sections.rva_to_offset(function.address)?;
96            function.forward_name = Some(bytes.ascii_string(forward.usize())?);
97        }
98        function.name = Some(bytes.ascii_string(sections.rva_to_offset(Rva(name.get()))?.usize())?);
99    }
100    Ok(functions)
101}