wraith/navigation/
module.rs1#[cfg(all(not(feature = "std"), feature = "alloc"))]
4use alloc::{format, string::{String, ToString}, vec::Vec};
5
6#[cfg(feature = "std")]
7use std::{format, string::{String, ToString}, vec::Vec};
8
9use crate::error::{Result, WraithError};
10use crate::structures::pe::{
11 DataDirectoryType, DosHeader, ExportDirectory, NtHeaders, NtHeaders32, NtHeaders64,
12};
13use crate::structures::{LdrDataTableEntry, ListEntry};
14use crate::structures::pe::nt_headers::{NT_SIGNATURE, PE32_MAGIC};
15use core::ptr::NonNull;
16
17const MAX_NAME_LENGTH: usize = 512;
19const MAX_EXPORT_COUNT: usize = 0x10000;
21
22pub struct Module<'a> {
24 entry: &'a LdrDataTableEntry,
25}
26
27impl<'a> Module<'a> {
28 pub(crate) fn from_entry(entry: &'a LdrDataTableEntry) -> Self {
30 Self { entry }
31 }
32
33 pub fn base(&self) -> usize {
35 self.entry.base()
36 }
37
38 pub fn size(&self) -> usize {
40 self.entry.size()
41 }
42
43 pub fn entry_point(&self) -> usize {
45 self.entry.entry_point()
46 }
47
48 pub fn full_path(&self) -> String {
50 unsafe { self.entry.full_name() }
52 }
53
54 pub fn name(&self) -> String {
56 unsafe { self.entry.base_name() }
58 }
59
60 pub fn name_lowercase(&self) -> String {
62 self.name().to_lowercase()
63 }
64
65 pub fn contains(&self, address: usize) -> bool {
67 self.entry.contains_address(address)
68 }
69
70 pub fn matches_name(&self, name: &str) -> bool {
72 unsafe { self.entry.matches_name(name) }
74 }
75
76 pub fn as_ldr_entry(&self) -> &LdrDataTableEntry {
78 self.entry
79 }
80
81 pub fn dos_header(&self) -> Result<&DosHeader> {
83 let base = self.base();
84 if base == 0 {
85 return Err(WraithError::NullPointer {
86 context: "module base",
87 });
88 }
89
90 let dos = unsafe { &*(base as *const DosHeader) };
92
93 if !dos.is_valid() {
94 return Err(WraithError::InvalidPeFormat {
95 reason: "invalid DOS signature".into(),
96 });
97 }
98
99 Ok(dos)
100 }
101
102 pub fn nt_headers(&self) -> Result<NtHeaders> {
104 let dos = self.dos_header()?;
105
106 if !dos.is_nt_offset_valid() {
108 let lfanew = dos.e_lfanew; return Err(WraithError::InvalidPeFormat {
110 reason: format!("invalid e_lfanew: {:#x}", lfanew),
111 });
112 }
113
114 let nt_offset = dos.nt_headers_offset();
115
116 const MIN_NT_HEADERS_SIZE: usize = 256; if nt_offset + MIN_NT_HEADERS_SIZE > self.size() {
119 return Err(WraithError::InvalidPeFormat {
120 reason: "e_lfanew points outside module bounds".into(),
121 });
122 }
123
124 let nt_addr = self.base() + nt_offset;
125
126 let signature = unsafe { *(nt_addr as *const u32) };
128 if signature != NT_SIGNATURE {
129 return Err(WraithError::InvalidPeFormat {
130 reason: "invalid NT signature".into(),
131 });
132 }
133
134 let magic_offset = nt_addr + 4 + 20; let magic = unsafe { *(magic_offset as *const u16) };
137
138 if magic == PE32_MAGIC {
139 let headers = unsafe { &*(nt_addr as *const NtHeaders32) };
140 Ok(NtHeaders::Headers32(*headers))
141 } else {
142 let headers = unsafe { &*(nt_addr as *const NtHeaders64) };
143 Ok(NtHeaders::Headers64(*headers))
144 }
145 }
146
147 pub fn rva_to_va(&self, rva: u32) -> usize {
149 self.base() + rva as usize
150 }
151
152 pub fn va_to_rva(&self, va: usize) -> Option<u32> {
154 if va >= self.base() && va < self.base() + self.size() {
155 Some((va - self.base()) as u32)
156 } else {
157 None
158 }
159 }
160
161 pub fn get_export(&self, name: &str) -> Result<usize> {
163 let nt = self.nt_headers()?;
164 let export_dir = nt
165 .data_directory(DataDirectoryType::Export.index())
166 .ok_or_else(|| WraithError::InvalidPeFormat {
167 reason: "no export directory".into(),
168 })?;
169
170 if !export_dir.is_present() {
171 return Err(WraithError::InvalidPeFormat {
172 reason: "export directory not present".into(),
173 });
174 }
175
176 if !self.is_rva_valid(export_dir.virtual_address, core::mem::size_of::<ExportDirectory>())
178 {
179 return Err(WraithError::InvalidPeFormat {
180 reason: "export directory RVA outside module bounds".into(),
181 });
182 }
183
184 let export_va = self.rva_to_va(export_dir.virtual_address);
185 let exports = unsafe { &*(export_va as *const ExportDirectory) };
187
188 let num_names = exports.number_of_names as usize;
189 let num_functions = exports.number_of_functions as usize;
190
191 if num_names > MAX_EXPORT_COUNT || num_functions > MAX_EXPORT_COUNT {
193 return Err(WraithError::InvalidPeFormat {
194 reason: format!("unreasonable export count: {num_names} names, {num_functions} functions"),
195 });
196 }
197
198 let names_size = num_names.saturating_mul(4);
200 let ordinals_size = num_names.saturating_mul(2);
201 let functions_size = num_functions.saturating_mul(4);
202
203 if !self.is_rva_valid(exports.address_of_names, names_size)
204 || !self.is_rva_valid(exports.address_of_name_ordinals, ordinals_size)
205 || !self.is_rva_valid(exports.address_of_functions, functions_size)
206 {
207 return Err(WraithError::InvalidPeFormat {
208 reason: "export table array RVA outside module bounds".into(),
209 });
210 }
211
212 let names_va = self.rva_to_va(exports.address_of_names);
213 let ordinals_va = self.rva_to_va(exports.address_of_name_ordinals);
214 let functions_va = self.rva_to_va(exports.address_of_functions);
215
216 for i in 0..num_names {
218 let name_rva = unsafe { *((names_va + i * 4) as *const u32) };
220
221 if !self.is_rva_valid(name_rva, 1) {
223 continue; }
225
226 let name_va = self.rva_to_va(name_rva);
227 let export_name = match self.read_string_at(name_va) {
228 Some(s) => s,
229 None => continue, };
231
232 if export_name == name {
233 let ordinal = unsafe { *((ordinals_va + i * 2) as *const u16) } as usize;
234
235 if ordinal >= num_functions {
237 return Err(WraithError::InvalidPeFormat {
238 reason: format!("ordinal {ordinal} exceeds function count {num_functions}"),
239 });
240 }
241
242 let func_rva = unsafe { *((functions_va + ordinal * 4) as *const u32) };
243
244 if func_rva >= export_dir.virtual_address
246 && func_rva < export_dir.virtual_address + export_dir.size
247 {
248 let forwarder_va = self.rva_to_va(func_rva);
250 let forwarder = self.read_string_at(forwarder_va)
251 .unwrap_or("unknown")
252 .to_string();
253 return Err(WraithError::ForwardedExport { forwarder });
254 }
255
256 let func_va = self.rva_to_va(func_rva);
257 return Ok(func_va);
258 }
259 }
260
261 Err(WraithError::ModuleNotFound {
262 name: format!("export {name} not found"),
263 })
264 }
265
266 fn is_rva_valid(&self, rva: u32, size: usize) -> bool {
268 let rva = rva as usize;
269 rva < self.size() && size <= self.size() - rva
270 }
271
272 fn read_string_at(&self, addr: usize) -> Option<&str> {
274 let base = self.base();
275 let end = base + self.size();
276
277 if addr < base || addr >= end {
278 return None;
279 }
280
281 let max_len = (end - addr).min(MAX_NAME_LENGTH);
282 let ptr = addr as *const u8;
283
284 let mut len = 0;
286 while len < max_len {
287 let byte = unsafe { *ptr.add(len) };
289 if byte == 0 {
290 break;
291 }
292 len += 1;
293 }
294
295 if len == 0 || len >= max_len {
296 return None; }
298
299 let bytes = unsafe { core::slice::from_raw_parts(ptr, len) };
301 core::str::from_utf8(bytes).ok()
302 }
303
304 pub fn get_export_by_ordinal(&self, ordinal: u16) -> Result<usize> {
306 let nt = self.nt_headers()?;
307 let export_dir = nt
308 .data_directory(DataDirectoryType::Export.index())
309 .ok_or_else(|| WraithError::InvalidPeFormat {
310 reason: "no export directory".into(),
311 })?;
312
313 if !export_dir.is_present() {
314 return Err(WraithError::InvalidPeFormat {
315 reason: "export directory not present".into(),
316 });
317 }
318
319 let export_va = self.rva_to_va(export_dir.virtual_address);
320 let exports = unsafe { &*(export_va as *const ExportDirectory) };
322
323 let index = ordinal as usize - exports.base as usize;
324 if index >= exports.number_of_functions as usize {
325 return Err(WraithError::InvalidPeFormat {
326 reason: "ordinal out of range".into(),
327 });
328 }
329
330 let functions_va = self.rva_to_va(exports.address_of_functions);
331 let func_rva = unsafe { *((functions_va + index * 4) as *const u32) };
332
333 Ok(self.rva_to_va(func_rva))
334 }
335}
336
337pub struct ModuleHandle {
339 entry: NonNull<LdrDataTableEntry>,
340}
341
342impl ModuleHandle {
343 pub unsafe fn from_raw(ptr: *mut LdrDataTableEntry) -> Option<Self> {
348 NonNull::new(ptr).map(|entry| Self { entry })
349 }
350
351 pub fn as_ptr(&self) -> *mut LdrDataTableEntry {
353 self.entry.as_ptr()
354 }
355
356 pub fn as_module(&self) -> Module<'_> {
358 Module::from_entry(unsafe { self.entry.as_ref() })
360 }
361
362 pub unsafe fn as_entry_mut(&mut self) -> &mut LdrDataTableEntry {
367 unsafe { self.entry.as_mut() }
368 }
369
370 pub fn get_link_pointers(&self) -> ModuleLinkPointers {
372 let entry = unsafe { self.entry.as_ref() };
374 ModuleLinkPointers {
375 in_load_order: &entry.in_load_order_links as *const _ as *mut ListEntry,
376 in_memory_order: &entry.in_memory_order_links as *const _ as *mut ListEntry,
377 in_initialization_order: &entry.in_initialization_order_links as *const _
378 as *mut ListEntry,
379 }
380 }
381}
382
383unsafe impl Send for ModuleHandle {}
387unsafe impl Sync for ModuleHandle {}
388
389pub struct ModuleLinkPointers {
391 pub in_load_order: *mut ListEntry,
392 pub in_memory_order: *mut ListEntry,
393 pub in_initialization_order: *mut ListEntry,
394}