balrog.git


balrog

~ ELF file analysis tool to be used after firmware extraction
git clone https://github.com/djnnvx/balrog.git
log | files | refs | archive | README

load.rs (5157B)

      1 use crate::arch::Arch;
      2 use anyhow::{bail, Result};
      3 use goblin::elf::Elf;
      4 use std::collections::{HashMap, HashSet};
      5 use std::path::{Path, PathBuf};
      6 
      7 const SHN_UNDEF: usize = 0;
      8 const MAX_CSTR_LEN: usize = 512;
      9 
     10 fn is_text_byte(byte: u8) -> bool {
     11     matches!(byte, 0x09..=0x0d | 0x20..=0x7e)
     12 }
     13 
     14 pub struct Seg {
     15     pub vaddr: u64,
     16     pub off: u64,
     17     pub filesz: u64,
     18     pub exec: bool,
     19 }
     20 
     21 pub struct Binary {
     22     pub path: PathBuf,
     23     pub data: Vec<u8>,
     24     pub arch: Arch,
     25     pub little: bool,
     26     pub entry: u64,
     27     pub segs: Vec<Seg>,
     28     pub got2sym: HashMap<u64, String>,
     29     pub undef_stub: Vec<(String, u64)>,
     30     pub imports: HashSet<String>,
     31     pub exports: Vec<(String, u64)>,
     32     pub locals: Vec<(String, u64)>,
     33     pub gp: Option<u64>,
     34 }
     35 
     36 fn machine(elf: &Elf) -> Result<Arch> {
     37     Ok(match elf.header.e_machine {
     38         goblin::elf::header::EM_ARM => Arch::Arm,
     39         goblin::elf::header::EM_AARCH64 => Arch::Aarch64,
     40         goblin::elf::header::EM_MIPS => Arch::Mips,
     41         goblin::elf::header::EM_386 => Arch::X86,
     42         goblin::elf::header::EM_X86_64 => Arch::X86_64,
     43         m => bail!("unsupported machine {m}"),
     44     })
     45 }
     46 
     47 pub(crate) fn sym_name(tab: &goblin::strtab::Strtab, idx: usize) -> Option<String> {
     48     tab.get_at(idx)
     49         .filter(|n| !n.is_empty())
     50         .map(str::to_string)
     51 }
     52 
     53 fn segments(elf: &Elf) -> Vec<Seg> {
     54     elf.program_headers
     55         .iter()
     56         .filter(|p| p.p_type == goblin::elf::program_header::PT_LOAD)
     57         .map(|p| Seg {
     58             vaddr: p.p_vaddr,
     59             off: p.p_offset,
     60             filesz: p.p_filesz,
     61             exec: p.is_executable(),
     62         })
     63         .collect()
     64 }
     65 
     66 fn plt_imports(elf: &Elf) -> HashMap<u64, String> {
     67     let mut got2sym = HashMap::new();
     68     for reloc in elf.pltrelocs.iter() {
     69         let Some(sym) = elf.dynsyms.get(reloc.r_sym) else {
     70             continue;
     71         };
     72         if let Some(name) = sym_name(&elf.dynstrtab, sym.st_name) {
     73             got2sym.insert(reloc.r_offset, name);
     74         }
     75     }
     76     got2sym
     77 }
     78 
     79 #[derive(Default)]
     80 struct DynSyms {
     81     imports: HashSet<String>,
     82     undef_stub: Vec<(String, u64)>,
     83     exports: Vec<(String, u64)>,
     84 }
     85 
     86 fn dyn_symbols(elf: &Elf) -> DynSyms {
     87     let mut syms = DynSyms::default();
     88     for sym in elf.dynsyms.iter() {
     89         let Some(name) = sym_name(&elf.dynstrtab, sym.st_name) else {
     90             continue;
     91         };
     92         if sym.st_shndx == SHN_UNDEF {
     93             if sym.st_value != 0 {
     94                 syms.undef_stub.push((name.clone(), sym.st_value));
     95             }
     96             syms.imports.insert(name);
     97         } else if sym.st_value != 0 && sym.is_function() {
     98             syms.exports.push((name, sym.st_value));
     99         }
    100     }
    101     syms
    102 }
    103 
    104 fn local_funcs(elf: &Elf) -> Vec<(String, u64)> {
    105     elf.syms
    106         .iter()
    107         .filter(|sym| sym.is_function() && sym.st_value != 0)
    108         .filter_map(|sym| sym_name(&elf.strtab, sym.st_name).map(|name| (name, sym.st_value)))
    109         .collect()
    110 }
    111 
    112 pub(crate) fn dyn_val(elf: &Elf, tag: u64) -> Option<u64> {
    113     let dynamic = elf.dynamic.as_ref()?;
    114     let entry = dynamic.dyns.iter().find(|entry| entry.d_tag == tag)?;
    115     Some(entry.d_val)
    116 }
    117 
    118 impl Binary {
    119     pub fn load(path: &Path) -> Result<Binary> {
    120         let data = std::fs::read(path)?;
    121         let elf = Elf::parse(&data)?;
    122         let arch = machine(&elf)?;
    123         let little = elf.little_endian;
    124         let entry = elf.header.e_entry;
    125         let segs = segments(&elf);
    126         let mut got2sym = plt_imports(&elf);
    127         let syms = dyn_symbols(&elf);
    128         let gp = match arch {
    129             Arch::Mips => crate::arch::mips::resolve_got(&elf, &mut got2sym),
    130             _ => None,
    131         };
    132         let locals = local_funcs(&elf);
    133         drop(elf);
    134 
    135         Ok(Binary {
    136             path: path.to_path_buf(),
    137             data,
    138             arch,
    139             little,
    140             entry,
    141             segs,
    142             got2sym,
    143             undef_stub: syms.undef_stub,
    144             imports: syms.imports,
    145             exports: syms.exports,
    146             locals,
    147             gp,
    148         })
    149     }
    150 
    151     pub fn read_vaddr(&self, va: u64, len: usize) -> Option<&[u8]> {
    152         let seg = self
    153             .segs
    154             .iter()
    155             .find(|seg| (seg.vaddr..seg.vaddr + seg.filesz).contains(&va))?;
    156         let start = (seg.off + (va - seg.vaddr)) as usize;
    157         if start >= self.data.len() {
    158             return None;
    159         }
    160         let seg_end = (seg.off + seg.filesz) as usize;
    161         let end = (start + len).min(seg_end).min(self.data.len());
    162         Some(&self.data[start..end])
    163     }
    164 
    165     pub fn cstr_at(&self, va: u64) -> Option<String> {
    166         let window = self.read_vaddr(va, MAX_CSTR_LEN)?;
    167         let nul = window.iter().position(|&byte| byte == 0)?;
    168         let text = &window[..nul];
    169         if text.is_empty() || !text.iter().all(|&byte| is_text_byte(byte)) {
    170             return None;
    171         }
    172         Some(String::from_utf8_lossy(text).into_owned())
    173     }
    174 
    175     pub fn name(&self) -> String {
    176         self.path
    177             .file_name()
    178             .map(|s| s.to_string_lossy().into_owned())
    179             .unwrap_or_default()
    180     }
    181 }