use core::ffi::{CStr, c_void};
use alloc::{
string::{String, ToString},
vec::Vec,
};
use log::{debug, warn};
use binrw::{BinRead, binread};
use binrw::io::Cursor;
use crate::error::{CoffError, CoffeeLdrError};
const COFF_MACHINE_X64: u16 = 0x8664;
const COFF_MACHINE_X32: u16 = 0x14c;
const MAX_SECTIONS: u16 = 96;
pub struct Coff<'a> {
pub file_header: IMAGE_FILE_HEADER,
pub symbols: Vec<IMAGE_SYMBOL>,
pub sections: Vec<IMAGE_SECTION_HEADER>,
pub buffer: &'a [u8],
pub arch: CoffMachine,
}
impl<'a> Default for Coff<'a> {
fn default() -> Self {
Self {
file_header: IMAGE_FILE_HEADER::default(),
symbols: Vec::new(),
sections: Vec::new(),
buffer: &[],
arch: CoffMachine::X64,
}
}
}
impl<'a> Coff<'a> {
pub fn parse(buffer: &'a [u8]) -> Result<Self, CoffError> {
debug!("Parsing COFF file header, buffer size: {}", buffer.len());
if buffer.len() < size_of::<IMAGE_FILE_HEADER>() {
return Err(CoffError::InvalidCoffFile);
}
let mut cursor = Cursor::new(buffer);
let file_header = IMAGE_FILE_HEADER::read(&mut cursor)
.map_err(|_| CoffError::InvalidCoffFile)?;
let arch = Self::validate_architecture(file_header)?;
let num_sections = file_header.NumberOfSections;
let num_symbols = file_header.NumberOfSymbols;
if num_sections == 0 || num_symbols == 0 {
return Err(CoffError::InvalidSectionsOrSymbols);
}
if num_sections > MAX_SECTIONS {
warn!("Exceeded maximum number of sections: {} > {}", num_sections, MAX_SECTIONS);
return Err(CoffError::SectionLimitExceeded);
}
let symbol_offset = file_header.PointerToSymbolTable as usize;
let mut cursor = Cursor::new(&buffer[symbol_offset..]);
let symbols = (0..num_symbols)
.map(|_| {
IMAGE_SYMBOL::read(&mut cursor)
.map_err(|_| CoffError::InvalidCoffSymbolsFile)
})
.collect::<Result<Vec<IMAGE_SYMBOL>, _>>()?;
let section_offset = size_of::<IMAGE_FILE_HEADER>() + file_header.SizeOfOptionalHeader as usize;
let mut section_cursor = Cursor::new(&buffer[section_offset..]);
let sections = (0..num_sections)
.map(|_| {
IMAGE_SECTION_HEADER::read(&mut section_cursor)
.map_err(|_| CoffError::InvalidCoffSectionFile)
})
.collect::<Result<Vec<IMAGE_SECTION_HEADER>, _>>()?;
Ok(Self {
file_header,
symbols,
sections,
buffer,
arch,
})
}
#[inline]
fn validate_architecture(file_header: IMAGE_FILE_HEADER) -> Result<CoffMachine, CoffError> {
match file_header.Machine {
COFF_MACHINE_X64 => Ok(CoffMachine::X64),
COFF_MACHINE_X32 => Ok(CoffMachine::X32),
_ => {
warn!("Unsupported COFF architecture: {:?}", file_header.Machine);
Err(CoffError::UnsupportedArchitecture)
}
}
}
pub fn size(&self) -> usize {
let length = self
.sections
.iter()
.filter(|section| section.SizeOfRawData > 0)
.map(|section| Self::page_align(section.SizeOfRawData as usize))
.sum();
let total_length = self
.sections
.iter()
.fold(length, |mut total_length, section| {
let relocations = self.get_relocations(section);
relocations.iter().for_each(|relocation| {
let sym = &self.symbols[relocation.SymbolTableIndex as usize];
let name = self.get_symbol_name(sym);
if name.starts_with("__imp_") {
total_length += size_of::<*const c_void>();
}
});
total_length
});
debug!("Total image size after alignment: {} bytes", total_length);
Self::page_align(total_length)
}
pub fn get_relocations(&self, section: &IMAGE_SECTION_HEADER) -> Vec<IMAGE_RELOCATION> {
let reloc_offset = section.PointerToRelocations as usize;
let num_relocs = section.NumberOfRelocations as usize;
let mut relocations = Vec::with_capacity(num_relocs);
let mut cursor = Cursor::new(&self.buffer[reloc_offset..]);
for _ in 0..num_relocs {
match IMAGE_RELOCATION::read(&mut cursor) {
Ok(reloc) => relocations.push(reloc),
Err(_e) => {
debug!("Failed to read relocation: {_e:?}");
continue;
}
}
}
relocations
}
pub fn get_symbol_name(&self, symtbl: &IMAGE_SYMBOL) -> String {
unsafe {
let name = if symtbl.N.ShortName[0] != 0 {
String::from_utf8_lossy(&symtbl.N.ShortName).into_owned()
} else {
let long_name_offset = symtbl.N.Name.Long as usize;
let string_table_offset = self.file_header.PointerToSymbolTable as usize
+ self.file_header.NumberOfSymbols as usize * size_of::<IMAGE_SYMBOL>();
let offset = string_table_offset + long_name_offset;
let name_ptr = &self.buffer[offset] as *const u8;
CStr::from_ptr(name_ptr.cast())
.to_string_lossy()
.into_owned()
};
name.trim_end_matches('\0').to_string()
}
}
#[inline]
pub fn page_align(page: usize) -> usize {
const SIZE_OF_PAGE: usize = 0x1000;
(page + SIZE_OF_PAGE - 1) & !(SIZE_OF_PAGE - 1)
}
#[inline]
pub fn get_section_name(section: &IMAGE_SECTION_HEADER) -> String {
let s = String::from_utf8_lossy(§ion.Name);
s.trim_end_matches('\0').to_string()
}
#[inline]
pub fn is_fcn(ty: u16) -> bool {
(ty & 0x30) == (2 << 4)
}
}
#[derive(Debug, PartialEq, Hash, Clone, Copy, Eq, PartialOrd, Ord)]
pub enum CoffMachine {
X64,
X32,
}
impl CoffMachine {
#[inline]
pub fn check_architecture(&self) -> Result<(), CoffeeLdrError> {
match self {
CoffMachine::X32 => {
if cfg!(target_pointer_width = "64") {
return Err(CoffeeLdrError::ArchitectureMismatch {
expected: "x32",
actual: "x64",
});
}
}
CoffMachine::X64 => {
if cfg!(target_pointer_width = "32") {
return Err(CoffeeLdrError::ArchitectureMismatch {
expected: "x64",
actual: "x32",
});
}
}
}
Ok(())
}
}
pub enum CoffSource<'a> {
File(&'a str),
Buffer(&'a [u8]),
}
impl<'a> From<&'a str> for CoffSource<'a> {
fn from(file: &'a str) -> Self {
CoffSource::File(file)
}
}
impl<'a, const N: usize> From<&'a [u8; N]> for CoffSource<'a> {
fn from(buffer: &'a [u8; N]) -> Self {
CoffSource::Buffer(buffer)
}
}
impl<'a> From<&'a [u8]> for CoffSource<'a> {
fn from(buffer: &'a [u8]) -> Self {
CoffSource::Buffer(buffer)
}
}
#[binread]
#[derive(Default, Debug, Clone, Copy)]
#[br(little)]
#[repr(C)]
pub struct IMAGE_FILE_HEADER {
pub Machine: u16,
pub NumberOfSections: u16,
pub TimeDateStamp: u32,
pub PointerToSymbolTable: u32,
pub NumberOfSymbols: u32,
pub SizeOfOptionalHeader: u16,
pub Characteristics: u16,
}
#[binread]
#[derive(Clone, Copy)]
#[br(little)]
#[repr(C, packed(2))]
pub struct IMAGE_SYMBOL {
#[br(temp)]
name_raw: [u8; 8],
pub Value: u32,
pub SectionNumber: i16,
pub Type: u16,
pub StorageClass: u8,
pub NumberOfAuxSymbols: u8,
#[br(calc = unsafe {
core::ptr::read_unaligned(name_raw.as_ptr() as *const IMAGE_SYMBOL_0)
})]
pub N: IMAGE_SYMBOL_0,
}
#[repr(C, packed(2))]
#[derive(Clone, Copy)]
pub union IMAGE_SYMBOL_0 {
pub ShortName: [u8; 8],
pub Name: IMAGE_SYMBOL_0_0,
pub LongName: [u32; 2],
}
#[repr(C, packed(2))]
#[derive(Clone, Copy)]
pub struct IMAGE_SYMBOL_0_0 {
pub Short: u32,
pub Long: u32,
}
#[binread]
#[repr(C)]
#[br(little)]
#[derive(Clone, Copy)]
pub struct IMAGE_SECTION_HEADER {
pub Name: [u8; 8],
#[br(temp)]
misc_raw: u32,
pub VirtualAddress: u32,
pub SizeOfRawData: u32,
pub PointerToRawData: u32,
pub PointerToRelocations: u32,
pub PointerToLinenumbers: u32,
pub NumberOfRelocations: u16,
pub NumberOfLinenumbers: u16,
pub Characteristics: u32,
#[br(calc = IMAGE_SECTION_HEADER_0 {
PhysicalAddress: misc_raw
})]
pub Misc: IMAGE_SECTION_HEADER_0,
}
#[repr(C)]
#[derive(Clone, Copy)]
pub union IMAGE_SECTION_HEADER_0 {
pub PhysicalAddress: u32,
pub VirtualSize: u32,
}
#[binread]
#[br(little)]
#[repr(C, packed(2))]
pub struct IMAGE_RELOCATION {
#[br(temp)]
va_raw: u32,
pub SymbolTableIndex: u32,
pub Type: u16,
#[br(calc = IMAGE_RELOCATION_0 {
VirtualAddress: va_raw
})]
pub Anonymous: IMAGE_RELOCATION_0,
}
#[repr(C, packed(2))]
pub union IMAGE_RELOCATION_0 {
pub VirtualAddress: u32,
pub RelocCount: u32,
}