feat(core): implement ELF64 header and program segment parser
This commit is contained in:
parent
58618eb3c0
commit
2d0cf8c478
3 changed files with 136 additions and 0 deletions
107
crates/molotov-core/src/elf.rs
Normal file
107
crates/molotov-core/src/elf.rs
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ElfError {
|
||||
#[error("File is smaller than minimum ELF64 header (64 bytes)")]
|
||||
TooSmall,
|
||||
#[error("Invalid ELF magic header: expected 0x7F 'ELF'")]
|
||||
InvalidMagic,
|
||||
#[error("Unsupported ELF class: expected 64-bit")]
|
||||
UnsupportedClass,
|
||||
#[error("Unsupported endianness: expected Little Endian")]
|
||||
UnsupportedEndianness,
|
||||
#[error("Unsupported architecture: expected x86_64 (62), found {0}")]
|
||||
UnsupportedMachine(u16),
|
||||
#[error("Invalid ELF type: expected ET_DYN (3), found {0}")]
|
||||
UnsupportedType(u16),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ProgramHeader {
|
||||
pub p_type: u32,
|
||||
pub p_flags: u32,
|
||||
pub p_offset: u64,
|
||||
pub p_vaddr: u64,
|
||||
pub p_paddr: u64,
|
||||
pub p_filesz: u64,
|
||||
pub p_memsz: u64,
|
||||
pub p_align: u64,
|
||||
}
|
||||
|
||||
pub const PT_LOAD: u32 = 1;
|
||||
pub const PT_DYNAMIC: u32 = 2;
|
||||
pub const PT_TLS: u32 = 7;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ElfBinary<'a> {
|
||||
pub raw: &'a [u8],
|
||||
pub entry_point: u64,
|
||||
pub program_headers: Vec<ProgramHeader>,
|
||||
}
|
||||
|
||||
impl<'a> ElfBinary<'a> {
|
||||
pub fn parse(bytes: &'a [u8]) -> Result<Self, ElfError> {
|
||||
if bytes.len() < 64 {
|
||||
return Err(ElfError::TooSmall);
|
||||
}
|
||||
|
||||
if &bytes[0..4] != b"\x7fELF" {
|
||||
return Err(ElfError::InvalidMagic);
|
||||
}
|
||||
|
||||
if bytes[4] != 2 {
|
||||
return Err(ElfError::UnsupportedClass);
|
||||
}
|
||||
|
||||
if bytes[5] != 1 {
|
||||
return Err(ElfError::UnsupportedEndianness);
|
||||
}
|
||||
|
||||
let e_type = u16::from_le_bytes([bytes[16], bytes[17]]);
|
||||
if e_type != 3 {
|
||||
return Err(ElfError::UnsupportedType(e_type));
|
||||
}
|
||||
|
||||
let e_machine = u16::from_le_bytes([bytes[18], bytes[19]]);
|
||||
if e_machine != 62 {
|
||||
return Err(ElfError::UnsupportedMachine(e_machine));
|
||||
}
|
||||
|
||||
let entry_point = u64::from_le_bytes(bytes[24..32].try_into().unwrap());
|
||||
let phoff = u64::from_le_bytes(bytes[32..40].try_into().unwrap()) as usize;
|
||||
let phentsize = u16::from_le_bytes([bytes[54], bytes[55]]) as usize;
|
||||
let phnum = u16::from_le_bytes([bytes[56], bytes[57]]) as usize;
|
||||
|
||||
let mut program_headers = Vec::with_capacity(phnum);
|
||||
|
||||
if phnum > 0 && phentsize >= 56 {
|
||||
for i in 0..phnum {
|
||||
let offset = phoff + i * phentsize;
|
||||
if offset + 56 <= bytes.len() {
|
||||
let ph = ProgramHeader {
|
||||
p_type: u32::from_le_bytes(bytes[offset..offset + 4].try_into().unwrap()),
|
||||
p_flags: u32::from_le_bytes(bytes[offset + 4..offset + 8].try_into().unwrap()),
|
||||
p_offset: u64::from_le_bytes(bytes[offset + 8..offset + 16].try_into().unwrap()),
|
||||
p_vaddr: u64::from_le_bytes(bytes[offset + 16..offset + 24].try_into().unwrap()),
|
||||
p_paddr: u64::from_le_bytes(bytes[offset + 24..offset + 32].try_into().unwrap()),
|
||||
p_filesz: u64::from_le_bytes(bytes[offset + 32..offset + 40].try_into().unwrap()),
|
||||
p_memsz: u64::from_le_bytes(bytes[offset + 40..offset + 48].try_into().unwrap()),
|
||||
p_align: u64::from_le_bytes(bytes[offset + 48..offset + 56].try_into().unwrap()),
|
||||
};
|
||||
program_headers.push(ph);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
raw: bytes,
|
||||
entry_point,
|
||||
program_headers,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn load_segments(&self) -> impl Iterator<Item = &ProgramHeader> {
|
||||
self.program_headers.iter().filter(|ph| ph.p_type == PT_LOAD)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +1,3 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
pub mod elf;
|
||||
pub mod symbols;
|
||||
|
|
|
|||
28
crates/molotov-core/tests/elf_parser_test.rs
Normal file
28
crates/molotov-core/tests/elf_parser_test.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
use molotov_core::elf::ElfBinary;
|
||||
|
||||
#[test]
|
||||
fn test_rejects_non_elf_magic() {
|
||||
let dummy = b"NOT_AN_ELF_BINARY";
|
||||
let res = ElfBinary::parse(dummy);
|
||||
assert!(res.is_err(), "Must reject files without 0x7F 'E' 'L' 'F' magic header");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parses_valid_minimal_elf64() {
|
||||
let mut header = vec![0u8; 64];
|
||||
// e_ident
|
||||
header[0..4].copy_from_slice(b"\x7fELF");
|
||||
header[4] = 2; // ELFCLASS64
|
||||
header[5] = 1; // ELFDATA2LSB (little endian)
|
||||
header[6] = 1; // EV_CURRENT
|
||||
// e_type = ET_DYN (3)
|
||||
header[16] = 3;
|
||||
// e_machine = EM_X86_64 (62)
|
||||
header[18] = 62;
|
||||
// e_phentsize = 56
|
||||
header[54] = 56;
|
||||
|
||||
let res = ElfBinary::parse(&header);
|
||||
assert!(res.is_ok(), "Valid minimal ELF64 dynamic library header must be accepted");
|
||||
}
|
||||
Reference in a new issue