Skip to main content

vivisect/pe/
section_table.rs

1use crate::error::{self, Error};
2use crate::pe::relocation;
3use alloc::string::{String, ToString};
4use scroll::{ctx, Pread, Pwrite};
5
6#[repr(C)]
7#[derive(Debug, PartialEq, Eq, Clone, Default)]
8pub struct SectionTable {
9    pub name: [u8; 8],
10    pub real_name: Option<String>,
11    pub virtual_size: u32,
12    pub virtual_address: u32,
13    pub size_of_raw_data: u32,
14    pub pointer_to_raw_data: u32,
15    pub pointer_to_relocations: u32,
16    pub pointer_to_linenumbers: u32,
17    pub number_of_relocations: u16,
18    pub number_of_linenumbers: u16,
19    pub characteristics: u32,
20}
21
22pub const SIZEOF_SECTION_TABLE: usize = 8 * 5;
23
24// Based on https://github.com/llvm-mirror/llvm/blob/af7b1832a03ab6486c42a40d21695b2c03b2d8a3/lib/Object/COFFObjectFile.cpp#L70
25// Decodes a string table entry in base 64 (//AAAAAA). Expects string without
26// prefixed slashes.
27fn base64_decode_string_entry(s: &str) -> Result<usize, ()> {
28    assert!(s.len() <= 6, "String too long, possible overflow.");
29
30    let mut val = 0;
31    for c in s.bytes() {
32        let v = if c.is_ascii_uppercase() {
33            // let v = if b'A' <= c && c <= b'Z' {
34            // 00..=25
35            c - b'A'
36        } else if c.is_ascii_lowercase() {
37            // } else if b'a' <= c && c <= b'z' {
38            // 26..=51
39            c - b'a' + 26
40        } else if c.is_ascii_digit() {
41            // } else if b'0' <= c && c <= b'9' {
42            // 52..=61
43            c - b'0' + 52
44        } else if c == b'+' {
45            // 62
46            62
47        } else if c == b'/' {
48            // 63
49            63
50        } else {
51            return Err(());
52        };
53        val = val * 64 + v as usize;
54    }
55    Ok(val)
56}
57
58impl SectionTable {
59    pub fn parse(
60        bytes: &[u8],
61        offset: &mut usize,
62        string_table_offset: usize,
63    ) -> error::Result<Self> {
64        let mut table = SectionTable::default();
65        let mut name = [0u8; 8];
66        name.copy_from_slice(bytes.gread_with(offset, 8)?);
67
68        table.name = name;
69        table.virtual_size = bytes.gread_with(offset, scroll::LE)?;
70        table.virtual_address = bytes.gread_with(offset, scroll::LE)?;
71        table.size_of_raw_data = bytes.gread_with(offset, scroll::LE)?;
72        table.pointer_to_raw_data = bytes.gread_with(offset, scroll::LE)?;
73        table.pointer_to_relocations = bytes.gread_with(offset, scroll::LE)?;
74        table.pointer_to_linenumbers = bytes.gread_with(offset, scroll::LE)?;
75        table.number_of_relocations = bytes.gread_with(offset, scroll::LE)?;
76        table.number_of_linenumbers = bytes.gread_with(offset, scroll::LE)?;
77        table.characteristics = bytes.gread_with(offset, scroll::LE)?;
78
79        if let Some(idx) = table.name_offset()? {
80            table.real_name = Some(bytes.pread::<&str>(string_table_offset + idx)?.to_string());
81        }
82        Ok(table)
83    }
84
85    pub fn name_offset(&self) -> error::Result<Option<usize>> {
86        // Based on https://github.com/llvm-mirror/llvm/blob/af7b1832a03ab6486c42a40d21695b2c03b2d8a3/lib/Object/COFFObjectFile.cpp#L1054
87        if self.name[0] == b'/' {
88            let idx: usize = if self.name[1] == b'/' {
89                let b64idx = self.name.pread::<&str>(2)?;
90                base64_decode_string_entry(b64idx).map_err(|_| {
91                    Error::Malformed(format!(
92                        "Invalid indirect section name //{}: base64 decoding failed",
93                        b64idx
94                    ))
95                })?
96            } else {
97                let name = self.name.pread::<&str>(1)?;
98                name.parse().map_err(|err| {
99                    Error::Malformed(format!("Invalid indirect section name /{}: {}", name, err))
100                })?
101            };
102            Ok(Some(idx))
103        } else {
104            Ok(None)
105        }
106    }
107
108    #[allow(clippy::useless_let_if_seq)]
109    pub fn set_name_offset(&mut self, mut idx: usize) -> error::Result<()> {
110        if idx <= 9_999_999 {
111            // 10^7 - 1
112            // write!(&mut self.name[1..], "{}", idx) without using io::Write.
113            // We write into a temporary since we calculate digits starting at the right.
114            let mut name = [0; 7];
115            let mut len = 0;
116            if idx == 0 {
117                name[6] = b'0';
118                len = 1;
119            } else {
120                while idx != 0 {
121                    let rem = (idx % 10) as u8;
122                    idx /= 10;
123                    name[6 - len] = b'0' + rem;
124                    len += 1;
125                }
126            }
127            self.name = [0; 8];
128            self.name[0] = b'/';
129            self.name[1..][..len].copy_from_slice(&name[7 - len..]);
130            Ok(())
131        // } else if idx as u64 <= 0xfff_fff_fff {
132        } else if idx as u64 <= 0x000f_ffff_ffff {
133            // 64^6 - 1
134            self.name[0] = b'/';
135            self.name[1] = b'/';
136            for i in 0..6 {
137                let rem = (idx % 64) as u8;
138                idx /= 64;
139                let c = match rem {
140                    0..=25 => b'A' + rem,
141                    26..=51 => b'a' + rem - 26,
142                    52..=61 => b'0' + rem - 52,
143                    62 => b'+',
144                    63 => b'/',
145                    _ => unreachable!(),
146                };
147                self.name[7 - i] = c;
148            }
149            Ok(())
150        } else {
151            Err(Error::Malformed(format!(
152                "Invalid section name offset: {}",
153                idx
154            )))
155        }
156    }
157
158    pub fn name(&self) -> error::Result<&str> {
159        match self.real_name.as_ref() {
160            Some(s) => Ok(s),
161            None => Ok(self.name.pread(0)?),
162        }
163    }
164
165    pub fn relocations<'a>(&self, bytes: &'a [u8]) -> error::Result<relocation::Relocations<'a>> {
166        let offset = self.pointer_to_relocations as usize;
167        let number = self.number_of_relocations as usize;
168        relocation::Relocations::parse(bytes, offset, number)
169    }
170}
171
172impl ctx::SizeWith<scroll::Endian> for SectionTable {
173    fn size_with(_ctx: &scroll::Endian) -> usize {
174        SIZEOF_SECTION_TABLE
175    }
176}
177
178impl ctx::TryIntoCtx<scroll::Endian> for SectionTable {
179    type Error = error::Error;
180    fn try_into_ctx(self, bytes: &mut [u8], ctx: scroll::Endian) -> Result<usize, Self::Error> {
181        let offset = &mut 0;
182        bytes.gwrite(&self.name[..], offset)?;
183        bytes.gwrite_with(self.virtual_size, offset, ctx)?;
184        bytes.gwrite_with(self.virtual_address, offset, ctx)?;
185        bytes.gwrite_with(self.size_of_raw_data, offset, ctx)?;
186        bytes.gwrite_with(self.pointer_to_raw_data, offset, ctx)?;
187        bytes.gwrite_with(self.pointer_to_relocations, offset, ctx)?;
188        bytes.gwrite_with(self.pointer_to_linenumbers, offset, ctx)?;
189        bytes.gwrite_with(self.number_of_relocations, offset, ctx)?;
190        bytes.gwrite_with(self.number_of_linenumbers, offset, ctx)?;
191        bytes.gwrite_with(self.characteristics, offset, ctx)?;
192        Ok(SIZEOF_SECTION_TABLE)
193    }
194}
195
196impl ctx::IntoCtx<scroll::Endian> for SectionTable {
197    fn into_ctx(self, bytes: &mut [u8], ctx: scroll::Endian) {
198        bytes.pwrite_with(self, 0, ctx).unwrap();
199    }
200}
201
202/// The section should not be padded to the next boundary. This flag is obsolete and is replaced
203/// by `IMAGE_SCN_ALIGN_1BYTES`. This is valid only for object files.
204pub const IMAGE_SCN_TYPE_NO_PAD: u32 = 0x0000_0008;
205/// The section contains executable code.
206pub const IMAGE_SCN_CNT_CODE: u32 = 0x0000_0020;
207/// The section contains initialized data.
208pub const IMAGE_SCN_CNT_INITIALIZED_DATA: u32 = 0x0000_0040;
209///  The section contains uninitialized data.
210pub const IMAGE_SCN_CNT_UNINITIALIZED_DATA: u32 = 0x0000_0080;
211pub const IMAGE_SCN_LNK_OTHER: u32 = 0x0000_0100;
212/// The section contains comments or other information. The .drectve section has this type.
213/// This is valid for object files only.
214pub const IMAGE_SCN_LNK_INFO: u32 = 0x0000_0200;
215/// The section will not become part of the image. This is valid only for object files.
216pub const IMAGE_SCN_LNK_REMOVE: u32 = 0x0000_0800;
217/// The section contains COMDAT data. This is valid only for object files.
218pub const IMAGE_SCN_LNK_COMDAT: u32 = 0x0000_1000;
219/// The section contains data referenced through the global pointer (GP).
220pub const IMAGE_SCN_GPREL: u32 = 0x0000_8000;
221pub const IMAGE_SCN_MEM_PURGEABLE: u32 = 0x0002_0000;
222pub const IMAGE_SCN_MEM_16BIT: u32 = 0x0002_0000;
223pub const IMAGE_SCN_MEM_LOCKED: u32 = 0x0004_0000;
224pub const IMAGE_SCN_MEM_PRELOAD: u32 = 0x0008_0000;
225
226pub const IMAGE_SCN_ALIGN_1BYTES: u32 = 0x0010_0000;
227pub const IMAGE_SCN_ALIGN_2BYTES: u32 = 0x0020_0000;
228pub const IMAGE_SCN_ALIGN_4BYTES: u32 = 0x0030_0000;
229pub const IMAGE_SCN_ALIGN_8BYTES: u32 = 0x0040_0000;
230pub const IMAGE_SCN_ALIGN_16BYTES: u32 = 0x0050_0000;
231pub const IMAGE_SCN_ALIGN_32BYTES: u32 = 0x0060_0000;
232pub const IMAGE_SCN_ALIGN_64BYTES: u32 = 0x0070_0000;
233pub const IMAGE_SCN_ALIGN_128BYTES: u32 = 0x0080_0000;
234pub const IMAGE_SCN_ALIGN_256BYTES: u32 = 0x0090_0000;
235pub const IMAGE_SCN_ALIGN_512BYTES: u32 = 0x00A0_0000;
236pub const IMAGE_SCN_ALIGN_1024BYTES: u32 = 0x00B0_0000;
237pub const IMAGE_SCN_ALIGN_2048BYTES: u32 = 0x00C0_0000;
238pub const IMAGE_SCN_ALIGN_4096BYTES: u32 = 0x00D0_0000;
239pub const IMAGE_SCN_ALIGN_8192BYTES: u32 = 0x00E0_0000;
240pub const IMAGE_SCN_ALIGN_MASK: u32 = 0x00F0_0000;
241
242/// The section contains extended relocations.
243pub const IMAGE_SCN_LNK_NRELOC_OVFL: u32 = 0x0100_0000;
244/// The section can be discarded as needed.
245pub const IMAGE_SCN_MEM_DISCARDABLE: u32 = 0x0200_0000;
246/// The section cannot be cached.
247pub const IMAGE_SCN_MEM_NOT_CACHED: u32 = 0x0400_0000;
248/// The section is not pageable.
249pub const IMAGE_SCN_MEM_NOT_PAGED: u32 = 0x0800_0000;
250/// The section can be shared in memory.
251pub const IMAGE_SCN_MEM_SHARED: u32 = 0x1000_0000;
252/// The section can be executed as code.
253pub const IMAGE_SCN_MEM_EXECUTE: u32 = 0x2000_0000;
254/// The section can be read.
255pub const IMAGE_SCN_MEM_READ: u32 = 0x4000_0000;
256/// The section can be written to.
257pub const IMAGE_SCN_MEM_WRITE: u32 = 0x8000_0000;
258
259#[cfg(test)]
260mod tests {
261    use super::*;
262
263    #[test]
264    fn set_name_offset() {
265        let mut section = SectionTable::default();
266        for &(offset, name) in [
267            (0usize, b"/0\0\0\0\0\0\0"),
268            (1, b"/1\0\0\0\0\0\0"),
269            (9_999_999, b"/9999999"),
270            (10_000_000, b"//AAmJaA"),
271            #[cfg(target_pointer_width = "64")]
272            (0xfff_fff_fff, b"////////"),
273        ]
274        .iter()
275        {
276            section.set_name_offset(offset).unwrap();
277            assert_eq!(&section.name, name);
278            assert_eq!(section.name_offset().unwrap(), Some(offset));
279        }
280        #[cfg(target_pointer_width = "64")]
281        assert!(section.set_name_offset(0x1_000_000_000).is_err());
282    }
283}