From 8829e2803fd21a9c2952c3131265548af75153c4 Mon Sep 17 00:00:00 2001 From: Garrett Dickinson Date: Mon, 10 Apr 2023 14:13:29 -0500 Subject: [PATCH] Add support for binary patching --- .gitignore | 14 ++++++ Cargo.toml | 1 + notes/hello_ghidra_out.txt | 37 +++++++++++++++ src/main.rs | 34 ++++++++++++- src/patcher.rs | 95 +++++++++++++++++++++++++++++++++++++ src/util.rs | 26 ++++++++++ testing/patches/hello.patch | 3 ++ 7 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 notes/hello_ghidra_out.txt create mode 100644 src/patcher.rs create mode 100644 testing/patches/hello.patch diff --git a/.gitignore b/.gitignore index 088ba6b..889f5a5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,17 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk + + +# Added by cargo + +/target + + +# Remove Ghidra related testing files +/testing/ghidra + +# Remove .vscode directory +.vscode + +*.log \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index e869039..3c60fe8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,3 +8,4 @@ edition = "2018" [dependencies] iced-x86 = "1.18.0" +clap = "4.2.1" \ No newline at end of file diff --git a/notes/hello_ghidra_out.txt b/notes/hello_ghidra_out.txt new file mode 100644 index 0000000..3f5471a --- /dev/null +++ b/notes/hello_ghidra_out.txt @@ -0,0 +1,37 @@ +Project File Name: hello +Last Modified: Mon Apr 10 03:28:12 CDT 2023 +Readonly: false +Program Name: hello +Language ID: x86:LE:64:default (2.13) +Compiler ID: gcc +Processor: x86 +Endian: Little +Address Size: 64 +Minimum Address: 00100000 +Maximum Address: _elfSectionHeaders::000007bf +# of Bytes: 6481 +# of Memory Blocks: 33 +# of Instructions: 8 +# of Defined Data: 105 +# of Functions: 14 +# of Symbols: 47 +# of Data Types: 32 +# of Data Type Categories: 2 +Created With Ghidra Version: 10.2.3 +Date Created: Mon Apr 10 03:28:12 CDT 2023 +ELF File Type: shared object +ELF Original Image Base: 0x0 +ELF Prelinked: false +ELF Required Library [ 0]: libc.so.6 +ELF Source File [ 0]: Scrt1.o +ELF Source File [ 1]: crtstuff.c +ELF Source File [ 2]: hello.c +ELF Source File [ 3]: crtstuff.c +ELF Source File [ 4]: +Executable Format: Executable and Linking Format (ELF) +Executable Location: /media/garrett/Storage/Projects/chisel/testing/hello +Executable MD5: 45ddd8df3ccde5aa5d143ae0d00e542d +Executable SHA256: d448eaf9e134d840c4c3c87a9320060a763d3fac0e172818b87b7eda8e661b9a +FSRL: file:///media/garrett/Storage/Projects/chisel/testing/hello?MD5=45ddd8df3ccde5aa5d143ae0d00e542d +Preferred Root Namespace Category: +Relocatable: true diff --git a/src/main.rs b/src/main.rs index 7214647..1b336f2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,19 +14,39 @@ use std::process::exit; // Import modules mod elf; mod util; +mod patcher; fn main() { // Collect our execution args let args: Vec = env::args().collect(); + let mut patch_mode: bool = false; + let mut patch_file_path: &String = &"".to_string(); // Grab our filepath from our options if &args.len() < &2 { // No file given, terminate - println!("[Error] Please provide a filepath to open"); + util::print_help(); exit(0); } + // Check if the arguments we have include the patching flag and file + if &args.len() > &2 { + if &args[2] == "-p" { + if &args.len() >= &4 { + patch_mode = true; + patch_file_path = &args[3]; + } else { + util::print_help(); + exit(0); + } + } else { + // More than 1 arg but no patching flag given, terminate + util::print_help(); + exit(0); + } + } + let file_path: &String = &args[1]; if path::Path::new(file_path).exists() { @@ -200,6 +220,18 @@ fn main() { } } + + if patch_mode { + + println!("\n==== Applying Patch To Binary ====\n"); + + patcher::patch_binary( + bytes, + &patch_file_path + ); + } + + } else { println!("[Error] Could not find magic number, is this an ELF executable?") } diff --git a/src/patcher.rs b/src/patcher.rs new file mode 100644 index 0000000..cd1944b --- /dev/null +++ b/src/patcher.rs @@ -0,0 +1,95 @@ +// patcher.rs +// Author: Garrett Dickinson +// Created: 04/06/2023 +// Description: Houses binary rewriting and patching functionality for chisel. + +use std::path; + +use crate::util; + + +pub fn patch_binary(binary_contents: &Vec, patch_file_path: &String) { + parse_patch_file(patch_file_path); + + // If valid + // Apply patch + // else + // err + + +} + + +fn parse_patch_file(patch_path: &String) { + // Load the file from patch_binary() arg + // Iterate through file, if line starts with # ignore + // Otherwise, parse as such + // [ADDRESS] [SPACE] [HEX],[HEX],[HEX],.... + // [ADDRESS] [SPACE] [STRING] + + if path::Path::new(patch_path).exists() && patch_path.ends_with(".patch") { + println!("Patch file exists, reading '{}'...", patch_path); + + let contents = util::read_lines(patch_path.to_string()); + + for line in contents { + let unwrapped = line.unwrap(); + if unwrapped.trim().starts_with("#") { + + } else { + let mut statement = unwrapped.split(":"); + let address: i32 = statement.next().unwrap().trim().parse::().unwrap(); + let data: &str = statement.next().unwrap().trim(); + + if !data.is_empty() { + if data.contains("\"") { + // Value is a string literal + let cleaned = data.replace("\"", ""); + let bytes = cleaned.as_bytes(); + + print!("{}: ", address); + + let mut i = 0; + while i < bytes.len() { + print!("{} ", bytes[i]); + i = i + 1; + } + + println!(); + + } else { + // Data is comma seperated list or a single value + let byte_str: String = data.replace(",", ""); + let mut bytes: Vec = util::decode_hex(&byte_str).unwrap(); + + print!("{}: ", address); + + let mut i = 0; + while i < bytes.len() { + print!("{} ", bytes[i]); + i = i + 1; + } + + println!(); + } + } + } + } + } else { + println!("[Error] Patch file '{}' is invalid or cannot be read, exiting...", patch_path); + std::process::exit(0); + } + + std::process::exit(0); +} + + +fn apply_patch() { + // Iterate through parsed patch information + // Find the address at start of line, then apply following data to section + // Make sure within size limits of data, otherwise boundary problems + + // Strings -> Iterate through characters specifically and parse them as individual ASCII chars +} + + diff --git a/src/util.rs b/src/util.rs index 645fbea..93fb05f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -5,6 +5,9 @@ // functions. use std::mem; +use std::io::{self, BufReader, BufRead}; +use std::fs::File; +use std::num::ParseIntError; use crate::elf::{self, EndianType, ArchitectureType}; @@ -328,3 +331,26 @@ pub fn pp_program_header(header: &elf::ProgramHeader, number: i32, ph_type: &Str println!(); } + +pub fn print_help() { + print!("Usage: chisel [EXECUTABLE] [-p] [PATCH_FILE]\n\n \ + Options:\n \ + \t-p\t\tToggle binary patching mode\n"); +} + + +pub fn read_lines(filename: String) -> io::Lines> { + // Open the file in read-only mode. + let file = File::open(filename).unwrap(); + // Read the file line by line, and return an iterator of the lines of the file. + return io::BufReader::new(file).lines(); +} + +// Borrowed from the following Stack Overflow post +// https://stackoverflow.com/questions/52987181/how-can-i-convert-a-hex-string-to-a-u8-slice +pub fn decode_hex(s: &str) -> Result, ParseIntError> { + (0..s.len()) + .step_by(2) + .map(|i| u8::from_str_radix(&s[i..i + 2], 16)) + .collect() +} \ No newline at end of file diff --git a/testing/patches/hello.patch b/testing/patches/hello.patch new file mode 100644 index 0000000..7d1cd92 --- /dev/null +++ b/testing/patches/hello.patch @@ -0,0 +1,3 @@ +2002 : "Hello, World!" +20041 : DE,AD,BE,EF +45620 : 00,01,02,03 \ No newline at end of file