1use elf::{
2 ElfBytes,
3 abi::{
4 DT_NEEDED, EM_386, EM_X86_64, ET_CORE, ET_DYN, ET_EXEC, ET_REL, PF_W, PF_X, PT_LOAD,
5 PT_PHDR, PT_TLS, R_X86_64_GLOB_DAT, R_X86_64_JUMP_SLOT, SHT_DYNSYM, SHT_REL, SHT_RELA,
6 SHT_SYMTAB, STT_FUNC,
7 },
8 endian::AnyEndian,
9};
10
11use crate::Arch;
12
13use crate::BinaryFormat;
14
15#[derive(Debug, Clone)]
17pub struct FunctionSymbol {
18 pub address: u64,
20 pub name: Option<String>,
22 pub is_external: bool,
24 pub library: Option<String>,
28}
29
30#[derive(Debug, Clone)]
32pub struct ImportSymbol {
33 pub address: u64,
35 pub name: String,
37 pub library: Option<String>,
40}
41
42#[derive(Debug, Clone)]
44pub struct ElfAnalysis {
45 pub entrypoint: u64,
47 pub known_functions: Vec<FunctionSymbol>,
49 pub imported_symbols: Vec<ImportSymbol>,
51}
52
53#[derive(Debug, Clone)]
54pub struct LoadSegment {
55 pub start: u64,
56 pub mem_size: u64,
57 pub data: Vec<u8>,
58 pub executable: bool,
60 pub writable: bool,
62 pub align: u64,
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum ElfKind {
70 Executable,
72 SharedObject,
75 Relocatable,
77 Core,
79 Other(u16),
81}
82
83impl ElfKind {
84 fn from_e_type(value: u16) -> Self {
85 match value {
86 ET_EXEC => ElfKind::Executable,
87 ET_DYN => ElfKind::SharedObject,
88 ET_REL => ElfKind::Relocatable,
89 ET_CORE => ElfKind::Core,
90 other => ElfKind::Other(other),
91 }
92 }
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub struct ProgramHeaderTable {
101 pub offset: u64,
103 pub entry_size: u16,
105 pub count: u16,
107 pub vaddr: Option<u64>,
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
116pub struct TlsSegment {
117 pub vaddr: u64,
119 pub file_size: u64,
121 pub mem_size: u64,
123 pub align: u64,
125}
126
127impl LoadSegment {
128 fn end(&self) -> u64 {
129 self.start + self.mem_size
130 }
131
132 fn contains(&self, addr: u64) -> bool {
133 addr >= self.start && addr < self.end()
134 }
135
136 fn byte_at(&self, addr: u64) -> Option<u8> {
137 if !self.contains(addr) {
138 return None;
139 }
140 let offset = (addr - self.start) as usize;
141 Some(self.data.get(offset).copied().unwrap_or(0))
142 }
143
144 fn bytes_at(&self, addr: u64) -> Option<&[u8]> {
145 if !self.contains(addr) {
146 return None;
147 }
148
149 let offset = (addr - self.start) as usize;
150
151 self.data.get(offset..)
152 }
153}
154
155#[derive(Debug, Clone)]
167pub struct ElfBinary {
168 pub load_address: u64,
169 pub segments: Vec<LoadSegment>,
170 pub analysis: ElfAnalysis,
171 pub architecture: Arch,
172 pub needed_libraries: Vec<String>,
175 pub kind: ElfKind,
177 pub program_headers: ProgramHeaderTable,
179 pub tls: Option<TlsSegment>,
181}
182
183#[derive(Debug)]
185pub enum ElfError {
186 Parse(elf::ParseError),
188 NoLoadableSegment,
190 UnknownArch,
192}
193
194impl std::fmt::Display for ElfError {
195 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196 match self {
197 ElfError::Parse(e) => write!(f, "ELF parse error: {e}"),
198 ElfError::NoLoadableSegment => write!(f, "ELF contains no PT_LOAD segment"),
199 ElfError::UnknownArch => write!(f, "ELF has an unknown arch"),
200 }
201 }
202}
203
204impl std::error::Error for ElfError {}
205
206impl From<elf::ParseError> for ElfError {
207 fn from(e: elf::ParseError) -> Self {
208 ElfError::Parse(e)
209 }
210}
211
212impl ElfBinary {
213 pub fn parse(file_bytes: &[u8]) -> Result<Self, ElfError> {
219 let elf = ElfBytes::<AnyEndian>::minimal_parse(file_bytes)?;
220
221 let segments = elf.segments().ok_or(ElfError::NoLoadableSegment)?;
223
224 let mut load_segments: Vec<_> = segments
225 .iter()
226 .filter(|p| p.p_type == PT_LOAD && p.p_memsz > 0)
227 .map(|p| {
228 let offset = p.p_offset as usize;
229 let filesz = p.p_filesz as usize;
230 let data = file_bytes
231 .get(offset..offset + filesz)
232 .unwrap_or(&[])
233 .to_vec();
234 LoadSegment {
235 start: p.p_vaddr,
236 mem_size: p.p_memsz,
237 data,
238 executable: p.p_flags & PF_X != 0,
239 writable: p.p_flags & PF_W != 0,
240 align: p.p_align,
241 }
242 })
243 .collect();
244
245 if load_segments.is_empty() {
246 return Err(ElfError::NoLoadableSegment);
247 }
248 load_segments.sort_by_key(|s| s.start);
249 let load_address = load_segments[0].start;
250
251 let entrypoint = elf.ehdr.e_entry;
253
254 let mut known_functions: Vec<FunctionSymbol> = Vec::new();
256 collect_func_symbols(&elf, SHT_SYMTAB, &mut known_functions);
257 collect_func_symbols(&elf, SHT_DYNSYM, &mut known_functions);
258
259 collect_plt_symbols(&elf, &mut known_functions);
261
262 let mut imported_symbols = Vec::new();
264 collect_import_symbols(&elf, &mut imported_symbols);
265
266 let needed_libraries = collect_needed_libraries(&elf);
268
269 let kind = ElfKind::from_e_type(elf.ehdr.e_type);
271 let phoff = elf.ehdr.e_phoff;
272 let phdr_vaddr = segments
273 .iter()
274 .find(|p| p.p_type == PT_PHDR)
275 .map(|p| p.p_vaddr)
276 .or_else(|| {
277 segments
278 .iter()
279 .filter(|p| p.p_type == PT_LOAD)
280 .find(|p| phoff >= p.p_offset && phoff < p.p_offset + p.p_filesz)
281 .map(|p| p.p_vaddr + (phoff - p.p_offset))
282 });
283 let program_headers = ProgramHeaderTable {
284 offset: phoff,
285 entry_size: elf.ehdr.e_phentsize,
286 count: elf.ehdr.e_phnum,
287 vaddr: phdr_vaddr,
288 };
289 let tls = segments
290 .iter()
291 .find(|p| p.p_type == PT_TLS)
292 .map(|p| TlsSegment {
293 vaddr: p.p_vaddr,
294 file_size: p.p_filesz,
295 mem_size: p.p_memsz,
296 align: p.p_align,
297 });
298
299 known_functions.sort_by_key(|f| f.address);
301 known_functions.dedup_by_key(|f| f.address);
302 imported_symbols.sort_by_key(|f| f.address);
303 imported_symbols.dedup_by_key(|f| f.address);
304
305 Ok(ElfBinary {
306 load_address,
307 segments: load_segments,
308 analysis: ElfAnalysis {
309 entrypoint,
310 known_functions,
311 imported_symbols,
312 },
313 architecture: from_elf_machine(elf.ehdr.e_machine).ok_or(ElfError::UnknownArch)?,
314 needed_libraries,
315 kind,
316 program_headers,
317 tls,
318 })
319 }
320}
321
322fn collect_needed_libraries(elf: &ElfBytes<AnyEndian>) -> Vec<String> {
326 let mut out = Vec::new();
327 let Some((_symtab, dynstr)) = elf.dynamic_symbol_table().ok().flatten() else {
328 return out;
329 };
330 let Ok(Some(dynamic)) = elf.dynamic() else {
331 return out;
332 };
333 for entry in dynamic.iter() {
334 if entry.d_tag == DT_NEEDED
335 && let Ok(name) = dynstr.get(entry.d_val() as usize)
336 && !name.is_empty()
337 {
338 out.push(name.to_owned());
339 }
340 }
341 out
342}
343
344fn collect_func_symbols(
347 elf: &ElfBytes<AnyEndian>,
348 section_type: u32,
349 out: &mut Vec<FunctionSymbol>,
350) {
351 if section_type == SHT_SYMTAB {
352 let Ok(Some((symtab, strtab))) = elf.symbol_table() else {
353 return;
354 };
355 for sym in symtab.iter() {
356 if sym.st_symtype() == STT_FUNC && sym.st_value != 0 {
357 let name = strtab.get(sym.st_name as usize).ok().map(str::to_owned);
358 out.push(FunctionSymbol {
359 address: sym.st_value,
360 name,
361 is_external: false,
362 library: None,
363 });
364 }
365 }
366 } else if section_type == SHT_DYNSYM {
367 let Ok(Some((dynsymtab, dynstrtab))) = elf.dynamic_symbol_table() else {
368 return;
369 };
370 for sym in dynsymtab.iter() {
371 if sym.st_symtype() == STT_FUNC && sym.st_value != 0 {
372 let name = dynstrtab.get(sym.st_name as usize).ok().map(str::to_owned);
373 out.push(FunctionSymbol {
374 address: sym.st_value,
375 name,
376 is_external: false,
377 library: None,
378 });
379 }
380 }
381 }
382}
383
384fn collect_plt_symbols(elf: &ElfBytes<AnyEndian>, out: &mut Vec<FunctionSymbol>) {
390 let _ = collect_plt_symbols_inner(elf, out);
391}
392
393fn collect_plt_symbols_inner(
394 elf: &ElfBytes<AnyEndian>,
395 out: &mut Vec<FunctionSymbol>,
396) -> Option<()> {
397 let (shdrs, shstrtab) = match elf.section_headers_with_strtab() {
398 Ok((Some(s), Some(st))) => (s, st),
399 _ => return None,
400 };
401
402 let section_addr = |name: &str| {
403 shdrs
404 .iter()
405 .find(|s| shstrtab.get(s.sh_name as usize).ok() == Some(name))
406 .map(|s| s.sh_addr)
407 };
408
409 let plt_start = section_addr(".plt")?;
410 let plt_entry_size = plt_entry_size(elf.ehdr.e_machine)?;
411 let plt_sec_start = section_addr(".plt.sec");
417
418 let (dynsymtab, dynstrtab) = elf.dynamic_symbol_table().ok().flatten()?;
419 let version_table = elf.symbol_version_table().ok().flatten();
423 let import_library = |sym_idx: u32| {
424 version_table
425 .as_ref()
426 .and_then(|vt| vt.get_requirement(sym_idx as usize).ok().flatten())
427 .map(|req| req.file.to_string())
428 };
429
430 let mut import_relocs: Vec<(usize, Option<String>, Option<String>)> = Vec::new();
433 if let Some(rela_plt_shdr) = shdrs
434 .iter()
435 .find(|s| shstrtab.get(s.sh_name as usize).ok() == Some(".rela.plt"))
436 {
437 for (i, rela) in elf.section_data_as_relas(&rela_plt_shdr).ok()?.enumerate() {
438 if is_import_relocation(elf.ehdr.e_machine, rela.r_type) {
439 let name = dyn_symbol_name(&dynsymtab, &dynstrtab, rela.r_sym).map(str::to_string);
440 import_relocs.push((i, name, import_library(rela.r_sym)));
441 }
442 }
443 } else if let Some(rel_plt_shdr) = shdrs
444 .iter()
445 .find(|s| shstrtab.get(s.sh_name as usize).ok() == Some(".rel.plt"))
446 {
447 for (i, rel) in elf.section_data_as_rels(&rel_plt_shdr).ok()?.enumerate() {
448 if is_import_relocation(elf.ehdr.e_machine, rel.r_type) {
449 let name = dyn_symbol_name(&dynsymtab, &dynstrtab, rel.r_sym).map(str::to_string);
450 import_relocs.push((i, name, import_library(rel.r_sym)));
451 }
452 }
453 }
454
455 for (i, name, library) in import_relocs {
456 let plt_stub_addr = plt_start + ((i + 1) as u64) * plt_entry_size;
458 match plt_sec_start {
459 Some(sec_start) => {
464 out.push(FunctionSymbol {
465 address: sec_start + (i as u64) * plt_entry_size,
466 name,
467 is_external: true,
468 library: library.clone(),
469 });
470 out.push(FunctionSymbol {
471 address: plt_stub_addr,
472 name: None,
473 is_external: true,
474 library,
475 });
476 }
477 None => out.push(FunctionSymbol {
478 address: plt_stub_addr,
479 name,
480 is_external: true,
481 library,
482 }),
483 }
484 }
485
486 Some(())
487}
488
489fn dyn_symbol_name<'a>(
490 dynsymtab: &elf::symbol::SymbolTable<'a, AnyEndian>,
491 dynstrtab: &elf::string_table::StringTable<'a>,
492 sym_idx: u32,
493) -> Option<&'a str> {
494 let sym = dynsymtab.get(sym_idx as usize).ok()?;
495 let name = dynstrtab.get(sym.st_name as usize).ok()?;
496 if name.is_empty() { None } else { Some(name) }
497}
498
499fn is_import_relocation(machine: u16, r_type: u32) -> bool {
500 match machine {
501 EM_386 | EM_X86_64 => r_type == R_X86_64_JUMP_SLOT,
502 _ => false,
503 }
504}
505
506fn plt_entry_size(machine: u16) -> Option<u64> {
507 match machine {
508 EM_386 | EM_X86_64 => Some(16),
509 _ => None,
510 }
511}
512
513fn collect_import_symbols(elf: &ElfBytes<AnyEndian>, out: &mut Vec<ImportSymbol>) {
514 let _ = collect_import_symbols_inner(elf, out);
515}
516
517fn collect_import_symbols_inner(
518 elf: &ElfBytes<AnyEndian>,
519 out: &mut Vec<ImportSymbol>,
520) -> Option<()> {
521 let (shdrs, _) = match elf.section_headers_with_strtab() {
522 Ok((Some(s), _)) => (s, ()),
523 _ => return None,
524 };
525
526 let (dynsymtab, dynstrtab) = elf.dynamic_symbol_table().ok().flatten()?;
527 let version_table = elf.symbol_version_table().ok().flatten();
528 let import_library = |sym_idx: u32| {
529 version_table
530 .as_ref()
531 .and_then(|vt| vt.get_requirement(sym_idx as usize).ok().flatten())
532 .map(|req| req.file.to_string())
533 };
534
535 for shdr in shdrs.iter() {
536 match shdr.sh_type {
537 SHT_RELA => {
538 let relas = elf.section_data_as_relas(&shdr).ok()?;
539 for rela in relas {
540 if !is_got_import_relocation(rela.r_type) {
541 continue;
542 }
543 let sym = dynsymtab.get(rela.r_sym as usize).ok()?;
544 let name = dynstrtab.get(sym.st_name as usize).ok()?.to_owned();
545 out.push(ImportSymbol {
546 address: rela.r_offset,
547 name,
548 library: import_library(rela.r_sym),
549 });
550 }
551 }
552 SHT_REL => {
553 let rels = elf.section_data_as_rels(&shdr).ok()?;
554 for rel in rels {
555 if !is_got_import_relocation(rel.r_type) {
556 continue;
557 }
558 let sym = dynsymtab.get(rel.r_sym as usize).ok()?;
559 let name = dynstrtab.get(sym.st_name as usize).ok()?.to_owned();
560 out.push(ImportSymbol {
561 address: rel.r_offset,
562 name,
563 library: import_library(rel.r_sym),
564 });
565 }
566 }
567 _ => {}
568 }
569 }
570
571 Some(())
572}
573
574fn is_got_import_relocation(r_type: u32) -> bool {
575 matches!(r_type, R_X86_64_GLOB_DAT | R_X86_64_JUMP_SLOT)
576}
577
578impl BinaryFormat for ElfBinary {
579 fn load_address(&self) -> u64 {
580 self.load_address
581 }
582
583 fn architecture(&self) -> Arch {
584 self.architecture
585 }
586
587 fn os(&self) -> crate::TargetOs {
588 crate::TargetOs::Linux
589 }
590
591 fn byte_at(&self, addr: u64) -> Option<u8> {
592 self.segments
593 .iter()
594 .find(|segment| segment.contains(addr))
595 .and_then(|segment| segment.byte_at(addr))
596 }
597
598 fn bytes_at(&self, addr: u64) -> Option<&[u8]> {
599 self.segments
600 .iter()
601 .find(|segment| segment.contains(addr))
602 .and_then(|segment| segment.bytes_at(addr))
603 }
604
605 fn is_executable(&self, addr: u64) -> bool {
606 self.segments
607 .iter()
608 .any(|segment| segment.executable && segment.contains(addr))
609 }
610
611 fn segment_bounds(&self, addr: u64) -> Option<(u64, u64)> {
612 self.segments
613 .iter()
614 .find(|segment| segment.contains(addr))
615 .map(|segment| (segment.start, segment.end()))
616 }
617
618 fn is_known_writable(&self, addr: u64) -> bool {
619 self.segments
620 .iter()
621 .any(|segment| segment.writable && segment.contains(addr))
622 }
623
624 fn is_known_read_only(&self, addr: u64) -> bool {
627 self.segments
628 .iter()
629 .any(|segment| !segment.writable && segment.contains(addr))
630 }
631
632 fn linked_libraries(&self) -> Vec<String> {
633 self.needed_libraries.clone()
634 }
635
636 fn mapped_regions(&self) -> Vec<(u64, Vec<u8>, bool, bool)> {
637 self.segments
638 .iter()
639 .map(|seg| {
640 let mut bytes = seg.data.clone();
643 bytes.resize(seg.mem_size as usize, 0);
644 (seg.start, bytes, seg.executable, seg.writable)
645 })
646 .collect()
647 }
648
649 fn symbol_name(&self, addr: u64) -> Option<&str> {
650 self.analysis
651 .known_functions
652 .iter()
653 .find(|f| f.address == addr)
654 .and_then(|f| f.name.as_deref())
655 .filter(|name| !name.is_empty())
656 .or_else(|| {
657 (addr == self.analysis.entrypoint && self.is_executable(addr)).then_some("_start")
658 })
659 }
660
661 fn is_external_symbol(&self, addr: u64) -> bool {
662 self.analysis
663 .known_functions
664 .iter()
665 .find(|f| f.address == addr)
666 .map(|f| f.is_external)
667 .unwrap_or(false)
668 }
669
670 fn import_symbol_name(&self, addr: u64) -> Option<&str> {
671 self.analysis
672 .imported_symbols
673 .iter()
674 .find(|f| f.address == addr)
675 .map(|f| f.name.as_str())
676 }
677
678 fn import_library(&self, addr: u64) -> Option<&str> {
679 self.analysis
682 .known_functions
683 .iter()
684 .find(|f| f.address == addr)
685 .and_then(|f| f.library.as_deref())
686 .or_else(|| {
687 self.analysis
688 .imported_symbols
689 .iter()
690 .find(|f| f.address == addr)
691 .and_then(|f| f.library.as_deref())
692 })
693 }
694
695 fn entry_points(&self) -> Vec<u64> {
697 let mut entries = vec![self.analysis.entrypoint];
698 for f in &self.analysis.known_functions {
699 entries.push(f.address);
700 }
701 entries.sort();
702 entries.dedup();
703 entries
704 }
705
706 fn entrypoint(&self) -> Option<u64> {
708 Some(self.analysis.entrypoint)
709 }
710}
711
712pub fn from_elf_machine(value: u16) -> Option<Arch> {
713 match value {
714 1 => Some(Arch::M32),
715 2 => Some(Arch::Sparc),
716 3 => Some(Arch::I386),
717 4 => Some(Arch::M68K),
718 5 => Some(Arch::M88K),
719 7 => Some(Arch::I860),
720 8 => Some(Arch::Mips),
721 9 => Some(Arch::S370),
722 10 => Some(Arch::MipsRs3Le),
723 15 => Some(Arch::PaRisc),
724 17 => Some(Arch::Vpp500),
725 18 => Some(Arch::Sparc32Plus),
726 19 => Some(Arch::I960),
727 20 => Some(Arch::Ppc),
728 21 => Some(Arch::Ppc64),
729 22 => Some(Arch::S390),
730 36 => Some(Arch::V800),
732 37 => Some(Arch::Fr20),
733 38 => Some(Arch::Rh32),
734 39 => Some(Arch::Rce),
735 40 => Some(Arch::Arm),
736 41 => Some(Arch::Alpha),
737 42 => Some(Arch::Sh),
738 43 => Some(Arch::SparcV9),
739 44 => Some(Arch::Tricore),
740 45 => Some(Arch::Arc),
741 46 => Some(Arch::H8300),
742 47 => Some(Arch::H8300H),
743 48 => Some(Arch::H8S),
744 49 => Some(Arch::H8500),
745 50 => Some(Arch::Ia64),
746 51 => Some(Arch::MipsX),
747 52 => Some(Arch::ColdFire),
748 53 => Some(Arch::M68Hc12),
749 54 => Some(Arch::Mma),
750 55 => Some(Arch::Pcp),
751 56 => Some(Arch::Ncpu),
752 57 => Some(Arch::Ndr1),
753 58 => Some(Arch::StarCore),
754 59 => Some(Arch::Me16),
755 60 => Some(Arch::St100),
756 61 => Some(Arch::TinyJ),
757 62 => Some(Arch::X86_64),
758 63 => Some(Arch::Pdsp),
759 64 => Some(Arch::Pdp10),
760 65 => Some(Arch::Pdp11),
761 66 => Some(Arch::Fx66),
762 67 => Some(Arch::St9Plus),
763 68 => Some(Arch::St7),
764 69 => Some(Arch::M68Hc16),
765 70 => Some(Arch::M68Hc11),
766 71 => Some(Arch::M68Hc08),
767 72 => Some(Arch::M68Hc05),
768 73 => Some(Arch::Svx),
769 74 => Some(Arch::St19),
770 75 => Some(Arch::Vax),
771 76 => Some(Arch::Cris),
772 77 => Some(Arch::Javelin),
773 78 => Some(Arch::FirePath),
774 79 => Some(Arch::Zsp),
775 80 => Some(Arch::Mmix),
776 81 => Some(Arch::Huany),
777 82 => Some(Arch::Prism),
778 83 => Some(Arch::Avr),
779 84 => Some(Arch::Fr30),
780 85 => Some(Arch::D10V),
781 86 => Some(Arch::D30V),
782 87 => Some(Arch::V850),
783 88 => Some(Arch::M32R),
784 89 => Some(Arch::Mn10300),
785 90 => Some(Arch::Mn10200),
786 91 => Some(Arch::PicoJava),
787 92 => Some(Arch::OpenRisc),
788 93 => Some(Arch::ArcA5),
789 94 => Some(Arch::Xtensa),
790 95 => Some(Arch::VideoCore),
791 96 => Some(Arch::TmmGpp),
792 97 => Some(Arch::Ns32K),
793 98 => Some(Arch::Tpc),
794 99 => Some(Arch::Snp1K),
795 100 => Some(Arch::St200),
796 106 => Some(Arch::Blackfin),
797 110 => Some(Arch::Unicore),
798 113 => Some(Arch::AlteraNios2),
799 140 => Some(Arch::TIC6000),
800 164 => Some(Arch::Hexagon),
801 167 => Some(Arch::NDS32),
802 183 => Some(Arch::AArch64),
803 188 => Some(Arch::TILEPro),
804 189 => Some(Arch::Microblaze),
805 191 => Some(Arch::TILEGx),
806 195 => Some(Arch::ARCv2),
807 243 => Some(Arch::RISCV),
808 247 => Some(Arch::BPF),
809 252 => Some(Arch::CSKY),
810 258 => Some(Arch::LoongArch),
811 0x5441 => Some(Arch::FRV),
812 _ => None,
813 }
814}