use super::mk_section;
use crate::model::{Arch, Binary, Format, HardeningFacts, ImportedLib, SymKind, Symbol};
use goblin::elf::Elf;
const SHF_WRITE: u64 = 0x1;
const SHF_ALLOC: u64 = 0x2;
const SHF_EXECINSTR: u64 = 0x4;
const PT_GNU_EH_FRAME: u32 = 0x6474_e550;
const PT_GNU_STACK: u32 = 0x6474_e551;
const PT_GNU_RELRO: u32 = 0x6474_e552;
const PF_X: u32 = 0x1;
const DF_BIND_NOW: u64 = 0x8;
const DF_1_NOW: u64 = 0x1;
const DF_1_PIE: u64 = 0x0800_0000;
pub fn build(path: &str, bytes: &[u8], elf: Elf) -> Binary {
let arch = match elf.header.e_machine {
3 => Arch::X86,
62 => Arch::X86_64,
40 => Arch::Arm,
183 => Arch::Aarch64,
8 => Arch::Mips,
243 => Arch::Riscv,
_ => Arch::Other,
};
let sections = elf
.section_headers
.iter()
.filter(|sh| sh.sh_type != 0) .map(|sh| {
let name = elf
.shdr_strtab
.get_at(sh.sh_name)
.unwrap_or("(bad)")
.to_string();
let f = sh.sh_flags;
let alloc = f & SHF_ALLOC != 0;
mk_section(
name,
sh.sh_addr,
sh.sh_size,
sh.sh_offset,
if sh.sh_type == 8 { 0 } else { sh.sh_size }, alloc,
f & SHF_WRITE != 0,
f & SHF_EXECINSTR != 0,
bytes,
)
})
.collect();
let mut imports_fns: Vec<String> = Vec::new();
let mut exports: Vec<String> = Vec::new();
for sym in elf.dynsyms.iter() {
let name = elf.dynstrtab.get_at(sym.st_name).unwrap_or("");
if name.is_empty() {
continue;
}
if sym.st_shndx == 0 {
imports_fns.push(name.to_string());
} else if sym.st_bind() == 1 && sym.st_type() != 4 {
exports.push(name.to_string());
}
}
imports_fns.sort();
imports_fns.dedup();
exports.sort();
exports.dedup();
let imports = if imports_fns.is_empty() {
Vec::new()
} else {
vec![ImportedLib {
name: "(dynamic symbols)".to_string(),
functions: imports_fns,
ordinals: Vec::new(),
}]
};
let mut symbols: Vec<Symbol> = Vec::new();
let mut collect_funcs = |it: goblin::elf::sym::SymIterator, strtab: &goblin::strtab::Strtab| {
for sym in it {
if sym.st_type() != 2 || sym.st_value == 0 || sym.st_shndx == 0 {
continue; }
let name = strtab.get_at(sym.st_name).unwrap_or("");
if !name.is_empty() {
symbols.push(Symbol {
addr: sym.st_value,
name: name.to_string(),
kind: SymKind::Func,
});
}
}
};
collect_funcs(elf.syms.iter(), &elf.strtab);
collect_funcs(elf.dynsyms.iter(), &elf.dynstrtab);
for reloc in elf
.pltrelocs
.iter()
.chain(elf.dynrelas.iter())
.chain(elf.dynrels.iter())
{
if reloc.r_offset == 0 {
continue;
}
let Some(sym) = elf.dynsyms.get(reloc.r_sym) else {
continue;
};
let name = elf.dynstrtab.get_at(sym.st_name).unwrap_or("");
if !name.is_empty() {
symbols.push(Symbol {
addr: reloc.r_offset,
name: name.to_string(),
kind: SymKind::Import,
});
}
}
let stripped = !elf.section_headers.iter().any(|sh| sh.sh_type == 2);
let gnu_stack_exec = elf
.program_headers
.iter()
.find(|ph| ph.p_type == PT_GNU_STACK)
.map(|ph| ph.p_flags & PF_X != 0);
let gnu_relro = elf
.program_headers
.iter()
.any(|ph| ph.p_type == PT_GNU_RELRO);
let (dyn_flags, dyn_flags_1) = elf
.dynamic
.as_ref()
.map(|d| (d.info.flags, d.info.flags_1))
.unwrap_or((0, 0));
let bind_now = dyn_flags & DF_BIND_NOW != 0
|| dyn_flags_1 & DF_1_NOW != 0
|| elf.dynamic.as_ref().is_some_and(|d| {
d.dyns
.iter()
.any(|e| e.d_tag == goblin::elf::dynamic::DT_BIND_NOW)
});
let textrel = elf.dynamic.as_ref().is_some_and(|d| d.info.textrel);
let sym_names = || {
elf.dynsyms
.iter()
.filter_map(|s| elf.dynstrtab.get_at(s.st_name))
.chain(elf.syms.iter().filter_map(|s| elf.strtab.get_at(s.st_name)))
};
let stack_chk = sym_names().any(|n| n.starts_with("__stack_chk"));
let fortify_syms = {
let mut v: Vec<&str> = sym_names().filter(|n| n.ends_with("_chk")).collect();
v.sort_unstable();
v.dedup();
v.iter().filter(|n| !n.starts_with("__stack_chk")).count()
};
let is_pie_exe = elf.header.e_type == 3
&& (elf.interpreter.is_some() || dyn_flags_1 & DF_1_PIE != 0)
&& elf.entry != 0;
let func_hints = eh_frame_hdr_starts(&elf, bytes);
let mut notes = Vec::new();
notes.push(format!("ELF{}", if elf.is_64 { "64" } else { "32" }));
if !func_hints.is_empty() {
notes.push(format!("{} eh_frame functions", func_hints.len()));
}
notes.push(if elf.little_endian {
"LE".into()
} else {
"BE".into()
});
match elf.header.e_type {
1 => notes.push("relocatable".into()),
2 => notes.push("executable".into()),
3 if is_pie_exe => notes.push("PIE executable".into()),
3 => notes.push("shared object".into()),
4 => notes.push("core dump".into()),
_ => {}
}
if elf.interpreter.is_some() {
notes.push("dynamically linked".into());
} else if elf.header.e_type == 2 {
notes.push("statically linked".into());
}
Binary {
path: path.to_string(),
size: bytes.len() as u64,
format: Format::Elf,
arch,
bits: if elf.is_64 { 64 } else { 32 },
endian_little: elf.little_endian,
is_lib: elf.header.e_type == 3 && !is_pie_exe,
is_stripped: stripped,
entry: elf.entry,
image_base: 0,
subsystem: None,
timestamp: None,
sections,
imports,
exports,
symbols,
func_hints,
libs: elf.libraries.iter().map(|s| s.to_string()).collect(),
rpaths: elf
.rpaths
.iter()
.chain(elf.runpaths.iter())
.map(|s| s.to_string())
.collect(),
overall_entropy: 0.0,
overlay_off: None,
overlay_size: 0,
overlay_entropy: 0.0,
has_signature: false,
sig_region: None,
pdb: crate::formats::pdbsym::Pdb::NotReferenced,
hardening: HardeningFacts {
gnu_stack_exec,
gnu_relro,
bind_now,
textrel,
has_interp: elf.interpreter.is_some(),
elf_type: elf.header.e_type,
stack_chk,
fortify_syms,
..Default::default()
},
notes,
}
}
fn eh_frame_hdr_starts(elf: &Elf, bytes: &[u8]) -> Vec<u64> {
const DW_EH_PE_DATAREL_SDATA4: u8 = 0x3b;
let Some(ph) = elf
.program_headers
.iter()
.find(|p| p.p_type == PT_GNU_EH_FRAME)
else {
return Vec::new();
};
let base_off = ph.p_offset as usize;
let hdr_vaddr = ph.p_vaddr;
let rd = |o: usize, n: usize| bytes.get(o..o + n);
let Some(head) = rd(base_off, 4) else {
return Vec::new();
};
if head[0] != 1 || head[3] != DW_EH_PE_DATAREL_SDATA4 {
return Vec::new();
}
let (eh_frame_ptr_enc, fde_count_enc) = (head[1], head[2]);
let mut cur = base_off + 4;
let Some(ptr_sz) = enc_width(eh_frame_ptr_enc) else {
return Vec::new();
};
cur += ptr_sz;
let Some(cnt_sz) = enc_width(fde_count_enc) else {
return Vec::new();
};
let Some(cnt_bytes) = rd(cur, cnt_sz) else {
return Vec::new();
};
let fde_count = read_uint(cnt_bytes);
cur += cnt_sz;
let max_rows = bytes.len().saturating_sub(cur) / 8;
let rows = (fde_count as usize).min(max_rows);
let mut out = Vec::with_capacity(rows.min(1 << 16));
for _ in 0..rows {
let Some(loc) = rd(cur, 4) else { break };
let delta = i32::from_le_bytes([loc[0], loc[1], loc[2], loc[3]]);
out.push(hdr_vaddr.wrapping_add_signed(delta as i64));
cur += 8; }
out.sort_unstable();
out.dedup();
out
}
fn enc_width(enc: u8) -> Option<usize> {
match enc & 0x0f {
0x02 | 0x0a => Some(2), 0x03 | 0x0b => Some(4), 0x04 | 0x0c => Some(8), _ => None,
}
}
fn read_uint(b: &[u8]) -> u64 {
let mut v = 0u64;
for (i, &byte) in b.iter().take(8).enumerate() {
v |= (byte as u64) << (8 * i);
}
v
}
#[cfg(test)]
mod tests {
#[test]
fn eh_frame_hdr_recovers_function_starts() {
let bytes = crate::formats::fixture::elf_with_eh_frame_hdr();
let bin = crate::formats::analyze("eh.elf", &bytes).unwrap();
assert_eq!(bin.func_hints, vec![0x1000, 0x1400]);
}
#[test]
fn a_binary_without_eh_frame_hdr_yields_no_hints() {
let bytes = crate::formats::fixture::elf_with_plt_call();
let bin = crate::formats::analyze("plt.elf", &bytes).unwrap();
assert!(bin.func_hints.is_empty());
}
}