1use core::convert::TryFrom;
2use core::{iter, result, slice, str};
3
4use crate::endian::LittleEndian as LE;
5use crate::pe;
6use crate::read::util::StringTable;
7use crate::read::{
8 self, CompressedData, CompressedFileRange, Error, ObjectSection, ObjectSegment, ReadError,
9 ReadRef, Result, SectionFlags, SectionIndex, SectionKind, SegmentFlags,
10};
11
12use super::{CoffFile, CoffHeader, CoffRelocationIterator};
13
14/// The table of section headers in a COFF or PE file.
15///
16/// Returned by [`CoffHeader::sections`] and
17/// [`ImageNtHeaders::sections`](crate::read::pe::ImageNtHeaders::sections).
18#[derive(Debug, Default, Clone, Copy)]
19pub struct SectionTable<'data> {
20 sections: &'data [pe::ImageSectionHeader],
21}
22
23impl<'data> SectionTable<'data> {
24 /// Parse the section table.
25 ///
26 /// `data` must be the entire file data.
27 /// `offset` must be after the optional file header.
28 pub fn parse<Coff: CoffHeader, R: ReadRef<'data>>(
29 header: &Coff,
30 data: R,
31 offset: u64,
32 ) -> Result<Self> {
33 let sections = data
34 .read_slice_at(offset, header.number_of_sections() as usize)
35 .read_error("Invalid COFF/PE section headers")?;
36 Ok(SectionTable { sections })
37 }
38
39 /// Iterate over the section headers.
40 ///
41 /// Warning: sections indices start at 1.
42 #[inline]
43 pub fn iter(&self) -> slice::Iter<'data, pe::ImageSectionHeader> {
44 self.sections.iter()
45 }
46
47 /// Return true if the section table is empty.
48 #[inline]
49 pub fn is_empty(&self) -> bool {
50 self.sections.is_empty()
51 }
52
53 /// The number of section headers.
54 #[inline]
55 pub fn len(&self) -> usize {
56 self.sections.len()
57 }
58
59 /// Return the section header at the given index.
60 ///
61 /// The index is 1-based.
62 pub fn section(&self, index: usize) -> read::Result<&'data pe::ImageSectionHeader> {
63 self.sections
64 .get(index.wrapping_sub(1))
65 .read_error("Invalid COFF/PE section index")
66 }
67
68 /// Return the section header with the given name.
69 ///
70 /// The returned index is 1-based.
71 ///
72 /// Ignores sections with invalid names.
73 pub fn section_by_name<R: ReadRef<'data>>(
74 &self,
75 strings: StringTable<'data, R>,
76 name: &[u8],
77 ) -> Option<(usize, &'data pe::ImageSectionHeader)> {
78 self.sections
79 .iter()
80 .enumerate()
81 .find(|(_, section)| section.name(strings) == Ok(name))
82 .map(|(index, section)| (index + 1, section))
83 }
84
85 /// Compute the maximum file offset used by sections.
86 ///
87 /// This will usually match the end of file, unless the PE file has a
88 /// [data overlay](https://security.stackexchange.com/questions/77336/how-is-the-file-overlay-read-by-an-exe-virus)
89 pub fn max_section_file_offset(&self) -> u64 {
90 let mut max = 0;
91 for section in self.iter() {
92 match (section.pointer_to_raw_data.get(LE) as u64)
93 .checked_add(section.size_of_raw_data.get(LE) as u64)
94 {
95 None => {
96 // This cannot happen, we're suming two u32 into a u64
97 continue;
98 }
99 Some(end_of_section) => {
100 if end_of_section > max {
101 max = end_of_section;
102 }
103 }
104 }
105 }
106 max
107 }
108}
109
110/// An iterator for the loadable sections in a [`CoffBigFile`](super::CoffBigFile).
111pub type CoffBigSegmentIterator<'data, 'file, R = &'data [u8]> =
112 CoffSegmentIterator<'data, 'file, R, pe::AnonObjectHeaderBigobj>;
113
114/// An iterator for the loadable sections in a [`CoffFile`].
115#[derive(Debug)]
116pub struct CoffSegmentIterator<
117 'data,
118 'file,
119 R: ReadRef<'data> = &'data [u8],
120 Coff: CoffHeader = pe::ImageFileHeader,
121> {
122 pub(super) file: &'file CoffFile<'data, R, Coff>,
123 pub(super) iter: slice::Iter<'data, pe::ImageSectionHeader>,
124}
125
126impl<'data, 'file, R: ReadRef<'data>, Coff: CoffHeader> Iterator
127 for CoffSegmentIterator<'data, 'file, R, Coff>
128{
129 type Item = CoffSegment<'data, 'file, R, Coff>;
130
131 fn next(&mut self) -> Option<Self::Item> {
132 self.iter.next().map(|section: &ImageSectionHeader| CoffSegment {
133 file: self.file,
134 section,
135 })
136 }
137}
138
139/// A loadable section in a [`CoffBigFile`](super::CoffBigFile).
140///
141/// Most functionality is provided by the [`ObjectSegment`] trait implementation.
142pub type CoffBigSegment<'data, 'file, R = &'data [u8]> =
143 CoffSegment<'data, 'file, R, pe::AnonObjectHeaderBigobj>;
144
145/// A loadable section in a [`CoffFile`].
146///
147/// Most functionality is provided by the [`ObjectSegment`] trait implementation.
148#[derive(Debug)]
149pub struct CoffSegment<
150 'data,
151 'file,
152 R: ReadRef<'data> = &'data [u8],
153 Coff: CoffHeader = pe::ImageFileHeader,
154> {
155 pub(super) file: &'file CoffFile<'data, R, Coff>,
156 pub(super) section: &'data pe::ImageSectionHeader,
157}
158
159impl<'data, 'file, R: ReadRef<'data>, Coff: CoffHeader> CoffSegment<'data, 'file, R, Coff> {
160 fn bytes(&self) -> Result<&'data [u8]> {
161 self.section
162 .coff_data(self.file.data)
163 .read_error("Invalid COFF section offset or size")
164 }
165}
166
167impl<'data, 'file, R: ReadRef<'data>, Coff: CoffHeader> read::private::Sealed
168 for CoffSegment<'data, 'file, R, Coff>
169{
170}
171
172impl<'data, 'file, R: ReadRef<'data>, Coff: CoffHeader> ObjectSegment<'data>
173 for CoffSegment<'data, 'file, R, Coff>
174{
175 #[inline]
176 fn address(&self) -> u64 {
177 u64::from(self.section.virtual_address.get(LE))
178 }
179
180 #[inline]
181 fn size(&self) -> u64 {
182 u64::from(self.section.virtual_size.get(LE))
183 }
184
185 #[inline]
186 fn align(&self) -> u64 {
187 self.section.coff_alignment()
188 }
189
190 #[inline]
191 fn file_range(&self) -> (u64, u64) {
192 let (offset, size) = self.section.coff_file_range().unwrap_or((0, 0));
193 (u64::from(offset), u64::from(size))
194 }
195
196 fn data(&self) -> Result<&'data [u8]> {
197 self.bytes()
198 }
199
200 fn data_range(&self, address: u64, size: u64) -> Result<Option<&'data [u8]>> {
201 Ok(read::util::data_range(
202 self.bytes()?,
203 self.address(),
204 address,
205 size,
206 ))
207 }
208
209 #[inline]
210 fn name_bytes(&self) -> Result<Option<&[u8]>> {
211 self.section
212 .name(self.file.common.symbols.strings())
213 .map(Some)
214 }
215
216 #[inline]
217 fn name(&self) -> Result<Option<&str>> {
218 let name = self.section.name(self.file.common.symbols.strings())?;
219 str::from_utf8(name)
220 .ok()
221 .read_error("Non UTF-8 COFF section name")
222 .map(Some)
223 }
224
225 #[inline]
226 fn flags(&self) -> SegmentFlags {
227 let characteristics = self.section.characteristics.get(LE);
228 SegmentFlags::Coff { characteristics }
229 }
230}
231
232/// An iterator for the sections in a [`CoffBigFile`](super::CoffBigFile).
233pub type CoffBigSectionIterator<'data, 'file, R = &'data [u8]> =
234 CoffSectionIterator<'data, 'file, R, pe::AnonObjectHeaderBigobj>;
235
236/// An iterator for the sections in a [`CoffFile`].
237#[derive(Debug)]
238pub struct CoffSectionIterator<
239 'data,
240 'file,
241 R: ReadRef<'data> = &'data [u8],
242 Coff: CoffHeader = pe::ImageFileHeader,
243> {
244 pub(super) file: &'file CoffFile<'data, R, Coff>,
245 pub(super) iter: iter::Enumerate<slice::Iter<'data, pe::ImageSectionHeader>>,
246}
247
248impl<'data, 'file, R: ReadRef<'data>, Coff: CoffHeader> Iterator
249 for CoffSectionIterator<'data, 'file, R, Coff>
250{
251 type Item = CoffSection<'data, 'file, R, Coff>;
252
253 fn next(&mut self) -> Option<Self::Item> {
254 self.iter.next().map(|(index: usize, section: &ImageSectionHeader)| CoffSection {
255 file: self.file,
256 index: SectionIndex(index + 1),
257 section,
258 })
259 }
260}
261
262/// A section in a [`CoffBigFile`](super::CoffBigFile).
263///
264/// Most functionality is provided by the [`ObjectSection`] trait implementation.
265pub type CoffBigSection<'data, 'file, R = &'data [u8]> =
266 CoffSection<'data, 'file, R, pe::AnonObjectHeaderBigobj>;
267
268/// A section in a [`CoffFile`].
269///
270/// Most functionality is provided by the [`ObjectSection`] trait implementation.
271#[derive(Debug)]
272pub struct CoffSection<
273 'data,
274 'file,
275 R: ReadRef<'data> = &'data [u8],
276 Coff: CoffHeader = pe::ImageFileHeader,
277> {
278 pub(super) file: &'file CoffFile<'data, R, Coff>,
279 pub(super) index: SectionIndex,
280 pub(super) section: &'data pe::ImageSectionHeader,
281}
282
283impl<'data, 'file, R: ReadRef<'data>, Coff: CoffHeader> CoffSection<'data, 'file, R, Coff> {
284 fn bytes(&self) -> Result<&'data [u8]> {
285 self.section
286 .coff_data(self.file.data)
287 .read_error("Invalid COFF section offset or size")
288 }
289}
290
291impl<'data, 'file, R: ReadRef<'data>, Coff: CoffHeader> read::private::Sealed
292 for CoffSection<'data, 'file, R, Coff>
293{
294}
295
296impl<'data, 'file, R: ReadRef<'data>, Coff: CoffHeader> ObjectSection<'data>
297 for CoffSection<'data, 'file, R, Coff>
298{
299 type RelocationIterator = CoffRelocationIterator<'data, 'file, R, Coff>;
300
301 #[inline]
302 fn index(&self) -> SectionIndex {
303 self.index
304 }
305
306 #[inline]
307 fn address(&self) -> u64 {
308 u64::from(self.section.virtual_address.get(LE))
309 }
310
311 #[inline]
312 fn size(&self) -> u64 {
313 // TODO: This may need to be the length from the auxiliary symbol for this section.
314 u64::from(self.section.size_of_raw_data.get(LE))
315 }
316
317 #[inline]
318 fn align(&self) -> u64 {
319 self.section.coff_alignment()
320 }
321
322 #[inline]
323 fn file_range(&self) -> Option<(u64, u64)> {
324 let (offset, size) = self.section.coff_file_range()?;
325 Some((u64::from(offset), u64::from(size)))
326 }
327
328 fn data(&self) -> Result<&'data [u8]> {
329 self.bytes()
330 }
331
332 fn data_range(&self, address: u64, size: u64) -> Result<Option<&'data [u8]>> {
333 Ok(read::util::data_range(
334 self.bytes()?,
335 self.address(),
336 address,
337 size,
338 ))
339 }
340
341 #[inline]
342 fn compressed_file_range(&self) -> Result<CompressedFileRange> {
343 Ok(CompressedFileRange::none(self.file_range()))
344 }
345
346 #[inline]
347 fn compressed_data(&self) -> Result<CompressedData<'data>> {
348 self.data().map(CompressedData::none)
349 }
350
351 #[inline]
352 fn name_bytes(&self) -> Result<&[u8]> {
353 self.section.name(self.file.common.symbols.strings())
354 }
355
356 #[inline]
357 fn name(&self) -> Result<&str> {
358 let name = self.name_bytes()?;
359 str::from_utf8(name)
360 .ok()
361 .read_error("Non UTF-8 COFF section name")
362 }
363
364 #[inline]
365 fn segment_name_bytes(&self) -> Result<Option<&[u8]>> {
366 Ok(None)
367 }
368
369 #[inline]
370 fn segment_name(&self) -> Result<Option<&str>> {
371 Ok(None)
372 }
373
374 #[inline]
375 fn kind(&self) -> SectionKind {
376 self.section.kind()
377 }
378
379 fn relocations(&self) -> CoffRelocationIterator<'data, 'file, R, Coff> {
380 let relocations = self.section.coff_relocations(self.file.data).unwrap_or(&[]);
381 CoffRelocationIterator {
382 file: self.file,
383 iter: relocations.iter(),
384 }
385 }
386
387 fn flags(&self) -> SectionFlags {
388 SectionFlags::Coff {
389 characteristics: self.section.characteristics.get(LE),
390 }
391 }
392}
393
394impl pe::ImageSectionHeader {
395 pub(crate) fn kind(&self) -> SectionKind {
396 let characteristics: u32 = self.characteristics.get(LE);
397 if characteristics & (pe::IMAGE_SCN_CNT_CODE | pe::IMAGE_SCN_MEM_EXECUTE) != 0 {
398 SectionKind::Text
399 } else if characteristics & pe::IMAGE_SCN_CNT_INITIALIZED_DATA != 0 {
400 if characteristics & pe::IMAGE_SCN_MEM_DISCARDABLE != 0 {
401 SectionKind::Other
402 } else if characteristics & pe::IMAGE_SCN_MEM_WRITE != 0 {
403 SectionKind::Data
404 } else {
405 SectionKind::ReadOnlyData
406 }
407 } else if characteristics & pe::IMAGE_SCN_CNT_UNINITIALIZED_DATA != 0 {
408 SectionKind::UninitializedData
409 } else if characteristics & pe::IMAGE_SCN_LNK_INFO != 0 {
410 SectionKind::Linker
411 } else {
412 SectionKind::Unknown
413 }
414 }
415}
416
417impl pe::ImageSectionHeader {
418 /// Return the string table offset of the section name.
419 ///
420 /// Returns `Ok(None)` if the name doesn't use the string table
421 /// and can be obtained with `raw_name` instead.
422 pub fn name_offset(&self) -> Result<Option<u32>> {
423 let bytes = &self.name;
424 if bytes[0] != b'/' {
425 return Ok(None);
426 }
427
428 if bytes[1] == b'/' {
429 let mut offset = 0;
430 for byte in bytes[2..].iter() {
431 let digit = match byte {
432 b'A'..=b'Z' => byte - b'A',
433 b'a'..=b'z' => byte - b'a' + 26,
434 b'0'..=b'9' => byte - b'0' + 52,
435 b'+' => 62,
436 b'/' => 63,
437 _ => return Err(Error("Invalid COFF section name base-64 offset")),
438 };
439 offset = offset * 64 + digit as u64;
440 }
441 u32::try_from(offset)
442 .ok()
443 .read_error("Invalid COFF section name base-64 offset")
444 .map(Some)
445 } else {
446 let mut offset = 0;
447 for byte in bytes[1..].iter() {
448 let digit = match byte {
449 b'0'..=b'9' => byte - b'0',
450 0 => break,
451 _ => return Err(Error("Invalid COFF section name base-10 offset")),
452 };
453 offset = offset * 10 + digit as u32;
454 }
455 Ok(Some(offset))
456 }
457 }
458
459 /// Return the section name.
460 ///
461 /// This handles decoding names that are offsets into the symbol string table.
462 pub fn name<'data, R: ReadRef<'data>>(
463 &'data self,
464 strings: StringTable<'data, R>,
465 ) -> Result<&'data [u8]> {
466 if let Some(offset) = self.name_offset()? {
467 strings
468 .get(offset)
469 .read_error("Invalid COFF section name offset")
470 } else {
471 Ok(self.raw_name())
472 }
473 }
474
475 /// Return the raw section name.
476 pub fn raw_name(&self) -> &[u8] {
477 let bytes = &self.name;
478 match memchr::memchr(b'\0', bytes) {
479 Some(end) => &bytes[..end],
480 None => &bytes[..],
481 }
482 }
483
484 /// Return the offset and size of the section in a COFF file.
485 ///
486 /// Returns `None` for sections that have no data in the file.
487 pub fn coff_file_range(&self) -> Option<(u32, u32)> {
488 if self.characteristics.get(LE) & pe::IMAGE_SCN_CNT_UNINITIALIZED_DATA != 0 {
489 None
490 } else {
491 let offset = self.pointer_to_raw_data.get(LE);
492 // Note: virtual size is not used for COFF.
493 let size = self.size_of_raw_data.get(LE);
494 Some((offset, size))
495 }
496 }
497
498 /// Return the section data in a COFF file.
499 ///
500 /// Returns `Ok(&[])` if the section has no data.
501 /// Returns `Err` for invalid values.
502 pub fn coff_data<'data, R: ReadRef<'data>>(&self, data: R) -> result::Result<&'data [u8], ()> {
503 if let Some((offset, size)) = self.coff_file_range() {
504 data.read_bytes_at(offset.into(), size.into())
505 } else {
506 Ok(&[])
507 }
508 }
509
510 /// Return the section alignment in bytes.
511 ///
512 /// This is only valid for sections in a COFF file.
513 pub fn coff_alignment(&self) -> u64 {
514 match self.characteristics.get(LE) & pe::IMAGE_SCN_ALIGN_MASK {
515 pe::IMAGE_SCN_ALIGN_1BYTES => 1,
516 pe::IMAGE_SCN_ALIGN_2BYTES => 2,
517 pe::IMAGE_SCN_ALIGN_4BYTES => 4,
518 pe::IMAGE_SCN_ALIGN_8BYTES => 8,
519 pe::IMAGE_SCN_ALIGN_16BYTES => 16,
520 pe::IMAGE_SCN_ALIGN_32BYTES => 32,
521 pe::IMAGE_SCN_ALIGN_64BYTES => 64,
522 pe::IMAGE_SCN_ALIGN_128BYTES => 128,
523 pe::IMAGE_SCN_ALIGN_256BYTES => 256,
524 pe::IMAGE_SCN_ALIGN_512BYTES => 512,
525 pe::IMAGE_SCN_ALIGN_1024BYTES => 1024,
526 pe::IMAGE_SCN_ALIGN_2048BYTES => 2048,
527 pe::IMAGE_SCN_ALIGN_4096BYTES => 4096,
528 pe::IMAGE_SCN_ALIGN_8192BYTES => 8192,
529 _ => 16,
530 }
531 }
532
533 /// Read the relocations in a COFF file.
534 ///
535 /// `data` must be the entire file data.
536 pub fn coff_relocations<'data, R: ReadRef<'data>>(
537 &self,
538 data: R,
539 ) -> read::Result<&'data [pe::ImageRelocation]> {
540 let mut pointer = self.pointer_to_relocations.get(LE).into();
541 let mut number: usize = self.number_of_relocations.get(LE).into();
542 if number == core::u16::MAX.into()
543 && self.characteristics.get(LE) & pe::IMAGE_SCN_LNK_NRELOC_OVFL != 0
544 {
545 // Extended relocations. Read first relocation (which contains extended count) & adjust
546 // relocations pointer.
547 let extended_relocation_info = data
548 .read_at::<pe::ImageRelocation>(pointer)
549 .read_error("Invalid COFF relocation offset or number")?;
550 number = extended_relocation_info.virtual_address.get(LE) as usize;
551 if number == 0 {
552 return Err(Error("Invalid COFF relocation number"));
553 }
554 pointer += core::mem::size_of::<pe::ImageRelocation>() as u64;
555 // Extended relocation info does not contribute to the count of sections.
556 number -= 1;
557 }
558 data.read_slice_at(pointer, number)
559 .read_error("Invalid COFF relocation offset or number")
560 }
561}
562
563#[cfg(test)]
564mod tests {
565 use super::*;
566
567 #[test]
568 fn name_offset() {
569 let mut section = pe::ImageSectionHeader::default();
570 section.name = *b"xxxxxxxx";
571 assert_eq!(section.name_offset(), Ok(None));
572 section.name = *b"/0\0\0\0\0\0\0";
573 assert_eq!(section.name_offset(), Ok(Some(0)));
574 section.name = *b"/9999999";
575 assert_eq!(section.name_offset(), Ok(Some(999_9999)));
576 section.name = *b"//AAAAAA";
577 assert_eq!(section.name_offset(), Ok(Some(0)));
578 section.name = *b"//D/////";
579 assert_eq!(section.name_offset(), Ok(Some(0xffff_ffff)));
580 section.name = *b"//EAAAAA";
581 assert!(section.name_offset().is_err());
582 section.name = *b"////////";
583 assert!(section.name_offset().is_err());
584 }
585}
586