object/read/coff/
section.rs

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