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