resource_dll_parser/
lib.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4#![forbid(unsafe_code)]
5
6//! Package to agnostically parse a resource dll for a given ID.
7//!
8//! # Example
9//! ```no_run
10//! use resource_dll_parser::{DllResourceDescriptor, try_find_resource_from_dll};
11//! use fs_err::File;
12//!
13//! # fn main() -> anyhow::Result<()> {
14//! let file = File::open("vmfirmware.dll")?;
15//! let descriptor = DllResourceDescriptor::new(b"VMFW", 13515);
16//! let resource = try_find_resource_from_dll(&file, &descriptor)?;
17//! # Ok(())
18//! # }
19//! ```
20
21use anyhow::Context;
22use anyhow::bail;
23use fs_err::File;
24use object::LittleEndian;
25use object::ReadCache;
26use object::read::pe::PeFile64;
27
28/// Tries to read the given resource from a resource dll. If the given data
29/// buffer is not a valid PE file this function returns Ok(None). If it is a PE
30/// file, but the given resource can not be found or loaded this function
31/// returns Err(...). On success the return value contains the starting offset
32/// into the file and its length.
33/// TODO: change the return types to a proper enum with variants like 'NotPeFile, NotFound, Ok(u64, usize)'
34pub fn try_find_resource_from_dll(
35    file: &File,
36    descriptor: &DllResourceDescriptor,
37) -> anyhow::Result<Option<(u64, usize)>> {
38    let data = &ReadCache::new(file);
39    if let Ok(pe_file) = PeFile64::parse(data) {
40        let rsrc = pe_file
41            .data_directories()
42            .resource_directory(data, &pe_file.section_table())?
43            .context("no resource section")?;
44
45        let type_match = rsrc
46            .root()?
47            .entries
48            .iter()
49            .find(|e| {
50                e.name_or_id().name().map(|n| n.raw_data(rsrc))
51                    == Some(Ok(&descriptor.resource_type))
52            })
53            .context("no entry for resource type found")?
54            .data(rsrc)?
55            .table()
56            .context("resource type entry not a table")?;
57
58        let id_match = type_match
59            .entries
60            .iter()
61            .find(|e| e.name_or_id.get(LittleEndian) == descriptor.id)
62            .context("no entry for id found")?
63            .data(rsrc)?
64            .table()
65            .context("id entry not a table")?;
66
67        if id_match.entries.len() != 1 {
68            bail!(
69                "id table doesn't contain exactly 1 entry, contains {}",
70                id_match.entries.len()
71            );
72        }
73        let data_desc = id_match.entries[0]
74            .data(rsrc)?
75            .data()
76            .context("resource entry not data")?;
77
78        let (offset, len) = (
79            data_desc.offset_to_data.get(LittleEndian),
80            data_desc.size.get(LittleEndian),
81        );
82
83        let result = &pe_file
84            .section_table()
85            .pe_file_range_at(offset)
86            .context("unable to map data offset")?;
87
88        Ok(Some((result.0 as u64, len as usize)))
89    } else {
90        // Failing to parse the file as a dll is fine, it means the file is
91        // probably a blob instead.
92        Ok(None)
93    }
94}
95
96/// Descriptor for locating a resource within a DLL file.
97///
98/// Contains the resource type (as a 4-character ASCII string encoded in LE UTF-16)
99/// and a numeric resource ID.
100pub struct DllResourceDescriptor {
101    /// 4 characters encoded in LE UTF-16
102    resource_type: [u8; 8],
103    id: u32,
104}
105
106impl DllResourceDescriptor {
107    /// Creates a new DLL resource descriptor with the given resource type and ID.
108    ///
109    /// The resource type must be a 4-character ASCII string, which will be converted
110    /// to little-endian UTF-16 encoding.
111    pub const fn new(resource_type: &[u8; 4], id: u32) -> Self {
112        Self {
113            id,
114            // Convert to LE UTF-16, only support ASCII names today
115            resource_type: [
116                resource_type[0],
117                0,
118                resource_type[1],
119                0,
120                resource_type[2],
121                0,
122                resource_type[3],
123                0,
124            ],
125        }
126    }
127}