Skip to main content

manualmap/
lib.rs

1//! Manual PE mapping: relocations, IAT rewrite, section permissions, TLS.
2//!
3//! [`read_and_map_module`] loads a file, maps it, and returns
4//! `(PeMetadata, base)`. [`manually_map_module`] does the same from a
5//! buffer. Set `clean_dos_header` to wipe MZ / DOS stub IOCs.
6//!
7//! ```ignore
8//! let (_pe, base) = manualmap::read_and_map_module(
9//!     r"C:\Windows\System32\ntdll.dll",
10//!     true,
11//!     false,
12//! ).unwrap();
13//! ```
14
15// dyncvoke - Manual PE Mapping Module
16// @5mukx
17
18#![cfg(windows)]
19#![cfg_attr(docsrs, feature(doc_cfg))]
20
21use data::lc;
22use data::LARGE_INTEGER;
23use std::ffi::c_void;
24use std::mem::size_of;
25use std::path::Path;
26use std::{fs, ptr};
27
28use data::{section_ascii_name, RuntimeFunction, OBJECT_ATTRIBUTES, UNICODE_STRING};
29use windows_sys::Win32::System::SystemServices::{IMAGE_BASE_RELOCATION, IMAGE_IMPORT_DESCRIPTOR};
30use windows_sys::Win32::System::WindowsProgramming::{IMAGE_THUNK_DATA32, IMAGE_THUNK_DATA64};
31use windows_sys::Win32::{
32    Foundation::HANDLE,
33    System::{
34        Diagnostics::Debug::{IMAGE_OPTIONAL_HEADER32, IMAGE_SECTION_HEADER},
35        IO::IO_STATUS_BLOCK,
36    },
37};
38
39use data::{
40    ImageFileHeader, ImageOptionalHeader64, PeManualMap, PeMetadata, FILE_EXECUTE,
41    FILE_NON_DIRECTORY_FILE, FILE_READ_ATTRIBUTES, FILE_READ_DATA, FILE_SHARE_DELETE,
42    FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_NONALERT, MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE,
43    PAGE_EXECUTE_READ, PAGE_EXECUTE_READWRITE, PAGE_READONLY, PAGE_READWRITE, PVOID,
44    SECTION_ALL_ACCESS, SECTION_MEM_EXECUTE, SECTION_MEM_READ, SECTION_MEM_WRITE, SEC_IMAGE,
45    SYNCHRONIZE,
46};
47
48/// Manually maps a PE from disk to the memory of the current process.
49///
50/// If the clean_headers parameters is set to true, the mapped pe's dos header will be removed during the
51/// mapping process. Otherwise, the dos header will be kept untouched.
52///
53/// The third parameter determines whether TLS callbacks are executed (true) or not (false).
54///
55/// It will return either a pair (PeMetadata,usize) containing the mapped PE
56/// metadata and its base address or a String with a descriptive error message.
57///
58/// # Examples
59///
60/// ```ignore
61/// let ntdll = manualmap::read_and_map_module(r"c:\windows\system32\ntdll.dll", true, false);
62///
63/// match ntdll {
64///     Ok(x) => if x.1 != 0 {println!("The base address of ntdll.dll is 0x{:X}.", x.1);},
65///     Err(e) => println!("{}", e),
66/// }
67/// ```
68pub fn read_and_map_module(
69    filepath: &str,
70    clean_dos_header: bool,
71    run_callbacks: bool,
72) -> Result<(PeMetadata, usize), String> {
73    let file_content = fs::read(filepath).expect(&lc!("[x] Error opening the specified file."));
74    let file_content_ptr = file_content.as_ptr() as *mut _;
75
76    let result = manually_map_module(file_content_ptr, clean_dos_header, run_callbacks)?;
77
78    unsafe {
79        for i in 0..file_content.len() {
80            *(file_content_ptr.add(i)) = 0u8;
81        }
82
83        Ok(result)
84    }
85}
86
87/// Manually maps a PE into the current process.
88///
89/// If the clean_headers parameters is set to true, the mapped pe's dos header will be removed during the
90/// mapping process. Otherwise, the dos header will be kept untouched.
91///
92/// The third parameter determines whether TLS callbacks are executed (true) or not (false).
93///
94/// It will return either a pair (PeMetadata,usize) containing the mapped PE
95/// metadata and its base address or a String with a descriptive error message.
96///
97/// # Examples
98///
99/// ```ignore
100/// use std::fs;
101///
102/// let file_content = fs::read("c:\\windows\\system32\\ntdll.dll").expect("[x] Error opening the specified file.");
103/// let file_content_ptr = file_content.as_ptr();
104/// let result = manualmap::manually_map_module(file_content_ptr, true, true);
105/// ```
106pub fn manually_map_module(
107    file_ptr: *const u8,
108    clean_dos_headers: bool,
109    run_callbacks: bool,
110) -> Result<(PeMetadata, usize), String> {
111    let pe_info = get_pe_metadata(file_ptr, false)?;
112    if (pe_info.is_32_bit && (size_of::<usize>() == 8))
113        || (!pe_info.is_32_bit && (size_of::<usize>() == 4))
114    {
115        return Err(lc!(
116            "[x] The module architecture does not match the process architecture."
117        ));
118    }
119
120    let dwsize;
121    if pe_info.is_32_bit {
122        dwsize = pe_info.opt_header_32.SizeOfImage as usize;
123    } else {
124        dwsize = pe_info.opt_header_64.size_of_image as usize;
125    }
126
127    unsafe {
128        let handle = dyncvoke_core::INVALID_HANDLE_VALUE;
129        let mut a = usize::default();
130        let base_address: *mut PVOID = &mut a as *mut usize as *mut PVOID;
131        let zero_bits = 0 as usize;
132        let size: *mut usize = std::mem::transmute(&dwsize);
133
134        let ret = dyncvoke_core::nt_allocate_virtual_memory(
135            handle,
136            base_address,
137            zero_bits,
138            size,
139            MEM_COMMIT | MEM_RESERVE,
140            PAGE_READWRITE,
141        );
142
143        if ret != 0 {
144            return Err(lc!("[x] Error allocating memory."));
145        }
146
147        let image_ptr = *base_address;
148
149        map_module_to_memory(file_ptr, image_ptr, &pe_info)?;
150
151        relocate_module(&pe_info, image_ptr);
152
153        rewrite_module_iat(&pe_info, image_ptr)?;
154
155        if clean_dos_headers {
156            clean_dos_header(image_ptr);
157        }
158
159        set_module_section_permissions(&pe_info, image_ptr)?;
160
161        add_runtime_table(&pe_info, image_ptr);
162
163        if run_callbacks {
164            run_tls_callbacks(&pe_info, image_ptr);
165        }
166
167        Ok((pe_info, image_ptr as usize))
168    }
169}
170
171/// Returns a pair containing a pointer to the Exception data of an arbitrary module and the size of the  
172/// corresponding PE section (.pdata). In case that it fails to retrieve this information, it returns
173/// null values.
174pub fn get_runtime_table(image_ptr: *mut c_void) -> (*mut data::RuntimeFunction, u32) {
175    let mut size: u32 = 0;
176    let module_metadata = get_pe_metadata(image_ptr as *const u8, false);
177    if !module_metadata.is_ok() {
178        return (ptr::null_mut(), size);
179    }
180
181    let metadata = module_metadata.unwrap();
182
183    let mut runtime: *mut data::RuntimeFunction = ptr::null_mut();
184    for section in &metadata.sections {
185        if section_ascii_name(&section.Name) == b".pdata" {
186            let base = image_ptr as usize;
187            let addr = base + section.VirtualAddress as usize;
188            runtime = std::ptr::with_exposed_provenance_mut::<RuntimeFunction>(addr);
189            size = section.SizeOfRawData;
190            break;
191        }
192    }
193
194    return (runtime, size);
195}
196
197/// Retrieves PE headers information from the module base address.
198///
199/// It will return either a data::PeMetada struct containing the PE
200/// metadata or a String with a descriptive error message.
201///
202/// # Examples
203///
204/// ```
205/// use std::fs;
206///
207/// let file_content = fs::read("c:\\windows\\system32\\ntdll.dll").expect("[x] Error opening the specified file.");
208/// let file_content_ptr = file_content.as_ptr();
209/// let result = manualmap::get_pe_metadata(file_content_ptr, false);
210/// ```
211pub fn get_pe_metadata(module_ptr: *const u8, check_signature: bool) -> Result<PeMetadata, String> {
212    if module_ptr.is_null() {
213        return Err(lc!("[x] Null module pointer."));
214    }
215    let mut pe_metadata = PeMetadata::default();
216
217    unsafe {
218        let e_lfanew = *((module_ptr as usize + 0x3C) as *const u32);
219        pe_metadata.pe = *((module_ptr as usize + e_lfanew as usize) as *const u32);
220
221        if pe_metadata.pe != 0x4550 && check_signature {
222            return Err(lc!("[x] Invalid PE signature."));
223        }
224
225        pe_metadata.image_file_header =
226            *((module_ptr as usize + e_lfanew as usize + 0x4) as *mut ImageFileHeader);
227
228        let opt_header: *const u16 = (module_ptr as usize + e_lfanew as usize + 0x18) as *const u16;
229        let pe_arch = *(opt_header);
230
231        if pe_arch == 0x010B {
232            pe_metadata.is_32_bit = true;
233            let opt_header_content: *const IMAGE_OPTIONAL_HEADER32 =
234                std::mem::transmute(opt_header);
235            pe_metadata.opt_header_32 = *opt_header_content;
236        } else if pe_arch == 0x020B {
237            pe_metadata.is_32_bit = false;
238            let opt_header_content: *const ImageOptionalHeader64 = std::mem::transmute(opt_header);
239            pe_metadata.opt_header_64 = *opt_header_content;
240        } else {
241            return Err(lc!("[x] Invalid magic value."));
242        }
243
244        let mut sections: Vec<IMAGE_SECTION_HEADER> = vec![];
245
246        for i in 0..pe_metadata.image_file_header.number_of_sections {
247            let section_ptr = (opt_header as usize
248                + pe_metadata.image_file_header.size_of_optional_header as usize
249                + (i * 0x28) as usize) as *const u8;
250            let section_ptr: *const IMAGE_SECTION_HEADER = std::mem::transmute(section_ptr);
251            sections.push(*section_ptr);
252        }
253
254        pe_metadata.sections = sections;
255
256        Ok(pe_metadata)
257    }
258}
259
260/// Maps a module to a valid memory space in the current process.
261///
262/// The parameters required are a vector with the module content, the base address where the module should be
263/// mapped and the module's metadata.
264pub fn map_module_to_memory(
265    module_ptr: *const u8,
266    image_ptr: *mut c_void,
267    pe_info: &PeMetadata,
268) -> Result<(), String> {
269    if (pe_info.is_32_bit && (size_of::<usize>() == 8))
270        || (!pe_info.is_32_bit && (size_of::<usize>() == 4))
271    {
272        return Err(lc!(
273            "[x] The module architecture does not match the process architecture."
274        ));
275    }
276
277    let nsize;
278    if pe_info.is_32_bit {
279        nsize = pe_info.opt_header_32.SizeOfHeaders as usize;
280    } else {
281        nsize = pe_info.opt_header_64.size_of_headers as usize;
282    }
283
284    unsafe {
285        let handle = dyncvoke_core::INVALID_HANDLE_VALUE;
286        let base_address: *mut c_void = std::mem::transmute(image_ptr);
287        let buffer: *mut c_void = std::mem::transmute(module_ptr);
288        let mut written: usize = 0;
289        let bytes_written: *mut usize = &mut written;
290        let ret = dyncvoke_core::nt_write_virtual_memory(
291            handle,
292            base_address,
293            buffer,
294            nsize,
295            bytes_written,
296        );
297
298        if ret != 0 {
299            return Err(lc!("[x] Error writing PE headers to the allocated memory."));
300        }
301
302        for section in &pe_info.sections {
303            let section_base_ptr =
304                (image_ptr as usize + section.VirtualAddress as usize) as *mut u8;
305            let section_content_ptr =
306                (module_ptr as usize + section.PointerToRawData as usize) as *mut u8;
307
308            let base_address: *mut c_void = std::mem::transmute(section_base_ptr);
309            let buffer: *mut c_void = std::mem::transmute(section_content_ptr);
310            let nsize = section.SizeOfRawData as usize;
311            let bytes_written: *mut usize = std::mem::transmute(&written);
312            let ret = dyncvoke_core::nt_write_virtual_memory(
313                handle,
314                base_address,
315                buffer,
316                nsize,
317                bytes_written,
318            );
319
320            if ret != 0 || *bytes_written != nsize {
321                return Err(lc!(
322                    "[x] Failed to write PE sections to the allocated memory."
323                ));
324            }
325        }
326
327        Ok(())
328    }
329}
330
331/// Relocates a module in memory.
332///
333/// The parameters required are the module's metadata information and a
334/// pointer to the base address where the module is mapped in memory.
335pub fn relocate_module(pe_info: &PeMetadata, image_ptr: *mut c_void) {
336    unsafe {
337        let module_memory_base: *mut usize = std::mem::transmute(image_ptr);
338        let image_data_directory;
339        let image_delta: isize;
340        if pe_info.is_32_bit {
341            image_data_directory = pe_info.opt_header_32.DataDirectory[5]; // BaseRelocationTable
342            image_delta = module_memory_base as isize - pe_info.opt_header_32.ImageBase as isize;
343        } else {
344            image_data_directory = pe_info.opt_header_64.datas_directory[5]; // BaseRelocationTable
345            image_delta = module_memory_base as isize - pe_info.opt_header_64.image_base as isize;
346        }
347
348        if image_data_directory.VirtualAddress == 0 || image_data_directory.Size == 0 {
349            return;
350        }
351
352        let reloc_table_start = module_memory_base as usize
353            + image_data_directory.VirtualAddress as usize;
354        let reloc_table_end = reloc_table_start + image_data_directory.Size as usize;
355
356        let mut reloc_table_ptr = reloc_table_start as *mut i32;
357
358        while (reloc_table_ptr as usize) + size_of::<IMAGE_BASE_RELOCATION>()
359            <= reloc_table_end
360        {
361            let ibr: *mut IMAGE_BASE_RELOCATION = std::mem::transmute(reloc_table_ptr);
362            let image_base_relocation = *ibr;
363
364            if image_base_relocation.VirtualAddress == 0
365                || (image_base_relocation.SizeOfBlock as usize)
366                    < size_of::<IMAGE_BASE_RELOCATION>()
367            {
368                break;
369            }
370
371            let reloc_count: isize = (image_base_relocation.SizeOfBlock as isize
372                - size_of::<IMAGE_BASE_RELOCATION>() as isize)
373                / 2;
374
375            for i in 0..reloc_count {
376                let reloc_entry_ptr = (reloc_table_ptr as usize
377                    + size_of::<IMAGE_BASE_RELOCATION>() as usize
378                    + (i * 2) as usize) as *mut u16;
379                let reloc_value = *reloc_entry_ptr;
380
381                let reloc_type = reloc_value >> 12;
382                let reloc_patch = reloc_value & 0xfff;
383
384                if reloc_type != 0 {
385                    let patch_addr = module_memory_base as usize
386                        + image_base_relocation.VirtualAddress as usize
387                        + reloc_patch as usize;
388
389                    if reloc_type == 0x3 {
390                        // IMAGE_REL_BASED_HIGHLOW: 32-bit; target must be 4-byte aligned
391                        if patch_addr % 4 == 0 {
392                            let patch_ptr = patch_addr as *mut i32;
393                            let original_ptr = *patch_ptr;
394                            let patch = original_ptr + image_delta as i32;
395                            *patch_ptr = patch;
396                        }
397                    } else {
398                        // IMAGE_REL_BASED_DIR64 (0xA) and others: 64-bit; target must be 8-byte aligned
399                        // Skip misaligned entries to avoid misaligned pointer dereference (e.g. ntdll on some builds)
400                        if patch_addr % 8 == 0 {
401                            let patch_ptr = patch_addr as *mut isize;
402                            let original_ptr = *patch_ptr;
403                            let patch = original_ptr + image_delta as isize;
404                            *patch_ptr = patch;
405                        }
406                    }
407                }
408            }
409
410            reloc_table_ptr = (reloc_table_ptr as usize
411                + image_base_relocation.SizeOfBlock as usize)
412                as *mut i32;
413        }
414    }
415}
416
417/// Rewrites the IAT of a manually mapped module.
418///
419/// The parameters required are the module's metadata information and a
420/// pointer to the base address where the module is mapped in memory.
421pub fn rewrite_module_iat(pe_info: &PeMetadata, image_ptr: *mut c_void) -> Result<(), String> {
422    unsafe {
423        let module_memory_base: *mut usize = std::mem::transmute(image_ptr);
424        let image_data_directory;
425        if pe_info.is_32_bit {
426            image_data_directory = pe_info.opt_header_32.DataDirectory[1]; // ImportTable
427        } else {
428            image_data_directory = pe_info.opt_header_64.datas_directory[1]; // ImportTable
429        }
430
431        if image_data_directory.VirtualAddress == 0 {
432            return Ok(());
433        }
434
435        let import_table_ptr = (module_memory_base as usize
436            + image_data_directory.VirtualAddress as usize)
437            as *mut usize;
438
439        let api_set_dict = dyncvoke_core::get_api_mapping();
440
441        let mut counter = 0;
442        let mut image_import_descriptor_ptr = (import_table_ptr as usize
443            + size_of::<IMAGE_IMPORT_DESCRIPTOR>() as usize * counter)
444            as *mut IMAGE_IMPORT_DESCRIPTOR;
445        let mut image_import_descriptor = *image_import_descriptor_ptr;
446
447        while image_import_descriptor.Name != 0 {
448            let mut dll_name = "".to_string();
449            let mut c: char = ' ';
450            let mut ptr =
451                (module_memory_base as usize + image_import_descriptor.Name as usize) as *mut u8;
452            while c != '\0' {
453                c = *ptr as char;
454                if c != '\0' {
455                    dll_name.push(c);
456                    ptr = ptr.add(1);
457                }
458            }
459
460            if dll_name == "" {
461                return Ok(());
462            } else {
463                let lookup_key = if dll_name.len() >= 6 {
464                    format!("{}{}", &dll_name[..dll_name.len() - 6], ".dll")
465                } else {
466                    dll_name.clone()
467                };
468
469                if (dll_name.starts_with("api-") || dll_name.starts_with("ext-"))
470                    && api_set_dict.contains_key(&lookup_key)
471                {
472                    let key = match api_set_dict.get(&lookup_key) {
473                        Some(x) => x.to_string(),
474                        None => "".to_string(),
475                    };
476
477                    if key.len() > 0 {
478                        dll_name = key.to_string();
479                    }
480                }
481
482                let mut module_handle = dyncvoke_core::get_module_base_address(&dll_name) as usize;
483
484                if module_handle == 0 {
485                    module_handle = dyncvoke_core::load_library_a(&dll_name) as usize;
486
487                    if module_handle == 0 {
488                        return Err(format!(
489                            "{}{}",
490                            lc!("[x] Unable to find the specified module: "),
491                            dll_name
492                        ));
493                    }
494                }
495
496                if pe_info.is_32_bit {
497                    let mut i: isize = 0;
498
499                    loop {
500                        let image_thunk_data = (module_memory_base as usize
501                            + image_import_descriptor.Anonymous.OriginalFirstThunk as usize
502                            + i as usize * size_of::<u32>() as usize)
503                            as *mut IMAGE_THUNK_DATA32;
504                        let image_thunk_data = *image_thunk_data;
505                        let ft_itd = (module_memory_base as usize
506                            + image_import_descriptor.FirstThunk as usize
507                            + i as usize * size_of::<u32>() as usize)
508                            as *mut i32;
509                        if image_thunk_data.u1.AddressOfData == 0 {
510                            break;
511                        }
512
513                        if image_thunk_data.u1.AddressOfData < 0x80000000 {
514                            let mut imp_by_name_ptr = (module_memory_base as usize
515                                + image_thunk_data.u1.AddressOfData as usize
516                                + size_of::<u16>() as usize)
517                                as *mut u8;
518                            let mut import_name: String = "".to_string();
519                            let mut c: char = ' ';
520                            while c != '\0' {
521                                c = *imp_by_name_ptr as char;
522                                if c != '\0' {
523                                    import_name.push(c);
524                                }
525
526                                imp_by_name_ptr = imp_by_name_ptr.add(1);
527                            }
528
529                            let func_ptr =
530                                dyncvoke_core::get_function_address(module_handle, &import_name);
531                            *ft_itd = func_ptr as i32;
532                        } else {
533                            let f_ordinal = (image_thunk_data.u1.AddressOfData & 0xFFFF) as u32;
534                            let func_ptr = dyncvoke_core::get_function_address_by_ordinal(
535                                module_handle,
536                                f_ordinal,
537                            );
538                            let func_ptr = func_ptr as *mut i32;
539                            *ft_itd = func_ptr as i32;
540                        }
541
542                        i = i + 1;
543                    }
544                } else {
545                    let mut i: isize = 0;
546
547                    loop {
548                        let image_thunk_data = (module_memory_base as u64
549                            + image_import_descriptor.Anonymous.OriginalFirstThunk as u64
550                            + i as u64 * size_of::<u64>() as u64)
551                            as *mut IMAGE_THUNK_DATA64;
552                        let image_thunk_data = *image_thunk_data;
553                        let ft_itd = (module_memory_base as u64
554                            + image_import_descriptor.FirstThunk as u64
555                            + i as u64 * size_of::<u64>() as u64)
556                            as *mut isize;
557
558                        if image_thunk_data.u1.AddressOfData == 0 {
559                            break;
560                        }
561
562                        if image_thunk_data.u1.AddressOfData < 0x8000000000000000 {
563                            let mut imp_by_name_ptr = (module_memory_base as u64
564                                + image_thunk_data.u1.AddressOfData as u64
565                                + size_of::<u16>() as u64)
566                                as *mut u8;
567                            let mut import_name: String = "".to_string();
568                            let mut c: char = ' ';
569                            while c != '\0' {
570                                c = *imp_by_name_ptr as char;
571                                if c != '\0' {
572                                    import_name.push(c);
573                                }
574
575                                imp_by_name_ptr = imp_by_name_ptr.add(1);
576                            }
577
578                            let func_ptr =
579                                dyncvoke_core::get_function_address(module_handle, &import_name)
580                                    as *mut isize;
581                            *ft_itd = func_ptr as isize;
582                        } else {
583                            let f_ordinal = (image_thunk_data.u1.AddressOfData & 0xFFFF) as u32;
584                            let func_ptr = dyncvoke_core::get_function_address_by_ordinal(
585                                module_handle,
586                                f_ordinal,
587                            );
588                            *ft_itd = func_ptr as isize;
589                        }
590
591                        i = i + 1;
592                    }
593                }
594            }
595
596            counter = counter + 1;
597            image_import_descriptor_ptr = (import_table_ptr as usize
598                + size_of::<IMAGE_IMPORT_DESCRIPTOR>() as usize * counter)
599                as *mut IMAGE_IMPORT_DESCRIPTOR;
600            image_import_descriptor = *image_import_descriptor_ptr;
601        }
602
603        Ok(())
604    }
605}
606
607fn clean_dos_header(image_ptr: *mut c_void) {
608    unsafe {
609        let mut base_addr = image_ptr as *mut u8;
610        let pe_header = image_ptr as isize + 0x3C;
611        while (base_addr as isize) < pe_header {
612            *base_addr = 0;
613            base_addr = base_addr.add(1);
614        }
615        base_addr = base_addr.add(4);
616
617        let e_lfanew = *((image_ptr as usize + 0x3C) as *const u32);
618        let pe = image_ptr as isize + e_lfanew as isize;
619
620        while (base_addr as isize) < pe {
621            *base_addr = 0;
622            base_addr = base_addr.add(1);
623        }
624
625        let pe = pe as *mut u16;
626        *pe = 0;
627    }
628}
629
630pub fn add_runtime_table(pe_info: &PeMetadata, image_ptr: *mut c_void) {
631    unsafe {
632        for section in &pe_info.sections {
633            if section_ascii_name(&section.Name) == b".pdata" {
634                let entry_count = (section.SizeOfRawData / 12) as i32; // 12 = size_of RUNTIME_FUNCTION
635
636                let func: data::RtlAddFunctionTable;
637                let _ret: Option<bool>;
638                let k32 = dyncvoke_core::get_module_base_address(&lc!("kernel32.dll"));
639                let function_table_addr: usize =
640                    image_ptr as usize + section.VirtualAddress as usize;
641                dyncvoke_core::dynamic_invoke!(
642                    k32,
643                    &lc!("RtlAddFunctionTable"),
644                    func,
645                    _ret,
646                    function_table_addr,
647                    entry_count,
648                    image_ptr as usize
649                );
650            }
651        }
652    }
653}
654
655/// Sets correct module section permissions for a manually mapped module.
656///
657/// The parameters required are the module's metadata information and a
658/// pointer to the base address where the module is mapped in memory.
659pub fn set_module_section_permissions(
660    pe_info: &PeMetadata,
661    image_ptr: *mut c_void,
662) -> Result<(), String> {
663    unsafe {
664        let base_of_code;
665
666        if pe_info.is_32_bit {
667            base_of_code = pe_info.opt_header_32.BaseOfCode as usize;
668        } else {
669            base_of_code = pe_info.opt_header_64.base_of_code as usize;
670        }
671
672        let handle = dyncvoke_core::INVALID_HANDLE_VALUE;
673        let mut image_ptr = image_ptr;
674        let base_address: *mut PVOID = &mut image_ptr as *mut *mut c_void as *mut PVOID;
675        let mut size: usize = base_of_code;
676        let mut old_protection: u32 = 0;
677        let _ret = dyncvoke_core::nt_protect_virtual_memory(
678            handle,
679            base_address,
680            &mut size,
681            PAGE_READONLY,
682            &mut old_protection,
683        );
684
685        for section in &pe_info.sections {
686            let is_read = (section.Characteristics & SECTION_MEM_READ) != 0;
687            let is_write = (section.Characteristics & SECTION_MEM_WRITE) != 0;
688            let is_execute = (section.Characteristics & SECTION_MEM_EXECUTE) != 0;
689            let new_protect: u32;
690
691            if is_read & !is_write & !is_execute {
692                new_protect = PAGE_READONLY;
693            } else if is_read & is_write & !is_execute {
694                new_protect = PAGE_READWRITE;
695            } else if is_read & is_write & is_execute {
696                new_protect = PAGE_EXECUTE_READWRITE;
697            } else if is_read & !is_write & is_execute {
698                new_protect = PAGE_EXECUTE_READ;
699            } else if !is_read & !is_write & is_execute {
700                new_protect = PAGE_EXECUTE;
701            } else {
702                return Err(lc!("[x] Unknown section permission."));
703            }
704
705            let mut address: *mut c_void =
706                (image_ptr as usize + section.VirtualAddress as usize) as *mut c_void;
707            let base_address: *mut PVOID = &mut address as *mut *mut c_void as *mut PVOID;
708            size = section.Misc.VirtualSize as usize;
709            let mut old_protection: u32 = 0;
710            let ret = dyncvoke_core::nt_protect_virtual_memory(
711                handle,
712                base_address,
713                &mut size,
714                new_protect,
715                &mut old_protection,
716            );
717
718            if ret != 0 {
719                return Err(lc!("[x] Error changing section permission."));
720            }
721        }
722
723        Ok(())
724    }
725}
726
727/// Executes any registered TLS Callback function.
728///
729/// The parameters required are the module's metadata information and a
730/// pointer to the base address where the module is mapped in memory.
731pub fn run_tls_callbacks(pe_info: &PeMetadata, image_ptr: *mut c_void) {
732    unsafe {
733        let entry_point;
734        if pe_info.is_32_bit {
735            entry_point = image_ptr as isize + pe_info.opt_header_32.AddressOfEntryPoint as isize;
736        } else {
737            entry_point =
738                image_ptr as isize + pe_info.opt_header_64.address_of_entry_point as isize;
739        }
740
741        let (tls_dir_rva, tls_cb_off) = if pe_info.is_32_bit {
742            (
743                pe_info.opt_header_32.NumberOfRvaAndSizes >= 10,
744                pe_info.opt_header_32.DataDirectory[9].VirtualAddress as usize,
745            )
746        } else {
747            (
748                pe_info.opt_header_64.number_of_rva_and_sizes >= 10,
749                pe_info.opt_header_64.datas_directory[9].VirtualAddress as usize,
750            )
751        };
752        if tls_dir_rva && tls_cb_off != 0 {
753            let address: *mut u8 = (image_ptr as usize + tls_cb_off) as *mut u8;
754            // IMAGE_TLS_DIRECTORY64.AddressOfCallBacks is at offset 24;
755            // IMAGE_TLS_DIRECTORY32.AddressOfCallBacks is at offset 12.
756            // winnt.h IMAGE_TLS_DIRECTORY.
757            let cb_off = if pe_info.is_32_bit { 12 } else { 24 };
758            let address_of_tls_callback = address.add(cb_off) as *mut usize;
759            let mut address_of_tls_callback_array: *mut usize =
760                std::ptr::with_exposed_provenance_mut::<usize>(*address_of_tls_callback);
761
762            while *address_of_tls_callback_array != 0 {
763                let tls_callback: extern "system" fn(isize, u32, PVOID) =
764                    std::mem::transmute(*address_of_tls_callback_array);
765                tls_callback(entry_point, 1, ptr::null_mut());
766                address_of_tls_callback_array = address_of_tls_callback_array.add(1);
767            }
768        }
769    }
770}
771
772/// Map a module to a memory section.
773///
774/// The parameter required is the file path of the module that should be mapped.
775pub fn map_to_section(module_path: &str) -> Result<(PeManualMap, HANDLE), String> {
776    unsafe {
777        if !Path::new(&module_path).is_file() {
778            return Err(lc!("[x] Filepath not found."));
779        }
780
781        let module_path = format!("{}{}", "\\??\\", module_path);
782        let mut module_path_utf16: Vec<u16> = module_path.encode_utf16().collect();
783        module_path_utf16.push(0);
784
785        let mut o_name: UNICODE_STRING = std::mem::zeroed();
786        dyncvoke_core::rtl_init_unicode_string(&mut o_name, module_path_utf16.as_ptr());
787
788        let mut object_attributes: OBJECT_ATTRIBUTES = std::mem::zeroed();
789        object_attributes.Length = size_of::<OBJECT_ATTRIBUTES>() as u32;
790        object_attributes.ObjectName = &mut o_name;
791        object_attributes.Attributes = 0x40; // OBJ_CASE_INSENSITIVE
792
793        let mut io: IO_STATUS_BLOCK = std::mem::zeroed();
794        let mut hfile: HANDLE = std::ptr::null_mut();
795        let r = dyncvoke_core::nt_open_file(
796            &mut hfile,
797            FILE_READ_DATA | FILE_EXECUTE | FILE_READ_ATTRIBUTES | SYNCHRONIZE,
798            &mut object_attributes,
799            &mut io,
800            FILE_SHARE_READ | FILE_SHARE_DELETE,
801            FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE,
802        );
803
804        if r != 0 {
805            return Err(lc!("[x] Error opening file."));
806        }
807
808        let mut max_size: LARGE_INTEGER = LARGE_INTEGER::default();
809        let mut hsection: HANDLE = std::ptr::null_mut();
810        let r = dyncvoke_core::nt_create_section(
811            &mut hsection,
812            SECTION_ALL_ACCESS,
813            ptr::null_mut(),
814            &mut max_size,
815            PAGE_READONLY,
816            SEC_IMAGE,
817            hfile,
818        );
819
820        if r != 0 {
821            return Err(lc!("[x] Error creating file section in memory."));
822        }
823
824        let mut offset: LARGE_INTEGER = LARGE_INTEGER::default();
825        let mut b = usize::default();
826        let base_address: *mut PVOID = &mut b as *mut usize as *mut PVOID;
827        let mut v = usize::default();
828        let r = dyncvoke_core::nt_map_view_of_section(
829            hsection,
830            dyncvoke_core::INVALID_HANDLE_VALUE,
831            base_address,
832            0,
833            0,
834            &mut offset,
835            &mut v,
836            0x2,
837            0x0,
838            PAGE_READWRITE,
839        );
840
841        if r != 0 {
842            return Err(lc!("[x] Error mapping file section."));
843        }
844
845        let base_address: *const u8 = std::mem::transmute(*base_address);
846        let sec_object: PeManualMap = PeManualMap {
847            pe_info: get_pe_metadata(base_address, false).unwrap(),
848            base_address: base_address as usize,
849            decoy_module: module_path,
850        };
851
852        let _r = dyncvoke_core::close_handle(hfile);
853
854        Ok((sec_object, hsection))
855    }
856}
857
858pub fn map_to_allocated_memory(
859    module_ptr: *const u8,
860    image_ptr: *mut c_void,
861    pe_info: &PeMetadata,
862) -> Result<(), String> {
863    map_module_to_memory(module_ptr, image_ptr, &pe_info)?;
864
865    relocate_module(&pe_info, image_ptr);
866
867    rewrite_module_iat(&pe_info, image_ptr)?;
868
869    clean_dos_header(image_ptr);
870
871    set_module_section_permissions(&pe_info, image_ptr)?;
872
873    add_runtime_table(&pe_info, image_ptr);
874
875    Ok(())
876}
877
878// ============================================================================
879// Tests
880// ============================================================================
881
882#[cfg(test)]
883mod tests {
884    use super::*;
885    use data::{PeMetadata, ImageOptionalHeader64, ImageFileHeader};
886    use windows_sys::Win32::System::Diagnostics::Debug::IMAGE_SECTION_HEADER;
887    use std::collections::BTreeMap;
888
889    // Test PeMetadata creation
890    #[test]
891    fn test_pe_metadata_creation() {
892        let pe = PeMetadata {
893            pe: 0x4550, // "PE\0\0"
894            is_32_bit: false,
895            image_file_header: ImageFileHeader {
896                machine: 0x8664, // AMD64
897                number_of_sections: 3,
898                time_data_stamp: 1234567890,
899                pointer_to_symbol_table: 0,
900                number_of_symbols: 0,
901                size_of_optional_header: 0xF0,
902                characteristics: 0x2002, // Executable, Large address aware
903            },
904            opt_header_32: unsafe { std::mem::zeroed() },
905            opt_header_64: ImageOptionalHeader64 {
906                magic: 0x20B, // PE32+
907                major_linker_version: 14,
908                minor_linker_version: 0,
909                size_of_code: 0x1000,
910                size_of_initialized_data: 0,
911                size_of_unitialized_data: 0,
912                address_of_entry_point: 0x1000,
913                base_of_code: 0x1000,
914                image_base: 0x140000000,
915                section_alignment: 0x1000,
916                file_alignment: 0x200,
917                major_operating_system_version: 10,
918                minor_operating_system_version: 0,
919                major_image_version: 0,
920                minor_image_version: 0,
921                major_subsystem_version: 10,
922                minor_subsystem_version: 0,
923                win32_version_value: 0,
924                size_of_image: 0x7000,
925                size_of_headers: 0x400,
926                checksum: 0,
927                subsystem: 3, // WINDOWS_CUI
928                dll_characteristics: 0x8160,
929                size_of_stack_reserve: 0x40000,
930                size_of_stack_commit: 0x1000,
931                size_of_heap_reserve: 0x100000,
932                size_of_heap_commit: 0x1000,
933                loader_flags: 0,
934                number_of_rva_and_sizes: 16,
935                datas_directory: [unsafe { std::mem::zeroed() }; 16],
936            },
937            sections: Vec::new(),
938        };
939
940        assert_eq!(pe.pe, 0x4550);
941        assert!(!pe.is_32_bit);
942        assert_eq!(pe.image_file_header.machine, 0x8664);
943        assert_eq!(pe.image_file_header.number_of_sections, 3);
944    }
945
946    // Test PeMetadata default
947    #[test]
948    fn test_pe_metadata_default() {
949        let pe = PeMetadata::default();
950        assert_eq!(pe.pe, 0);
951        assert!(!pe.is_32_bit);
952    }
953
954
955    #[test]
956    fn test_image_section_header_size() {
957        assert_eq!(std::mem::size_of::<IMAGE_SECTION_HEADER>(), 40);
958    }
959
960    // Test PeManualMap creation
961    #[test]
962    fn test_pe_manual_map_creation() {
963        let pmm = PeManualMap {
964            decoy_module: "kernel32.dll".to_string(),
965            base_address: 0x12340000,
966            pe_info: PeMetadata::default(),
967        };
968
969        assert_eq!(pmm.decoy_module, "kernel32.dll");
970        assert_eq!(pmm.base_address, 0x12340000);
971    }
972
973    // Test module parsing validation
974    #[test]
975    fn test_pe_header_validation() {
976        // Test that we can validate a PE header
977        let valid_pe: [u8; 2] = [0x50, 0x45]; // "PE"
978        assert_eq!(valid_pe[0], 0x50);
979        assert_eq!(valid_pe[1], 0x45);
980
981        let invalid_pe: [u8; 2] = [0x00, 0x00];
982        assert_ne!(invalid_pe[0], 0x50);
983    }
984
985    // Test DOS header magic
986    #[test]
987    fn test_dos_header_magic() {
988        let dos_magic: [u8; 2] = [0x5A, 0x4D]; // "MZ"
989        assert_eq!(dos_magic[0], 0x5A); // 'M'
990        assert_eq!(dos_magic[1], 0x4D); // 'Z'
991    }
992
993    // Test section characteristics
994    #[test]
995    fn test_section_characteristics() {
996        // CODE section
997        const IMAGE_SCN_CNT_CODE: u32 = 0x20000000;
998        const IMAGE_SCN_MEM_EXECUTE: u32 = 0x20000000;
999        const IMAGE_SCN_MEM_READ: u32 = 0x40000000;
1000        const IMAGE_SCN_MEM_WRITE: u32 = 0x80000000;
1001
1002        let code_section = IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE | IMAGE_SCN_MEM_READ;
1003        assert!(code_section & IMAGE_SCN_CNT_CODE != 0);
1004        assert!(code_section & IMAGE_SCN_MEM_EXECUTE != 0);
1005        assert!(code_section & IMAGE_SCN_MEM_READ != 0);
1006
1007        let data_section = IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE;
1008        assert!(data_section & IMAGE_SCN_MEM_READ != 0);
1009        assert!(data_section & IMAGE_SCN_MEM_WRITE != 0);
1010        assert!(data_section & IMAGE_SCN_MEM_EXECUTE == 0);
1011    }
1012
1013    // Test optional header magic values
1014    #[test]
1015    fn test_optional_header_magic() {
1016        const PE32_MAGIC: u16 = 0x10B;
1017        const PE32_PLUS_MAGIC: u16 = 0x20B;
1018
1019        assert_eq!(PE32_MAGIC, 0x10B);
1020        assert_eq!(PE32_PLUS_MAGIC, 0x20B);
1021    }
1022
1023    // Test subsystem values
1024    #[test]
1025    fn test_subsystem_values() {
1026        const IMAGE_SUBSYSTEM_UNKNOWN: u16 = 0;
1027        const IMAGE_SUBSYSTEM_NATIVE: u16 = 1;
1028        const IMAGE_SUBSYSTEM_WINDOWS_GUI: u16 = 2;
1029        const IMAGE_SUBSYSTEM_WINDOWS_CUI: u16 = 3;
1030
1031        assert_eq!(IMAGE_SUBSYSTEM_UNKNOWN, 0);
1032        assert_eq!(IMAGE_SUBSYSTEM_NATIVE, 1);
1033        assert_eq!(IMAGE_SUBSYSTEM_WINDOWS_GUI, 2);
1034        assert_eq!(IMAGE_SUBSYSTEM_WINDOWS_CUI, 3);
1035    }
1036
1037    // Test DLL characteristics
1038    #[test]
1039    fn test_dll_characteristics() {
1040        const IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE: u16 = 0x0040;
1041        const IMAGE_DLL_CHARACTERISTICS_NX_COMPAT: u16 = 0x0100;
1042        const IMAGE_DLL_CHARACTERISTICS_NO_ISOLATION: u16 = 0x0200;
1043        const IMAGE_DLL_CHARACTERISTICS_NO_SEH: u16 = 0x0400;
1044
1045        let chars = IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE | IMAGE_DLL_CHARACTERISTICS_NX_COMPAT;
1046        assert!(chars & IMAGE_DLL_CHARACTERISTICS_DYNAMIC_BASE != 0);
1047        assert!(chars & IMAGE_DLL_CHARACTERISTICS_NX_COMPAT != 0);
1048        assert!(chars & IMAGE_DLL_CHARACTERISTICS_NO_ISOLATION == 0);
1049        assert!(chars & IMAGE_DLL_CHARACTERISTICS_NO_SEH == 0);
1050    }
1051
1052    // Test machine types
1053    #[test]
1054    fn test_machine_types() {
1055        const IMAGE_FILE_MACHINE_I386: u16 = 0x014C;
1056        const IMAGE_FILE_MACHINE_AMD64: u16 = 0x8664;
1057        const IMAGE_FILE_MACHINE_ARM: u16 = 0x01C0;
1058        const IMAGE_FILE_MACHINE_ARM64: u16 = 0xAA64;
1059
1060        assert_eq!(IMAGE_FILE_MACHINE_I386, 0x014C);
1061        assert_eq!(IMAGE_FILE_MACHINE_AMD64, 0x8664);
1062        assert_eq!(IMAGE_FILE_MACHINE_ARM, 0x01C0);
1063        assert_eq!(IMAGE_FILE_MACHINE_ARM64, 0xAA64);
1064    }
1065
1066    // Test data directories
1067    #[test]
1068    fn test_data_directories() {
1069        const IMAGE_DIRECTORY_ENTRY_EXPORT: usize = 0;
1070        const IMAGE_DIRECTORY_ENTRY_IMPORT: usize = 1;
1071        const IMAGE_DIRECTORY_ENTRY_RESOURCE: usize = 2;
1072        const IMAGE_DIRECTORY_ENTRY_EXCEPTION: usize = 3;
1073        const IMAGE_DIRECTORY_ENTRY_TLS: usize = 9;
1074
1075        assert_eq!(IMAGE_DIRECTORY_ENTRY_EXPORT, 0);
1076        assert_eq!(IMAGE_DIRECTORY_ENTRY_IMPORT, 1);
1077        assert_eq!(IMAGE_DIRECTORY_ENTRY_RESOURCE, 2);
1078        assert_eq!(IMAGE_DIRECTORY_ENTRY_EXCEPTION, 3);
1079        assert_eq!(IMAGE_DIRECTORY_ENTRY_TLS, 9);
1080    }
1081
1082    // Test runtime function structure
1083    #[test]
1084    fn test_runtime_function_structure() {
1085        use data::RuntimeFunction;
1086
1087        let rf = RuntimeFunction {
1088            begin_addr: 0x1000,
1089            end_addr: 0x2000,
1090            unwind_addr: 0x1500,
1091        };
1092
1093        assert_eq!(rf.begin_addr, 0x1000);
1094        assert_eq!(rf.end_addr, 0x2000);
1095        assert_eq!(rf.unwind_addr, 0x1500);
1096    }
1097
1098    // Test module size calculations
1099    #[test]
1100    fn test_module_size_calculations() {
1101        let section_alignment = 0x1000u32;
1102        let file_alignment = 0x200u32;
1103        let raw_size = 0x1800u32;
1104
1105        // Calculate aligned size
1106        let aligned_size = ((raw_size + section_alignment - 1) / section_alignment) * section_alignment;
1107        assert_eq!(aligned_size, 0x2000);
1108
1109        // File size alignment
1110        let file_aligned = ((raw_size + file_alignment - 1) / file_alignment) * file_alignment;
1111        assert_eq!(file_aligned, 0x1800);
1112    }
1113
1114    // Test RVA to file offset calculation
1115    #[test]
1116    fn test_rva_to_offset() {
1117        // Simulate section info
1118        struct SectionInfo {
1119            virtual_address: u32,
1120            pointer_to_raw_data: u32,
1121            virtual_size: u32,
1122        }
1123
1124        let sections = vec![
1125            SectionInfo { virtual_address: 0x1000, pointer_to_raw_data: 0x400, virtual_size: 0x1000 },
1126            SectionInfo { virtual_address: 0x2000, pointer_to_raw_data: 0x1400, virtual_size: 0x800 },
1127        ];
1128
1129        // Test RVA in first section
1130        // RVA 0x1500 is in first section (0x1000-0x2000)
1131        // offset = 0x400 + (0x1500 - 0x1000) = 0x400 + 0x500 = 0x900
1132        let rva = 0x1500u32;
1133        let offset = sections.iter().find(|s| rva >= s.virtual_address && rva < s.virtual_address + s.virtual_size)
1134            .map(|s| s.pointer_to_raw_data + (rva - s.virtual_address));
1135
1136        assert_eq!(offset, Some(0x900));
1137    }
1138
1139    // Test import lookup
1140    #[test]
1141    fn test_import_lookup() {
1142        let mut imports: BTreeMap<String, usize> = BTreeMap::new();
1143        imports.insert("kernel32.dll".to_string(), 0x1000);
1144        imports.insert("ntdll.dll".to_string(), 0x2000);
1145        imports.insert("user32.dll".to_string(), 0x3000);
1146
1147        assert_eq!(imports.len(), 3);
1148        assert_eq!(imports.get("kernel32.dll"), Some(&0x1000));
1149        assert!(imports.get("nonexistent.dll").is_none());
1150    }
1151}