1use std::collections::BTreeSet;
7use std::sync::atomic::{AtomicU64, Ordering};
8
9#[derive(Debug, Clone)]
11pub enum FixupStatus {
12 Absent,
14 Applied { pairs: usize },
17 Skipped { reason: String },
22}
23
24pub struct Pe {
26 data: Vec<u8>,
27 pub image_base: u64,
28 sections: Vec<Section>,
29 fixup_posts: Vec<(u64, u64)>,
42 fixup_status: FixupStatus,
44 id: u64,
52}
53
54fn next_pe_id() -> u64 {
55 static NEXT_ID: AtomicU64 = AtomicU64::new(1);
56 NEXT_ID.fetch_add(1, Ordering::Relaxed)
57}
58
59#[derive(Clone, Debug)]
60pub struct Section {
61 pub name: String,
62 pub virtual_address: u32, pub virtual_size: u32,
64 pub raw_offset: u32,
65 pub raw_size: u32,
66 pub characteristics: u32,
70}
71
72pub mod section_flags {
75 pub const CODE: u32 = 0x0000_0020;
77 pub const DISCARDABLE: u32 = 0x0200_0000;
79 pub const EXECUTE: u32 = 0x2000_0000;
81 pub const READ: u32 = 0x4000_0000;
83 pub const WRITE: u32 = 0x8000_0000;
85}
86
87impl Section {
88 pub fn is_executable(&self) -> bool {
91 self.characteristics & (section_flags::EXECUTE | section_flags::CODE) != 0
92 }
93}
94
95fn u16(d: &[u8], o: usize) -> u16 {
96 u16::from_le_bytes([d[o], d[o + 1]])
97}
98fn u32(d: &[u8], o: usize) -> u32 {
99 u32::from_le_bytes([d[o], d[o + 1], d[o + 2], d[o + 3]])
100}
101
102impl Pe {
103 pub fn load(path: &str) -> Result<Pe, String> {
104 let data = std::fs::read(path).map_err(|e| format!("read {path}: {e}"))?;
105 Pe::parse(data)
106 }
107
108 pub fn parse(data: Vec<u8>) -> Result<Pe, String> {
109 let sz = data.len();
110 if sz < 0x40 || &data[0..2] != b"MZ" {
111 return Err(format!(
112 "not an MZ image: first 2 bytes are {:?} (expected b\"MZ\"), file size = {sz} bytes",
113 &data[..data.len().min(2)]
114 ));
115 }
116 let e_lfanew = u32(&data, 0x3c) as usize;
117 if data.len() >= e_lfanew + 2 && &data[e_lfanew..e_lfanew + 2] == b"LE" {
118 return parse_le(data, e_lfanew);
119 }
120 if data.len() < e_lfanew + 24 || &data[e_lfanew..e_lfanew + 4] != b"PE\0\0" {
121 let there = data
126 .get(e_lfanew..e_lfanew + 4)
127 .map(|s| format!("{:02x?}", s))
128 .unwrap_or_else(|| "<past EOF>".into());
129 return Err(format!(
130 "bad PE signature: e_lfanew = {e_lfanew:#x}, bytes there = [{there}] (expected b\"PE\\0\\0\"); file size = {sz} bytes"
131 ));
132 }
133 let coff = e_lfanew + 4;
134 let machine = u16(&data, coff);
135 if machine != 0x014c {
136 return Err(format!(
137 "unsupported machine {machine:#06x} (expected i386 = 0x014c); this crate only decodes 32-bit x86"
138 ));
139 }
140 let num_sections = u16(&data, coff + 2) as usize;
141 let opt_size = u16(&data, coff + 16) as usize;
142 let opt = coff + 20;
143 let magic = u16(&data, opt);
144 if magic != 0x10b {
145 return Err(format!(
146 "unsupported optional-header magic {magic:#06x} (expected PE32 = 0x10b); PE32+ (64-bit) and ROM images are not handled"
147 ));
148 }
149 let image_base = u32(&data, opt + 28) as u64;
150
151 let sec_table = opt + opt_size;
152 let mut sections = Vec::with_capacity(num_sections);
153 for i in 0..num_sections {
154 let s = sec_table + i * 40;
155 if s + 40 > data.len() {
156 return Err(format!(
157 "section table runs past EOF: section #{i} would start at file offset {s:#x} \
158 (e_lfanew={e_lfanew:#x} + opt_size={opt_size:#x} + {i}*40), \
159 but file size is only {sz} bytes — corrupt header or wrong format"
160 ));
161 }
162 let raw_name = &data[s..s + 8];
163 let end = raw_name.iter().position(|&b| b == 0).unwrap_or(8);
164 sections.push(Section {
165 name: String::from_utf8_lossy(&raw_name[..end]).into_owned(),
166 virtual_size: u32(&data, s + 8),
167 virtual_address: u32(&data, s + 12),
168 raw_size: u32(&data, s + 16),
169 raw_offset: u32(&data, s + 20),
170 characteristics: u32(&data, s + 36),
171 });
172 }
173 Ok(Pe {
174 data,
175 image_base,
176 sections,
177 fixup_posts: Vec::new(),
178 fixup_status: FixupStatus::Absent,
179 id: next_pe_id(),
180 })
181}
182
183 pub fn id(&self) -> u64 {
185 self.id
186 }
187
188 pub fn fixup_post_images(&self, pres: &BTreeSet<u64>) -> BTreeSet<u64> {
194 let mut out = BTreeSet::new();
195 if self.fixup_posts.is_empty() || pres.is_empty() {
196 return out;
197 }
198 for &(pre, post) in &self.fixup_posts {
199 if pres.contains(&pre) {
200 out.insert(post);
201 }
202 }
203 out
204 }
205
206 pub fn fixup_count(&self) -> usize {
209 self.fixup_posts.len()
210 }
211
212 pub fn fixup_pairs(&self) -> &[(u64, u64)] {
216 &self.fixup_posts
217 }
218
219 pub fn fixup_status(&self) -> &FixupStatus {
221 &self.fixup_status
222 }
223
224 pub fn section_of(&self, va: u64) -> Option<&Section> {
226 let rva = va.checked_sub(self.image_base)? as u32;
227 self.sections.iter().find(|s| {
228 let span = s.virtual_size.max(s.raw_size);
231 rva >= s.virtual_address && rva < s.virtual_address + span
232 })
233 }
234
235 pub fn read(&self, va: u64, len: usize) -> Option<&[u8]> {
239 let sec = self.section_of(va)?;
240 let rva = (va - self.image_base) as u32;
241 let off_in_sec = rva - sec.virtual_address;
242 if off_in_sec >= sec.raw_size {
243 return None; }
245 let file_off = (sec.raw_offset + off_in_sec) as usize;
246 let avail = (sec.raw_size - off_in_sec) as usize;
247 let take = len.min(avail).min(self.data.len().saturating_sub(file_off));
248 Some(&self.data[file_off..file_off + take])
249 }
250
251 pub fn sections(&self) -> &[Section] {
253 &self.sections
254 }
255
256 pub fn is_executable_va(&self, va: u64) -> bool {
257 self.section_of(va).is_some()
269 }
270
271 pub fn is_executable_section_va(&self, va: u64) -> bool {
278 self.section_of(va)
279 .map(|s| s.is_executable())
280 .unwrap_or(false)
281 }
282
283 pub fn describe_sections(&self, va: u64) -> String {
288 let mut s = String::new();
289 let mut best: Option<(&Section, u64)> = None;
290 for sec in &self.sections {
291 let start = self.image_base + sec.virtual_address as u64;
292 let span = sec.virtual_size.max(sec.raw_size) as u64;
293 let end = start + span;
294 s.push_str(&format!(
295 " {name:<8} [0x{start:08x}..0x{end:08x}) ({span} bytes){flag}\n",
296 name = sec.name,
297 start = start,
298 end = end,
299 span = span,
300 flag = if sec.is_executable() { " code" } else { "" },
301 ));
302 let d = if va < start {
303 start - va
304 } else { va.saturating_sub(end) };
305 if best.map(|(_, bd)| d < bd).unwrap_or(true) {
306 best = Some((sec, d));
307 }
308 }
309 if let Some((sec, d)) = best {
310 s.push_str(&format!(
311 " closest section to {va:#010x}: {name} (distance {d:#x} bytes) — \
312 did you mean 0x{start:08x}?",
313 name = sec.name,
314 start = self.image_base + sec.virtual_address as u64,
315 ));
316 }
317 s
318 }
319
320 pub fn image_range(&self) -> (u64, u64) {
322 let end = self
323 .sections
324 .iter()
325 .map(|s| s.virtual_address as u64 + s.virtual_size.max(s.raw_size) as u64)
326 .max()
327 .unwrap_or(0);
328 (self.image_base, self.image_base + end)
329 }
330
331 pub fn referenced_addrs(&self) -> std::collections::HashSet<u32> {
344 let (lo, hi) = self.image_range();
345 let mut out: std::collections::HashSet<u32> = std::collections::HashSet::new();
346 for sec in &self.sections {
347 let start = sec.raw_offset as usize;
348 let end = (start + sec.raw_size as usize).min(self.data.len());
349 if start >= end {
350 continue;
351 }
352 let blob = &self.data[start..end];
353 let sec_va = self.image_base + sec.virtual_address as u64;
354 if sec.name == ".text" {
356 for i in 0..blob.len().saturating_sub(4) {
357 if blob[i] == 0xEB && i + 2 <= blob.len() {
364 let rel = blob[i + 1] as i8 as i64;
365 let t = (sec_va + i as u64 + 2).wrapping_add(rel as u64);
366 if (lo..hi).contains(&t) {
367 out.insert(t as u32);
368 }
369 }
370 let (op, rel_at) = match blob[i] {
371 0xE8 | 0xE9 => (blob[i], i + 1), 0x0F if i + 6 <= blob.len() && (0x80..=0x8F).contains(&blob[i + 1]) => {
373 (blob[i + 1], i + 2) }
375 _ => continue,
376 };
377 let _ = op;
378 if rel_at + 4 > blob.len() {
379 continue;
380 }
381 let rel = i32::from_le_bytes([
382 blob[rel_at],
383 blob[rel_at + 1],
384 blob[rel_at + 2],
385 blob[rel_at + 3],
386 ]) as i64;
387 let t = (sec_va + (rel_at as u64 + 4)).wrapping_add(rel as u64);
388 if (lo..hi).contains(&t) {
389 out.insert(t as u32);
390 }
391 }
392 }
393 for w in blob.windows(4) {
395 let v = u32::from_le_bytes([w[0], w[1], w[2], w[3]]) as u64;
396 if (lo..hi).contains(&v) {
397 out.insert(v as u32);
398 }
399 }
400 }
401 if !self.fixup_posts.is_empty() {
412 let pres: BTreeSet<u64> = out.iter().map(|&v| v as u64).collect();
413 for post in self.fixup_post_images(&pres) {
414 if (lo..hi).contains(&post) {
415 out.insert(post as u32);
416 }
417 }
418 }
419 out
420 }
421}
422
423fn parse_le(data: Vec<u8>, le_off: usize) -> Result<Pe, String> {
444 let sz = data.len();
445 let need = le_off + 0xAC;
446 if sz < need + 4 {
447 return Err(format!(
448 "truncated LE header: need {need:#x} bytes for header, file size = {sz} bytes"
449 ));
450 }
451 let at32 = |o: usize| -> Result<u32, String> {
452 if o + 4 > sz {
453 return Err(format!("LE header read past EOF at {o:#x}"));
454 }
455 Ok(u32::from_le_bytes([data[o], data[o + 1], data[o + 2], data[o + 3]]))
456 };
457 let at16 = |o: usize| -> Result<u16, String> {
458 if o + 2 > sz {
459 return Err(format!("LE header read past EOF at {o:#x}"));
460 }
461 Ok(u16::from_le_bytes([data[o], data[o + 1]]))
462 };
463 let cpu = at16(le_off + 8)?;
464 if cpu != 2 {
465 return Err(format!(
466 "unsupported LE cpu {cpu:#x} (expected 386 = 0x2); only 32-bit x86 LE is handled"
467 ));
468 }
469 let numpages = at32(le_off + 0x14)? as usize;
470 let pagesize = at32(le_off + 0x28)? as usize;
471 let lastpagesize = at32(le_off + 0x2C)? as usize;
472 let objtab_rel = at32(le_off + 0x40)? as usize;
473 let objcnt = at32(le_off + 0x44)? as usize;
474 let pagemap_rel = at32(le_off + 0x48)? as usize;
475 let itermap = at32(le_off + 0x4C)?;
476 let datapage_abs = at32(le_off + 0x80)? as usize;
477 if !(512..=16384).contains(&pagesize) {
478 return Err(format!("implausible LE pagesize {pagesize}"));
479 }
480 if numpages == 0 || numpages > 100_000 {
481 return Err(format!("implausible LE page count {numpages}"));
482 }
483 if objcnt == 0 || objcnt > 100 {
484 return Err(format!("implausible LE object count {objcnt}"));
485 }
486 if itermap != 0 {
487 return Err(format!(
488 "unsupported LE iterated-data map at {itermap:#x}: iterated pages not yet supported"
489 ));
490 }
491 let objtab = le_off.checked_add(objtab_rel).ok_or("LE objtab overflow")?;
492 let pagemap = le_off.checked_add(pagemap_rel).ok_or("LE pagemap overflow")?;
493 if objtab + objcnt * 24 > sz {
494 return Err(format!(
495 "LE object table runs past EOF: objtab={objtab:#x} + {objcnt}*24, file size {sz:#x}"
496 ));
497 }
498 if pagemap + numpages * 4 > sz {
499 return Err(format!(
500 "LE page map runs past EOF: pagemap={pagemap:#x} + {numpages}*4, file size {sz:#x}"
501 ));
502 }
503 if datapage_abs >= sz {
504 return Err(format!(
505 "LE data pages offset {datapage_abs:#x} past EOF (file size {sz:#x})"
506 ));
507 }
508 let mut phys_of_logical: Vec<(u32, u8)> = Vec::with_capacity(numpages);
512 for i in 0..numpages {
513 let o = pagemap + i * 4;
514 let b0 = data[o] as u32;
515 let b1 = data[o + 1] as u32;
516 let b2 = data[o + 2] as u32;
517 let flags = data[o + 3];
518 let page = (b0 << 16) | (b1 << 8) | b2;
519 if flags != 0 {
520 return Err(format!(
521 "unsupported LE page {i} flags={flags:#x} (page={page}): only flag-0 legal pages are supported; iterated/zero pages TODO"
522 ));
523 }
524 if page == 0 || page as usize > numpages {
525 return Err(format!(
526 "LE page map entry {i} has out-of-range physical page {page} (numpages={numpages})"
527 ));
528 }
529 phys_of_logical.push((page, flags));
530 }
531 let mut new_data: Vec<u8> = Vec::new();
532 let mut sections: Vec<Section> = Vec::with_capacity(objcnt);
533 let mut objs: Vec<(u32, usize, usize, usize)> = Vec::with_capacity(objcnt);
536 let mut exec_seen = 0usize;
537 let mut data_seen = 0usize;
538 for oi in 0..objcnt {
539 let o = objtab + oi * 24;
540 let vsize = u32::from_le_bytes([data[o], data[o + 1], data[o + 2], data[o + 3]]) as usize;
541 let base =
542 u32::from_le_bytes([data[o + 4], data[o + 5], data[o + 6], data[o + 7]]) as u32;
543 let flags =
544 u32::from_le_bytes([data[o + 8], data[o + 9], data[o + 10], data[o + 11]]);
545 let page_idx =
546 u32::from_le_bytes([data[o + 12], data[o + 13], data[o + 14], data[o + 15]])
547 as usize;
548 let page_cnt =
549 u32::from_le_bytes([data[o + 16], data[o + 17], data[o + 18], data[o + 19]])
550 as usize;
551 if page_idx == 0 || page_idx - 1 + page_cnt > numpages {
552 return Err(format!(
553 "LE object {oi} page range [{page_idx}..{}) out of 1..={numpages}",
554 page_idx + page_cnt
555 ));
556 }
557 objs.push((base, vsize, page_idx, page_cnt));
558 let start = new_data.len();
559 for j in 0..page_cnt {
560 let (phys, _) = phys_of_logical[page_idx - 1 + j];
561 let is_last_phys = phys as usize == numpages;
562 let plen = if is_last_phys { lastpagesize } else { pagesize };
563 let foff = datapage_abs + (phys as usize - 1) * pagesize;
564 if foff + plen > sz {
565 return Err(format!(
566 "LE object {oi} page {j} (phys {phys}) file range [{foff:#x}..{:#x}) past EOF {sz:#x}",
567 foff + plen
568 ));
569 }
570 new_data.extend_from_slice(&data[foff..foff + plen]);
571 }
572 let file_len = new_data.len() - start;
573 let raw_size = vsize.min(file_len);
574 new_data.truncate(start + raw_size);
577 let is_exec = flags & 0x4 != 0;
578 let (name, characteristics) = if is_exec {
579 let n = if exec_seen == 0 {
580 ".text".to_string()
581 } else {
582 format!(".text{exec_seen}")
583 };
584 exec_seen += 1;
585 (
586 n,
587 section_flags::CODE | section_flags::EXECUTE | section_flags::READ,
588 )
589 } else {
590 let n = if data_seen == 0 {
591 ".data".to_string()
592 } else {
593 format!(".data{data_seen}")
594 };
595 data_seen += 1;
596 (
597 n,
598 section_flags::READ | section_flags::WRITE,
599 )
600 };
601 sections.push(Section {
602 name,
603 virtual_address: base,
604 virtual_size: vsize as u32,
605 raw_offset: start as u32,
606 raw_size: raw_size as u32,
607 characteristics,
608 });
609 }
610 let (fixup_posts, fixup_status) = match collect_fixups(
611 &data, le_off, &objs, numpages, pagesize, §ions, &new_data,
612 ) {
613 Ok(pairs) => {
614 let n = pairs.len();
615 (pairs, FixupStatus::Applied { pairs: n })
616 }
617 Err(e) if e.starts_with(NOVEL_FIXUP_PREFIX) => {
618 (Vec::new(), FixupStatus::Skipped { reason: e })
619 }
620 Err(e) => return Err(e),
621 };
622 Ok(Pe {
623 data: new_data,
624 image_base: 0,
625 sections,
626 fixup_posts,
627 fixup_status,
628 id: next_pe_id(),
629 })
630}
631
632const NOVEL_FIXUP_PREFIX: &str = "NOVEL-FIXUP-RECORD: ";
640
641#[allow(clippy::too_many_arguments)]
654fn collect_fixups(
655 data: &[u8],
656 le_off: usize,
657 objs: &[(u32, usize, usize, usize)],
658 numpages: usize,
659 pagesize: usize,
660 sections: &[Section],
661 image: &[u8],
662) -> Result<Vec<(u64, u64)>, String> {
663 let pre_of = |va: u64| -> Option<u32> {
664 for s in sections {
665 let lo = s.virtual_address as u64;
666 let hi = lo + s.virtual_size as u64;
667 if (lo..hi).contains(&va) {
668 let off = s.raw_offset as u64 + (va - lo);
669 if off + 4 <= s.raw_offset as u64 + s.raw_size as u64 {
670 let o = off as usize;
671 return Some(u32::from_le_bytes([
672 image[o],
673 image[o + 1],
674 image[o + 2],
675 image[o + 3],
676 ]));
677 }
678 return Some(0);
680 }
681 }
682 None
683 };
684 let at32 = |o: usize| -> Result<u32, String> {
685 if o + 4 > data.len() {
686 return Err(format!("LE fixup header read past EOF at {o:#x}"));
687 }
688 Ok(u32::from_le_bytes([data[o], data[o + 1], data[o + 2], data[o + 3]]))
689 };
690 let fpt_field = le_off
691 .checked_add(0x68)
692 .ok_or("LE fixup page-table field overflow")?;
693 let frt_field = le_off
694 .checked_add(0x6C)
695 .ok_or("LE fixup record-table field overflow")?;
696 let fpt = le_off
697 .checked_add(at32(fpt_field)? as usize)
698 .ok_or("LE fixup page-table offset overflow")?;
699 let frt = le_off
700 .checked_add(at32(frt_field)? as usize)
701 .ok_or("LE fixup record-table offset overflow")?;
702 if fpt == le_off && frt == le_off {
703 return Ok(Vec::new());
705 }
706 let tab_len = (numpages + 1)
707 .checked_mul(4)
708 .ok_or("LE fixup page-table size overflow")?;
709 if fpt.checked_add(tab_len) != Some(frt) {
712 return Err(format!(
713 "LE fixup tables not adjacent: pagetab={fpt:#x} + {tab_len:#x} != rectab={frt:#x}"
714 ));
715 }
716 if frt > data.len() {
717 return Err(format!(
718 "LE fixup record table {frt:#x} past EOF (file size {:#x})",
719 data.len()
720 ));
721 }
722 let mut bounds: Vec<(usize, usize)> = Vec::with_capacity(numpages);
723 let mut prev = 0usize;
724 let mut first = true;
725 for i in 0..=numpages {
726 let e = at32(fpt + i * 4)? as usize;
727 if !first && e < prev {
728 return Err(format!(
729 "LE fixup page table not monotonic at page {i}: {e:#x} < {prev:#x}"
730 ));
731 }
732 first = false;
733 prev = e;
734 if i < numpages {
735 bounds.push((e, 0));
736 } else if frt.checked_add(e).map_or(true, |end| end > data.len()) {
737 return Err(format!(
738 "LE fixup record table end {e:#x} past EOF (file size {:#x})",
739 data.len()
740 ));
741 }
742 if i > 0 {
743 bounds[i - 1].1 = e;
744 }
745 }
746 let mut out: Vec<(u64, u64)> = Vec::new();
747 for (pi, (start, end)) in bounds.iter().enumerate() {
748 let lp = pi + 1; let (obase, _, opage, _) = objs
750 .iter()
751 .find(|(_, _, p, c)| lp >= *p && lp < p + c)
752 .ok_or(format!("LE fixup page {lp} belongs to no object"))?;
753 let lin_base = (*obase as usize)
754 .checked_add((lp - opage) * pagesize)
755 .ok_or(format!("LE fixup page {lp} linear base overflow"))?;
756 let mut p = frt
757 .checked_add(*start)
758 .ok_or("LE fixup record offset overflow")?;
759 let pend = frt
760 .checked_add(*end)
761 .ok_or("LE fixup record end overflow")?;
762 while p < pend {
763 let rec_off = p - frt;
764 let st = *data.get(p).ok_or(format!(
765 "LE fixup record {rec_off:#x} (page {lp}) truncated: missing srctype"
766 ))?;
767 let fl = *data.get(p + 1).ok_or(format!(
768 "LE fixup record {rec_off:#x} (page {lp}) truncated: missing tgtflags"
769 ))?;
770 p += 2;
771 let mut srcs: Vec<u16> = Vec::new();
772 if st & 0x20 != 0 {
773 let cnt = *data.get(p).ok_or(format!(
774 "LE fixup record {rec_off:#x} (page {lp}) truncated: missing source count"
775 ))?;
776 p += 1;
777 for _ in 0..cnt {
778 if p + 2 > pend || p + 2 > data.len() {
779 return Err(format!(
780 "LE fixup record {rec_off:#x} (page {lp}) source runs past page end"
781 ));
782 }
783 srcs.push(u16::from_le_bytes([data[p], data[p + 1]]));
784 p += 2;
785 }
786 } else {
787 if p + 2 > pend || p + 2 > data.len() {
788 return Err(format!(
789 "LE fixup record {rec_off:#x} (page {lp}) source runs past page end"
790 ));
791 }
792 srcs.push(u16::from_le_bytes([data[p], data[p + 1]]));
793 p += 2;
794 }
795 let low4 = st & 0x0F;
796 let is_sel = (st == 0x02 || st == 0x22) && fl == 0x00;
797 let is_off = (low4 == 7 || low4 == 8)
798 && (st & 0xD0) == 0
799 && (fl == 0x00 || fl == 0x10);
800 if !is_sel && !is_off {
801 return Err(format!(
806 "{NOVEL_FIXUP_PREFIX}record {rec_off:#x} (page {lp}): srctype=0x{st:02X} tgtflags=0x{fl:02X}"
807 ));
808 }
809 if p + 1 > pend || p + 1 > data.len() {
810 return Err(format!(
811 "LE fixup record {rec_off:#x} (page {lp}) truncated: missing target object"
812 ));
813 }
814 let obj = data[p] as usize;
815 p += 1;
816 if obj < 1 || obj > objs.len() {
817 return Err(format!(
818 "LE fixup record {rec_off:#x} (page {lp}) has bad target object {obj}"
819 ));
820 }
821 let wide = fl & 0x10 != 0;
822 let toff: u32 = if is_sel {
823 0
824 } else if wide {
825 if p + 4 > pend || p + 4 > data.len() {
826 return Err(format!(
827 "LE fixup record {rec_off:#x} (page {lp}) truncated: missing wide target offset"
828 ));
829 }
830 let v = u32::from_le_bytes([data[p], data[p + 1], data[p + 2], data[p + 3]]);
831 p += 4;
832 v
833 } else {
834 if p + 2 > pend || p + 2 > data.len() {
835 return Err(format!(
836 "LE fixup record {rec_off:#x} (page {lp}) truncated: missing narrow target offset"
837 ));
838 }
839 let v = u16::from_le_bytes([data[p], data[p + 1]]) as u32;
840 p += 2;
841 v
842 };
843 let target = objs[obj - 1].0.wrapping_add(toff);
844 for s in srcs {
845 let loc = if (s as usize) < pagesize {
849 lin_base.checked_add(s as usize)
850 } else if s >= 0xFFFD {
851 lin_base
852 .checked_add(s as usize)
853 .and_then(|l| l.checked_sub(0x1_0000))
854 } else {
855 None
856 };
857 let loc = loc.ok_or(format!(
858 "LE fixup record {rec_off:#x} (page {lp}) bad source offset {s:#x} (pagesize {pagesize:#x})"
859 ))?;
860 let pre = pre_of(loc as u64).ok_or(format!(
861 "LE fixup record {rec_off:#x} (page {lp}) location {loc:#x} outside image"
862 ))?;
863 if is_sel {
864 continue; }
866 if low4 == 7 {
867 out.push((pre as u64, target as u64));
868 }
869 }
872 }
873 if p != pend {
874 return Err(format!(
875 "LE fixup page {lp} consumed to {:#x}, expected end {:#x}",
876 p - frt,
877 pend - frt
878 ));
879 }
880 }
881 Ok(out)
882}
883
884#[cfg(test)]
885mod tests {
886 use super::*;
887
888 fn build_fixup_le() -> Vec<u8> {
894 let le_off = 0x40usize;
895 let pagesize = 0x1000usize;
896 let mut data = vec![0u8; 0x1400];
897 data[0..2].copy_from_slice(b"MZ");
898 data[0x3c..0x40].copy_from_slice(&(le_off as u32).to_le_bytes());
899 data[le_off..le_off + 2].copy_from_slice(b"LE");
900 data[le_off + 8..le_off + 10].copy_from_slice(&2u16.to_le_bytes()); data[le_off + 0x14..le_off + 0x18].copy_from_slice(&1u32.to_le_bytes()); data[le_off + 0x28..le_off + 0x2C]
903 .copy_from_slice(&(pagesize as u32).to_le_bytes()); data[le_off + 0x2C..le_off + 0x30]
905 .copy_from_slice(&(pagesize as u32).to_le_bytes()); data[le_off + 0x40..le_off + 0x44].copy_from_slice(&0x1C0u32.to_le_bytes()); data[le_off + 0x44..le_off + 0x48].copy_from_slice(&1u32.to_le_bytes()); data[le_off + 0x48..le_off + 0x4C].copy_from_slice(&0x1D8u32.to_le_bytes()); data[le_off + 0x4C..le_off + 0x50].copy_from_slice(&0u32.to_le_bytes()); data[le_off + 0x68..le_off + 0x6C].copy_from_slice(&0x2C0u32.to_le_bytes()); data[le_off + 0x6C..le_off + 0x70].copy_from_slice(&0x2C8u32.to_le_bytes()); data[le_off + 0x80..le_off + 0x84].copy_from_slice(&0x400u32.to_le_bytes()); let o = 0x200usize;
915 data[o..o + 4].copy_from_slice(&0x1000u32.to_le_bytes());
916 data[o + 4..o + 8].copy_from_slice(&0x10000u32.to_le_bytes());
917 data[o + 8..o + 12].copy_from_slice(&0x4u32.to_le_bytes());
918 data[o + 12..o + 16].copy_from_slice(&1u32.to_le_bytes());
919 data[o + 16..o + 20].copy_from_slice(&1u32.to_le_bytes());
920 data[0x218..0x21C].copy_from_slice(&[0x00, 0x00, 0x01, 0x00]);
922 data[0x300..0x304].copy_from_slice(&0u32.to_le_bytes());
924 data[0x304..0x308].copy_from_slice(&26u32.to_le_bytes());
925 data[0x308..0x30F].copy_from_slice(&[0x07, 0x00, 0x10, 0x00, 0x01, 0x00, 0x05]);
928 data[0x30F..0x316].copy_from_slice(&[0x08, 0x00, 0x21, 0x00, 0x01, 0x00, 0x01]);
931 data[0x316..0x322].copy_from_slice(&[
935 0x27, 0x10, 0x02, 0x30, 0x00, 0x34, 0x00, 0x01, 0x00, 0x06, 0x00, 0x00,
936 ]);
937 let pg = 0x400usize;
939 data[pg..pg + 6].copy_from_slice(&[0xA1, 0x00, 0x04, 0x01, 0x00, 0xC3]);
940 data[pg + 0x10..pg + 0x14].copy_from_slice(&0x10400u32.to_le_bytes());
941 data[pg + 0x20..pg + 0x25].copy_from_slice(&[0xE8, 0x00, 0x00, 0x00, 0x00]);
942 data[pg + 0x30..pg + 0x34].copy_from_slice(&0x10410u32.to_le_bytes());
943 data[pg + 0x34..pg + 0x38].copy_from_slice(&0x10420u32.to_le_bytes());
944 data
945 }
946
947 #[test]
948 fn le_fixup_07_pairs_absorbed() {
949 let pe = Pe::parse(build_fixup_le()).expect("parse synthetic LE");
950 assert_eq!(pe.fixup_count(), 3, "one narrow 07 + two listed 07 dwords");
951 let got = pe.fixup_post_images(&[0x10400].into_iter().collect());
952 assert_eq!(got, [0x10500].into_iter().collect());
953 let got = pe.fixup_post_images(&[0x10410, 0x10420].into_iter().collect());
954 assert_eq!(got, [0x10600].into_iter().collect());
955 let got = pe.fixup_post_images(&[0x99999].into_iter().collect());
957 assert!(got.is_empty());
958 }
959
960 #[test]
961 fn le_fixup_post_global_passes_spec() {
962 use crate::{analyze, Spec};
963 let pe = Pe::parse(build_fixup_le()).expect("parse synthetic LE");
964 let cg = analyze(&pe, 0x10000).expect("analyze synthetic fn");
965 assert!(cg.globals.contains(&0x10400), "{cg:?}");
966 Spec::new(0x10000).globals(&[0x10400]).check(&pe).expect("pre global");
968 Spec::new(0x10000).globals(&[0x10500]).check(&pe).expect("post global");
970 assert!(Spec::new(0x10000).globals(&[0x10999]).check(&pe).is_err());
972 }
973
974 #[test]
975 fn le_referenced_addrs_include_fixup_post_images() {
976 let pe = Pe::parse(build_fixup_le()).expect("parse synthetic LE");
977 let refs = pe.referenced_addrs();
978 assert!(refs.contains(&0x10400), "pre operand ref missing: {refs:x?}");
981 assert!(refs.contains(&0x10410), "pre stored dword ref missing");
982 assert!(refs.contains(&0x10420), "pre stored dword ref missing");
983 assert!(refs.contains(&0x10500), "post image of 0x10400 missing");
987 assert!(
988 refs.contains(&0x10600),
989 "post image of 0x10410/0x10420 missing"
990 );
991 assert!(
993 !refs.contains(&0x10999),
994 "garbage address must stay unreferenced"
995 );
996 }
997
998 #[test]
999 fn le_fixup_unknown_record_skips_loudly() {
1000 use super::FixupStatus;
1001 let mut data = build_fixup_le();
1002 data[0x308] = 0x09; let pe = Pe::parse(data).expect("novel fixup kind must skip, not fail");
1006 assert_eq!(pe.fixup_count(), 0);
1007 match pe.fixup_status() {
1008 FixupStatus::Skipped { reason } => {
1009 assert!(reason.contains("srctype=0x09"), "{reason}")
1010 }
1011 other => panic!("expected Skipped, got {other:?}"),
1012 }
1013 }
1014
1015 #[test]
1016 fn le_fixup_tables_must_be_adjacent() {
1017 let mut data = build_fixup_le();
1018 data[0x40 + 0x68..0x40 + 0x6C].copy_from_slice(&0x2C4u32.to_le_bytes());
1019 match Pe::parse(data) {
1020 Ok(_) => panic!("moved page table must fail"),
1021 Err(err) => assert!(err.contains("not adjacent"), "{err}"),
1022 }
1023 }
1024}