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