wraith/manipulation/manual_map/
mod.rs

1//! Manual PE mapping - LoadLibrary bypass
2//!
3//! This module provides a complete PE loader that maps DLLs without using
4//! the Windows loader, creating "ghost DLLs" invisible to GetModuleHandle.
5//!
6//! # Example
7//!
8//! ```no_run
9//! use wraith::manipulation::manual_map::{ManualMapper, map_file};
10//!
11//! // convenience function for quick mapping
12//! let mapper = map_file(r"C:\path\to\module.dll")?;
13//! println!("Mapped at {:#x}", mapper.base());
14//!
15//! // or step-by-step for more control
16//! let mapper = ManualMapper::from_file(r"C:\path\to\module.dll")?
17//!     .allocate()?
18//!     .map_sections()?
19//!     .relocate()?
20//!     .resolve_imports()?
21//!     .process_tls()?
22//!     .finalize()?;
23//!
24//! mapper.call_entry_point()?;
25//! # Ok::<(), wraith::WraithError>(())
26//! ```
27
28mod allocator;
29mod entry;
30mod mapper;
31mod parser;
32mod relocator;
33mod resolver;
34mod tls;
35
36pub use allocator::MappedMemory;
37pub use entry::reason;
38pub use parser::ParsedPe;
39
40use crate::error::{Result, WraithError};
41use core::marker::PhantomData;
42
43/// type-state markers for manual mapping stages
44pub mod state {
45    /// PE has been parsed but no memory allocated
46    pub struct Parsed;
47    /// Memory has been allocated for the image
48    pub struct Allocated;
49    /// PE sections have been mapped to memory
50    pub struct SectionsMapped;
51    /// Base relocations have been applied
52    pub struct Relocated;
53    /// Import Address Table has been resolved
54    pub struct ImportsResolved;
55    /// TLS callbacks have been processed
56    pub struct TlsProcessed;
57    /// Image is ready for execution
58    pub struct Ready;
59}
60
61/// manual mapper with type-state progression
62///
63/// the type parameter ensures mapping steps happen in correct order:
64/// Parsed -> Allocated -> SectionsMapped -> Relocated -> ImportsResolved -> TlsProcessed -> Ready
65pub struct ManualMapper<S> {
66    pe: ParsedPe,
67    memory: Option<MappedMemory>,
68    _state: PhantomData<S>,
69}
70
71impl ManualMapper<state::Parsed> {
72    /// parse PE from bytes
73    pub fn parse(data: &[u8]) -> Result<Self> {
74        let pe = ParsedPe::parse(data)?;
75        Ok(Self {
76            pe,
77            memory: None,
78            _state: PhantomData,
79        })
80    }
81
82    /// parse PE from file
83    pub fn from_file(path: &str) -> Result<Self> {
84        let data = std::fs::read(path).map_err(|e| WraithError::InvalidPeFormat {
85            reason: format!("failed to read file: {e}"),
86        })?;
87        Self::parse(&data)
88    }
89
90    /// get reference to parsed PE
91    pub fn pe(&self) -> &ParsedPe {
92        &self.pe
93    }
94
95    /// allocate memory for the PE image
96    ///
97    /// tries preferred base first, falls back to any available address
98    pub fn allocate(self) -> Result<ManualMapper<state::Allocated>> {
99        let size = self.pe.size_of_image();
100        let preferred_base = self.pe.preferred_base();
101
102        let memory = allocator::allocate_image(size, preferred_base)?;
103
104        Ok(ManualMapper {
105            pe: self.pe,
106            memory: Some(memory),
107            _state: PhantomData,
108        })
109    }
110
111    /// allocate at specific address
112    ///
113    /// fails if address is not available
114    pub fn allocate_at(self, base: usize) -> Result<ManualMapper<state::Allocated>> {
115        let size = self.pe.size_of_image();
116        let memory = allocator::allocate_at(base, size)?;
117
118        Ok(ManualMapper {
119            pe: self.pe,
120            memory: Some(memory),
121            _state: PhantomData,
122        })
123    }
124
125    /// allocate anywhere (no preference)
126    pub fn allocate_anywhere(self) -> Result<ManualMapper<state::Allocated>> {
127        let size = self.pe.size_of_image();
128        let memory = allocator::allocate_anywhere(size)?;
129
130        Ok(ManualMapper {
131            pe: self.pe,
132            memory: Some(memory),
133            _state: PhantomData,
134        })
135    }
136}
137
138impl ManualMapper<state::Allocated> {
139    /// get allocated base address
140    pub fn base(&self) -> usize {
141        self.memory.as_ref().unwrap().base()
142    }
143
144    /// get reference to parsed PE
145    pub fn pe(&self) -> &ParsedPe {
146        &self.pe
147    }
148
149    /// map PE sections to allocated memory
150    pub fn map_sections(mut self) -> Result<ManualMapper<state::SectionsMapped>> {
151        let memory = self.memory.as_mut().unwrap();
152        mapper::map_sections(&self.pe, memory)?;
153
154        Ok(ManualMapper {
155            pe: self.pe,
156            memory: self.memory,
157            _state: PhantomData,
158        })
159    }
160}
161
162impl ManualMapper<state::SectionsMapped> {
163    /// get base address
164    pub fn base(&self) -> usize {
165        self.memory.as_ref().unwrap().base()
166    }
167
168    /// get reference to parsed PE
169    pub fn pe(&self) -> &ParsedPe {
170        &self.pe
171    }
172
173    /// apply base relocations
174    pub fn relocate(mut self) -> Result<ManualMapper<state::Relocated>> {
175        let memory = self.memory.as_mut().unwrap();
176        let delta = memory.base() as i64 - self.pe.preferred_base() as i64;
177
178        if delta != 0 {
179            relocator::apply_relocations(&self.pe, memory, delta)?;
180        }
181
182        Ok(ManualMapper {
183            pe: self.pe,
184            memory: self.memory,
185            _state: PhantomData,
186        })
187    }
188
189    /// skip relocations (use if loaded at preferred base)
190    pub fn skip_relocations(self) -> ManualMapper<state::Relocated> {
191        ManualMapper {
192            pe: self.pe,
193            memory: self.memory,
194            _state: PhantomData,
195        }
196    }
197}
198
199impl ManualMapper<state::Relocated> {
200    /// get base address
201    pub fn base(&self) -> usize {
202        self.memory.as_ref().unwrap().base()
203    }
204
205    /// get reference to parsed PE
206    pub fn pe(&self) -> &ParsedPe {
207        &self.pe
208    }
209
210    /// resolve import address table
211    pub fn resolve_imports(mut self) -> Result<ManualMapper<state::ImportsResolved>> {
212        let memory = self.memory.as_mut().unwrap();
213        resolver::resolve_imports(&self.pe, memory)?;
214
215        Ok(ManualMapper {
216            pe: self.pe,
217            memory: self.memory,
218            _state: PhantomData,
219        })
220    }
221
222    /// resolve imports with custom resolver function
223    pub fn resolve_imports_with<F>(
224        mut self,
225        resolver_fn: F,
226    ) -> Result<ManualMapper<state::ImportsResolved>>
227    where
228        F: Fn(&str, &str) -> Option<usize>,
229    {
230        let memory = self.memory.as_mut().unwrap();
231        resolver::resolve_imports_custom(&self.pe, memory, resolver_fn)?;
232
233        Ok(ManualMapper {
234            pe: self.pe,
235            memory: self.memory,
236            _state: PhantomData,
237        })
238    }
239
240    /// skip import resolution (use if PE has no imports or manually resolved)
241    pub fn skip_imports(self) -> ManualMapper<state::ImportsResolved> {
242        ManualMapper {
243            pe: self.pe,
244            memory: self.memory,
245            _state: PhantomData,
246        }
247    }
248}
249
250impl ManualMapper<state::ImportsResolved> {
251    /// get base address
252    pub fn base(&self) -> usize {
253        self.memory.as_ref().unwrap().base()
254    }
255
256    /// get reference to parsed PE
257    pub fn pe(&self) -> &ParsedPe {
258        &self.pe
259    }
260
261    /// process TLS callbacks
262    pub fn process_tls(mut self) -> Result<ManualMapper<state::TlsProcessed>> {
263        let memory = self.memory.as_mut().unwrap();
264        tls::process_tls(&self.pe, memory)?;
265
266        Ok(ManualMapper {
267            pe: self.pe,
268            memory: self.memory,
269            _state: PhantomData,
270        })
271    }
272
273    /// skip TLS processing
274    pub fn skip_tls(self) -> ManualMapper<state::TlsProcessed> {
275        ManualMapper {
276            pe: self.pe,
277            memory: self.memory,
278            _state: PhantomData,
279        }
280    }
281}
282
283impl ManualMapper<state::TlsProcessed> {
284    /// get base address
285    pub fn base(&self) -> usize {
286        self.memory.as_ref().unwrap().base()
287    }
288
289    /// get reference to parsed PE
290    pub fn pe(&self) -> &ParsedPe {
291        &self.pe
292    }
293
294    /// finalize mapping with proper memory protections
295    pub fn finalize(mut self) -> Result<ManualMapper<state::Ready>> {
296        let memory = self.memory.as_mut().unwrap();
297        mapper::set_section_protections(&self.pe, memory)?;
298
299        Ok(ManualMapper {
300            pe: self.pe,
301            memory: self.memory,
302            _state: PhantomData,
303        })
304    }
305
306    /// finalize without setting protections (keeps RW everywhere)
307    pub fn finalize_without_protections(self) -> ManualMapper<state::Ready> {
308        ManualMapper {
309            pe: self.pe,
310            memory: self.memory,
311            _state: PhantomData,
312        }
313    }
314}
315
316impl ManualMapper<state::Ready> {
317    /// call DllMain with DLL_PROCESS_ATTACH
318    pub fn call_entry_point(&self) -> Result<bool> {
319        let memory = self.memory.as_ref().unwrap();
320        entry::call_dll_attach(&self.pe, memory)
321    }
322
323    /// call DllMain with custom reason
324    pub fn call_entry_point_with_reason(&self, call_reason: u32) -> Result<bool> {
325        let memory = self.memory.as_ref().unwrap();
326        entry::call_entry_point(&self.pe, memory, call_reason)
327    }
328
329    /// get export address by name
330    pub fn get_export(&self, name: &str) -> Result<usize> {
331        let memory = self.memory.as_ref().unwrap();
332        resolver::get_mapped_export(&self.pe, memory, name)
333    }
334
335    /// get export address by ordinal
336    pub fn get_export_by_ordinal(&self, ordinal: u16) -> Result<usize> {
337        let memory = self.memory.as_ref().unwrap();
338        resolver::get_mapped_export_by_ordinal(&self.pe, memory, ordinal)
339    }
340
341    /// get base address of mapped image
342    pub fn base(&self) -> usize {
343        self.memory.as_ref().unwrap().base()
344    }
345
346    /// get size of mapped image
347    pub fn size(&self) -> usize {
348        self.memory.as_ref().unwrap().size()
349    }
350
351    /// get reference to parsed PE
352    pub fn pe(&self) -> &ParsedPe {
353        &self.pe
354    }
355
356    /// consume and return raw memory handle
357    pub fn into_memory(mut self) -> MappedMemory {
358        self.memory.take().unwrap()
359    }
360
361    /// get pointer to specific offset in mapped image
362    pub fn ptr_at(&self, offset: usize) -> *mut u8 {
363        self.memory.as_ref().unwrap().ptr_at(offset)
364    }
365
366    /// unmap and free memory
367    pub fn unmap(mut self) -> Result<()> {
368        if let Some(memory) = self.memory.take() {
369            // call DllMain with DLL_PROCESS_DETACH first (ignore errors)
370            let _ = entry::call_dll_detach(&self.pe, &memory);
371            memory.free()?;
372        }
373        Ok(())
374    }
375}
376
377/// convenience function: map PE from bytes with all default steps
378pub fn map_pe(data: &[u8]) -> Result<ManualMapper<state::Ready>> {
379    ManualMapper::parse(data)?
380        .allocate()?
381        .map_sections()?
382        .relocate()?
383        .resolve_imports()?
384        .process_tls()?
385        .finalize()
386}
387
388/// convenience function: map PE from file with all default steps
389pub fn map_file(path: &str) -> Result<ManualMapper<state::Ready>> {
390    ManualMapper::from_file(path)?
391        .allocate()?
392        .map_sections()?
393        .relocate()?
394        .resolve_imports()?
395        .process_tls()?
396        .finalize()
397}
398
399/// convenience function: map PE from bytes and call entry point
400pub fn map_and_call(data: &[u8]) -> Result<ManualMapper<state::Ready>> {
401    let mapper = map_pe(data)?;
402    mapper.call_entry_point()?;
403    Ok(mapper)
404}
405
406/// convenience function: map PE from file and call entry point
407pub fn map_file_and_call(path: &str) -> Result<ManualMapper<state::Ready>> {
408    let mapper = map_file(path)?;
409    mapper.call_entry_point()?;
410    Ok(mapper)
411}
412
413#[cfg(test)]
414mod tests {
415    use super::*;
416
417    #[test]
418    fn test_parse_and_allocate() {
419        let exe_path = std::env::current_exe().unwrap();
420        let data = std::fs::read(&exe_path).unwrap();
421
422        let mapper = ManualMapper::parse(&data).unwrap();
423        assert!(mapper.pe().size_of_image() > 0);
424
425        let mapper = mapper.allocate().unwrap();
426        assert!(mapper.base() != 0);
427    }
428
429    #[test]
430    fn test_map_sections() {
431        let exe_path = std::env::current_exe().unwrap();
432        let data = std::fs::read(&exe_path).unwrap();
433
434        let mapper = ManualMapper::parse(&data)
435            .unwrap()
436            .allocate()
437            .unwrap()
438            .map_sections()
439            .unwrap();
440
441        // verify MZ header was copied
442        assert!(mapper.base() != 0);
443    }
444
445    // note: full integration tests that call entry points should be done
446    // with actual test DLLs, not the running executable
447}