1use elf::{
2 ElfBytes,
3 abi::{
4 DT_NEEDED, EM_386, EM_X86_64, PF_W, PF_X, PT_LOAD, R_X86_64_GLOB_DAT, R_X86_64_JUMP_SLOT,
5 SHT_DYNSYM, SHT_REL, SHT_RELA, SHT_SYMTAB, STT_FUNC,
6 },
7 endian::AnyEndian,
8};
9
10use crate::Arch;
11
12use crate::BinaryFormat;
13
14#[derive(Debug, Clone)]
16pub struct FunctionSymbol {
17 pub address: u64,
19 pub name: Option<String>,
21 pub is_external: bool,
23 pub library: Option<String>,
27}
28
29#[derive(Debug, Clone)]
31pub struct ImportSymbol {
32 pub address: u64,
34 pub name: String,
36 pub library: Option<String>,
39}
40
41#[derive(Debug, Clone)]
43pub struct ElfAnalysis {
44 pub entrypoint: u64,
46 pub known_functions: Vec<FunctionSymbol>,
48 pub imported_symbols: Vec<ImportSymbol>,
50}
51
52#[derive(Debug, Clone)]
53pub struct LoadSegment {
54 pub start: u64,
55 pub mem_size: u64,
56 pub data: Vec<u8>,
57 pub executable: bool,
59 pub writable: bool,
61}
62
63impl LoadSegment {
64 fn end(&self) -> u64 {
65 self.start + self.mem_size
66 }
67
68 fn contains(&self, addr: u64) -> bool {
69 addr >= self.start && addr < self.end()
70 }
71
72 fn byte_at(&self, addr: u64) -> Option<u8> {
73 if !self.contains(addr) {
74 return None;
75 }
76 let offset = (addr - self.start) as usize;
77 Some(self.data.get(offset).copied().unwrap_or(0))
78 }
79
80 fn bytes_at(&self, addr: u64) -> Option<&[u8]> {
81 if !self.contains(addr) {
82 return None;
83 }
84
85 let offset = (addr - self.start) as usize;
86
87 self.data.get(offset..)
88 }
89}
90
91#[derive(Debug, Clone)]
103pub struct ElfBinary {
104 pub load_address: u64,
105 pub segments: Vec<LoadSegment>,
106 pub analysis: ElfAnalysis,
107 pub architecture: Arch,
108 pub needed_libraries: Vec<String>,
111}
112
113#[derive(Debug)]
115pub enum ElfError {
116 Parse(elf::ParseError),
118 NoLoadableSegment,
120 UnknownArch,
122}
123
124impl std::fmt::Display for ElfError {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 match self {
127 ElfError::Parse(e) => write!(f, "ELF parse error: {e}"),
128 ElfError::NoLoadableSegment => write!(f, "ELF contains no PT_LOAD segment"),
129 ElfError::UnknownArch => write!(f, "ELF has an unknown arch"),
130 }
131 }
132}
133
134impl std::error::Error for ElfError {}
135
136impl From<elf::ParseError> for ElfError {
137 fn from(e: elf::ParseError) -> Self {
138 ElfError::Parse(e)
139 }
140}
141
142impl ElfBinary {
143 pub fn parse(file_bytes: &[u8]) -> Result<Self, ElfError> {
149 let elf = ElfBytes::<AnyEndian>::minimal_parse(file_bytes)?;
150
151 let segments = elf.segments().ok_or(ElfError::NoLoadableSegment)?;
153
154 let mut load_segments: Vec<_> = segments
155 .iter()
156 .filter(|p| p.p_type == PT_LOAD && p.p_memsz > 0)
157 .map(|p| {
158 let offset = p.p_offset as usize;
159 let filesz = p.p_filesz as usize;
160 let data = file_bytes
161 .get(offset..offset + filesz)
162 .unwrap_or(&[])
163 .to_vec();
164 LoadSegment {
165 start: p.p_vaddr,
166 mem_size: p.p_memsz,
167 data,
168 executable: p.p_flags & PF_X != 0,
169 writable: p.p_flags & PF_W != 0,
170 }
171 })
172 .collect();
173
174 if load_segments.is_empty() {
175 return Err(ElfError::NoLoadableSegment);
176 }
177 load_segments.sort_by_key(|s| s.start);
178 let load_address = load_segments[0].start;
179
180 let entrypoint = elf.ehdr.e_entry;
182
183 let mut known_functions: Vec<FunctionSymbol> = Vec::new();
185 collect_func_symbols(&elf, SHT_SYMTAB, &mut known_functions);
186 collect_func_symbols(&elf, SHT_DYNSYM, &mut known_functions);
187
188 collect_plt_symbols(&elf, &mut known_functions);
190
191 let mut imported_symbols = Vec::new();
193 collect_import_symbols(&elf, &mut imported_symbols);
194
195 let needed_libraries = collect_needed_libraries(&elf);
197
198 known_functions.sort_by_key(|f| f.address);
200 known_functions.dedup_by_key(|f| f.address);
201 imported_symbols.sort_by_key(|f| f.address);
202 imported_symbols.dedup_by_key(|f| f.address);
203
204 Ok(ElfBinary {
205 load_address,
206 segments: load_segments,
207 analysis: ElfAnalysis {
208 entrypoint,
209 known_functions,
210 imported_symbols,
211 },
212 architecture: from_elf_machine(elf.ehdr.e_machine).ok_or(ElfError::UnknownArch)?,
213 needed_libraries,
214 })
215 }
216}
217
218fn collect_needed_libraries(elf: &ElfBytes<AnyEndian>) -> Vec<String> {
222 let mut out = Vec::new();
223 let Some((_symtab, dynstr)) = elf.dynamic_symbol_table().ok().flatten() else {
224 return out;
225 };
226 let Ok(Some(dynamic)) = elf.dynamic() else {
227 return out;
228 };
229 for entry in dynamic.iter() {
230 if entry.d_tag == DT_NEEDED
231 && let Ok(name) = dynstr.get(entry.d_val() as usize)
232 && !name.is_empty()
233 {
234 out.push(name.to_owned());
235 }
236 }
237 out
238}
239
240fn collect_func_symbols(
243 elf: &ElfBytes<AnyEndian>,
244 section_type: u32,
245 out: &mut Vec<FunctionSymbol>,
246) {
247 if section_type == SHT_SYMTAB {
248 let Ok(Some((symtab, strtab))) = elf.symbol_table() else {
249 return;
250 };
251 for sym in symtab.iter() {
252 if sym.st_symtype() == STT_FUNC && sym.st_value != 0 {
253 let name = strtab.get(sym.st_name as usize).ok().map(str::to_owned);
254 out.push(FunctionSymbol {
255 address: sym.st_value,
256 name,
257 is_external: false,
258 library: None,
259 });
260 }
261 }
262 } else if section_type == SHT_DYNSYM {
263 let Ok(Some((dynsymtab, dynstrtab))) = elf.dynamic_symbol_table() else {
264 return;
265 };
266 for sym in dynsymtab.iter() {
267 if sym.st_symtype() == STT_FUNC && sym.st_value != 0 {
268 let name = dynstrtab.get(sym.st_name as usize).ok().map(str::to_owned);
269 out.push(FunctionSymbol {
270 address: sym.st_value,
271 name,
272 is_external: false,
273 library: None,
274 });
275 }
276 }
277 }
278}
279
280fn collect_plt_symbols(elf: &ElfBytes<AnyEndian>, out: &mut Vec<FunctionSymbol>) {
286 let _ = collect_plt_symbols_inner(elf, out);
287}
288
289fn collect_plt_symbols_inner(
290 elf: &ElfBytes<AnyEndian>,
291 out: &mut Vec<FunctionSymbol>,
292) -> Option<()> {
293 let (shdrs, shstrtab) = match elf.section_headers_with_strtab() {
294 Ok((Some(s), Some(st))) => (s, st),
295 _ => return None,
296 };
297
298 let section_addr = |name: &str| {
299 shdrs
300 .iter()
301 .find(|s| shstrtab.get(s.sh_name as usize).ok() == Some(name))
302 .map(|s| s.sh_addr)
303 };
304
305 let plt_start = section_addr(".plt")?;
306 let plt_entry_size = plt_entry_size(elf.ehdr.e_machine)?;
307 let plt_sec_start = section_addr(".plt.sec");
313
314 let (dynsymtab, dynstrtab) = elf.dynamic_symbol_table().ok().flatten()?;
315 let version_table = elf.symbol_version_table().ok().flatten();
319 let import_library = |sym_idx: u32| {
320 version_table
321 .as_ref()
322 .and_then(|vt| vt.get_requirement(sym_idx as usize).ok().flatten())
323 .map(|req| req.file.to_string())
324 };
325
326 let mut import_relocs: Vec<(usize, Option<String>, Option<String>)> = Vec::new();
329 if let Some(rela_plt_shdr) = shdrs
330 .iter()
331 .find(|s| shstrtab.get(s.sh_name as usize).ok() == Some(".rela.plt"))
332 {
333 for (i, rela) in elf.section_data_as_relas(&rela_plt_shdr).ok()?.enumerate() {
334 if is_import_relocation(elf.ehdr.e_machine, rela.r_type) {
335 let name = dyn_symbol_name(&dynsymtab, &dynstrtab, rela.r_sym).map(str::to_string);
336 import_relocs.push((i, name, import_library(rela.r_sym)));
337 }
338 }
339 } else if let Some(rel_plt_shdr) = shdrs
340 .iter()
341 .find(|s| shstrtab.get(s.sh_name as usize).ok() == Some(".rel.plt"))
342 {
343 for (i, rel) in elf.section_data_as_rels(&rel_plt_shdr).ok()?.enumerate() {
344 if is_import_relocation(elf.ehdr.e_machine, rel.r_type) {
345 let name = dyn_symbol_name(&dynsymtab, &dynstrtab, rel.r_sym).map(str::to_string);
346 import_relocs.push((i, name, import_library(rel.r_sym)));
347 }
348 }
349 }
350
351 for (i, name, library) in import_relocs {
352 let plt_stub_addr = plt_start + ((i + 1) as u64) * plt_entry_size;
354 match plt_sec_start {
355 Some(sec_start) => {
360 out.push(FunctionSymbol {
361 address: sec_start + (i as u64) * plt_entry_size,
362 name,
363 is_external: true,
364 library: library.clone(),
365 });
366 out.push(FunctionSymbol {
367 address: plt_stub_addr,
368 name: None,
369 is_external: true,
370 library,
371 });
372 }
373 None => out.push(FunctionSymbol {
374 address: plt_stub_addr,
375 name,
376 is_external: true,
377 library,
378 }),
379 }
380 }
381
382 Some(())
383}
384
385fn dyn_symbol_name<'a>(
386 dynsymtab: &elf::symbol::SymbolTable<'a, AnyEndian>,
387 dynstrtab: &elf::string_table::StringTable<'a>,
388 sym_idx: u32,
389) -> Option<&'a str> {
390 let sym = dynsymtab.get(sym_idx as usize).ok()?;
391 let name = dynstrtab.get(sym.st_name as usize).ok()?;
392 if name.is_empty() { None } else { Some(name) }
393}
394
395fn is_import_relocation(machine: u16, r_type: u32) -> bool {
396 match machine {
397 EM_386 | EM_X86_64 => r_type == R_X86_64_JUMP_SLOT,
398 _ => false,
399 }
400}
401
402fn plt_entry_size(machine: u16) -> Option<u64> {
403 match machine {
404 EM_386 | EM_X86_64 => Some(16),
405 _ => None,
406 }
407}
408
409fn collect_import_symbols(elf: &ElfBytes<AnyEndian>, out: &mut Vec<ImportSymbol>) {
410 let _ = collect_import_symbols_inner(elf, out);
411}
412
413fn collect_import_symbols_inner(
414 elf: &ElfBytes<AnyEndian>,
415 out: &mut Vec<ImportSymbol>,
416) -> Option<()> {
417 let (shdrs, _) = match elf.section_headers_with_strtab() {
418 Ok((Some(s), _)) => (s, ()),
419 _ => return None,
420 };
421
422 let (dynsymtab, dynstrtab) = elf.dynamic_symbol_table().ok().flatten()?;
423 let version_table = elf.symbol_version_table().ok().flatten();
424 let import_library = |sym_idx: u32| {
425 version_table
426 .as_ref()
427 .and_then(|vt| vt.get_requirement(sym_idx as usize).ok().flatten())
428 .map(|req| req.file.to_string())
429 };
430
431 for shdr in shdrs.iter() {
432 match shdr.sh_type {
433 SHT_RELA => {
434 let relas = elf.section_data_as_relas(&shdr).ok()?;
435 for rela in relas {
436 if !is_got_import_relocation(rela.r_type) {
437 continue;
438 }
439 let sym = dynsymtab.get(rela.r_sym as usize).ok()?;
440 let name = dynstrtab.get(sym.st_name as usize).ok()?.to_owned();
441 out.push(ImportSymbol {
442 address: rela.r_offset,
443 name,
444 library: import_library(rela.r_sym),
445 });
446 }
447 }
448 SHT_REL => {
449 let rels = elf.section_data_as_rels(&shdr).ok()?;
450 for rel in rels {
451 if !is_got_import_relocation(rel.r_type) {
452 continue;
453 }
454 let sym = dynsymtab.get(rel.r_sym as usize).ok()?;
455 let name = dynstrtab.get(sym.st_name as usize).ok()?.to_owned();
456 out.push(ImportSymbol {
457 address: rel.r_offset,
458 name,
459 library: import_library(rel.r_sym),
460 });
461 }
462 }
463 _ => {}
464 }
465 }
466
467 Some(())
468}
469
470fn is_got_import_relocation(r_type: u32) -> bool {
471 matches!(r_type, R_X86_64_GLOB_DAT | R_X86_64_JUMP_SLOT)
472}
473
474impl BinaryFormat for ElfBinary {
475 fn load_address(&self) -> u64 {
476 self.load_address
477 }
478
479 fn architecture(&self) -> Arch {
480 self.architecture
481 }
482
483 fn os(&self) -> crate::TargetOs {
484 crate::TargetOs::Linux
485 }
486
487 fn byte_at(&self, addr: u64) -> Option<u8> {
488 self.segments
489 .iter()
490 .find(|segment| segment.contains(addr))
491 .and_then(|segment| segment.byte_at(addr))
492 }
493
494 fn bytes_at(&self, addr: u64) -> Option<&[u8]> {
495 self.segments
496 .iter()
497 .find(|segment| segment.contains(addr))
498 .and_then(|segment| segment.bytes_at(addr))
499 }
500
501 fn is_executable(&self, addr: u64) -> bool {
502 self.segments
503 .iter()
504 .any(|segment| segment.executable && segment.contains(addr))
505 }
506
507 fn segment_bounds(&self, addr: u64) -> Option<(u64, u64)> {
508 self.segments
509 .iter()
510 .find(|segment| segment.contains(addr))
511 .map(|segment| (segment.start, segment.end()))
512 }
513
514 fn is_known_writable(&self, addr: u64) -> bool {
515 self.segments
516 .iter()
517 .any(|segment| segment.writable && segment.contains(addr))
518 }
519
520 fn is_known_read_only(&self, addr: u64) -> bool {
523 self.segments
524 .iter()
525 .any(|segment| !segment.writable && segment.contains(addr))
526 }
527
528 fn linked_libraries(&self) -> Vec<String> {
529 self.needed_libraries.clone()
530 }
531
532 fn mapped_regions(&self) -> Vec<(u64, Vec<u8>, bool, bool)> {
533 self.segments
534 .iter()
535 .map(|seg| {
536 let mut bytes = seg.data.clone();
539 bytes.resize(seg.mem_size as usize, 0);
540 (seg.start, bytes, seg.executable, seg.writable)
541 })
542 .collect()
543 }
544
545 fn symbol_name(&self, addr: u64) -> Option<&str> {
546 self.analysis
547 .known_functions
548 .iter()
549 .find(|f| f.address == addr)
550 .and_then(|f| f.name.as_deref())
551 .filter(|name| !name.is_empty())
552 .or_else(|| {
553 (addr == self.analysis.entrypoint && self.is_executable(addr)).then_some("_start")
554 })
555 }
556
557 fn is_external_symbol(&self, addr: u64) -> bool {
558 self.analysis
559 .known_functions
560 .iter()
561 .find(|f| f.address == addr)
562 .map(|f| f.is_external)
563 .unwrap_or(false)
564 }
565
566 fn import_symbol_name(&self, addr: u64) -> Option<&str> {
567 self.analysis
568 .imported_symbols
569 .iter()
570 .find(|f| f.address == addr)
571 .map(|f| f.name.as_str())
572 }
573
574 fn import_library(&self, addr: u64) -> Option<&str> {
575 self.analysis
578 .known_functions
579 .iter()
580 .find(|f| f.address == addr)
581 .and_then(|f| f.library.as_deref())
582 .or_else(|| {
583 self.analysis
584 .imported_symbols
585 .iter()
586 .find(|f| f.address == addr)
587 .and_then(|f| f.library.as_deref())
588 })
589 }
590
591 fn entry_points(&self) -> Vec<u64> {
593 let mut entries = vec![self.analysis.entrypoint];
594 for f in &self.analysis.known_functions {
595 entries.push(f.address);
596 }
597 entries.sort();
598 entries.dedup();
599 entries
600 }
601
602 fn entrypoint(&self) -> Option<u64> {
604 Some(self.analysis.entrypoint)
605 }
606}
607
608pub fn from_elf_machine(value: u16) -> Option<Arch> {
609 match value {
610 1 => Some(Arch::M32),
611 2 => Some(Arch::Sparc),
612 3 => Some(Arch::I386),
613 4 => Some(Arch::M68K),
614 5 => Some(Arch::M88K),
615 7 => Some(Arch::I860),
616 8 => Some(Arch::Mips),
617 9 => Some(Arch::S370),
618 10 => Some(Arch::MipsRs3Le),
619 15 => Some(Arch::PaRisc),
620 17 => Some(Arch::Vpp500),
621 18 => Some(Arch::Sparc32Plus),
622 19 => Some(Arch::I960),
623 20 => Some(Arch::Ppc),
624 21 => Some(Arch::Ppc64),
625 22 => Some(Arch::S390),
626 36 => Some(Arch::V800),
628 37 => Some(Arch::Fr20),
629 38 => Some(Arch::Rh32),
630 39 => Some(Arch::Rce),
631 40 => Some(Arch::Arm),
632 41 => Some(Arch::Alpha),
633 42 => Some(Arch::Sh),
634 43 => Some(Arch::SparcV9),
635 44 => Some(Arch::Tricore),
636 45 => Some(Arch::Arc),
637 46 => Some(Arch::H8300),
638 47 => Some(Arch::H8300H),
639 48 => Some(Arch::H8S),
640 49 => Some(Arch::H8500),
641 50 => Some(Arch::Ia64),
642 51 => Some(Arch::MipsX),
643 52 => Some(Arch::ColdFire),
644 53 => Some(Arch::M68Hc12),
645 54 => Some(Arch::Mma),
646 55 => Some(Arch::Pcp),
647 56 => Some(Arch::Ncpu),
648 57 => Some(Arch::Ndr1),
649 58 => Some(Arch::StarCore),
650 59 => Some(Arch::Me16),
651 60 => Some(Arch::St100),
652 61 => Some(Arch::TinyJ),
653 62 => Some(Arch::X86_64),
654 63 => Some(Arch::Pdsp),
655 64 => Some(Arch::Pdp10),
656 65 => Some(Arch::Pdp11),
657 66 => Some(Arch::Fx66),
658 67 => Some(Arch::St9Plus),
659 68 => Some(Arch::St7),
660 69 => Some(Arch::M68Hc16),
661 70 => Some(Arch::M68Hc11),
662 71 => Some(Arch::M68Hc08),
663 72 => Some(Arch::M68Hc05),
664 73 => Some(Arch::Svx),
665 74 => Some(Arch::St19),
666 75 => Some(Arch::Vax),
667 76 => Some(Arch::Cris),
668 77 => Some(Arch::Javelin),
669 78 => Some(Arch::FirePath),
670 79 => Some(Arch::Zsp),
671 80 => Some(Arch::Mmix),
672 81 => Some(Arch::Huany),
673 82 => Some(Arch::Prism),
674 83 => Some(Arch::Avr),
675 84 => Some(Arch::Fr30),
676 85 => Some(Arch::D10V),
677 86 => Some(Arch::D30V),
678 87 => Some(Arch::V850),
679 88 => Some(Arch::M32R),
680 89 => Some(Arch::Mn10300),
681 90 => Some(Arch::Mn10200),
682 91 => Some(Arch::PicoJava),
683 92 => Some(Arch::OpenRisc),
684 93 => Some(Arch::ArcA5),
685 94 => Some(Arch::Xtensa),
686 95 => Some(Arch::VideoCore),
687 96 => Some(Arch::TmmGpp),
688 97 => Some(Arch::Ns32K),
689 98 => Some(Arch::Tpc),
690 99 => Some(Arch::Snp1K),
691 100 => Some(Arch::St200),
692 106 => Some(Arch::Blackfin),
693 110 => Some(Arch::Unicore),
694 113 => Some(Arch::AlteraNios2),
695 140 => Some(Arch::TIC6000),
696 164 => Some(Arch::Hexagon),
697 167 => Some(Arch::NDS32),
698 183 => Some(Arch::AArch64),
699 188 => Some(Arch::TILEPro),
700 189 => Some(Arch::Microblaze),
701 191 => Some(Arch::TILEGx),
702 195 => Some(Arch::ARCv2),
703 243 => Some(Arch::RISCV),
704 247 => Some(Arch::BPF),
705 252 => Some(Arch::CSKY),
706 258 => Some(Arch::LoongArch),
707 0x5441 => Some(Arch::FRV),
708 _ => None,
709 }
710}