Skip to main content

goblin_experimental/pe/
import.rs

1use alloc::borrow::Cow;
2use alloc::vec::Vec;
3use core::fmt::{Debug, LowerHex};
4
5use crate::error;
6use scroll::ctx::TryFromCtx;
7use scroll::{Pread, Pwrite, SizeWith};
8
9use crate::pe::data_directories;
10use crate::pe::options;
11use crate::pe::section_table;
12use crate::pe::utils;
13
14use log::{debug, warn};
15
16pub const IMPORT_BY_ORDINAL_32: u32 = 0x8000_0000;
17pub const IMPORT_BY_ORDINAL_64: u64 = 0x8000_0000_0000_0000;
18pub const IMPORT_RVA_MASK_32: u32 = 0x7fff_ffff;
19pub const IMPORT_RVA_MASK_64: u64 = 0x0000_0000_7fff_ffff;
20
21pub trait Bitfield<'a>:
22    Into<u64>
23    + PartialEq
24    + Eq
25    + LowerHex
26    + Debug
27    + TryFromCtx<'a, scroll::Endian, Error = scroll::Error>
28{
29    fn is_ordinal(&self) -> bool;
30    fn to_ordinal(&self) -> u16;
31    fn to_rva(&self) -> u32;
32    fn size_of() -> usize;
33    fn is_zero(&self) -> bool;
34}
35
36impl<'a> Bitfield<'a> for u64 {
37    fn is_ordinal(&self) -> bool {
38        self & IMPORT_BY_ORDINAL_64 == IMPORT_BY_ORDINAL_64
39    }
40    fn to_ordinal(&self) -> u16 {
41        (0xffff & self) as u16
42    }
43    fn to_rva(&self) -> u32 {
44        (self & IMPORT_RVA_MASK_64) as u32
45    }
46    fn size_of() -> usize {
47        8
48    }
49    fn is_zero(&self) -> bool {
50        *self == 0
51    }
52}
53
54impl<'a> Bitfield<'a> for u32 {
55    fn is_ordinal(&self) -> bool {
56        self & IMPORT_BY_ORDINAL_32 == IMPORT_BY_ORDINAL_32
57    }
58    fn to_ordinal(&self) -> u16 {
59        (0xffff & self) as u16
60    }
61    fn to_rva(&self) -> u32 {
62        (self & IMPORT_RVA_MASK_32) as u32
63    }
64    fn size_of() -> usize {
65        4
66    }
67    fn is_zero(&self) -> bool {
68        *self == 0
69    }
70}
71
72#[derive(Debug, Clone)]
73pub struct HintNameTableEntry<'a> {
74    pub hint: u16,
75    pub name: &'a str,
76}
77
78impl<'a> HintNameTableEntry<'a> {
79    fn parse(bytes: &'a [u8], mut offset: usize) -> error::Result<Self> {
80        let offset = &mut offset;
81        let hint = bytes.gread_with(offset, scroll::LE)?;
82        let name = bytes.pread::<&'a str>(*offset)?;
83        Ok(HintNameTableEntry { hint, name })
84    }
85}
86
87#[derive(Debug, Clone)]
88pub enum SyntheticImportLookupTableEntry<'a> {
89    OrdinalNumber(u16),
90    HintNameTableRVA((u32, HintNameTableEntry<'a>)), // [u8; 31] bitfield :/
91}
92
93pub type ImportLookupTable<'a> = Vec<SyntheticImportLookupTableEntry<'a>>;
94
95impl<'a> SyntheticImportLookupTableEntry<'a> {
96    pub fn parse<T: Bitfield<'a>>(
97        bytes: &'a [u8],
98        offset: usize,
99        sections: &[section_table::SectionTable],
100        file_alignment: u32,
101    ) -> error::Result<ImportLookupTable<'a>> {
102        Self::parse_with_opts::<T>(
103            bytes,
104            offset,
105            sections,
106            file_alignment,
107            &options::ParseOptions::default(),
108        )
109    }
110
111    pub fn parse_with_opts<T: Bitfield<'a>>(
112        bytes: &'a [u8],
113        mut offset: usize,
114        sections: &[section_table::SectionTable],
115        file_alignment: u32,
116        opts: &options::ParseOptions,
117    ) -> error::Result<ImportLookupTable<'a>> {
118        let le = scroll::LE;
119        let offset = &mut offset;
120        let mut table = Vec::new();
121        loop {
122            let bitfield: T = bytes.gread_with(offset, le)?;
123            if bitfield.is_zero() {
124                debug!("imports done");
125                break;
126            } else {
127                let entry = {
128                    debug!("bitfield {:#x}", bitfield);
129                    use self::SyntheticImportLookupTableEntry::*;
130                    if bitfield.is_ordinal() {
131                        let ordinal = bitfield.to_ordinal();
132                        debug!("importing by ordinal {:#x} ({})", ordinal, ordinal);
133                        OrdinalNumber(ordinal)
134                    } else {
135                        let rva = bitfield.to_rva();
136                        let hentry = {
137                            debug!("searching for RVA {:#x}", rva);
138                            if let Some(offset) =
139                                utils::find_offset(rva as usize, sections, file_alignment, opts)
140                            {
141                                debug!("offset {:#x}", offset);
142                                HintNameTableEntry::parse(bytes, offset)?
143                            } else {
144                                warn!("Entry {} has bad RVA: {:#x}", table.len(), rva);
145                                continue;
146                            }
147                        };
148                        HintNameTableRVA((rva, hentry))
149                    }
150                };
151                table.push(entry);
152            }
153        }
154        Ok(table)
155    }
156}
157
158// get until entry is 0
159pub type ImportAddressTable = Vec<u64>;
160
161#[repr(C)]
162#[derive(Debug, Pread, Pwrite, SizeWith)]
163pub struct ImportDirectoryEntry {
164    pub import_lookup_table_rva: u32,
165    pub time_date_stamp: u32,
166    pub forwarder_chain: u32,
167    pub name_rva: u32,
168    pub import_address_table_rva: u32,
169}
170
171pub const SIZEOF_IMPORT_DIRECTORY_ENTRY: usize = 20;
172
173impl ImportDirectoryEntry {
174    /// Whether the entire fields are set to zero
175    pub fn is_null(&self) -> bool {
176        (self.import_lookup_table_rva == 0)
177            && (self.time_date_stamp == 0)
178            && (self.forwarder_chain == 0)
179            && (self.name_rva == 0)
180            && (self.import_address_table_rva == 0)
181    }
182
183    /// Whether the entry is _possibly_ valid.
184    ///
185    /// Both [`Self::name_rva`] and [`Self::import_address_table_rva`] must be non-zero
186    pub fn is_possibly_valid(&self) -> bool {
187        self.name_rva != 0 && self.import_address_table_rva != 0
188    }
189}
190
191#[derive(Debug)]
192pub struct SyntheticImportDirectoryEntry<'a> {
193    pub import_directory_entry: ImportDirectoryEntry,
194    /// Computed
195    pub name: &'a str,
196    /// The import lookup table is a vector of either ordinals, or RVAs + import names
197    pub import_lookup_table: Option<ImportLookupTable<'a>>,
198    /// Computed
199    pub import_address_table: ImportAddressTable,
200}
201
202impl<'a> SyntheticImportDirectoryEntry<'a> {
203    pub fn parse<T: Bitfield<'a>>(
204        bytes: &'a [u8],
205        import_directory_entry: ImportDirectoryEntry,
206        sections: &[section_table::SectionTable],
207        file_alignment: u32,
208    ) -> error::Result<SyntheticImportDirectoryEntry<'a>> {
209        Self::parse_with_opts::<T>(
210            bytes,
211            import_directory_entry,
212            sections,
213            file_alignment,
214            &options::ParseOptions::default(),
215        )
216    }
217
218    pub fn parse_with_opts<T: Bitfield<'a>>(
219        bytes: &'a [u8],
220        import_directory_entry: ImportDirectoryEntry,
221        sections: &[section_table::SectionTable],
222        file_alignment: u32,
223        opts: &options::ParseOptions,
224    ) -> error::Result<SyntheticImportDirectoryEntry<'a>> {
225        const LE: scroll::Endian = scroll::LE;
226        let name_rva = import_directory_entry.name_rva;
227        let name = utils::try_name(bytes, name_rva as usize, sections, file_alignment, opts)?;
228        let import_lookup_table = {
229            let import_lookup_table_rva = import_directory_entry.import_lookup_table_rva;
230            let import_address_table_rva = import_directory_entry.import_address_table_rva;
231            if let Some(import_lookup_table_offset) = utils::find_offset(
232                import_lookup_table_rva as usize,
233                sections,
234                file_alignment,
235                opts,
236            ) {
237                debug!("Synthesizing lookup table imports for {} lib, with import lookup table rva: {:#x}", name, import_lookup_table_rva);
238                let import_lookup_table = SyntheticImportLookupTableEntry::parse_with_opts::<T>(
239                    bytes,
240                    import_lookup_table_offset,
241                    sections,
242                    file_alignment,
243                    opts,
244                )?;
245                debug!(
246                    "Successfully synthesized import lookup table entry from lookup table: {:#?}",
247                    import_lookup_table
248                );
249                Some(import_lookup_table)
250            } else if let Some(import_address_table_offset) = utils::find_offset(
251                import_address_table_rva as usize,
252                sections,
253                file_alignment,
254                opts,
255            ) {
256                debug!("Synthesizing lookup table imports for {} lib, with import address table rva: {:#x}", name, import_lookup_table_rva);
257                let import_address_table = SyntheticImportLookupTableEntry::parse_with_opts::<T>(
258                    bytes,
259                    import_address_table_offset,
260                    sections,
261                    file_alignment,
262                    opts,
263                )?;
264                debug!(
265                    "Successfully synthesized import lookup table entry from IAT: {:#?}",
266                    import_address_table
267                );
268                Some(import_address_table)
269            } else {
270                None
271            }
272        };
273
274        let rva = match import_directory_entry.import_address_table_rva.is_zero() {
275            true => import_directory_entry.import_lookup_table_rva,
276            false => import_directory_entry.import_address_table_rva,
277        };
278
279        let import_address_table_offset =
280            &mut utils::find_offset(rva as usize, sections, file_alignment, opts).ok_or_else(
281                || {
282                    let target = if import_directory_entry.import_address_table_rva.is_zero() {
283                        "import_lookup_table_rva"
284                    } else {
285                        "import_address_table_rva"
286                    };
287                    error::Error::Malformed(format!(
288                        "Cannot map {} {:#x} into offset for {}",
289                        target, rva, name
290                    ))
291                },
292            )?;
293        let mut import_address_table = Vec::new();
294        loop {
295            let import_address = bytes
296                .gread_with::<T>(import_address_table_offset, LE)?
297                .into();
298            if import_address == 0 {
299                break;
300            } else {
301                import_address_table.push(import_address);
302            }
303        }
304        Ok(SyntheticImportDirectoryEntry {
305            import_directory_entry,
306            name,
307            import_lookup_table,
308            import_address_table,
309        })
310    }
311}
312
313#[derive(Debug)]
314/// Contains a list of synthesized import data for this binary, e.g., which symbols from which libraries it is importing from
315pub struct ImportData<'a> {
316    pub import_data: Vec<SyntheticImportDirectoryEntry<'a>>,
317}
318
319impl<'a> ImportData<'a> {
320    pub fn parse<T: Bitfield<'a>>(
321        bytes: &'a [u8],
322        dd: data_directories::DataDirectory,
323        sections: &[section_table::SectionTable],
324        file_alignment: u32,
325    ) -> error::Result<ImportData<'a>> {
326        Self::parse_with_opts::<T>(
327            bytes,
328            dd,
329            sections,
330            file_alignment,
331            &options::ParseOptions::default(),
332        )
333    }
334
335    pub fn parse_with_opts<T: Bitfield<'a>>(
336        bytes: &'a [u8],
337        dd: data_directories::DataDirectory,
338        sections: &[section_table::SectionTable],
339        file_alignment: u32,
340        opts: &options::ParseOptions,
341    ) -> error::Result<ImportData<'a>> {
342        let import_directory_table_rva = dd.virtual_address as usize;
343        debug!(
344            "import_directory_table_rva {:#x}",
345            import_directory_table_rva
346        );
347        let offset =
348            &mut utils::find_offset(import_directory_table_rva, sections, file_alignment, opts)
349                .ok_or_else(|| {
350                    error::Error::Malformed(format!(
351                "Cannot create ImportData; cannot map import_directory_table_rva {:#x} into offset",
352                import_directory_table_rva
353            ))
354                })?;
355        debug!("import data offset {:#x}", offset);
356        let mut import_data = Vec::new();
357        loop {
358            let import_directory_entry: ImportDirectoryEntry =
359                bytes.gread_with(offset, scroll::LE)?;
360            debug!("{:#?} at {:#x}", import_directory_entry, offset);
361            if import_directory_entry.is_null() || !import_directory_entry.is_possibly_valid() {
362                break;
363            } else {
364                let entry = SyntheticImportDirectoryEntry::parse_with_opts::<T>(
365                    bytes,
366                    import_directory_entry,
367                    sections,
368                    file_alignment,
369                    opts,
370                )?;
371                debug!("entry {:#?} at {:#x}", entry, offset);
372                import_data.push(entry);
373            }
374        }
375        debug!("finished ImportData");
376        Ok(ImportData { import_data })
377    }
378}
379
380#[derive(Debug)]
381/// A synthesized symbol import, the name is pre-indexed, and the binary offset is computed, as well as which dll it belongs to
382pub struct Import<'a> {
383    pub name: Cow<'a, str>,
384    pub dll: &'a str,
385    pub ordinal: u16,
386    pub offset: usize,
387    pub rva: usize,
388    pub size: usize,
389}
390
391impl<'a> Import<'a> {
392    pub fn parse<T: Bitfield<'a>>(
393        _bytes: &'a [u8],
394        import_data: &ImportData<'a>,
395        _sections: &[section_table::SectionTable],
396    ) -> error::Result<Vec<Import<'a>>> {
397        let mut imports = Vec::new();
398        for data in &import_data.import_data {
399            if let Some(ref import_lookup_table) = data.import_lookup_table {
400                let dll = data.name;
401                let import_base = data.import_directory_entry.import_address_table_rva as usize;
402                debug!("Getting imports from {}", &dll);
403                for (i, entry) in import_lookup_table.iter().enumerate() {
404                    let offset = import_base + (i * T::size_of());
405                    use self::SyntheticImportLookupTableEntry::*;
406                    let (rva, name, ordinal) = match *entry {
407                        HintNameTableRVA((rva, ref hint_entry)) => {
408                            // if hint_entry.name = "" && hint_entry.hint = 0 {
409                            //     println!("<PE.Import> warning hint/name table rva from {} without hint {:#x}", dll, rva);
410                            // }
411                            (rva, Cow::Borrowed(hint_entry.name), hint_entry.hint)
412                        }
413                        OrdinalNumber(ordinal) => {
414                            let name = format!("ORDINAL {}", ordinal);
415                            (0x0, Cow::Owned(name), ordinal)
416                        }
417                    };
418                    let import = Import {
419                        name,
420                        ordinal,
421                        dll,
422                        size: T::size_of(),
423                        offset,
424                        rva: rva as usize,
425                    };
426                    imports.push(import);
427                }
428            }
429        }
430        Ok(imports)
431    }
432}
433
434#[cfg(test)]
435mod tests {
436    const NOT_WELL_FORMED_IMPORT: &[u8] =
437        include_bytes!("../../tests/bins/pe/not_well_formed_import.exe.bin");
438    const WELL_FORMED_IMPORT: &[u8] =
439        include_bytes!("../../tests/bins/pe/well_formed_import.exe.bin");
440
441    #[test]
442    fn parse_non_well_formed_import_table() {
443        let binary = crate::pe::PE::parse(NOT_WELL_FORMED_IMPORT).expect("Unable to parse binary");
444        assert_eq!(binary.import_data.is_some(), true);
445        assert_eq!(binary.imports.len(), 1);
446        assert_eq!(binary.imports[0].name, "ORDINAL 51398");
447        assert_eq!(binary.imports[0].dll, "abcd.dll");
448        assert_eq!(binary.imports[0].ordinal, 51398);
449        assert_eq!(binary.imports[0].offset, 0x7014);
450        assert_eq!(binary.imports[0].rva, 0);
451        assert_eq!(binary.imports[0].size, 8);
452    }
453
454    #[test]
455    fn parse_well_formed_import_table() {
456        let binary = crate::pe::PE::parse(WELL_FORMED_IMPORT).expect("Unable to parse binary");
457        assert_eq!(binary.import_data.is_some(), true);
458        assert_eq!(binary.imports.len(), 1);
459        assert_eq!(binary.imports[0].name, "GetLastError");
460        assert_eq!(binary.imports[0].dll, "KERNEL32.dll");
461        assert_eq!(binary.imports[0].ordinal, 647);
462        assert_eq!(binary.imports[0].offset, 0x2000);
463        assert_eq!(binary.imports[0].rva, 0x21B8);
464        assert_eq!(binary.imports[0].size, 8);
465    }
466}