use std::collections::{HashMap, HashSet};
use crate::{
cilassembly::{
changes::AssemblyChanges,
writer::{heaps::HeapRemapping, output::Output},
CilAssembly,
},
file::pe::{OptionalHeader, SectionTable},
prelude::NativeImports,
CilAssemblyView, Error, Result,
};
pub const FILE_ALIGNMENT_DEFAULT: u32 = 0x200;
pub const SECTION_ALIGNMENT_DEFAULT: u32 = 0x2000;
pub const DOS_HEADER_SIZE: u64 = 128;
pub const PE_SIGNATURE_SIZE: u64 = 4;
pub const COFF_HEADER_SIZE: u64 = 20;
pub const SECTION_HEADER_SIZE: u64 = 40;
#[derive(Debug, Clone, Default)]
pub struct SectionWriteInfo {
pub name: String,
pub characteristics: u32,
pub header_offset: u64,
pub data_offset: Option<u64>,
pub rva: Option<u32>,
pub data_size: Option<u32>,
pub removed: bool,
}
pub struct WriteContext<'a> {
pub assembly: &'a CilAssembly,
pub changes: &'a AssemblyChanges,
pub output: Output,
pub position: u64,
pub is_pe32_plus: bool,
pub file_alignment: u32,
pub section_alignment: u32,
pub image_base: u64,
pub dos_header_offset: u64,
pub pe_signature_offset: u64,
pub coff_header_offset: u64,
pub optional_header_offset: u64,
pub section_table_offset: u64,
pub section_count: u16,
pub sections: Vec<SectionWriteInfo>,
pub text_section_offset: u64,
pub text_section_rva: u32,
pub text_section_size: u64,
pub iat_offset: u64,
pub iat_size: u64,
pub cor20_header_offset: u64,
pub method_bodies_offset: u64,
pub method_bodies_size: u64,
pub resource_data_offset: u64,
pub resource_data_size: u64,
pub metadata_offset: u64,
pub metadata_size: u64,
pub tables_stream_offset: u64,
pub tables_stream_size: u64,
pub strings_heap_offset: u64,
pub strings_heap_size: u64,
pub us_heap_offset: u64,
pub us_heap_size: u64,
pub guid_heap_offset: u64,
pub guid_heap_size: u64,
pub blob_heap_offset: u64,
pub blob_heap_size: u64,
pub import_data_offset: Option<u64>,
pub import_data_rva: Option<u32>,
pub import_data_size: Option<u32>,
pub pending_imports: Option<NativeImports>,
pub native_entry_rva: Option<u32>,
pub export_data_offset: Option<u64>,
pub export_data_rva: Option<u32>,
pub export_data_size: Option<u32>,
pub export_table_bytes: Option<Vec<u8>>,
pub original_debug_dir: Option<(u32, u32)>,
pub original_certificate_dir: Option<(u32, u32)>,
pub pe_resource_offset: u64,
pub pe_resource_size: u32,
pub method_body_rva_map: HashMap<u32, u32>,
pub field_data_rva_map: HashMap<u32, u32>,
pub original_method_rva_delta: i32,
pub heap_remapping: HeapRemapping,
pub token_remapping: HashMap<u32, u32>,
pub typedef_rid_remap: HashMap<u32, u32>,
pub typeref_rid_remap: HashMap<u32, u32>,
pub standalonesig_skip: HashSet<u32>,
pub entry_point_token: u32,
pub bytes_written: u64,
pub placeholder_fixups: Vec<PlaceholderFixup>,
pub relocs_stripped: bool,
}
#[derive(Debug, Clone)]
pub struct PlaceholderFixup {
pub file_offset: u64,
pub field_size: usize,
pub placeholder_value: u32,
}
#[derive(Debug, Clone)]
pub struct SectionCopyInfo {
pub source_offset: u32,
pub source_size: u32,
pub source_rva: u32,
pub source_virtual_size: u32,
pub characteristics: u32,
}
impl SectionCopyInfo {
pub fn from_section(section: &SectionTable) -> Self {
Self {
source_offset: section.pointer_to_raw_data,
source_size: section.size_of_raw_data,
source_rva: section.virtual_address,
source_virtual_size: section.virtual_size,
characteristics: section.characteristics,
}
}
}
impl<'a> WriteContext<'a> {
pub fn new(
assembly: &'a CilAssembly,
changes: &'a AssemblyChanges,
output: Output,
) -> Result<Self> {
let view = assembly.view();
let file = view.file();
let optional_header = file
.header_optional()
.as_ref()
.ok_or_else(|| Error::LayoutFailed("Assembly has no optional header".to_string()))?;
let is_pe32_plus = optional_header.standard_fields.magic == 0x20b;
let file_alignment = optional_header.windows_fields.file_alignment;
let section_alignment = optional_header.windows_fields.section_alignment;
let image_base = optional_header.windows_fields.image_base;
let section_count = file.header().number_of_sections;
let entry_point_token = view.cor20header().entry_point_token;
Ok(Self {
assembly,
changes,
output,
position: 0,
is_pe32_plus,
file_alignment,
section_alignment,
image_base,
dos_header_offset: 0,
pe_signature_offset: 0,
coff_header_offset: 0,
optional_header_offset: 0,
section_table_offset: 0,
section_count,
sections: Vec::new(),
text_section_offset: 0,
text_section_rva: section_alignment,
text_section_size: 0,
iat_offset: 0,
iat_size: 0,
cor20_header_offset: 0,
method_bodies_offset: 0,
method_bodies_size: 0,
resource_data_offset: 0,
resource_data_size: 0,
metadata_offset: 0,
metadata_size: 0,
tables_stream_offset: 0,
tables_stream_size: 0,
strings_heap_offset: 0,
strings_heap_size: 0,
us_heap_offset: 0,
us_heap_size: 0,
guid_heap_offset: 0,
guid_heap_size: 0,
blob_heap_offset: 0,
blob_heap_size: 0,
import_data_offset: None,
import_data_rva: None,
import_data_size: None,
pending_imports: None,
native_entry_rva: None,
export_data_offset: None,
export_data_rva: None,
export_data_size: None,
export_table_bytes: None,
original_debug_dir: None,
original_certificate_dir: None,
pe_resource_offset: 0,
pe_resource_size: 0,
method_body_rva_map: HashMap::new(),
field_data_rva_map: HashMap::new(),
original_method_rva_delta: 0,
heap_remapping: HeapRemapping::default(),
token_remapping: HashMap::new(),
typedef_rid_remap: HashMap::new(),
typeref_rid_remap: HashMap::new(),
standalonesig_skip: HashSet::new(),
entry_point_token,
bytes_written: 0,
placeholder_fixups: Vec::new(),
relocs_stripped: false,
})
}
pub fn pos(&self) -> u64 {
self.position
}
pub fn advance(&mut self, amount: u64) {
self.position += amount;
if self.position > self.bytes_written {
self.bytes_written = self.position;
}
}
pub fn align_to(&mut self, alignment: u64) {
let remainder = self.position % alignment;
if remainder != 0 {
self.position += alignment - remainder;
}
}
pub fn align_to_file(&mut self) -> Result<()> {
self.align_to_with_padding(u64::from(self.file_alignment))
}
pub fn align_to_4(&mut self) {
self.align_to(4);
}
pub fn align_to_with_padding(&mut self, alignment: u64) -> Result<()> {
let remainder = self.position % alignment;
if remainder != 0 {
let padding = alignment - remainder;
let padding_usize = usize::try_from(padding).map_err(|_| {
Error::LayoutFailed(format!("Padding {padding} exceeds usize range"))
})?;
let zeros = vec![0u8; padding_usize];
self.write(&zeros)?;
}
Ok(())
}
pub fn align_to_4_with_padding(&mut self) -> Result<()> {
self.align_to_with_padding(4)
}
pub fn write(&mut self, data: &[u8]) -> Result<()> {
self.output.write_at(self.position, data)?;
self.advance(data.len() as u64);
Ok(())
}
pub fn write_at(&mut self, offset: u64, data: &[u8]) -> Result<()> {
self.output.write_at(offset, data)?;
if offset + data.len() as u64 > self.bytes_written {
self.bytes_written = offset + data.len() as u64;
}
Ok(())
}
pub fn write_u16_at(&mut self, offset: u64, value: u16) -> Result<()> {
self.write_at(offset, &value.to_le_bytes())
}
pub fn write_u32_at(&mut self, offset: u64, value: u32) -> Result<()> {
self.write_at(offset, &value.to_le_bytes())
}
pub fn write_u64_at(&mut self, offset: u64, value: u64) -> Result<()> {
self.write_at(offset, &value.to_le_bytes())
}
pub fn offset_to_rva(&self, offset: u64) -> u32 {
if offset >= self.text_section_offset {
let diff = offset - self.text_section_offset;
let diff_u32 = u32::try_from(diff).unwrap_or(u32::MAX);
self.text_section_rva.saturating_add(diff_u32)
} else {
0
}
}
pub fn current_rva(&self) -> u32 {
self.offset_to_rva(self.position)
}
pub fn view(&self) -> &CilAssemblyView {
self.assembly.view()
}
pub fn optional_header_size(&self) -> u64 {
OptionalHeader::size_for_format(self.is_pe32_plus) as u64
}
pub fn find_section_index(&self, name_prefix: &str) -> Option<usize> {
self.sections
.iter()
.position(|s| s.name.starts_with(name_prefix))
}
pub fn update_section(&mut self, index: usize, data_offset: u64, rva: u32, data_size: u32) {
if let Some(section) = self.sections.get_mut(index) {
section.data_offset = Some(data_offset);
section.rva = Some(rva);
section.data_size = Some(data_size);
}
}
pub fn mark_section_removed(&mut self, index: usize) {
if let Some(section) = self.sections.get_mut(index) {
section.removed = true;
}
}
pub fn active_section_count(&self) -> u16 {
self.sections
.iter()
.filter(|s| !s.removed)
.count()
.try_into()
.unwrap_or(0)
}
}
impl std::io::Write for WriteContext<'_> {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.output
.write_at(self.position, buf)
.map_err(|e| std::io::Error::other(e.to_string()))?;
self.advance(buf.len() as u64);
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
self.output
.flush()
.map_err(|e| std::io::Error::other(e.to_string()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_heap_remapping_has_changes() {
let mut remapping = HeapRemapping::default();
assert!(!remapping.has_changes());
remapping.strings.insert(1, 2);
assert!(remapping.has_changes());
}
#[test]
fn test_section_copy_info() {
let section = SectionTable {
name: ".rsrc".to_string(),
virtual_size: 0x1000,
virtual_address: 0x4000,
size_of_raw_data: 0x1000,
pointer_to_raw_data: 0x2000,
pointer_to_relocations: 0,
pointer_to_line_numbers: 0,
number_of_relocations: 0,
number_of_line_numbers: 0,
characteristics: 0x40000040,
};
let info = SectionCopyInfo::from_section(§ion);
assert_eq!(info.source_offset, 0x2000);
assert_eq!(info.source_size, 0x1000);
assert_eq!(info.source_rva, 0x4000);
}
}