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_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,
}]
};
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 mut notes = Vec::new();
notes.push(format!("ELF{}", if elf.is_64 { "64" } else { "32" }));
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,
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,
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,
}
}