From c4173fc50cd3a195af477ec9415435be5e9d6625 Mon Sep 17 00:00:00 2001 From: egibs <20933572+egibs@users.noreply.github.com> Date: Sat, 8 Jun 2024 19:51:31 -0500 Subject: [PATCH] Initial commit Signed-off-by: egibs <20933572+egibs@users.noreply.github.com> --- .github/dependabot.yml | 11 ++ .github/workflows/ci.yml | 30 ++++ .gitignore | 3 + Cargo.toml | 6 + Dockerfile | 13 ++ Makefile | 19 ++ README.md | 146 ++++++++++++++- src/display.rs | 9 + src/main.rs | 377 +++++++++++++++++++++++++++++++++++++++ 9 files changed, 613 insertions(+), 1 deletion(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 Cargo.toml create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 src/display.rs create mode 100644 src/main.rs diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..92fb72a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" # Location of package manifests + schedule: + interval: "daily" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c3341c9 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: rsd CI + +on: + push: + pull_request: + branches: + - main + +env: + CARGO_TERM_COLOR: always + +jobs: + rust-ci: + runs-on: ubuntu-latest + steps: + - uses: step-security/harden-runner@17d0e2bd7d51742c71671bd19fa12bdc9d40a3d6 + with: + egress-policy: audit + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 + - run: | + sudo apt-get update + sudo apt-get install -y curl + curl https://sh.rustup.rs -sSf | sh -s -- -y + export PATH="$HOME/.cargo/bin:$PATH" + rustup default stable + rustup component add rustfmt + - name: Run Checks + run: | + cargo fmt --all -- --check + cargo build --all --release diff --git a/.gitignore b/.gitignore index 6985cf1..c5f2887 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,6 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + +# Binaries +rsd diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..93bdb61 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "rsd" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a342d81 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM cgr.dev/chainguard/rust as build + +WORKDIR /build + +COPY . . + +RUN make build + +FROM cgr.dev/chainguard/static + +COPY --from=build /build/rsd /rsd + +ENTRYPOINT ["/rsd"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..42dad62 --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +.PHONY: build + +build: + rustc -C target-feature=+crt-static src/main.rs -o rsd + +docker: + docker buildx build -t rsd:latest . + +fmt: + cargo fmt --all + +fmt-check: + cargo fmt --all -- --check + +test: + cargo test --all --release + +release: + cargo build --all --release diff --git a/README.md b/README.md index 758da75..b75abb0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,146 @@ # rsd -Rust implemention of xxd -e -l 64 + +`rsd` Rust implemention of something resembling `xxd -e -l 64`. Its functionality is limited to looking at the headers of ELF binaries and outputting the details in a mostly- human-readable format. + +## Why? + +I wanted to learn Rust a little better; I also wanted a more readable version of `xxd -e -l 64` when parsing ELF headers rather than parsing something like this: +``` +ced27abc2bef:/# xxd -e -l 64 /bin/sh +00000000: 464c457f 00010102 00000000 00000000 .ELF............ +00000010: 00b70003 00000001 0000a780 00000000 ................ +00000020: 00000040 00000000 000a0348 00000000 @.......H....... +00000030: 00000000 00380040 00400009 00180019 ....@.8...@..... +``` +or this: +``` +ced27abc2bef:/# xxd -l 64 /bin/sh +00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............ +00000010: 0300 b700 0100 0000 80a7 0000 0000 0000 ................ +00000020: 4000 0000 0000 0000 4803 0a00 0000 0000 @.......H....... +00000030: 0000 0000 4000 3800 0900 4000 1900 1800 ....@.8...@..... +``` + +I've been writing YARA rules recently and knowing how to locate information like this can prove useful, though making it more human-readable is more efficient as well. + +## What works for now? + +- Building locally via `rustc` or via Dockerfile +- ELF binaries + +## Usage + +`./rsd ` + +Example (run from Wolfi): +``` +./rsd /bin/sh +Full header: +7F 45 4C 46 02 01 01 00 00 00 00 00 00 00 00 00 03 00 B7 00 01 00 00 00 80 A7 00 00 00 00 00 00 40 00 00 00 00 00 00 00 48 03 0A 00 00 00 00 00 00 00 00 00 40 00 38 00 09 00 40 00 19 00 18 00 ELF File Type: Shared (0x03) +Machine Type: AArch64 (0x00B7) + +ELF Class: 64-bit +Data Encoding: Little-endian +ELF Version: 1 +Entry Point Address: 42880 +Program Header Table Offset: 64 +Section Header Table Offset: 656200 +ELF Header Size: 64 bytes +Program Header Table Entry Size: 56 bytes +Number of Program Header Table Entries: 9 +Section Header Table Entry Size: 64 bytes +Number of Section Header Table Entries: 25 +Section Header String Table Index: 24 + +Segment Information: +Segment 0: + Type: PT_NOTE (0x00000004) + Offset: 64 + Virtual Address: 64 + Physical Address: 64 + File Size: 0x00000000000001F8 (504 bytes) + Memory Size: 0x00000000000001F8 (504 bytes) + Flags: Unknown (0x00000008) + +Segment 1: + Type: PT_NOTE (0x00000004) + Offset: 568 + Virtual Address: 568 + Physical Address: 568 + File Size: 0x000000000000001B (27 bytes) + Memory Size: 0x000000000000001B (27 bytes) + Flags: R (0x00000001) + +Segment 2: + Type: PT_SHLIB (0x00000005) + Offset: 0 + Virtual Address: 0 + Physical Address: 0 + File Size: 0x000000000008F118 (586008 bytes) + Memory Size: 0x000000000008F118 (586008 bytes) + Flags: Unknown (0x00010000) + +Segment 3: + Type: PT_PHDR (0x00000006) + Offset: 646864 + Virtual Address: 646864 + Physical Address: 646864 + File Size: 0x0000000000002399 (9113 bytes) + Memory Size: 0x0000000000002A00 (10752 bytes) + Flags: Unknown (0x00010000) + +Segment 4: + Type: PT_PHDR (0x00000006) + Offset: 651720 + Virtual Address: 651720 + Physical Address: 651720 + File Size: 0x0000000000000220 (544 bytes) + Memory Size: 0x0000000000000220 (544 bytes) + Flags: Unknown (0x00000008) + +Segment 5: + Type: PT_NOTE (0x00000004) + Offset: 596 + Virtual Address: 596 + Physical Address: 596 + File Size: 0x0000000000000020 (32 bytes) + Memory Size: 0x0000000000000020 (32 bytes) + Flags: X (0x00000004) + +Segment 6: + Type: PT_NOTE (0x00000004) + Offset: 585820 + Virtual Address: 585820 + Physical Address: 585820 + File Size: 0x0000000000000034 (52 bytes) + Memory Size: 0x0000000000000034 (52 bytes) + Flags: X (0x00000004) + +Segment 7: + Type: PT_PHDR (0x00000006) + Offset: 0 + Virtual Address: 0 + Physical Address: 0 + File Size: 0x0000000000000000 (0 bytes) + Memory Size: 0x0000000000000000 (0 bytes) + Flags: Unknown (0x00000010) + +Segment 8: + Type: PT_NOTE (0x00000004) + Offset: 646864 + Virtual Address: 646864 + Physical Address: 646864 + File Size: 0x0000000000002130 (8496 bytes) + Memory Size: 0x0000000000002130 (8496 bytes) + Flags: R (0x00000001) +``` + +Running `rsd` from MacOS will result in this: +``` +❯ ./rsd /bin/sh +/bin/sh is not an ELF file (CAFEBABE). +``` + +## Will anything be added to this project? + +Maybe -- I want to start automating various things in whatever language seems right. Analyizing Mach-O bianries (i.e., MacOS binaries) doesn't seem to be as common but that would be easy to support. diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 0000000..08217ba --- /dev/null +++ b/src/display.rs @@ -0,0 +1,9 @@ +use std::fmt; + +pub struct HexAddress(pub u64); + +impl fmt::Display for HexAddress { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "0x{:016X} ({} bytes)", self.0, self.0) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..5110a76 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,377 @@ +use std::env; +use std::fs::File; +use std::io::{Read, Seek, SeekFrom}; +use std::process; + +mod display; + +const ELF_HEADER_SIZE: usize = 64; +const ELF_MAGIC: [u8; 4] = [0x7F, 0x45, 0x4C, 0x46]; +const PHDR_ENTRY_SIZE: usize = 56; + +fn main() { + // Get the command-line arguments + let args: Vec = env::args().collect(); + + // Check if the program name is provided + if args.len() != 2 { + println!("Usage: {} ", args[0]); + return; + } + + // Open the file + let mut file = get_file(); + + // Read the ELF header + let mut header = [0u8; ELF_HEADER_SIZE]; + if let Err(e) = file.read_exact(&mut header) { + println!("Failed to read the ELF header: {}", e); + return; + } + + let magic = &header[..4]; + let magic_string = magic + .iter() + .map(|b| format!("{:02X}", b)) + .collect::(); + + // Check if the file is an ELF file + if magic != ELF_MAGIC { + println!("{} is not an ELF file ({})", args[1], magic_string); + return; + } + + // Extract the ELF file type + let (elf_type_str, elf_type) = get_elf_type(&header); + + let (machine_type_str, machine_type) = get_machine_type(&header); + + // Extract the ELF class + let (elf_class_str, _) = get_elf_class(&header); + + // Extract the data encoding + let (data_encoding_str, _) = get_data_encoding(&header); + + // Extract the ELF version + let elf_version = get_elf_version(&header); + + // Extract the entry point address + let entry_point = get_entry_point(&header); + + // Extract the program header table offset + let phdr_offset = get_phdr_offset(&header); + + // Extract the section header table offset + let shdr_offset = get_shdr_offset(&header); + + // Write a function that takes two bytes and returns a u16 + // Extract the ELF header size + let elf_header_size = bytes_to_u16(&[header[0x34], header[0x35]]); + + // Extract the program header table entry size + let phdr_entry_size = bytes_to_u16(&[header[0x36], header[0x37]]); + + // Extract the number of program header table entries + let phdr_num_entries = bytes_to_u16(&[header[0x38], header[0x39]]); + + // Extract the section header table entry size + let shdr_entry_size = bytes_to_u16(&[header[0x3A], header[0x3B]]); + + // Extract the number of section header table entries + let shdr_num_entries = bytes_to_u16(&[header[0x3C], header[0x3D]]); + + // Extract the section header string table index + let shdr_str_index = bytes_to_u16(&[header[0x3E], header[0x3F]]); + + // Print the extracted information + println!("Full header:"); + for byte in &header[..ELF_HEADER_SIZE] { + print!("{:02X} ", byte); + } + println!("ELF File Type: {} (0x{:02X})", elf_type_str, elf_type); + println!( + "Machine Type: {} (0x{:04X})", + machine_type_str, machine_type + ); + println!(); + println!("ELF Class: {}", elf_class_str); + println!("Data Encoding: {}", data_encoding_str); + println!("ELF Version: {}", elf_version); + println!("Entry Point Address: {}", entry_point); + println!("Program Header Table Offset: {}", phdr_offset); + println!("Section Header Table Offset: {}", shdr_offset); + println!("ELF Header Size: {} bytes", elf_header_size); + println!("Program Header Table Entry Size: {} bytes", phdr_entry_size); + println!( + "Number of Program Header Table Entries: {}", + phdr_num_entries + ); + println!("Section Header Table Entry Size: {} bytes", shdr_entry_size); + println!( + "Number of Section Header Table Entries: {}", + shdr_num_entries + ); + println!("Section Header String Table Index: {}", shdr_str_index); + + // Extract the number of program header table entries + let phdr_num_entries = bytes_to_u16(&[header[0x38], header[0x39]]); + + // Seek to the program header table offset + file.seek(SeekFrom::Start(phdr_offset)).unwrap(); + + // Print the segment information + println!("\nSegment Information:"); + for i in 0..phdr_num_entries { + let mut phdr_entry = [0u8; PHDR_ENTRY_SIZE]; + file.read_exact(&mut phdr_entry).unwrap(); + + let segment_type = get_phdr_segment_type(&phdr_entry); + let segment_offset = get_phdr_segment_offset(&phdr_entry); + let segment_vaddr = get_phdr_segment_vaddr(&phdr_entry); + let segment_paddr = get_phdr_segment_paddr(&phdr_entry); + let segment_filesz = get_phdr_segment_filesz(&phdr_entry); + let segment_memsz = get_phdr_segment_memsz(&phdr_entry); + let segment_flags = get_phdr_segment_flags(&phdr_entry); + + println!("Segment {}:", i); + println!( + " Type: {} (0x{:08X})", + get_segment_type(segment_type), + segment_type + ); + println!(" Offset: {}", segment_offset); + println!(" Virtual Address: {}", segment_vaddr); + println!(" Physical Address: {}", segment_paddr); + println!(" File Size: {}", segment_filesz); + println!(" Memory Size: {}", segment_memsz); + println!( + " Flags: {} (0x{:08X})", + get_segment_flags(segment_flags), + segment_flags + ); + println!(); + } +} + +fn get_entry_point(header: &[u8]) -> u64 { + u64::from_le_bytes([ + header[0x18], + header[0x19], + header[0x1A], + header[0x1B], + header[0x1C], + header[0x1D], + header[0x1E], + header[0x1F], + ]) +} + +fn get_phdr_offset(header: &[u8]) -> u64 { + u64::from_le_bytes([ + header[0x20], + header[0x21], + header[0x22], + header[0x23], + header[0x24], + header[0x25], + header[0x26], + header[0x27], + ]) +} + +fn get_shdr_offset(header: &[u8]) -> u64 { + u64::from_le_bytes([ + header[0x28], + header[0x29], + header[0x2A], + header[0x2B], + header[0x2C], + header[0x2D], + header[0x2E], + header[0x2F], + ]) +} + +fn get_phdr_segment_type(phdr_entry: &[u8]) -> u32 { + u32::from_le_bytes([ + phdr_entry[0x04], + phdr_entry[0x05], + phdr_entry[0x06], + phdr_entry[0x07], + ]) +} + +fn get_phdr_segment_offset(phdr_entry: &[u8]) -> u64 { + u64::from_le_bytes([ + phdr_entry[0x08], + phdr_entry[0x09], + phdr_entry[0x0A], + phdr_entry[0x0B], + phdr_entry[0x0C], + phdr_entry[0x0D], + phdr_entry[0x0E], + phdr_entry[0x0F], + ]) +} + +fn get_phdr_segment_vaddr(phdr_entry: &[u8]) -> u64 { + u64::from_le_bytes([ + phdr_entry[0x10], + phdr_entry[0x11], + phdr_entry[0x12], + phdr_entry[0x13], + phdr_entry[0x14], + phdr_entry[0x15], + phdr_entry[0x16], + phdr_entry[0x17], + ]) +} + +fn get_phdr_segment_paddr(phdr_entry: &[u8]) -> u64 { + u64::from_le_bytes([ + phdr_entry[0x18], + phdr_entry[0x19], + phdr_entry[0x1A], + phdr_entry[0x1B], + phdr_entry[0x1C], + phdr_entry[0x1D], + phdr_entry[0x1E], + phdr_entry[0x1F], + ]) +} + +fn get_phdr_segment_filesz(phdr_entry: &[u8]) -> display::HexAddress { + display::HexAddress(u64::from_le_bytes([ + phdr_entry[0x20], + phdr_entry[0x21], + phdr_entry[0x22], + phdr_entry[0x23], + phdr_entry[0x24], + phdr_entry[0x25], + phdr_entry[0x26], + phdr_entry[0x27], + ])) +} + +fn get_phdr_segment_memsz(phdr_entry: &[u8]) -> display::HexAddress { + display::HexAddress(u64::from_le_bytes([ + phdr_entry[0x28], + phdr_entry[0x29], + phdr_entry[0x2A], + phdr_entry[0x2B], + phdr_entry[0x2C], + phdr_entry[0x2D], + phdr_entry[0x2E], + phdr_entry[0x2F], + ])) +} + +fn get_phdr_segment_flags(phdr_entry: &[u8]) -> u32 { + u32::from_le_bytes([ + phdr_entry[0x30], + phdr_entry[0x31], + phdr_entry[0x32], + phdr_entry[0x33], + ]) +} + +fn get_file() -> File { + let args: Vec = env::args().collect(); + if args.len() != 2 { + eprintln!("Usage: {} ", args[0]); + process::exit(1); + } + + let file = File::open(&args[1]); + if file.is_err() { + eprintln!("Failed to open file: {}", args[1]); + process::exit(1); + } + + file.unwrap() +} + +fn get_segment_type(segment_type: u32) -> &'static str { + match segment_type { + 0x00000000 => "PT_NULL", + 0x00000001 => "PT_LOAD", + 0x00000002 => "PT_DYNAMIC", + 0x00000003 => "PT_INTERP", + 0x00000004 => "PT_NOTE", + 0x00000005 => "PT_SHLIB", + 0x00000006 => "PT_PHDR", + 0x00000007 => "PT_TLS", + 0x6474e550 => "PT_GNU_EH_FRAME", + 0x6474e551 => "PT_GNU_STACK", + 0x6474e552 => "PT_GNU_RELRO", + _ => "Unknown", + } +} + +fn get_segment_flags(segment_flags: u32) -> String { + let mut flags = String::new(); + match segment_flags { + 0x01 => flags.push_str("R"), + 0x02 => flags.push_str("W"), + 0x04 => flags.push_str("X"), + _ => flags.push_str("Unknown"), + } + flags +} + +fn get_machine_type(header: &[u8]) -> (String, u16) { + let machine_type = u16::from_le_bytes([header[0x12], header[0x13]]); + let machine_type_str = match machine_type { + 0x03 => "x86", + 0x3E => "x86-64", + 0xB7 => "AArch64", + _ => "Unknown", + } + .to_string(); + (machine_type_str, machine_type) +} + +fn get_elf_type(header: &[u8]) -> (String, u8) { + let elf_type = header[0x10]; + let elf_type_str = match elf_type { + 0x01 => "Relocatable", + 0x02 => "Executable", + 0x03 => "Shared", + 0x04 => "Core", + _ => "Unknown", + } + .to_string(); + (elf_type_str, elf_type) +} + +fn get_elf_class(header: &[u8]) -> (String, u8) { + let elf_class = header[0x04]; + let elf_class_str = match elf_class { + 1 => "32-bit", + 2 => "64-bit", + _ => "Unknown", + } + .to_string(); + (elf_class_str, elf_class) +} + +fn get_data_encoding(header: &[u8]) -> (String, u8) { + let data_encoding = header[0x05]; + let data_encoding_str = match data_encoding { + 1 => "Little-endian", + 2 => "Big-endian", + _ => "Unknown", + } + .to_string(); + (data_encoding_str, data_encoding) +} + +fn bytes_to_u16(two_bytes: &[u8; 2]) -> u16 { + let value = u16::from_le_bytes(*two_bytes); + value +} + +fn get_elf_version(header: &[u8]) -> u8 { + let elf_version = header[0x06]; + elf_version +}