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