use crate::types::{DataDirectory, DosHeader, NtHeader, OptionalHeader, PeProgram, PeSection, SubsystemType};
use gaia_binary::{LittleEndian, WriteBytesExt};
use gaia_types::GaiaError;
use std::io::{Seek, Write};
pub trait PeWriter<W: Write + Seek> {
fn get_writer(&mut self) -> &mut W;
fn stream_position(&mut self) -> Result<u64, GaiaError> {
Ok(self.get_writer().stream_position()?)
}
fn write_program(&mut self, program: &PeProgram) -> Result<(), GaiaError> {
self.write_dos_header(&program.header.dos_header)?;
self.write_dos_stub()?;
let pe_header_offset = program.header.dos_header.e_lfanew as u64;
self.pad_to_offset(pe_header_offset)?;
self.write_nt_header(&program.header.nt_header)?;
self.write_coff_header(&program.header.coff_header)?;
self.write_optional_header(&program.header.optional_header)?;
for section in &program.sections {
self.write_section_header(section)?;
}
let file_alignment = program.header.optional_header.file_alignment;
self.align_to_boundary(file_alignment)?;
for section in &program.sections {
if section.name == ".idata" && !program.imports.entries.is_empty() {
self.pad_to_offset(section.pointer_to_raw_data as u64)?;
self.write_import_table(program)?;
self.align_to_boundary(file_alignment)?;
}
else if !section.data.is_empty() {
self.pad_to_offset(section.pointer_to_raw_data as u64)?;
self.get_writer().write_all(§ion.data)?;
self.align_to_boundary(file_alignment)?;
}
}
Ok(())
}
fn write_import_table(&mut self, program: &PeProgram) -> Result<(), GaiaError> {
let arch_magic = program.header.optional_header.magic;
let is_64 = arch_magic == 0x20b;
let pointer_size = if is_64 { 8 } else { 4 };
let idata_section = program
.sections
.iter()
.find(|s| s.name == ".idata")
.ok_or_else(|| GaiaError::syntax_error("Missing .idata section", gaia_types::SourceLocation::default()))?;
let import_rva_base = idata_section.virtual_address;
let mut current_rva = import_rva_base + ((program.imports.entries.len() + 1) * 20) as u32;
let mut dll_name_rvas = Vec::new();
for entry in &program.imports.entries {
dll_name_rvas.push(current_rva);
current_rva += (entry.dll_name.len() as u32) + 1;
}
if current_rva % 2 != 0 {
current_rva += 1;
}
let mut hint_name_rvas = Vec::new();
for entry in &program.imports.entries {
let mut entry_hint_name_rvas = Vec::new();
for func in &entry.functions {
if current_rva % 2 != 0 {
current_rva += 1;
}
entry_hint_name_rvas.push(current_rva);
current_rva += 2 + (func.len() as u32) + 1;
}
hint_name_rvas.push(entry_hint_name_rvas);
}
if current_rva % pointer_size != 0 {
current_rva = (current_rva + pointer_size - 1) & !(pointer_size - 1);
}
let mut int_rvas = Vec::new();
for entry in &program.imports.entries {
int_rvas.push(current_rva);
current_rva += ((entry.functions.len() + 1) as u32) * pointer_size;
}
if current_rva % pointer_size != 0 {
current_rva = (current_rva + pointer_size - 1) & !(pointer_size - 1);
}
let mut iat_rvas = Vec::new();
for entry in &program.imports.entries {
iat_rvas.push(current_rva);
current_rva += ((entry.functions.len() + 1) as u32) * pointer_size;
}
for i in 0..program.imports.entries.len() {
let writer = self.get_writer();
writer.write_u32::<LittleEndian>(int_rvas[i])?; writer.write_u32::<LittleEndian>(0)?; writer.write_u32::<LittleEndian>(0)?; writer.write_u32::<LittleEndian>(dll_name_rvas[i])?; writer.write_u32::<LittleEndian>(iat_rvas[i])?; }
{
let writer = self.get_writer();
for _ in 0..5 {
writer.write_u32::<LittleEndian>(0)?;
}
}
for entry in &program.imports.entries {
let writer = self.get_writer();
writer.write_all(entry.dll_name.as_bytes())?;
writer.write_u8(0)?;
}
{
let current_pos = self.stream_position()?;
if (current_pos - idata_section.pointer_to_raw_data as u64) % 2 != 0 {
self.get_writer().write_u8(0)?;
}
}
for entry in &program.imports.entries {
for func in &entry.functions {
let current_pos = self.stream_position()?;
if (current_pos - idata_section.pointer_to_raw_data as u64) % 2 != 0 {
self.get_writer().write_u8(0)?;
}
let writer = self.get_writer();
writer.write_u16::<LittleEndian>(0)?; writer.write_all(func.as_bytes())?;
writer.write_u8(0)?;
}
}
self.pad_to_offset(idata_section.pointer_to_raw_data as u64 + (int_rvas[0] - import_rva_base) as u64)?;
for (i, entry) in program.imports.entries.iter().enumerate() {
for j in 0..entry.functions.len() {
let writer = self.get_writer();
if is_64 {
writer.write_u64::<LittleEndian>(hint_name_rvas[i][j] as u64)?;
}
else {
writer.write_u32::<LittleEndian>(hint_name_rvas[i][j])?;
}
}
let writer = self.get_writer();
if is_64 {
writer.write_u64::<LittleEndian>(0)?;
}
else {
writer.write_u32::<LittleEndian>(0)?;
}
}
self.pad_to_offset(idata_section.pointer_to_raw_data as u64 + (iat_rvas[0] - import_rva_base) as u64)?;
for (i, entry) in program.imports.entries.iter().enumerate() {
for j in 0..entry.functions.len() {
let writer = self.get_writer();
if is_64 {
writer.write_u64::<LittleEndian>(hint_name_rvas[i][j] as u64)?;
}
else {
writer.write_u32::<LittleEndian>(hint_name_rvas[i][j])?;
}
}
let writer = self.get_writer();
if is_64 {
writer.write_u64::<LittleEndian>(0)?;
}
else {
writer.write_u32::<LittleEndian>(0)?;
}
}
Ok(())
}
fn write_dos_header(&mut self, dos_header: &DosHeader) -> Result<(), GaiaError> {
let writer = self.get_writer();
writer.write_u16::<LittleEndian>(dos_header.e_magic)?;
writer.write_u16::<LittleEndian>(dos_header.e_cblp)?;
writer.write_u16::<LittleEndian>(dos_header.e_cp)?;
writer.write_u16::<LittleEndian>(dos_header.e_crlc)?;
writer.write_u16::<LittleEndian>(dos_header.e_cparhdr)?;
writer.write_u16::<LittleEndian>(dos_header.e_min_allocate)?;
writer.write_u16::<LittleEndian>(dos_header.e_max_allocate)?;
writer.write_u16::<LittleEndian>(dos_header.e_ss)?;
writer.write_u16::<LittleEndian>(dos_header.e_sp)?;
writer.write_u16::<LittleEndian>(dos_header.e_check_sum)?;
writer.write_u16::<LittleEndian>(dos_header.e_ip)?;
writer.write_u16::<LittleEndian>(dos_header.e_cs)?;
writer.write_u16::<LittleEndian>(dos_header.e_lfarlc)?;
writer.write_u16::<LittleEndian>(dos_header.e_ovno)?;
for &res in &dos_header.e_res {
writer.write_u16::<LittleEndian>(res)?;
}
writer.write_u16::<LittleEndian>(dos_header.e_oem_id)?;
writer.write_u16::<LittleEndian>(dos_header.e_oem_info)?;
for &res in &dos_header.e_res2 {
writer.write_u16::<LittleEndian>(res)?;
}
writer.write_u32::<LittleEndian>(dos_header.e_lfanew)?;
Ok(())
}
fn write_dos_stub(&mut self) -> Result<(), GaiaError> {
let dos_stub = b"This program cannot be run in DOS mode.\r\n$";
self.get_writer().write_all(dos_stub)?;
while self.stream_position()? < 0x80 {
self.get_writer().write_u8(0)?;
}
Ok(())
}
fn write_nt_header(&mut self, nt_header: &NtHeader) -> Result<(), GaiaError> {
self.get_writer().write_u32::<LittleEndian>(nt_header.signature)?;
Ok(())
}
fn write_coff_header(&mut self, coff_header: &crate::types::coff::CoffHeader) -> Result<(), GaiaError> {
let writer = self.get_writer();
writer.write_u16::<LittleEndian>(coff_header.machine)?;
writer.write_u16::<LittleEndian>(coff_header.number_of_sections)?;
writer.write_u32::<LittleEndian>(coff_header.time_date_stamp)?;
writer.write_u32::<LittleEndian>(coff_header.pointer_to_symbol_table)?;
writer.write_u32::<LittleEndian>(coff_header.number_of_symbols)?;
writer.write_u16::<LittleEndian>(coff_header.size_of_optional_header)?;
writer.write_u16::<LittleEndian>(coff_header.characteristics)?;
Ok(())
}
fn write_optional_header(&mut self, optional_header: &OptionalHeader) -> Result<(), GaiaError> {
let writer = self.get_writer();
writer.write_u16::<LittleEndian>(optional_header.magic)?;
writer.write_u8(optional_header.major_linker_version)?;
writer.write_u8(optional_header.minor_linker_version)?;
writer.write_u32::<LittleEndian>(optional_header.size_of_code)?;
writer.write_u32::<LittleEndian>(optional_header.size_of_initialized_data)?;
writer.write_u32::<LittleEndian>(optional_header.size_of_uninitialized_data)?;
writer.write_u32::<LittleEndian>(optional_header.address_of_entry_point)?;
writer.write_u32::<LittleEndian>(optional_header.base_of_code)?;
if optional_header.magic == 0x20b {
writer.write_u64::<LittleEndian>(optional_header.image_base)?;
}
else {
let base_of_data = optional_header.base_of_data.unwrap_or(0);
writer.write_u32::<LittleEndian>(base_of_data)?;
writer.write_u32::<LittleEndian>(optional_header.image_base as u32)?;
}
writer.write_u32::<LittleEndian>(optional_header.section_alignment)?;
writer.write_u32::<LittleEndian>(optional_header.file_alignment)?;
writer.write_u16::<LittleEndian>(optional_header.major_operating_system_version)?;
writer.write_u16::<LittleEndian>(optional_header.minor_operating_system_version)?;
writer.write_u16::<LittleEndian>(optional_header.major_image_version)?;
writer.write_u16::<LittleEndian>(optional_header.minor_image_version)?;
writer.write_u16::<LittleEndian>(optional_header.major_subsystem_version)?;
writer.write_u16::<LittleEndian>(optional_header.minor_subsystem_version)?;
writer.write_u32::<LittleEndian>(optional_header.win32_version_value)?;
writer.write_u32::<LittleEndian>(optional_header.size_of_image)?;
writer.write_u32::<LittleEndian>(optional_header.size_of_headers)?;
writer.write_u32::<LittleEndian>(optional_header.checksum)?;
let subsystem_value = match optional_header.subsystem {
SubsystemType::Console => 3,
SubsystemType::Windows => 2,
SubsystemType::Native => 1,
_ => 3, };
writer.write_u16::<LittleEndian>(subsystem_value)?;
writer.write_u16::<LittleEndian>(optional_header.dll_characteristics)?;
if optional_header.magic == 0x20b {
writer.write_u64::<LittleEndian>(optional_header.size_of_stack_reserve)?;
writer.write_u64::<LittleEndian>(optional_header.size_of_stack_commit)?;
writer.write_u64::<LittleEndian>(optional_header.size_of_heap_reserve)?;
writer.write_u64::<LittleEndian>(optional_header.size_of_heap_commit)?;
}
else {
writer.write_u32::<LittleEndian>(optional_header.size_of_stack_reserve as u32)?;
writer.write_u32::<LittleEndian>(optional_header.size_of_stack_commit as u32)?;
writer.write_u32::<LittleEndian>(optional_header.size_of_heap_reserve as u32)?;
writer.write_u32::<LittleEndian>(optional_header.size_of_heap_commit as u32)?;
}
writer.write_u32::<LittleEndian>(optional_header.loader_flags)?;
writer.write_u32::<LittleEndian>(optional_header.number_of_rva_and_sizes)?;
for data_dir in &optional_header.data_directories {
self.write_data_directory(data_dir)?;
}
Ok(())
}
fn write_data_directory(&mut self, data_dir: &DataDirectory) -> Result<(), GaiaError> {
let writer = self.get_writer();
writer.write_u32::<LittleEndian>(data_dir.virtual_address)?;
writer.write_u32::<LittleEndian>(data_dir.size)?;
Ok(())
}
fn write_section_header(&mut self, section: &PeSection) -> Result<(), GaiaError> {
let writer = self.get_writer();
let mut name_bytes = [0u8; 8];
let name_len = section.name.len().min(8);
name_bytes[..name_len].copy_from_slice(§ion.name.as_bytes()[..name_len]);
writer.write_all(&name_bytes)?;
writer.write_u32::<LittleEndian>(section.virtual_size)?;
writer.write_u32::<LittleEndian>(section.virtual_address)?;
writer.write_u32::<LittleEndian>(section.size_of_raw_data)?;
writer.write_u32::<LittleEndian>(section.pointer_to_raw_data)?;
writer.write_u32::<LittleEndian>(section.pointer_to_relocations)?;
writer.write_u32::<LittleEndian>(section.pointer_to_line_numbers)?;
writer.write_u16::<LittleEndian>(section.number_of_relocations)?;
writer.write_u16::<LittleEndian>(section.number_of_line_numbers)?;
writer.write_u32::<LittleEndian>(section.characteristics)?;
Ok(())
}
fn pad_to_offset(&mut self, target_offset: u64) -> Result<(), GaiaError> {
let current_pos = self.stream_position()?;
if current_pos < target_offset {
let padding_size = target_offset - current_pos;
for _ in 0..padding_size {
self.get_writer().write_u8(0)?;
}
}
Ok(())
}
fn align_to_boundary(&mut self, alignment: u32) -> Result<(), GaiaError> {
let current_pos = self.stream_position()?;
let remainder = current_pos % alignment as u64;
if remainder != 0 {
let padding = alignment as u64 - remainder;
for _ in 0..padding {
self.get_writer().write_u8(0)?;
}
}
Ok(())
}
}