Skip to content

Commit

Permalink
Generating a valid ELF executable
Browse files Browse the repository at this point in the history
  • Loading branch information
anima-libera committed Jul 15, 2023
0 parents commit a9a741e
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/target
binary
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"editor.tabSize": 3
}
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[package]
name = "spine"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
15 changes: 15 additions & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# See https://github.com/rust-lang/rustfmt/blob/master/Configurations.md
# for actual up-to-date config options.
edition = "2021"
binop_separator = "Back"
match_arm_blocks = false
match_block_trailing_comma = true
hard_tabs = true
tab_spaces = 3
blank_lines_upper_bound = 1
use_field_init_shorthand = true
max_width = 100
struct_lit_width = 65
struct_lit_single_line = true
struct_variant_width = 50
wrap_comments = true
180 changes: 180 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
struct ByteBuffer {
bytes: Vec<u8>,
}
impl ByteBuffer {
fn new() -> ByteBuffer {
ByteBuffer { bytes: vec![] }
}
fn into_bytes(self) -> Vec<u8> {
self.bytes
}

fn make_sure_index_exists(&mut self, index: usize) {
if self.bytes.len() <= index {
self.bytes.resize(index + 1, 0);
}
}

fn write_bytes(&mut self, index: usize, bytes: &[u8]) -> usize {
self.make_sure_index_exists(index + bytes.len() - 1);
self.bytes[index..].clone_from_slice(bytes);
index + bytes.len()
}
}

macro_rules! byte_buffer_fn_write {
($func:ident, $type:ty) => {
impl ByteBuffer {
fn $func(&mut self, index: usize, value: $type) -> usize {
self.make_sure_index_exists(index + std::mem::size_of::<$type>() - 1);
self.bytes[index..].clone_from_slice(&value.to_le_bytes());
index + std::mem::size_of::<$type>()
}
}
};
}
byte_buffer_fn_write!(write_u8, u8);
byte_buffer_fn_write!(write_u16, u16);
byte_buffer_fn_write!(write_u32, u32);
byte_buffer_fn_write!(write_u64, u64);

struct Binary {
entry_point_offset_in_code: usize,
code_segment_address: usize,
data_segment_address: usize,
}

impl Binary {
fn new() -> Binary {
Binary {
entry_point_offset_in_code: 0,
code_segment_address: 0x400000,
data_segment_address: 0x600000,
}
}

fn code_segment_binary_machine_code(&self) -> Vec<u8> {
let mut buf = ByteBuffer::new();
let mut i = 0;
// Exit(0)
i = buf.write_bytes(i, &[0x48, 0xc7, 0xc0, 0x3c, 0x00, 0x00, 0x00]); // movq $60, %rax
i = buf.write_bytes(i, &[0x48, 0xc7, 0xc7, 0x00, 0x00, 0x00, 0x00]); // movq $0, %rdi
buf.write_bytes(i, &[0x0f, 0x05]); // syscall
buf.into_bytes()
}

fn code_size_in_binary(&self) -> usize {
self.code_segment_binary_machine_code().len()
}

fn data_segment_binary_content(&self) -> Vec<u8> {
ByteBuffer::new().into_bytes()
}

fn data_size_in_binary(&self) -> usize {
self.data_segment_binary_content().len()
}

fn to_binary(&self) -> ByteBuffer {
let mut buf = ByteBuffer::new();
let mut i = 0;

// See https://en.wikipedia.org/wiki/Executable_and_Linkable_Format
// Also see https://github.com/vishen/go-x64-executable/blob/master/main.go
// Beware! This is a certified ELF moment!
// 64 bits

const ELF_HEADER_SIZE: usize = 0x40;
const PROGRAM_HEADER_TABLE_ENTRY_SIZE: usize = 0x38;
const PROGRAM_HEADER_TABLE_LENGTH: usize = 2; // Code and data are enough for us
const CODE_OFFSET_IN_BINARY: usize = ELF_HEADER_SIZE + PROGRAM_HEADER_TABLE_ENTRY_SIZE * 2;

let entry_point_address =
CODE_OFFSET_IN_BINARY + self.entry_point_offset_in_code + self.code_segment_address;

// ELF header
i = buf.write_bytes(i, &[0x7f, b'E', b'L', b'F']); // ELF magic number
i = buf.write_u8(i, 2); // 1 -> 32-bits, 2 -> 64-bits
i = buf.write_u8(i, 1); // 1 -> little endian, 2 -> big endian
i = buf.write_u8(i, 1); // ELF format version (still 1 in 2023)
i = buf.write_u8(i, 3); // Target Linux
i = buf.write_u8(i, 0); // Required dynamic linker version (we don't care)
i = buf.write_bytes(i, &[0, 0, 0, 0, 0, 0, 0]); // Padding
i = buf.write_u16(i, 2); // This is an executable
i = buf.write_u16(i, 0x3e); // Target x86-64
i = buf.write_u32(i, 1); // ELF format version (again??)
i = buf.write_u64(i, entry_point_address as u64); // Entry point address
i = buf.write_u64(i, ELF_HEADER_SIZE as u64); // Program header table offset in binary
i = buf.write_u64(i, 0); // Section header table offset in binary (we don't have one)
i = buf.write_u32(i, 0); // Target architecture dependent flags
i = buf.write_u16(i, ELF_HEADER_SIZE as u16); // Size of this header
i = buf.write_u16(i, PROGRAM_HEADER_TABLE_ENTRY_SIZE as u16); // Size of a prog entry
i = buf.write_u16(i, PROGRAM_HEADER_TABLE_LENGTH as u16); // Number of entries in program header table
i = buf.write_u16(i, 0); // Size of a section header table entry (we don't have one)
i = buf.write_u16(i, 0); // Number of entries in section header table
i = buf.write_u16(i, 0); // Index of the section header table entry that has the section names
assert_eq!(i, ELF_HEADER_SIZE);

// Program header table
let mut program_header_table_entry_count = 0;
{
// Code segment
let bin_offset = CODE_OFFSET_IN_BINARY as u64;
let addr_offset = self.code_segment_address as u64;
let size = self.code_size_in_binary() as u64;

i = buf.write_u32(i, 1); // Loadable segment
i = buf.write_u32(
i,
(1 << 0/*Readable*/) | (1 << 1/*Writable*/) | (1 << 2/*Executable*/),
); // Flags
i = buf.write_u64(i, bin_offset); // Offset in binary
i = buf.write_u64(i, addr_offset + bin_offset); // Address in virtual memory
i = buf.write_u64(i, addr_offset + bin_offset); // Address in physical memory (wtf)
i = buf.write_u64(i, size); // Size in binary
i = buf.write_u64(i, size); // Size in memory
i = buf.write_u64(i, 0); // Alignment (0 means no alignment)
assert_eq!(i, ELF_HEADER_SIZE + PROGRAM_HEADER_TABLE_ENTRY_SIZE);
}
program_header_table_entry_count += 1;
{
// Data segment
let bin_offset = (CODE_OFFSET_IN_BINARY + self.code_size_in_binary()) as u64;
let addr_offset = self.data_segment_address as u64;
let size = self.data_size_in_binary() as u64;

i = buf.write_u32(i, 1); // Loadable segment
i = buf.write_u32(
i,
(1 << 0/*Readable*/) | (1 << 1/*Writable*/) | (1 << 2/*Executable*/),
); // Flags
i = buf.write_u64(i, bin_offset); // Offset in binary
i = buf.write_u64(i, addr_offset + bin_offset); // Address in virtual memory
i = buf.write_u64(i, addr_offset + bin_offset); // Address in physical memory (wtf)
i = buf.write_u64(i, size); // Size in binary
i = buf.write_u64(i, size); // Size in memory
i = buf.write_u64(i, 0); // Alignment (0 means no alignment)
assert_eq!(i, ELF_HEADER_SIZE + PROGRAM_HEADER_TABLE_ENTRY_SIZE * 2);
}
program_header_table_entry_count += 1;
assert_eq!(
program_header_table_entry_count,
PROGRAM_HEADER_TABLE_LENGTH
);

// Code
i = buf.write_bytes(i, self.code_segment_binary_machine_code().as_slice());

// Data
i = buf.write_bytes(i, self.data_segment_binary_content().as_slice());

assert_eq!(i, buf.bytes.len());

buf
}
}

fn main() {
let bin = Binary::new();
std::fs::write("binary", bin.to_binary().bytes).unwrap();
}

0 comments on commit a9a741e

Please sign in to comment.