1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
// Copyright 2023 Colin Finck <colin@reactos.org>
// SPDX-License-Identifier: MIT OR Apache-2.0
use core::cmp::Ordering;
use core::mem;
use bitflags::bitflags;
use zerocopy::{FromBytes, LayoutVerified, LittleEndian, Unaligned, U32};
use crate::error::{NtApiSetError, Result};
use crate::hash_entry::{ApiSetHashEntries, ApiSetHashEntryHeader};
use crate::namespace_entry::{
ApiSetNamespaceEntries, ApiSetNamespaceEntry, ApiSetNamespaceEntryHeader,
};
#[allow(dead_code)]
#[derive(Debug, FromBytes, Unaligned)]
#[repr(packed)]
struct ApiSetMapHeader {
version: U32<LittleEndian>,
size: U32<LittleEndian>,
/// See [`ApiSetMapFlags`]
flags: U32<LittleEndian>,
count: U32<LittleEndian>,
namespace_entry_offset: U32<LittleEndian>,
hash_entry_offset: U32<LittleEndian>,
hash_factor: U32<LittleEndian>,
}
const APISET_VERSION_WINDOWS_10: u32 = 6;
bitflags! {
/// Flags returned by [`ApiSetMap::flags`].
pub struct ApiSetMapFlags: u32 {
/// This API Set Map is sealed, meaning the loader shall not look for schema extensions.
const SEALED = 1 << 0;
/// This API Set Map is a schema extension.
const IS_EXTENSION = 1 << 1;
}
}
/// Root structure describing an API Set Map.
#[derive(Debug)]
pub struct ApiSetMap<'a> {
section_bytes: &'a [u8],
header: LayoutVerified<&'a [u8], ApiSetMapHeader>,
}
impl<'a> ApiSetMap<'a> {
/// Returns flags set for this [`ApiSetMap`] as specified by [`ApiSetMapFlags`].
pub fn flags(&self) -> ApiSetMapFlags {
ApiSetMapFlags::from_bits_truncate(self.header.flags.get())
}
/// Finds a namespace entry efficiently in the hash table of the API Set Map.
///
/// `namespace_entry_name` must be non-empty and only consist of lowercase characters, digits, and hyphens.
/// This is asserted in debug builds.
/// If you fail to adhere to these requirements in release builds, the lookup will be performed anyway and return `None`.
pub fn find_namespace_entry(
&self,
namespace_entry_name: &str,
) -> Option<Result<ApiSetNamespaceEntry<'a>>> {
debug_assert!(!namespace_entry_name.is_empty());
debug_assert!(namespace_entry_name
.chars()
.all(|x| x.is_ascii_lowercase() || x.is_ascii_digit() || x == '-'));
// "NTDLL first hashes the supposed name up to but not including the last hyphen"
let (name_to_hash, _) = namespace_entry_name.rsplit_once('-')?;
let hash_factor = self.header.hash_factor.get();
let hash = name_to_hash.chars().fold(0u32, |acc, x| {
acc.wrapping_mul(hash_factor).wrapping_add(x as u32)
});
let hash_entries = iter_try!(self.hash_entries());
let mut namespace_entries = iter_try!(self.namespace_entries());
// Perform binary search in the sorted array of hash entries.
let mut left = 0i64;
let mut right = hash_entries.len() as i64 - 1;
while left <= right {
let mid = (left + right) / 2;
let hash_entry = hash_entries.clone().nth(mid as usize).unwrap();
match hash_entry.hash().cmp(&hash) {
Ordering::Equal => {
// This must be the entry we are looking for.
// Check the name to make absolutely sure.
let index = hash_entry.index();
let namespace_entry = namespace_entries.nth(index as usize)?;
let name = iter_try!(namespace_entry.name());
if name == namespace_entry_name {
return Some(Ok(namespace_entry));
} else {
return None;
}
}
Ordering::Less => left = mid + 1,
Ordering::Greater => right = mid - 1,
}
}
None
}
/// Returns an iterator over the [`ApiSetHashEntry`]s of this [`ApiSetMap`].
///
/// You usually don't need to iterate through the hash entries manually.
/// Use [`find_namespace_entry`](Self::find_namespace_entry) instead.
///
/// [`ApiSetHashEntry`]: crate::hash_entry::ApiSetHashEntry
/// [`ApiSetMap`]: crate::map::ApiSetMap
pub fn hash_entries(&self) -> Result<ApiSetHashEntries<'a>> {
let start = self.header.hash_entry_offset.get() as usize;
let count = self.header.count.get() as usize;
let end = start + mem::size_of::<ApiSetHashEntryHeader>() * count;
let range = start..end;
self.section_bytes
.get(range.clone())
.ok_or(NtApiSetError::HashEntriesOutOfBounds {
range: start..end,
actual: self.section_bytes.len(),
})?;
Ok(ApiSetHashEntries::new(self.section_bytes, range))
}
/// Returns an iterator over the [`ApiSetNamespaceEntry`] elements of this [`ApiSetMap`].
///
/// Alternatively, you can lookup a specific namespace entry via the [`find_namespace_entry`](Self::find_namespace_entry) method.
pub fn namespace_entries(&self) -> Result<ApiSetNamespaceEntries<'a>> {
let start = self.header.namespace_entry_offset.get() as usize;
let count = self.header.count.get() as usize;
let end = start + mem::size_of::<ApiSetNamespaceEntryHeader>() * count;
let range = start..end;
self.section_bytes.get(range.clone()).ok_or(
NtApiSetError::NamespaceEntriesOutOfBounds {
range: start..end,
actual: self.section_bytes.len(),
},
)?;
Ok(ApiSetNamespaceEntries::new(self.section_bytes, range))
}
/// Creates an [`ApiSetMap`] from an API Set Map file opened via the `pelite` crate.
///
/// If you already have the raw bytes of the `.apiset` section of that file, consider using [`try_from_apiset_section_bytes`](Self::try_from_apiset_section_bytes).
#[cfg(feature = "pelite")]
#[cfg_attr(docsrs, doc(cfg(feature = "pelite")))]
pub fn try_from_pe64<T>(pe64: T) -> Result<Self>
where
T: pelite::pe64::Pe<'a>,
{
let apiset_section_header = pe64
.section_headers()
.by_name(".apiset")
.ok_or(NtApiSetError::ApiSetSectionNotFound)?;
let section_bytes = pe64
.get_section_bytes(apiset_section_header)
.map_err(|_| NtApiSetError::ApiSetSectionOutOfBounds)?;
Self::try_from_apiset_section_bytes(section_bytes)
}
/// Creates an [`ApiSetMap`] from the raw bytes of the `.apiset` section of an API Set Map file.
///
/// If you only have the DLL file and not the `.apiset` section bytes, consider using [`try_from_pe64`](Self::try_from_pe64).
pub fn try_from_apiset_section_bytes(section_bytes: &'a [u8]) -> Result<Self> {
let length = section_bytes.len();
let (header, _) = LayoutVerified::<_, ApiSetMapHeader>::new_unaligned_from_prefix(
section_bytes,
)
.ok_or(NtApiSetError::InvalidMapHeaderSize {
expected: mem::size_of::<ApiSetMapHeader>(),
actual: length,
})?;
// The internal structures are slightly different for older Windows versions.
// See https://www.geoffchappell.com/studies/windows/win32/apisetschema/index.htm
let version = header.version.get();
if version != APISET_VERSION_WINDOWS_10 {
return Err(NtApiSetError::UnsupportedVersion { version });
}
Ok(Self {
section_bytes,
header,
})
}
}