1use std::sync::atomic::{AtomicU64, Ordering};
7
8pub struct Pe {
10 data: Vec<u8>,
11 pub image_base: u64,
12 sections: Vec<Section>,
13 id: u64,
21}
22
23fn next_pe_id() -> u64 {
24 static NEXT_ID: AtomicU64 = AtomicU64::new(1);
25 NEXT_ID.fetch_add(1, Ordering::Relaxed)
26}
27
28#[derive(Clone, Debug)]
29pub struct Section {
30 pub name: String,
31 pub virtual_address: u32, pub virtual_size: u32,
33 pub raw_offset: u32,
34 pub raw_size: u32,
35 pub characteristics: u32,
39}
40
41pub mod section_flags {
44 pub const CODE: u32 = 0x0000_0020;
46 pub const DISCARDABLE: u32 = 0x0200_0000;
48 pub const EXECUTE: u32 = 0x2000_0000;
50 pub const READ: u32 = 0x4000_0000;
52 pub const WRITE: u32 = 0x8000_0000;
54}
55
56impl Section {
57 pub fn is_executable(&self) -> bool {
60 self.characteristics & (section_flags::EXECUTE | section_flags::CODE) != 0
61 }
62}
63
64fn u16(d: &[u8], o: usize) -> u16 {
65 u16::from_le_bytes([d[o], d[o + 1]])
66}
67fn u32(d: &[u8], o: usize) -> u32 {
68 u32::from_le_bytes([d[o], d[o + 1], d[o + 2], d[o + 3]])
69}
70
71impl Pe {
72 pub fn load(path: &str) -> Result<Pe, String> {
73 let data = std::fs::read(path).map_err(|e| format!("read {path}: {e}"))?;
74 Pe::parse(data)
75 }
76
77 pub fn parse(data: Vec<u8>) -> Result<Pe, String> {
78 let sz = data.len();
79 if sz < 0x40 || &data[0..2] != b"MZ" {
80 return Err(format!(
81 "not an MZ image: first 2 bytes are {:?} (expected b\"MZ\"), file size = {sz} bytes",
82 &data[..data.len().min(2)]
83 ));
84 }
85 let e_lfanew = u32(&data, 0x3c) as usize;
86 if data.len() >= e_lfanew + 2 && &data[e_lfanew..e_lfanew + 2] == b"LE" {
87 return parse_le(data, e_lfanew);
88 }
89 if data.len() < e_lfanew + 24 || &data[e_lfanew..e_lfanew + 4] != b"PE\0\0" {
90 let there = data
95 .get(e_lfanew..e_lfanew + 4)
96 .map(|s| format!("{:02x?}", s))
97 .unwrap_or_else(|| "<past EOF>".into());
98 return Err(format!(
99 "bad PE signature: e_lfanew = {e_lfanew:#x}, bytes there = [{there}] (expected b\"PE\\0\\0\"); file size = {sz} bytes"
100 ));
101 }
102 let coff = e_lfanew + 4;
103 let machine = u16(&data, coff);
104 if machine != 0x014c {
105 return Err(format!(
106 "unsupported machine {machine:#06x} (expected i386 = 0x014c); this crate only decodes 32-bit x86"
107 ));
108 }
109 let num_sections = u16(&data, coff + 2) as usize;
110 let opt_size = u16(&data, coff + 16) as usize;
111 let opt = coff + 20;
112 let magic = u16(&data, opt);
113 if magic != 0x10b {
114 return Err(format!(
115 "unsupported optional-header magic {magic:#06x} (expected PE32 = 0x10b); PE32+ (64-bit) and ROM images are not handled"
116 ));
117 }
118 let image_base = u32(&data, opt + 28) as u64;
119
120 let sec_table = opt + opt_size;
121 let mut sections = Vec::with_capacity(num_sections);
122 for i in 0..num_sections {
123 let s = sec_table + i * 40;
124 if s + 40 > data.len() {
125 return Err(format!(
126 "section table runs past EOF: section #{i} would start at file offset {s:#x} \
127 (e_lfanew={e_lfanew:#x} + opt_size={opt_size:#x} + {i}*40), \
128 but file size is only {sz} bytes — corrupt header or wrong format"
129 ));
130 }
131 let raw_name = &data[s..s + 8];
132 let end = raw_name.iter().position(|&b| b == 0).unwrap_or(8);
133 sections.push(Section {
134 name: String::from_utf8_lossy(&raw_name[..end]).into_owned(),
135 virtual_size: u32(&data, s + 8),
136 virtual_address: u32(&data, s + 12),
137 raw_size: u32(&data, s + 16),
138 raw_offset: u32(&data, s + 20),
139 characteristics: u32(&data, s + 36),
140 });
141 }
142 Ok(Pe {
143 data,
144 image_base,
145 sections,
146 id: next_pe_id(),
147 })
148 }
149
150 pub fn id(&self) -> u64 {
152 self.id
153 }
154
155 pub fn section_of(&self, va: u64) -> Option<&Section> {
157 let rva = va.checked_sub(self.image_base)? as u32;
158 self.sections.iter().find(|s| {
159 let span = s.virtual_size.max(s.raw_size);
162 rva >= s.virtual_address && rva < s.virtual_address + span
163 })
164 }
165
166 pub fn read(&self, va: u64, len: usize) -> Option<&[u8]> {
170 let sec = self.section_of(va)?;
171 let rva = (va - self.image_base) as u32;
172 let off_in_sec = rva - sec.virtual_address;
173 if off_in_sec >= sec.raw_size {
174 return None; }
176 let file_off = (sec.raw_offset + off_in_sec) as usize;
177 let avail = (sec.raw_size - off_in_sec) as usize;
178 let take = len.min(avail).min(self.data.len().saturating_sub(file_off));
179 Some(&self.data[file_off..file_off + take])
180 }
181
182 pub fn sections(&self) -> &[Section] {
184 &self.sections
185 }
186
187 pub fn is_executable_va(&self, va: u64) -> bool {
188 self.section_of(va).is_some()
200 }
201
202 pub fn is_executable_section_va(&self, va: u64) -> bool {
209 self.section_of(va)
210 .map(|s| s.is_executable())
211 .unwrap_or(false)
212 }
213
214 pub fn describe_sections(&self, va: u64) -> String {
219 let mut s = String::new();
220 let mut best: Option<(&Section, u64)> = None;
221 for sec in &self.sections {
222 let start = self.image_base + sec.virtual_address as u64;
223 let span = sec.virtual_size.max(sec.raw_size) as u64;
224 let end = start + span;
225 s.push_str(&format!(
226 " {name:<8} [0x{start:08x}..0x{end:08x}) ({span} bytes){flag}\n",
227 name = sec.name,
228 start = start,
229 end = end,
230 span = span,
231 flag = if sec.is_executable() { " code" } else { "" },
232 ));
233 let d = if va < start {
234 start - va
235 } else { va.saturating_sub(end) };
236 if best.map(|(_, bd)| d < bd).unwrap_or(true) {
237 best = Some((sec, d));
238 }
239 }
240 if let Some((sec, d)) = best {
241 s.push_str(&format!(
242 " closest section to {va:#010x}: {name} (distance {d:#x} bytes) — \
243 did you mean 0x{start:08x}?",
244 name = sec.name,
245 start = self.image_base + sec.virtual_address as u64,
246 ));
247 }
248 s
249 }
250
251 pub fn image_range(&self) -> (u64, u64) {
253 let end = self
254 .sections
255 .iter()
256 .map(|s| s.virtual_address as u64 + s.virtual_size.max(s.raw_size) as u64)
257 .max()
258 .unwrap_or(0);
259 (self.image_base, self.image_base + end)
260 }
261
262 pub fn referenced_addrs(&self) -> std::collections::HashSet<u32> {
275 let (lo, hi) = self.image_range();
276 let mut out: std::collections::HashSet<u32> = std::collections::HashSet::new();
277 for sec in &self.sections {
278 let start = sec.raw_offset as usize;
279 let end = (start + sec.raw_size as usize).min(self.data.len());
280 if start >= end {
281 continue;
282 }
283 let blob = &self.data[start..end];
284 let sec_va = self.image_base + sec.virtual_address as u64;
285 if sec.name == ".text" {
287 for i in 0..blob.len().saturating_sub(4) {
288 if blob[i] == 0xEB && i + 2 <= blob.len() {
295 let rel = blob[i + 1] as i8 as i64;
296 let t = (sec_va + i as u64 + 2).wrapping_add(rel as u64);
297 if (lo..hi).contains(&t) {
298 out.insert(t as u32);
299 }
300 }
301 let (op, rel_at) = match blob[i] {
302 0xE8 | 0xE9 => (blob[i], i + 1), 0x0F if i + 6 <= blob.len() && (0x80..=0x8F).contains(&blob[i + 1]) => {
304 (blob[i + 1], i + 2) }
306 _ => continue,
307 };
308 let _ = op;
309 if rel_at + 4 > blob.len() {
310 continue;
311 }
312 let rel = i32::from_le_bytes([
313 blob[rel_at],
314 blob[rel_at + 1],
315 blob[rel_at + 2],
316 blob[rel_at + 3],
317 ]) as i64;
318 let t = (sec_va + (rel_at as u64 + 4)).wrapping_add(rel as u64);
319 if (lo..hi).contains(&t) {
320 out.insert(t as u32);
321 }
322 }
323 }
324 for w in blob.windows(4) {
326 let v = u32::from_le_bytes([w[0], w[1], w[2], w[3]]) as u64;
327 if (lo..hi).contains(&v) {
328 out.insert(v as u32);
329 }
330 }
331 }
332 out
333 }
334}
335
336fn parse_le(data: Vec<u8>, le_off: usize) -> Result<Pe, String> {
355 let sz = data.len();
356 let need = le_off + 0xAC;
357 if sz < need + 4 {
358 return Err(format!(
359 "truncated LE header: need {need:#x} bytes for header, file size = {sz} bytes"
360 ));
361 }
362 let at32 = |o: usize| -> Result<u32, String> {
363 if o + 4 > sz {
364 return Err(format!("LE header read past EOF at {o:#x}"));
365 }
366 Ok(u32::from_le_bytes([data[o], data[o + 1], data[o + 2], data[o + 3]]))
367 };
368 let at16 = |o: usize| -> Result<u16, String> {
369 if o + 2 > sz {
370 return Err(format!("LE header read past EOF at {o:#x}"));
371 }
372 Ok(u16::from_le_bytes([data[o], data[o + 1]]))
373 };
374 let cpu = at16(le_off + 8)?;
375 if cpu != 2 {
376 return Err(format!(
377 "unsupported LE cpu {cpu:#x} (expected 386 = 0x2); only 32-bit x86 LE is handled"
378 ));
379 }
380 let numpages = at32(le_off + 0x14)? as usize;
381 let pagesize = at32(le_off + 0x28)? as usize;
382 let lastpagesize = at32(le_off + 0x2C)? as usize;
383 let objtab_rel = at32(le_off + 0x40)? as usize;
384 let objcnt = at32(le_off + 0x44)? as usize;
385 let pagemap_rel = at32(le_off + 0x48)? as usize;
386 let itermap = at32(le_off + 0x4C)?;
387 let datapage_abs = at32(le_off + 0x80)? as usize;
388 if !(512..=16384).contains(&pagesize) {
389 return Err(format!("implausible LE pagesize {pagesize}"));
390 }
391 if numpages == 0 || numpages > 100_000 {
392 return Err(format!("implausible LE page count {numpages}"));
393 }
394 if objcnt == 0 || objcnt > 100 {
395 return Err(format!("implausible LE object count {objcnt}"));
396 }
397 if itermap != 0 {
398 return Err(format!(
399 "unsupported LE iterated-data map at {itermap:#x}: iterated pages not yet supported"
400 ));
401 }
402 let objtab = le_off.checked_add(objtab_rel).ok_or("LE objtab overflow")?;
403 let pagemap = le_off.checked_add(pagemap_rel).ok_or("LE pagemap overflow")?;
404 if objtab + objcnt * 24 > sz {
405 return Err(format!(
406 "LE object table runs past EOF: objtab={objtab:#x} + {objcnt}*24, file size {sz:#x}"
407 ));
408 }
409 if pagemap + numpages * 4 > sz {
410 return Err(format!(
411 "LE page map runs past EOF: pagemap={pagemap:#x} + {numpages}*4, file size {sz:#x}"
412 ));
413 }
414 if datapage_abs >= sz {
415 return Err(format!(
416 "LE data pages offset {datapage_abs:#x} past EOF (file size {sz:#x})"
417 ));
418 }
419 let mut phys_of_logical: Vec<(u32, u8)> = Vec::with_capacity(numpages);
423 for i in 0..numpages {
424 let o = pagemap + i * 4;
425 let b0 = data[o] as u32;
426 let b1 = data[o + 1] as u32;
427 let b2 = data[o + 2] as u32;
428 let flags = data[o + 3];
429 let page = (b0 << 16) | (b1 << 8) | b2;
430 if flags != 0 {
431 return Err(format!(
432 "unsupported LE page {i} flags={flags:#x} (page={page}): only flag-0 legal pages are supported; iterated/zero pages TODO"
433 ));
434 }
435 if page == 0 || page as usize > numpages {
436 return Err(format!(
437 "LE page map entry {i} has out-of-range physical page {page} (numpages={numpages})"
438 ));
439 }
440 phys_of_logical.push((page, flags));
441 }
442 let mut new_data: Vec<u8> = Vec::new();
443 let mut sections: Vec<Section> = Vec::with_capacity(objcnt);
444 let mut exec_seen = 0usize;
445 let mut data_seen = 0usize;
446 for oi in 0..objcnt {
447 let o = objtab + oi * 24;
448 let vsize = u32::from_le_bytes([data[o], data[o + 1], data[o + 2], data[o + 3]]) as usize;
449 let base =
450 u32::from_le_bytes([data[o + 4], data[o + 5], data[o + 6], data[o + 7]]) as u32;
451 let flags =
452 u32::from_le_bytes([data[o + 8], data[o + 9], data[o + 10], data[o + 11]]);
453 let page_idx =
454 u32::from_le_bytes([data[o + 12], data[o + 13], data[o + 14], data[o + 15]])
455 as usize;
456 let page_cnt =
457 u32::from_le_bytes([data[o + 16], data[o + 17], data[o + 18], data[o + 19]])
458 as usize;
459 if page_idx == 0 || page_idx - 1 + page_cnt > numpages {
460 return Err(format!(
461 "LE object {oi} page range [{page_idx}..{}) out of 1..={numpages}",
462 page_idx + page_cnt
463 ));
464 }
465 let start = new_data.len();
466 for j in 0..page_cnt {
467 let (phys, _) = phys_of_logical[page_idx - 1 + j];
468 let is_last_phys = phys as usize == numpages;
469 let plen = if is_last_phys { lastpagesize } else { pagesize };
470 let foff = datapage_abs + (phys as usize - 1) * pagesize;
471 if foff + plen > sz {
472 return Err(format!(
473 "LE object {oi} page {j} (phys {phys}) file range [{foff:#x}..{:#x}) past EOF {sz:#x}",
474 foff + plen
475 ));
476 }
477 new_data.extend_from_slice(&data[foff..foff + plen]);
478 }
479 let file_len = new_data.len() - start;
480 let raw_size = vsize.min(file_len);
481 new_data.truncate(start + raw_size);
484 let is_exec = flags & 0x4 != 0;
485 let (name, characteristics) = if is_exec {
486 let n = if exec_seen == 0 {
487 ".text".to_string()
488 } else {
489 format!(".text{exec_seen}")
490 };
491 exec_seen += 1;
492 (
493 n,
494 section_flags::CODE | section_flags::EXECUTE | section_flags::READ,
495 )
496 } else {
497 let n = if data_seen == 0 {
498 ".data".to_string()
499 } else {
500 format!(".data{data_seen}")
501 };
502 data_seen += 1;
503 (
504 n,
505 section_flags::READ | section_flags::WRITE,
506 )
507 };
508 sections.push(Section {
509 name,
510 virtual_address: base,
511 virtual_size: vsize as u32,
512 raw_offset: start as u32,
513 raw_size: raw_size as u32,
514 characteristics,
515 });
516 }
517 Ok(Pe {
518 data: new_data,
519 image_base: 0,
520 sections,
521 id: next_pe_id(),
522 })
523}