#![doc = include_str!("readme.md")]
pub use self::{
dos::DosHeader,
nt::NtHeader,
tables::{ExportTable, ImportEntry, ImportTable},
};
use gaia_binary::{LittleEndian, ReadBytesExt, WriteBytesExt};
use gaia_types::helpers::Architecture;
use serde::{Deserialize, Serialize};
use std::{
fmt::{Display, Formatter},
io::{Read, Write},
};
pub mod coff;
mod dos;
mod nt;
pub mod tables;
pub use coff::*;
use gaia_types::GaiaError;
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub enum SubsystemType {
Console,
Windows,
Native,
Posix,
WindowsCe,
Efi,
EfiBootServiceDriver,
EfiRuntimeDriver,
EfiRom,
Xbox,
WindowsBootApplication,
}
impl Display for SubsystemType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
SubsystemType::Console => write!(f, "Console application"),
SubsystemType::Windows => write!(f, "Windows GUI application"),
SubsystemType::Native => write!(f, "Native driver"),
SubsystemType::Posix => write!(f, "POSIX subsystem application"),
SubsystemType::WindowsCe => write!(f, "Windows CE subsystem"),
SubsystemType::Efi => write!(f, "EFI application"),
SubsystemType::EfiBootServiceDriver => write!(f, "EFI boot service driver"),
SubsystemType::EfiRuntimeDriver => write!(f, "EFI runtime driver"),
SubsystemType::EfiRom => write!(f, "EFI ROM image"),
SubsystemType::Xbox => write!(f, "Xbox application"),
SubsystemType::WindowsBootApplication => write!(f, "Windows boot application"),
}
}
}
impl From<u16> for SubsystemType {
fn from(value: u16) -> Self {
match value {
1 => SubsystemType::Native,
2 => SubsystemType::Windows,
3 => SubsystemType::Console,
7 => SubsystemType::Posix,
9 => SubsystemType::WindowsCe,
10 => SubsystemType::Efi,
11 => SubsystemType::EfiBootServiceDriver,
12 => SubsystemType::EfiRuntimeDriver,
13 => SubsystemType::EfiRom,
14 => SubsystemType::Xbox,
16 => SubsystemType::WindowsBootApplication,
_ => SubsystemType::Console, }
}
}
impl Default for DataDirectory {
fn default() -> Self {
Self { virtual_address: 0, size: 0 }
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OptionalHeader {
pub magic: u16,
pub major_linker_version: u8,
pub minor_linker_version: u8,
pub size_of_code: u32,
pub size_of_initialized_data: u32,
pub size_of_uninitialized_data: u32,
pub address_of_entry_point: u32,
pub base_of_code: u32,
pub base_of_data: Option<u32>, pub image_base: u64,
pub section_alignment: u32,
pub file_alignment: u32,
pub major_operating_system_version: u16,
pub minor_operating_system_version: u16,
pub major_image_version: u16,
pub minor_image_version: u16,
pub major_subsystem_version: u16,
pub minor_subsystem_version: u16,
pub win32_version_value: u32,
pub size_of_image: u32,
pub size_of_headers: u32,
pub checksum: u32,
pub subsystem: SubsystemType,
pub dll_characteristics: u16,
pub size_of_stack_reserve: u64,
pub size_of_stack_commit: u64,
pub size_of_heap_reserve: u64,
pub size_of_heap_commit: u64,
pub loader_flags: u32,
pub number_of_rva_and_sizes: u32,
pub data_directories: Vec<DataDirectory>,
}
impl OptionalHeader {
pub fn new(
entry_point: u32,
image_base: u64,
size_of_code: u32,
size_of_headers: u32,
size_of_image: u32,
subsystem: SubsystemType,
) -> Self {
let mut data_directories = Vec::with_capacity(16);
for _ in 0..16 {
data_directories.push(DataDirectory::default());
}
Self {
magic: 0x020B, major_linker_version: 14,
minor_linker_version: 0,
size_of_code,
size_of_initialized_data: 0,
size_of_uninitialized_data: 0,
address_of_entry_point: entry_point,
base_of_code: 0x2000,
base_of_data: None, image_base,
section_alignment: 0x2000,
file_alignment: 0x200,
major_operating_system_version: 6,
minor_operating_system_version: 0,
major_image_version: 0,
minor_image_version: 0,
major_subsystem_version: 6,
minor_subsystem_version: 0,
win32_version_value: 0,
size_of_image,
size_of_headers,
checksum: 0,
subsystem,
dll_characteristics: 0x8540, size_of_stack_reserve: 0x100000,
size_of_stack_commit: 0x1000,
size_of_heap_reserve: 0x100000,
size_of_heap_commit: 0x1000,
loader_flags: 0,
number_of_rva_and_sizes: 16,
data_directories,
}
}
pub fn new_for_architecture(
architecture: &Architecture,
entry_point: u32,
image_base: u64,
size_of_code: u32,
size_of_headers: u32,
size_of_image: u32,
subsystem: SubsystemType,
) -> Self {
let mut data_directories = Vec::with_capacity(16);
for _ in 0..16 {
data_directories.push(DataDirectory::default());
}
let (magic, base_of_data) = match architecture {
Architecture::X86 => (0x010B, Some(0x2000)), Architecture::X86_64 => (0x020B, None), _ => (0x010B, Some(0x2000)), };
Self {
magic,
major_linker_version: 14,
minor_linker_version: 0,
size_of_code,
size_of_initialized_data: 0,
size_of_uninitialized_data: 0,
address_of_entry_point: entry_point,
base_of_code: 0x1000,
base_of_data,
image_base,
section_alignment: 0x1000,
file_alignment: 0x200,
major_operating_system_version: 6,
minor_operating_system_version: 0,
major_image_version: 0,
minor_image_version: 0,
major_subsystem_version: 6,
minor_subsystem_version: 0,
win32_version_value: 0,
size_of_image,
size_of_headers,
checksum: 0,
subsystem,
dll_characteristics: 0x8160, size_of_stack_reserve: 0x100000,
size_of_stack_commit: 0x1000,
size_of_heap_reserve: 0x100000,
size_of_heap_commit: 0x1000,
loader_flags: 0,
number_of_rva_and_sizes: 16,
data_directories,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PeHeader {
pub dos_header: DosHeader,
pub nt_header: NtHeader,
pub coff_header: CoffHeader,
pub optional_header: OptionalHeader,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PeSection {
pub name: String,
pub virtual_size: u32,
pub virtual_address: u32,
pub size_of_raw_data: u32,
pub pointer_to_raw_data: u32,
pub pointer_to_relocations: u32,
pub pointer_to_line_numbers: u32,
pub number_of_relocations: u16,
pub number_of_line_numbers: u16,
pub characteristics: u32,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub data: Vec<u8>,
}
#[derive(Debug, Clone, Copy)]
pub struct ReadConfig {
pub include_sections: bool,
pub validate_checksum: bool,
pub parse_imports: bool,
pub parse_exports: bool,
}
impl Default for ReadConfig {
fn default() -> Self {
Self { include_sections: true, validate_checksum: false, parse_imports: true, parse_exports: true }
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PeProgram {
pub header: PeHeader,
pub sections: Vec<PeSection>,
pub imports: ImportTable,
pub exports: ExportTable,
}
impl PeProgram {
pub fn create_executable(machine_code: Vec<u8>) -> Self {
let arch = Architecture::X86_64;
let machine = 0x8664; let section_count = 1;
let entry_point = 0x1000;
let image_base = 0x400000;
let size_of_code = machine_code.len() as u32;
let size_of_headers = 0x200; let size_of_image = 0x2000;
let optional_header = OptionalHeader::new_for_architecture(
&arch,
entry_point,
image_base,
size_of_code,
size_of_headers,
size_of_image,
SubsystemType::Console,
);
let coff_header = CoffHeader::new(machine, section_count)
.with_optional_header_size(if arch == Architecture::X86_64 { 240 } else { 224 })
.with_characteristics(0x0022);
let header =
PeHeader { dos_header: DosHeader::default(), nt_header: NtHeader::default(), coff_header, optional_header };
let section = PeSection {
name: ".text".to_string(),
virtual_size: size_of_code,
virtual_address: 0x1000,
size_of_raw_data: (size_of_code + 0x1FF) & !0x1FF, pointer_to_raw_data: 0x200,
pointer_to_relocations: 0,
pointer_to_line_numbers: 0,
number_of_relocations: 0,
number_of_line_numbers: 0,
characteristics: 0x60000020, data: machine_code,
};
Self { header, sections: vec![section], imports: ImportTable::default(), exports: ExportTable::default() }
}
pub fn with_imports(mut self, imports: ImportTable) -> Self {
if imports.entries.is_empty() {
return self;
}
self.imports = imports;
let is_64bit = self.header.optional_header.magic == 0x020B;
let pointer_size = if is_64bit { 8 } else { 4 };
let base_rva = self.sections.last().map(|s| s.virtual_address + ((s.virtual_size + 0xFFF) & !0xFFF)).unwrap_or(0x1000);
let last_section_offset = self.sections.last().map(|s| s.pointer_to_raw_data + s.size_of_raw_data).unwrap_or(0x200);
let idt_size = (self.imports.entries.len() + 1) * 20;
let mut current_offset = idt_size;
let mut dll_name_offsets = Vec::new();
for entry in &self.imports.entries {
dll_name_offsets.push(current_offset);
current_offset += entry.dll_name.len() + 1;
}
if current_offset % 2 != 0 {
current_offset += 1;
}
let mut function_name_offsets = Vec::new();
for entry in &self.imports.entries {
let mut entry_offsets = Vec::new();
for func in &entry.functions {
entry_offsets.push(current_offset);
current_offset += 2 + func.len() + 1;
if current_offset % 2 != 0 {
current_offset += 1;
}
}
function_name_offsets.push(entry_offsets);
}
current_offset = (current_offset + pointer_size - 1) & !(pointer_size - 1);
let mut int_offsets = Vec::new();
for entry in &self.imports.entries {
int_offsets.push(current_offset);
current_offset += (entry.functions.len() + 1) * pointer_size;
}
let mut iat_offsets = Vec::new();
for entry in &self.imports.entries {
iat_offsets.push(current_offset);
current_offset += (entry.functions.len() + 1) * pointer_size;
}
let idata_size = current_offset;
let idata_raw_size = (idata_size as u32 + 0x1FF) & !0x1FF;
let mut data = vec![0u8; idata_raw_size as usize];
{
use gaia_binary::{LittleEndian, WriteBytesExt};
let mut cursor = std::io::Cursor::new(&mut data);
for (i, _entry) in self.imports.entries.iter().enumerate() {
cursor.write_u32::<LittleEndian>(base_rva + int_offsets[i] as u32).unwrap(); cursor.write_u32::<LittleEndian>(0).unwrap(); cursor.write_u32::<LittleEndian>(0).unwrap(); cursor.write_u32::<LittleEndian>(base_rva + dll_name_offsets[i] as u32).unwrap(); cursor.write_u32::<LittleEndian>(base_rva + iat_offsets[i] as u32).unwrap();
}
for (i, entry) in self.imports.entries.iter().enumerate() {
cursor.set_position(dll_name_offsets[i] as u64);
cursor.write_all(entry.dll_name.as_bytes()).unwrap();
cursor.write_u8(0).unwrap();
}
for (i, entry) in self.imports.entries.iter().enumerate() {
for (j, func) in entry.functions.iter().enumerate() {
cursor.set_position(function_name_offsets[i][j] as u64);
cursor.write_u16::<LittleEndian>(0).unwrap(); cursor.write_all(func.as_bytes()).unwrap();
cursor.write_u8(0).unwrap();
}
}
for (i, entry) in self.imports.entries.iter().enumerate() {
for (j, _) in entry.functions.iter().enumerate() {
let name_rva = base_rva + function_name_offsets[i][j] as u32;
cursor.set_position((int_offsets[i] + j * pointer_size) as u64);
if is_64bit {
cursor.write_u64::<LittleEndian>(name_rva as u64).unwrap();
}
else {
cursor.write_u32::<LittleEndian>(name_rva).unwrap();
}
cursor.set_position((iat_offsets[i] + j * pointer_size) as u64);
if is_64bit {
cursor.write_u64::<LittleEndian>(name_rva as u64).unwrap();
}
else {
cursor.write_u32::<LittleEndian>(name_rva).unwrap();
}
}
}
}
let idata_section = PeSection {
name: ".idata".to_string(),
virtual_size: idata_size as u32,
virtual_address: base_rva,
size_of_raw_data: idata_raw_size,
pointer_to_raw_data: last_section_offset,
pointer_to_relocations: 0,
pointer_to_line_numbers: 0,
number_of_relocations: 0,
number_of_line_numbers: 0,
characteristics: 0xC0000040, data,
};
self.sections.push(idata_section);
self.header.coff_header.number_of_sections = self.sections.len() as u16;
if self.header.optional_header.data_directories.len() >= 2 {
self.header.optional_header.data_directories[1].virtual_address = base_rva;
self.header.optional_header.data_directories[1].size = idata_size as u32;
}
if self.header.optional_header.data_directories.len() >= 13 {
let iat_start_offset = iat_offsets[0];
let iat_total_size = idata_size - iat_start_offset;
self.header.optional_header.data_directories[12].virtual_address = base_rva + iat_start_offset as u32;
self.header.optional_header.data_directories[12].size = iat_total_size as u32;
}
self.header.optional_header.size_of_image = base_rva + ((idata_raw_size + 0xFFF) & !0xFFF);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PeInfo {
pub target_arch: Architecture,
pub subsystem: SubsystemType,
pub entry_point: u32,
pub image_base: u64,
pub section_count: u16,
pub file_size: u64,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct DataDirectory {
pub virtual_address: u32,
pub size: u32,
}
impl DataDirectory {
pub fn read<R: Read>(mut reader: R) -> Result<Self, GaiaError> {
Ok(DataDirectory { virtual_address: reader.read_u32::<LittleEndian>()?, size: reader.read_u32::<LittleEndian>()? })
}
}
impl OptionalHeader {
pub fn read<R: Read>(mut reader: R) -> Result<Self, GaiaError> {
let magic = reader.read_u16::<LittleEndian>()?;
let is_pe32_plus = magic == 0x020B;
let major_linker_version = reader.read_u8()?;
let minor_linker_version = reader.read_u8()?;
let size_of_code = reader.read_u32::<LittleEndian>()?;
let size_of_initialized_data = reader.read_u32::<LittleEndian>()?;
let size_of_uninitialized_data = reader.read_u32::<LittleEndian>()?;
let address_of_entry_point = reader.read_u32::<LittleEndian>()?;
let base_of_code = reader.read_u32::<LittleEndian>()?;
let (base_of_data, image_base) = if is_pe32_plus {
(None, reader.read_u64::<LittleEndian>()?)
}
else {
(Some(reader.read_u32::<LittleEndian>()?), reader.read_u32::<LittleEndian>()? as u64)
};
let section_alignment = reader.read_u32::<LittleEndian>()?;
let file_alignment = reader.read_u32::<LittleEndian>()?;
let major_operating_system_version = reader.read_u16::<LittleEndian>()?;
let minor_operating_system_version = reader.read_u16::<LittleEndian>()?;
let major_image_version = reader.read_u16::<LittleEndian>()?;
let minor_image_version = reader.read_u16::<LittleEndian>()?;
let major_subsystem_version = reader.read_u16::<LittleEndian>()?;
let minor_subsystem_version = reader.read_u16::<LittleEndian>()?;
let win32_version_value = reader.read_u32::<LittleEndian>()?;
let size_of_image = reader.read_u32::<LittleEndian>()?;
let size_of_headers = reader.read_u32::<LittleEndian>()?;
let checksum = reader.read_u32::<LittleEndian>()?;
let subsystem = reader.read_u16::<LittleEndian>()?.into();
let dll_characteristics = reader.read_u16::<LittleEndian>()?;
let (size_of_stack_reserve, size_of_stack_commit, size_of_heap_reserve, size_of_heap_commit) = if is_pe32_plus {
(
reader.read_u64::<LittleEndian>()?,
reader.read_u64::<LittleEndian>()?,
reader.read_u64::<LittleEndian>()?,
reader.read_u64::<LittleEndian>()?,
)
}
else {
(
reader.read_u32::<LittleEndian>()? as u64,
reader.read_u32::<LittleEndian>()? as u64,
reader.read_u32::<LittleEndian>()? as u64,
reader.read_u32::<LittleEndian>()? as u64,
)
};
let loader_flags = reader.read_u32::<LittleEndian>()?;
let number_of_rva_and_sizes = reader.read_u32::<LittleEndian>()?;
let mut data_directories = Vec::new();
for _ in 0..number_of_rva_and_sizes {
data_directories.push(DataDirectory::read(&mut reader)?);
}
Ok(OptionalHeader {
magic,
major_linker_version,
minor_linker_version,
size_of_code,
size_of_initialized_data,
size_of_uninitialized_data,
address_of_entry_point,
base_of_code,
base_of_data,
image_base,
section_alignment,
file_alignment,
major_operating_system_version,
minor_operating_system_version,
major_image_version,
minor_image_version,
major_subsystem_version,
minor_subsystem_version,
win32_version_value,
size_of_image,
size_of_headers,
checksum,
subsystem,
dll_characteristics,
size_of_stack_reserve,
size_of_stack_commit,
size_of_heap_reserve,
size_of_heap_commit,
loader_flags,
number_of_rva_and_sizes,
data_directories,
})
}
}