This commit is contained in:
2026-06-07 21:22:32 -04:00
parent a086fa6590
commit c9add923be
14 changed files with 413 additions and 46 deletions
+129
View File
@@ -0,0 +1,129 @@
use crate::backend::{Addr, LinkedProgram, container::as_u8_slice};
#[repr(C)]
pub struct ELF64Header {
magic: u32,
class: u8,
endianness: u8,
ei_version: u8,
os_abi: u8,
os_abi_ver: u8,
pad: [u8; 7],
ty: u16,
machine: u16,
e_version: u32,
entry: u64,
program_header_offset: u64,
section_header_offset: u64,
flags: u32,
header_size: u16,
program_header_entry_size: u16,
program_header_num: u16,
section_header_entry_size: u16,
section_header_num: u16,
section_header_str_idx: u16,
}
#[repr(C)]
pub struct ProgramHeader {
ty: u32,
flags: u32,
offset: u64,
vaddr: u64,
paddr: u64,
filesz: u64,
memsz: u64,
align: u64,
}
#[repr(C)]
pub struct SectionHeader {
name_idx: u32,
ty: u32,
flags: u64,
addr: u64,
offset: u64,
size: u64,
link: u32,
info: u32,
addr_align: u64,
entry_size: u64,
}
pub enum Arch {
X86_64,
Riscv,
}
impl Arch {
pub fn machine(&self) -> u16 {
match self {
Arch::X86_64 => 0x3e,
Arch::Riscv => 0xf3,
}
}
}
#[repr(u8)]
pub enum EType {
None = 0,
Rel = 1,
Exec = 2,
Dyn = 3,
Core = 4,
}
// this is currently specialized for x86_64; obviously add params later
pub fn create(program: &[u8], start_offset: u64) -> Vec<u8> {
let pie = true;
let addr_start = if pie { 0 } else { 0x400000 };
let page_size = 0x1000;
// I don't know if I have to add addr_start here, idk how it maps the memory
let program_size = std::mem::size_of_val(program) as u64;
let program_header = ProgramHeader {
ty: 0x1, // LOAD
flags: 0b101, // executable, readable
offset: 0x0,
vaddr: addr_start,
paddr: 0x0,
filesz: program_size,
memsz: program_size,
align: page_size,
};
let header_len = (size_of::<ELF64Header>() + size_of::<ProgramHeader>()) as u64;
let program_pos = header_len;
let header = ELF64Header {
magic: 0x7f_45_4c_46u32.swap_bytes(),
class: 0x2, // 64 bit
endianness: 0x1, // little endian
ei_version: 0x1,
os_abi: 0x0, // system-v
os_abi_ver: 0x0,
pad: [0x0; 7],
ty: if pie { EType::Dyn } else { EType::Exec } as u16,
machine: Arch::X86_64.machine(),
e_version: 0x1,
entry: addr_start + program_pos + start_offset as u64,
program_header_offset: size_of::<ELF64Header>() as u64,
section_header_offset: 0x0,
// C ABI (16 bit instruction align) + double precision floats
flags: 0x1 | 0x4,
header_size: size_of::<ELF64Header>() as u16,
program_header_entry_size: size_of::<ProgramHeader>() as u16,
program_header_num: 0x1,
section_header_entry_size: size_of::<SectionHeader>() as u16,
section_header_num: 0x0,
section_header_str_idx: 0x0,
};
let mut bytes: Vec<u8> = Vec::new();
bytes.extend(as_u8_slice(&header));
bytes.extend(as_u8_slice(&program_header));
bytes.extend(program);
bytes
}
impl LinkedProgram<u64> {
pub fn to_elf(&self) -> Vec<u8> {
create(&self.code, self.entry.expect("no start"))
}
}
+38
View File
@@ -0,0 +1,38 @@
pub mod elf;
pub mod pe;
#[derive(Default)]
pub struct ByteEncoder {
pub data: Vec<u8>,
}
trait Pushable {
fn push(self, data: &mut Vec<u8>);
}
impl ByteEncoder {
pub fn push(&mut self, byte: u8) {
self.data.push(byte);
}
pub fn extend(&mut self, iter: impl IntoIterator<Item = u8>) {
self.data.extend(iter);
}
pub fn val<T>(&mut self, val: &T) {
self.data.extend(as_u8_slice(val));
}
pub fn fill<T>(&mut self, pos: usize, val: &T) {
self.data[pos..pos + size_of::<T>()].copy_from_slice(as_u8_slice(val));
}
pub fn pos(&self) -> usize {
self.data.len()
}
pub fn reserve<T: Default>(&mut self) -> usize {
let pos = self.pos();
self.val(&T::default());
pos
}
}
fn as_u8_slice<T: Sized>(p: &T) -> &[u8] {
unsafe { core::slice::from_raw_parts((p as *const T) as *const u8, size_of::<T>()) }
}
+27
View File
@@ -0,0 +1,27 @@
#[derive(Default)]
#[repr(C)]
pub struct DataDirs {
pub export: DataDir,
pub import: DataDir,
pub rsc: DataDir,
pub exception: DataDir,
pub cert: DataDir,
pub base_reloc: DataDir,
pub debug: DataDir,
pub arch: DataDir,
pub global_ptr: DataDir,
pub tls: DataDir,
pub load_config: DataDir,
pub bound_import: DataDir,
pub import_addr: DataDir,
pub delay_import_desc: DataDir,
pub clr_runtime_header: DataDir,
pub reserved: DataDir,
}
#[derive(Default)]
#[repr(C)]
pub struct DataDir {
pub virt_addr_rva: u32,
pub size: u32,
}
+67
View File
@@ -0,0 +1,67 @@
#[repr(C)]
pub struct MZHeader {
pub magic: u16,
pub stuff: [u16; 15 + 4 + 10],
pub lfanew: u32,
}
#[repr(C)]
pub struct PeHeader {
pub magic: u32,
pub machine: u16,
pub num_sections: u16,
pub time_date_stamp: u32,
pub sym_tab_ptr: u32,
pub num_symbols: u32,
pub opt_header_size: u16,
pub characteristics: u16,
}
#[repr(C)]
#[derive(Default)]
pub struct OptHeader64 {
pub magic: u16,
pub major_linker_ver: u8,
pub minor_linker_ver: u8,
pub code_size: u32,
pub init_data_size: u32,
pub uninit_data_size: u32,
pub entry_addr: u32,
pub code_base: u32,
pub image_base: u64,
pub section_align: u32,
pub file_align: u32,
pub major_os_ver: u16,
pub minor_os_ver: u16,
pub major_image_ver: u16,
pub minor_image_ver: u16,
pub major_subsystem_ver: u16,
pub minor_subsystem_ver: u16,
pub win32_ver: u32,
pub image_size: u32,
pub headers_size: u32,
pub checksum: u32,
pub subsystem: u16,
pub dll_characteristics: u16,
pub stack_reserve_size: u64,
pub stack_commit_size: u64,
pub heap_reserve_size: u64,
pub heap_commit_size: u64,
pub loader_flags: u32,
pub num_of_rva_and_sizes: u32,
}
#[repr(C)]
#[derive(Default)]
pub struct Section {
pub name: [u8; 8],
pub virtual_size: u32,
pub virtual_addr: u32,
pub raw_data_size: u32,
pub raw_data_ptr: u32,
pub reloc_ptr: u32,
pub line_num_ptr: u32,
pub num_relocs: u16,
pub num_line_nums: u16,
pub characteristics: u32,
}
+77
View File
@@ -0,0 +1,77 @@
use crate::backend::ByteEncoder;
pub struct Import {
pub name: String,
pub names: Vec<String>,
}
pub fn encode(data: &mut ByteEncoder, imports: &[Import]) -> usize {
let mut names = 0;
for import in imports {
for name in &import.names {
data.extend(ImportLookupEntry::name().bytes());
names += 1;
}
data.extend(ImportLookupEntry::NULL.bytes());
}
let table_addr = data.pos();
for import in imports {
let idt = ImportDirTable {
lookup_table_rva: todo!(),
time_date_stamp: 0,
forwarder_chain: 0,
name_rva: todo!(),
address_table_rva: todo!(),
};
}
for import in imports {
for name in &import.names {
hint_name_entry(data, 0, &name);
}
}
}
#[repr(C)]
pub struct ImportDirTable {
pub lookup_table_rva: u32,
pub time_date_stamp: u32,
pub forwarder_chain: u32,
pub name_rva: u32,
pub address_table_rva: u32,
}
impl ImportDirTable {
pub const NULL: Self = Self {
lookup_table_rva: 0,
time_date_stamp: 0,
forwarder_chain: 0,
name_rva: 0,
address_table_rva: 0,
};
}
#[repr(C)]
pub struct ImportLookupEntry(u64);
impl ImportLookupEntry {
pub const NULL: Self = Self(0);
pub fn name(hint_name_table_rva: u32) -> Self {
assert!(hint_name_table_rva >> 30 == 0);
Self(hint_name_table_rva as u64)
}
pub fn ordinal(ordinal: u16) -> Self {
Self(ordinal as u64 | (1 << 63))
}
pub fn bytes(&self) -> [u8; 8] {
self.0.to_le_bytes()
}
}
pub fn hint_name_entry(out: &mut Vec<u8>, hint: u16, name: &str) {
out.extend(hint.to_le_bytes());
out.extend(name.as_bytes());
out.push(0);
if out.len() % 2 == 1 {
out.push(0);
}
}
+112
View File
@@ -0,0 +1,112 @@
mod data_dir;
mod header;
mod import;
use data_dir::*;
use header::*;
use crate::backend::{ByteEncoder, LinkedProgram, pe::import::Import};
pub fn create(program: &[u8], start_offset: u64) -> Vec<u8> {
let mut data = ByteEncoder::default();
let file_align = 1;
let section_align = 1;
let code_size = program.len() as u32;
let mzheader = MZHeader {
magic: u16::from_ne_bytes(*b"MZ"),
stuff: [0; _],
lfanew: size_of::<MZHeader>() as u32,
};
data.val(&mzheader);
let header = PeHeader {
magic: u32::from_ne_bytes(*b"PE\0\0"),
machine: 0x8664,
num_sections: 1,
time_date_stamp: 0,
sym_tab_ptr: 0,
num_symbols: 0,
opt_header_size: size_of::<OptHeader64>() as u16,
// https://learn.microsoft.com/en-us/windows/win32/debug/pe-format
// executable | can handle >2GB addrs | debug info removed
characteristics: 0x2 | 0x20 | 0x0200,
};
data.val(&header);
let opt_header_pos = data.reserve::<OptHeader64>();
let num_of_rva_and_sizes: u32 = (size_of::<DataDirs>() / size_of::<DataDir>()) as u32;
data.val(&DataDirs::default());
let code_sect_pos = data.reserve::<Section>();
let hdr_size = data.pos() as u32;
let imports = [Import {
name: "kernel32.lib".to_string(),
names: ["GetStdHandle", "WriteFile", "ExitProcess"]
.map(String::from)
.to_vec(),
}];
import::encode(&mut data, &imports);
let code_addr = data.pos() as u32;
data.data.extend(program);
let code_section = Section {
name: *b".text\0\0\0",
virtual_size: code_size,
virtual_addr: hdr_size.next_multiple_of(section_align),
raw_data_size: code_size.next_multiple_of(file_align),
raw_data_ptr: code_addr,
reloc_ptr: 0,
line_num_ptr: 0,
num_relocs: 0,
num_line_nums: 0,
characteristics: 0x60000020,
};
let file_size = data.pos() as u32;
let opt_header = OptHeader64 {
magic: 0x20b,
major_linker_ver: 8,
minor_linker_ver: 0,
code_size: code_size.next_multiple_of(file_align),
init_data_size: 0,
uninit_data_size: 0,
entry_addr: code_addr + start_offset as u32,
code_base: code_addr,
image_base: 0x400000,
section_align,
file_align,
major_os_ver: 4,
minor_os_ver: 0,
major_image_ver: 0,
minor_image_ver: 0,
major_subsystem_ver: 4,
minor_subsystem_ver: 0,
win32_ver: 0,
image_size: file_size.next_multiple_of(section_align),
headers_size: hdr_size.next_multiple_of(file_align),
checksum: 0,
subsystem: 3, // windows CLI app
dll_characteristics: 0x400,
stack_reserve_size: 0x100000,
stack_commit_size: 0x1000,
heap_reserve_size: 0x100000,
heap_commit_size: 0x1000,
loader_flags: 0,
num_of_rva_and_sizes,
};
data.fill(opt_header_pos, &opt_header);
data.fill(code_sect_pos, &code_section);
data.data
}
impl LinkedProgram<u64> {
pub fn to_pe(&self) -> Vec<u8> {
create(&self.code, self.entry.expect("no start"))
}
}