Skip to main content

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