binkit 0.1.0

A modular toolbox for analyzing, disassembling, and patching binary formats
Documentation
mod elf64;
mod traits;
mod utils;
mod disasm;

use elf64::Elf64Binary;
use elf64::printers::Elf64Printer;
use traits::binary_printer::BinaryPrinter;
use traits::binary::Binary;
use disasm::disass;
use utils::parse_hex::parse_hex;

use std::fs;
use std::os::unix::fs::PermissionsExt;

use clap::{Parser, Error, Subcommand};

#[derive(Parser)]
struct Cli {
    #[command(subcommand)]
    command: Commands,
}

#[derive(Subcommand, Debug)]
enum Commands {
    #[command(about = "Inject bytes into an ELF file")]
    Inject {
        #[arg(help = "Path to the ELF file to inject into")]
        file: String,

        #[arg(short = 'i', long, help = "Path to the file containing bytes to inject")]
        inject: String,

        #[arg(short = 'a', long, help = "Address to inject at (hexadecimal). Overrides section if provided")]
        address: Option<String>,

        #[arg(short = 's', long, help = "Section name to inject into. Used if address is not provided")]
        section: Option<String>,

        #[arg(short = 'r', long, help = "Return address after injected code executes (hexadecimal). Defaults to ELF entry point")]
        return_address: Option<String>,

        #[arg(short = 'o', long, help = "Path to save the modified ELF output")]
        output: String,
    },

    #[command(about = "Check available injection point in an ELF file")]
    CheckInject {
        #[arg(help = "Path to the ELF file to analyze")]
        file: String,

        #[arg(short = 'r', long, help = "Return address for calculating relative offsets (hexadecimal). Defaults to ELF entry point")]
        return_address: Option<String>,
    },

    #[command(about = "Disassemble a section or address range of an ELF file")]
    Disasm {
        #[arg(help = "Path to the ELF file to disassemble")]
        file: String,

        #[arg(short = 's', long, help = "Section name to disassemble. (default: .text) ")]
        section: Option<String>,
    },

    #[command(about = "Display ELF file information")]
    Info {
        #[arg(help = "Path to the ELF file to analyze")]
        file: String,

        #[arg(short = 'H', long, help = "Display the ELF header information")]
        header: bool,

        #[arg(short = 'p', long, help = "Display the program headers of the ELF file")]
        programs: bool,

        #[arg(short = 's', long, help = "Display the section headers of the ELF file")]
        sections: bool,
    },

    #[command(about = "Update ELF metadata")]
    Update {
        #[arg(help = "Path to the ELF file to modify")]
        file: String,       

        #[arg(long, help = "Set a new entry point for the ELF file (hexadecimal)")]
        entry: Option<String>,

        #[arg(short = 'o', long, help = "Path to save the updated ELF file")]
        output: Option<String>,
    }
}

fn load_file(file: &str) -> Result<Elf64Binary, Error> {
    let bytes: Vec<u8> = fs::read(file)?;

    Ok(Elf64Binary::new(&bytes))
}

fn save_file(file: &str, buf: &[u8]) -> Result<(), Error>{
    let _ = fs::write(file, buf);
    let mut perms = fs::metadata(&file)?.permissions();
    perms.set_mode(0o755); 
    fs::set_permissions(&file, perms)?;
    Ok(())
}

fn main() -> Result<(), Error> {
    let cli = Cli::parse();

    let mut binary: Elf64Binary;

    let printer: Elf64Printer = Elf64Printer;

    match &cli.command {
        Commands::Inject { file, address, return_address, inject, output, section } => {
            binary = load_file(file)?;

            let bytes = fs::read(inject)?; 

            let mut final_address = binary.get_address_to_inject();
            let mut final_return_address = binary.entry();
            let mut final_section = ".note.ABI-tag";

            if let Some(address) = address.as_ref() {
                final_address = parse_hex(address);
            }

            if let Some(return_address) = return_address.as_ref() {
                final_return_address = parse_hex(return_address);
            }

            if let Some(section) = section.as_ref() {
                final_section = section;
            }

            let injected: Vec<u8> = binary.inject(bytes, final_address, final_section);
            println!("Payload injected at 0x{:X}", final_address);
            let rel32_addr = binary.calculate_rel32(final_address, final_return_address);
            if return_address.is_some() {
                println!("Rel32 to 0x{:X}: 0x{:X}", final_return_address, rel32_addr);
            }else {
                println!("Rel32 to original entry point (0x{:X}): 0x{:X}", final_return_address, rel32_addr);
            }
            save_file(output, &injected)?;
            println!("Output written to: {}", output);
        },
        Commands::CheckInject { file, return_address } => {
            binary = load_file(file)?;

            let mut final_return_address = binary.entry();

            if let Some(return_address) = return_address.as_ref() {
                final_return_address = parse_hex(return_address);
            }

            let addr = binary.get_address_to_inject();
            println!("Injection slot available at: 0x{:X}", addr);

            let rel32_addr = binary.calculate_rel32(addr, final_return_address);
            if return_address.is_some() {
                println!("Rel32 relative to 0x{:X}: 0x{:X}", final_return_address, rel32_addr);
            }else {
                println!("Rel32 to original entry point (0x{:X}): 0x{:X}", final_return_address, rel32_addr);
            }
        },
        Commands::Disasm { file, section } => {
            binary = load_file(file)?;

            let mut final_section = ".text";

            if let Some(section) = section.as_ref() {
                final_section = section;
            }

            if let Some((addr, bytes)) = binary.get_bytes_section(final_section) {
                disass(addr, &bytes);
            }
        },
        Commands::Info { file, header, programs, sections } => {
            binary = load_file(file)?;
            if *header {
                printer.print_header(binary.get_header());
            } else if *programs {
                printer.print_program_headers(binary.get_program_headers());
            } else if *sections {
                printer.print_section_headers(binary.get_section_headers());
            } 
        },
        Commands::Update { file, entry, output } => {
            binary = load_file(file)?;

            let mut final_output = file;

            if let Some(output) = output {
                final_output = output;
            }

            if entry.is_some() {
                binary.set_entry(entry.as_ref().unwrap().to_string());
                let bytes: Vec<u8> = (&binary).into();
                save_file(final_output, &bytes)?;
                println!("Output written to: {}", final_output);
            }
        }
    }

    Ok(())
}