1pub mod fields;
5
6use crate::exec::{
7 Architecture, ExecutableFile, ExecutableType, Imports, OperatingSystem, Section, Sections,
8};
9use crate::utils::{
10 EntropyCalc, bytes_offset_match, string_from_offset, u16_from_offset, u32_from_offset,
11 u64_from_offset,
12};
13use crate::{Ordering, SpecimenFile};
14
15use std::fmt::{Display, Formatter};
16
17use anyhow::{Result, anyhow, bail};
18use chrono::{DateTime, Utc};
19use flagset::FlagSet;
20use tracing::{error, info, instrument};
21use uuid::Uuid;
22
23const MAGIC: [u8; 4] = [0x7f, 0x45, 0x4c, 0x46];
27
28#[derive(Clone, Debug)]
36pub struct Elf<'a> {
37 pub is64bit: bool,
39
40 pub arch: Architecture,
42
43 pub has_overlay: Option<bool>,
45
46 pub ordering: Ordering,
48
49 pub executable_type: ExecutableType,
51
52 pub os: OperatingSystem,
54
55 pub sections: Option<Sections<'a>>,
57
58 pub imports: Option<Imports>,
60
61 pub interpreter: Option<String>,
63
64 pub contents: &'a [u8],
66}
67
68impl<'a> Elf<'a> {
69 #[allow(
75 clippy::too_many_lines,
76 clippy::cast_possible_truncation,
77 clippy::similar_names
78 )] #[instrument(name = "ELF parser", skip(contents))]
80 pub fn from(contents: &'a [u8]) -> Result<Self> {
81 if !bytes_offset_match(contents, 0, &MAGIC) {
82 bail!("Not an ELF file");
83 }
84
85 let is_64bit = contents[4] == 2;
86 let ordering = {
87 if contents[5] == 2 {
88 Ordering::BigEndian
89 } else {
90 Ordering::LittleEndian
91 }
92 };
93 let mut os = match contents[7] {
94 1 => OperatingSystem::HPUX,
95 2 => OperatingSystem::NetBSD,
96
97 0 | 3 => OperatingSystem::Linux,
100 6 => OperatingSystem::Solaris,
101 7 => OperatingSystem::AIX,
102 8 => OperatingSystem::Irix,
103 9 => OperatingSystem::FreeBSD,
104 0xC => OperatingSystem::OpenBSD,
105 other => OperatingSystem::Other(u16::from(other)),
106 };
107
108 let elf_type = match u16_from_offset(contents, 0x10, ordering)
109 .ok_or(anyhow!("ELF buffer too small for elf type"))?
110 {
111 1 | 2 => ExecutableType::Program,
112 3 => ExecutableType::Library,
113 4 => ExecutableType::Core,
114 other => ExecutableType::Unknown(other),
115 };
116
117 let arch = match u16_from_offset(contents, 0x12, ordering)
118 .ok_or(anyhow!("ELF buffer too small for architecture"))?
119 {
120 0 => Architecture::Unknown,
122 2 => Architecture::Sparc,
123 3 => Architecture::X86,
124 4 => Architecture::M68k,
125 5 => Architecture::M88k,
126 8 => {
127 if is_64bit {
128 Architecture::MIPS64
129 } else {
130 Architecture::MIPS
131 }
132 }
133 0x0A => {
134 if is_64bit {
135 Architecture::MIPSEL64
136 } else {
137 Architecture::MIPSEL
138 }
139 }
140 0x14 => {
141 if ordering == Ordering::BigEndian {
142 Architecture::PowerPC
143 } else {
144 Architecture::PowerPCLE
145 }
146 }
147 0x15 => {
148 if ordering == Ordering::BigEndian {
149 Architecture::PowerPC64
150 } else {
151 Architecture::PowerPC64LE
152 }
153 }
154 0x16 => {
155 if is_64bit {
156 Architecture::S390x
157 } else {
158 Architecture::S390
159 }
160 }
161
162 0x28 => Architecture::ARM,
163 0x29 => {
164 if is_64bit {
165 Architecture::Alpha64
166 } else {
167 Architecture::Alpha
168 }
169 }
170 0x2b => Architecture::Sparc64,
171
172 0x32 => Architecture::Itanium,
173 0x3E => Architecture::X86_64,
174
175 0xB7 => Architecture::ARM64,
176
177 0xF3 => {
178 if is_64bit {
179 Architecture::RISCV64
180 } else {
181 Architecture::RISCV
182 }
183 }
184
185 0x39d => Architecture::Hobbit,
186
187 other => Architecture::Other(u32::from(other)),
188 };
189
190 let e_shoff = {
192 if is_64bit {
193 u64_from_offset(contents, 0x28, ordering)
194 .ok_or(anyhow!("ELF contents too short for section offset"))?
195 as usize
196 } else {
197 u32_from_offset(contents, 0x20, ordering)
198 .ok_or(anyhow!("ELF contents too short for section offset"))?
199 as usize
200 }
201 };
202
203 let e_phentsize = {
204 if is_64bit {
205 u16_from_offset(contents, 0x36, ordering)
206 } else {
207 u16_from_offset(contents, 0x2A, ordering)
208 }
209 }
210 .ok_or(anyhow!("ELF contents too short program entry size"))?
211 as usize;
212
213 let e_phnum = {
214 if is_64bit {
215 u16_from_offset(contents, 0x38, ordering)
216 } else {
217 u16_from_offset(contents, 0x2C, ordering)
218 }
219 }
220 .ok_or(anyhow!("ELF contents too short program entries"))? as usize;
221
222 let mut interpreter = None;
223 for p_header_index in 0..e_phnum {
224 let start_index = {
225 if is_64bit {
226 0x40 + p_header_index * e_phentsize
227 } else {
228 0x34 + p_header_index * e_phentsize
229 }
230 };
231 let p_type = FlagSet::<fields::ProgramHeaderFlags>::new_truncated(
232 u32_from_offset(contents, start_index, ordering).unwrap_or_default(),
233 );
234
235 if p_type.contains(fields::ProgramHeaderFlags::Interpreter) {
236 let header = &contents[start_index..start_index + e_phentsize];
237 let p_offset = {
238 if is_64bit {
239 u64_from_offset(header, 0x08, ordering).unwrap_or_default() as usize
240 } else {
241 u32_from_offset(header, 0x04, ordering).unwrap_or_default() as usize
242 }
243 };
244 let p_filesz = {
245 if is_64bit {
246 u64_from_offset(header, 0x20, ordering).unwrap_or_default() as usize
247 } else {
248 u32_from_offset(header, 0x10, ordering).unwrap_or_default() as usize
249 }
250 };
251
252 if p_offset > 0 && p_filesz > 0 {
253 let interpreter_path =
254 String::from_utf8(Vec::from(&contents[p_offset..p_offset + p_filesz]))
255 .map_err(|e| {
256 error!(
257 "Interpreter error {e}, bytes: {:?}",
258 &contents[p_offset..p_offset + p_filesz]
259 );
260 })
261 .unwrap_or_default();
262 if !interpreter_path.is_empty() {
263 if interpreter_path.contains("/system/runtime_loader") {
264 os = OperatingSystem::Haiku;
265 }
266 interpreter = Some(interpreter_path);
267 }
268 break;
269 }
270 }
271 }
272
273 let e_shentsize = {
275 if is_64bit {
276 u16_from_offset(contents, 0x3A, ordering)
277 } else {
278 u16_from_offset(contents, 0x2E, ordering)
279 }
280 }
281 .ok_or(anyhow!("ELF contents too short for section entry size"))?;
282
283 let e_shnum = {
285 if is_64bit {
286 u16_from_offset(contents, 0x3C, ordering)
287 } else {
288 u16_from_offset(contents, 0x30, ordering)
289 }
290 }
291 .ok_or(anyhow!("ELF contents too short for section count"))?;
292
293 let e_shstrndx = {
295 if is_64bit {
296 u16_from_offset(contents, 0x3E, ordering)
297 } else {
298 u16_from_offset(contents, 0x32, ordering)
299 }
300 }
301 .ok_or(anyhow!("ELF contents too short for section header table with section names"))?;
302
303 let section_names_offset = {
305 if is_64bit {
306 u64_from_offset(
307 contents,
308 e_shoff + (e_shstrndx * e_shentsize) as usize + 0x18,
309 ordering,
310 )
311 .ok_or(anyhow!("ELF contents too short for section name"))? as usize
312 } else {
313 u32_from_offset(
314 contents,
315 e_shoff + (e_shstrndx * e_shentsize) as usize + 0x10,
316 ordering,
317 )
318 .ok_or(anyhow!("ELF contents too short for section name"))? as usize
319 }
320 };
321
322 let mut section_offset = e_shoff;
323 let mut sections = Sections::default();
324 for section_index in 0..e_shnum {
325 let section_name_offset = u32_from_offset(contents, section_offset, ordering)
326 .unwrap_or_default() as usize
327 + section_names_offset;
328 let section_name = if section_name_offset < contents.len() {
329 string_from_offset(contents, section_name_offset).unwrap_or_default()
330 } else {
331 info!(
332 "ELF: section name offset {section_name_offset} greater than buffer length {}.",
333 contents.len()
334 );
335 String::new()
336 };
337
338 let section_type = FlagSet::<fields::SectionHeaderTypes>::new_truncated(
339 u32_from_offset(contents, section_offset + 0x4, ordering).unwrap_or_default(),
340 );
341
342 if section_type.contains(fields::SectionHeaderTypes::DynamicSymbolsTable) {
343 }
345
346 let this_section_offset = {
347 if is_64bit {
348 u64_from_offset(contents, section_offset + 0x18, ordering).unwrap_or_default()
349 as usize
350 } else {
351 u32_from_offset(contents, section_offset + 0x10, ordering).unwrap_or_default()
352 as usize
353 }
354 };
355 let section_size = {
356 if is_64bit {
357 if let Some(size) = u64_from_offset(contents, section_offset + 0x20, ordering) {
358 size as usize
359 } else {
360 continue;
361 }
362 } else if let Some(size) =
363 u32_from_offset(contents, section_offset + 0x14, ordering)
364 {
365 size as usize
366 } else {
367 continue;
368 }
369 };
370 let section_flags = {
371 if is_64bit {
372 u64_from_offset(contents, section_offset + 0x08, ordering).unwrap_or_default()
373 as usize
374 } else {
375 u32_from_offset(contents, section_offset + 0x08, ordering).unwrap_or_default()
376 as usize
377 }
378 };
379
380 if this_section_offset + section_size <= contents.len() {
381 let section_bytes =
382 &contents[this_section_offset..this_section_offset + section_size];
383
384 sections.push(Section {
385 name: section_name,
386 is_executable: (section_flags & 4) != 0,
387 size: section_size,
388 offset: this_section_offset,
389 virtual_address: 0,
390 virtual_size: 0,
391 entropy: section_bytes.to_vec().entropy(),
392 data: Some(section_bytes),
393 });
394 } else {
395 error!(
396 "Section {section_index}: {section_name} offset {this_section_offset} + size {section_size} (end {}) is beyond the ELF buffer {}!",
397 this_section_offset + section_size,
398 contents.len()
399 );
400 }
401
402 section_offset += e_shentsize as usize;
404 }
405
406 Ok(Self {
407 is64bit: is_64bit,
408 arch,
409 has_overlay: Some(section_offset < contents.len()),
410 ordering,
411 executable_type: elf_type,
412 os,
413 sections: Some(sections),
414 imports: None,
415 interpreter,
416 contents,
417 })
418 }
419}
420
421impl ExecutableFile for Elf<'_> {
422 fn architecture(&self) -> Option<Architecture> {
423 Some(self.arch)
424 }
425
426 fn pointer_size(&self) -> usize {
427 if self.is64bit { 64 } else { 32 }
428 }
429
430 fn operating_system(&self) -> OperatingSystem {
431 self.os
432 }
433
434 fn compiled_timestamp(&self) -> Option<DateTime<Utc>> {
435 None
436 }
437
438 #[allow(clippy::cast_possible_truncation)]
439 fn num_sections(&self) -> u32 {
440 self.sections.as_ref().unwrap_or(&Sections::default()).len() as u32
441 }
442
443 fn sections(&self) -> Option<&Sections<'_>> {
444 self.sections.as_ref()
445 }
446
447 fn import_hash(&self) -> Option<Uuid> {
448 self.imports.as_ref().map(Imports::hash)
449 }
450
451 fn fuzzy_imports(&self) -> Option<String> {
452 self.imports.as_ref().map(Imports::fuzzy_hash)
453 }
454}
455
456impl SpecimenFile for Elf<'_> {
457 const MAGIC: &'static [&'static [u8]] = &[&MAGIC];
458
459 fn type_name(&self) -> &'static str {
460 "ELF"
461 }
462}
463
464impl Display for Elf<'_> {
465 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
466 writeln!(f, "ELF file:")?;
467 writeln!(f, "\tOS: {}", self.os)?;
468 writeln!(f, "\tArchitecture: {}", self.arch)?;
469 writeln!(f, "\tOrdering: {}", self.ordering)?;
470 writeln!(f, "\tType: {}", self.executable_type)?;
471 if let Some(interp) = &self.interpreter {
472 writeln!(f, "\tInterpreter: {interp}")?;
473 }
474 if let Some(sections) = &self.sections {
475 writeln!(f, "\t{} sections:", sections.len())?;
476 for section in sections {
477 writeln!(f, "\t\t{section}")?;
478 }
479 }
480 if self.has_overlay == Some(true) {
481 writeln!(f, "\tHas extra bytes at the end (overlay).")?;
482 }
483 writeln!(f, "\tSize: {}", self.contents.len())?;
484 writeln!(f, "\tEntropy: {:.4}", self.contents.entropy())
485 }
486}
487
488#[cfg(test)]
489mod tests {
490 use super::*;
491
492 use rstest::rstest;
493
494 #[rstest]
495 #[case::arm32(include_bytes!("../../../testdata/elf/elf_linux_arm"), false, OperatingSystem::Linux, Architecture::ARM, Ordering::LittleEndian, ExecutableType::Program)]
496 #[case::arm64(include_bytes!("../../../testdata/elf/elf_linux_arm64"), true, OperatingSystem::Linux, Architecture::ARM64, Ordering::LittleEndian, ExecutableType::Library )]
497 #[case::mips32(include_bytes!("../../../testdata/elf/elf_linux_mips"), false, OperatingSystem::Linux, Architecture::MIPS, Ordering::BigEndian, ExecutableType::Program)]
498 #[case::mips64(include_bytes!("../../../testdata/elf/elf_linux_mips64"), true, OperatingSystem::Linux, Architecture::MIPS64, Ordering::BigEndian, ExecutableType::Program)]
499 #[case::ppc64le(include_bytes!("../../../testdata/elf/elf_linux_ppc64le"), true, OperatingSystem::Linux, Architecture::PowerPC64LE, Ordering::LittleEndian, ExecutableType::Program)]
500 #[case::ppc64le_lib(include_bytes!("../../../testdata/elf/elf_linux_ppc64le.so"), true, OperatingSystem::Linux, Architecture::PowerPC64LE, Ordering::LittleEndian, ExecutableType::Library)]
501 #[case::riscv(include_bytes!("../../../testdata/elf/elf_linux_riscv64"), true, OperatingSystem::Linux, Architecture::RISCV64, Ordering::LittleEndian, ExecutableType::Library )]
502 #[case::s390x(include_bytes!("../../../testdata/elf/elf_linux_s390x"), true, OperatingSystem::Linux, Architecture::S390x, Ordering::BigEndian, ExecutableType::Library )]
503 #[case::x86_haiku(include_bytes!("../../../testdata/elf/elf_haiku_x86"), false, OperatingSystem::Haiku, Architecture::X86, Ordering::LittleEndian, ExecutableType::Library )]
504 #[case::x86_64_freebsd(include_bytes!("../../../testdata/elf/elf_freebsd_x86_64"), true, OperatingSystem::FreeBSD, Architecture::X86_64, Ordering::LittleEndian, ExecutableType::Program)]
505 #[test]
506 fn binaries(
507 #[case] bytes: &[u8],
508 #[case] is_64bit: bool,
509 #[case] os: OperatingSystem,
510 #[case] arch: Architecture,
511 #[case] ordering: Ordering,
512 #[case] elf_type: ExecutableType,
513 ) {
514 let elf = Elf::from(bytes).unwrap();
515 eprintln!("{elf}");
516 assert_eq!(elf.is64bit, is_64bit);
517 assert_eq!(elf.os, os);
518 if elf_type == ExecutableType::Program
519 && arch != Architecture::ARM64
520 && arch != Architecture::RISCV64
521 && arch != Architecture::S390x
522 {
523 assert!(elf.interpreter.is_some());
524 }
525 assert_eq!(elf.executable_type, elf_type);
526 assert_eq!(elf.ordering, ordering);
527 assert_eq!(elf.arch, arch);
528 }
529
530 #[test]
531 fn hobbit() {
532 const BYTES: &[u8] = include_bytes!("../../../testdata/elf/elf_aclock_hobbit_beos");
535
536 let elf = Elf::from(BYTES).unwrap();
537 eprintln!("{elf}");
538 assert!(!elf.is64bit);
539 assert_eq!(elf.ordering, Ordering::BigEndian);
540 assert_eq!(elf.arch, Architecture::Hobbit);
541 }
542}