binkit 0.1.0

A modular toolbox for analyzing, disassembling, and patching binary formats
Documentation
mod loaders;
pub mod types;
pub mod printers;

use loaders::load_elf64_header::LoadELF64Header;
use loaders::load_elf64_program_header::LoadELF64ProgramHeader;
use loaders::load_elf64_section_header::LoadELF64SectionHeader;

use types::elf64_header::Elf64Header;
use types::elf64_program_header::Elf64ProgramHeader;
use types::elf64_section_header::Elf64SectionHeader;
use crate::traits::binary::Binary;
use crate::utils::endian::Endian;
use crate::utils::string_until_null::string_until_null;

fn parse_program_headers(buf: &[u8], elf_header: &Elf64Header, endian: &Endian) -> Vec<Elf64ProgramHeader> {
    let mut headers = Vec::with_capacity(elf_header.e_phnum.value as usize);

    for i in 0..elf_header.e_phnum.value as usize {
        let start = elf_header.e_phoff.value as usize + i * elf_header.e_phentsize.value as usize;
        let end = start + elf_header.e_phentsize.value as usize;

        let raw_header = LoadELF64ProgramHeader::from_bytes(&buf[start..end]);
        headers.push(Elf64ProgramHeader::new(raw_header, endian));
    }

    headers
}

fn parse_section_headers(buf: &[u8], elf_header: &Elf64Header, endian: &Endian) -> Vec<Elf64SectionHeader> {
    let mut headers = Vec::with_capacity(elf_header.e_shnum.value as usize);

    for i in 0..elf_header.e_shnum.value as usize {
        let start = elf_header.e_shoff.value as usize + i * elf_header.e_shentsize.value as usize;
        let end = start + elf_header.e_shentsize.value as usize;

        let raw_header = LoadELF64SectionHeader::from_bytes(&buf[start..end]);
        headers.push(Elf64SectionHeader::new(raw_header, endian));
    }

    headers
}

fn resolve_section_name(section_headers: &mut Vec<Elf64SectionHeader>, buf: &[u8], elf_header: &Elf64Header, endian: &Endian){
    let strtab_section = &section_headers[elf_header.e_shstrndx.value as usize ];
    let strtab_section_offset = endian.read_u64(strtab_section.sh_offset.raw);

    for section in section_headers {
        let name_index = section.sh_name.value;
        let name = string_until_null(&buf[(strtab_section_offset as usize + name_index as usize)..]);
        section.sh_name.update_name(name.to_string());
    }
}

const ALIGN: u64 = 0x1000;

#[derive(Debug)]
pub struct Elf64Binary {
    header: Elf64Header,
    program_headers: Vec<Elf64ProgramHeader>,
    section_headers: Vec<Elf64SectionHeader>,
    raw: Vec<u8>
}

impl Elf64Binary {
    pub fn new(buf: &[u8]) -> Self{
        let load_elf_header =  LoadELF64Header::from_bytes(buf);
        let elf_header = Elf64Header::new(load_elf_header);
        let endian: Endian = elf_header.e_ident.endian();
        
        let program_headers = parse_program_headers(buf, &elf_header, &endian);
        let mut section_headers = parse_section_headers(buf, &elf_header, &endian);

        resolve_section_name(&mut section_headers, buf, &elf_header, &endian);

        Self { 
            header: elf_header, 
            program_headers,
            section_headers,
            raw: buf.to_vec()
        }
    }

    pub fn get_bytes_section(&self, section_name: &str) -> Option<(u64, Vec<u8>)> {
        let section = self.get_section_headers()
            .iter()
            .find(|s| s.sh_name.name == section_name)
            .map(|s| s);

        if let Some(section) = section {
            let endian = self.header.e_ident.endian();

            let bytes: Vec<u8> = self.into();
            let offset = endian.read_u64(section.sh_offset.raw) as usize;
            let size = endian.read_u64(section.sh_size.raw) as usize;
            return Some((endian.read_u64(section.sh_addr.raw), bytes[offset..offset + size].to_vec()));
        }else {
            return None;
        }
    }

    pub fn endian(&self) -> Endian {
        self.header.e_ident.endian()
    }

    pub fn entry(&self) -> u64 {
        let endian = self.endian();
        return endian.read_u64(self.header.e_entry.raw);
    }

    pub fn get_address_to_inject(&self) -> u64 {
        let program_headers = &self.program_headers;
        let endian = self.endian();
        let mut higher_addr: u64 = 0;
        for program in program_headers {
            let initial_address = endian.read_u64(program.p_vaddr.raw);
            let memsz = endian.read_u64(program.p_filesz.raw);
            let final_address = initial_address + memsz;
            if final_address > higher_addr {
                higher_addr = final_address;
            }
        };
        self.calculate_new_addr(higher_addr + ALIGN)
    }

    pub fn calculate_new_addr(&self, addr: u64) -> u64 {
        let bytes: Vec<u8> = self.into();
        let offset = bytes.len() as u64;
        let delta = (offset % ALIGN + ALIGN - (addr as u64 % ALIGN)) % ALIGN;
        addr + delta
    }

    pub fn calculate_rel32(&self, addr_base: u64, addr_target: u64) -> i64 {
        return addr_target as i64 - addr_base as i64;
    }

    pub fn update_section_name(&mut self, section_name_idx: usize){
        let endian = self.header.e_ident.endian();

        let shstrtab_idx = endian.read_u16(self.header.e_shstrndx.raw);
        let shstrtab_section_header = &self.section_headers[shstrtab_idx as usize];
        let shstrtab_section_header_offset = endian.read_u64(shstrtab_section_header.sh_offset.raw);
        
        let new_name = ".injected\0".as_bytes(); 
        let start = shstrtab_section_header_offset as usize + section_name_idx;
        let end = start + new_name.len();
        
        self.raw[start..end].copy_from_slice(new_name);
    }

    pub fn inject(&mut self, buf: Vec<u8>, new_addr: u64, section: &str) -> Vec<u8> {
        let target_section: &str = section;

        let bytes: Vec<u8> = self.into();
        let file_off = bytes.len() as u64;

        let endian = self.header.e_ident.endian();

        let note_section = self.section_headers
            .iter_mut()
            .find(|s| s.sh_name.name == target_section)
            .map(|s| s);

        let note_offset = if let Some(section) = note_section {
            let note_offset = section.sh_offset.raw;
            let section_name_idx = endian.read_u32(section.sh_name.raw) as usize;

            section.sh_type.raw = endian.to_bytes_u32(1);            
            section.sh_addr.raw = endian.to_bytes_u64(new_addr);
            section.sh_size.raw = endian.to_bytes_u64(buf.len() as  u64);
            section.sh_offset.raw = endian.to_bytes_u64(file_off);
            section.sh_addralign.raw = endian.to_bytes_u64(16);
            section.sh_flags.raw = endian.to_bytes_u64(6);

            self.update_section_name(section_name_idx);

            note_offset
        } else {
            println!("{} not found", target_section);
            return Vec::new();
        };        

        if let Some(program) = self.program_headers
            .iter_mut()
            .find(|p| p.p_offset.raw == note_offset)
        {
            program.p_offset.raw = endian.to_bytes_u64(self.raw.len() as u64);
            program.p_flags.raw = endian.to_bytes_u32(5);
            program.p_type.raw = endian.to_bytes_u32(1);
            program.p_vaddr.raw = endian.to_bytes_u64(new_addr);
            program.p_paddr.raw = endian.to_bytes_u64(new_addr);
            program.p_memsz.raw = endian.to_bytes_u64(buf.len() as u64);
            program.p_filesz.raw = endian.to_bytes_u64(buf.len() as u64);
            program.p_align.raw = endian.to_bytes_u64(ALIGN);
        } else {
            println!("Program header not found!");
            return Vec::new();
        }

        let mut injected: Vec<u8> = self.into();
        injected.extend(buf);

        injected
    }

    pub fn set_entry(&mut self, hex_entry: String) {
        let endian = self.header.e_ident.endian();

        let entry = u64::from_str_radix(hex_entry.trim_start_matches("0x"), 16)
            .expect("Failed to parse hex string");

        self.header.e_entry.raw = endian.to_bytes_u64(entry);
    }
}

impl Binary for Elf64Binary {
    type Header = Elf64Header;
    type ProgramHeader = Elf64ProgramHeader;
    type SectionHeader = Elf64SectionHeader;

    fn get_header(&self) -> &Self::Header {
        &self.header
    }

    fn get_program_headers(&self) -> &[Self::ProgramHeader] {
        &self.program_headers
    }

    fn get_section_headers(&self) -> &[Self::SectionHeader] {
        &self.section_headers
    }
}

impl From<&Elf64Binary> for Vec<u8> {
    fn from(h: &Elf64Binary) -> Vec<u8> {
        let mut bytes = h.raw.clone();

        let header_bytes: Vec<u8> = (&h.header).into();
        bytes[0..header_bytes.len()].copy_from_slice(&header_bytes);

        for (i, ph) in h.program_headers.iter().enumerate() {
            let ph_bytes: Vec<u8> = ph.into();
            let offset = h.header.e_phoff.value as usize + i * h.header.e_phentsize.value as usize;
            bytes[offset..offset + ph_bytes.len()].copy_from_slice(&ph_bytes);
        }

        for (i, sh) in h.section_headers.iter().enumerate() {
            let sh_bytes: Vec<u8> = sh.into();
            let offset = h.header.e_shoff.value as usize + i * h.header.e_shentsize.value as usize;
            bytes[offset..offset + sh_bytes.len()].copy_from_slice(&sh_bytes);
        }

        bytes
    }
}

impl From<&mut Elf64Binary> for Vec<u8> {
    fn from(h: &mut Elf64Binary) -> Vec<u8> {
        let mut bytes = h.raw.clone();

        let header_bytes: Vec<u8> = (&h.header).into();
        bytes[0..header_bytes.len()].copy_from_slice(&header_bytes);

        for (i, ph) in h.program_headers.iter().enumerate() {
            let ph_bytes: Vec<u8> = ph.into();
            let offset = h.header.e_phoff.value as usize + i * h.header.e_phentsize.value as usize;
            bytes[offset..offset + ph_bytes.len()].copy_from_slice(&ph_bytes);
        }

        for (i, sh) in h.section_headers.iter().enumerate() {
            let sh_bytes: Vec<u8> = sh.into();
            let offset = h.header.e_shoff.value as usize + i * h.header.e_shentsize.value as usize;
            bytes[offset..offset + sh_bytes.len()].copy_from_slice(&sh_bytes);
        }

        bytes
    }
}