Skip to main content

ud_emulator/pe/
sections.rs

1//! Section mapping into the emulator MMU.
2//!
3//! Reference: PE/COFF spec §"Section Table" + §"Section Flags".
4
5use super::header::{Parsed, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ, IMAGE_SCN_MEM_WRITE};
6use super::PeError;
7use crate::emulator::mmu::{Mmu, Perm};
8
9/// Loaded-section descriptor — the union of the file header's
10/// `IMAGE_SECTION_HEADER` and the runtime layout choices we
11/// make.
12#[derive(Debug, Clone)]
13pub struct Section {
14    pub name: String,
15    /// Final VA where this section was mapped.
16    pub va_start: u32,
17    /// Mapped size (rounded up to the page size).
18    pub mapped_size: u32,
19    /// Final permission bits.
20    pub perm: Perm,
21}
22
23const PAGE_SIZE: u32 = 0x1000;
24
25/// Walk the parsed section table, copy raw bytes into emulator
26/// memory, zero-fill the BSS tail, and record permissions to be
27/// stamped on by [`apply_section_permissions`].
28pub fn map_sections(mmu: &mut Mmu, parsed: &Parsed, bytes: &[u8]) -> Result<Vec<Section>, PeError> {
29    map_sections_at(mmu, parsed, bytes, parsed.optional.image_base)
30}
31
32/// Like [`map_sections`] but lays sections out at an explicit
33/// image base. `CreateProcessA` uses this to load each child PE
34/// at a unique base so multiple processes can coexist in the
35/// shared MMU.
36pub fn map_sections_at(
37    mmu: &mut Mmu,
38    parsed: &Parsed,
39    bytes: &[u8],
40    image_base: u32,
41) -> Result<Vec<Section>, PeError> {
42    let mut out = Vec::with_capacity(parsed.sections.len());
43
44    // Map the headers themselves so the codec can read them via
45    // RVA-relative reads.
46    let hdrs_size = round_up(parsed.optional.size_of_headers, PAGE_SIZE);
47    if hdrs_size > 0 {
48        mmu.map(image_base, hdrs_size, Perm::R | Perm::W);
49        let n = (parsed.optional.size_of_headers as usize).min(bytes.len());
50        mmu.write_initializer(image_base, &bytes[..n])?;
51    }
52
53    for sh in &parsed.sections {
54        let va = image_base.wrapping_add(sh.virtual_address);
55        let virt = sh.virtual_size.max(sh.size_of_raw_data);
56        let mapped = round_up(virt, PAGE_SIZE);
57
58        // Map as R+W initially so write_initializer can populate.
59        mmu.map(va, mapped, Perm::R | Perm::W);
60
61        // Copy raw bytes from the file. `SizeOfRawData` may be
62        // larger than `VirtualSize` (file alignment padding); we
63        // copy `min(raw, virt)` to avoid overrunning the mapped
64        // region.
65        let copy_n = (sh.size_of_raw_data.min(virt)) as usize;
66        let raw_off = sh.pointer_to_raw_data as usize;
67        if copy_n > 0 {
68            let src = bytes
69                .get(raw_off..raw_off + copy_n)
70                .ok_or(PeError::SectionOutOfRange {
71                    name: sh.name.clone(),
72                    raw_off: sh.pointer_to_raw_data,
73                    raw_size: sh.size_of_raw_data,
74                })?;
75            mmu.write_initializer(va, src)?;
76        }
77        // Bytes beyond `copy_n` are already zero from the
78        // `Mmu::map` allocation.
79
80        // Final permissions per Characteristics flags.
81        let mut perm = Perm::from_bits(0);
82        if (sh.characteristics & IMAGE_SCN_MEM_READ) != 0 {
83            perm = perm | Perm::R;
84        }
85        if (sh.characteristics & IMAGE_SCN_MEM_WRITE) != 0 {
86            perm = perm | Perm::W;
87        }
88        if (sh.characteristics & IMAGE_SCN_MEM_EXECUTE) != 0 {
89            perm = perm | Perm::X;
90        }
91        // Empty perm sets are coerced to R-only — codec sections
92        // tend to have at least one bit but malformed binaries
93        // can ship zero, and a totally-no-perm region would be
94        // unreachable.
95        if perm.bits() == 0 {
96            perm = Perm::R;
97        }
98        out.push(Section {
99            name: sh.name.clone(),
100            va_start: va,
101            mapped_size: mapped,
102            perm,
103        });
104    }
105    Ok(out)
106}
107
108/// Stamp the recorded final permissions on each mapped page.
109/// Done after import resolution + relocation so that the loader
110/// can write into code pages while populating the IAT / fixups.
111pub fn apply_section_permissions(mmu: &mut Mmu, section: &Section) {
112    mmu.map(section.va_start, section.mapped_size, section.perm);
113}
114
115fn round_up(v: u32, align: u32) -> u32 {
116    if align == 0 {
117        v
118    } else {
119        v.div_ceil(align).wrapping_mul(align)
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn round_up_helper() {
129        assert_eq!(round_up(0, 0x1000), 0);
130        assert_eq!(round_up(1, 0x1000), 0x1000);
131        assert_eq!(round_up(0x1000, 0x1000), 0x1000);
132        assert_eq!(round_up(0x1001, 0x1000), 0x2000);
133    }
134}