//! Helper for writing PE files. use alloc::string::String; use alloc::vec::Vec; use core::mem;
usecrate::endian::{LittleEndian as LE, *}; usecrate::pe; usecrate::write::util; usecrate::write::{Error, Result, WritableBuffer};
/// A helper for writing PE files. /// /// Writing uses a two phase approach. The first phase reserves file ranges and virtual /// address ranges for everything in the order that they will be written. /// /// The second phase writes everything out in order. Thus the caller must ensure writing /// is in the same order that file ranges were reserved. #[allow(missing_debug_implementations)] pubstruct Writer<'a> {
is_64: bool,
section_alignment: u32,
file_alignment: u32,
/// Return the current virtual address size that has been reserved. /// /// This is only valid after section headers have been reserved. pubfn virtual_len(&self) -> u32 { self.virtual_len
}
/// Reserve a virtual address range with the given size. /// /// The reserved length will be increased to match the section alignment. /// /// Returns the aligned offset of the start of the range. pubfn reserve_virtual(&mutself, len: u32) -> u32 { let offset = self.virtual_len; self.virtual_len += len; self.virtual_len = util::align_u32(self.virtual_len, self.section_alignment);
offset
}
/// Reserve up to the given virtual address. /// /// The reserved length will be increased to match the section alignment. pubfn reserve_virtual_until(&mutself, address: u32) {
debug_assert!(self.virtual_len <= address); self.virtual_len = util::align_u32(address, self.section_alignment);
}
/// Return the current file length that has been reserved. pubfn reserved_len(&self) -> u32 { self.len
}
/// Return the current file length that has been written. #[allow(clippy::len_without_is_empty)] pubfn len(&self) -> usize { self.buffer.len()
}
/// Reserve a file range with the given size and starting alignment. /// /// Returns the aligned offset of the start of the range. pubfn reserve(&mutself, len: u32, align_start: u32) -> u32 { if len == 0 { returnself.len;
} self.reserve_align(align_start); let offset = self.len; self.len += len;
offset
}
/// Reserve a file range with the given size and using the file alignment. /// /// Returns the aligned offset of the start of the range. pubfn reserve_file(&mutself, len: u32) -> u32 { self.reserve(len, self.file_alignment)
}
/// Write padding up to the next multiple of file alignment. pubfn write_file_align(&mutself) { self.write_align(self.file_alignment);
}
/// Reserve the file range up to the given file offset. pubfn reserve_until(&mutself, offset: u32) {
debug_assert!(self.len <= offset); self.len = offset;
}
/// Write padding up to the given file offset. pubfn pad_until(&mutself, offset: u32) {
debug_assert!(self.buffer.len() <= offset as usize); self.buffer.resize(offset as usize);
}
/// Reserve the range for the DOS header. /// /// This must be at the start of the file. /// /// When writing, you may use `write_custom_dos_header` or `write_empty_dos_header`. pubfn reserve_dos_header(&mutself) {
debug_assert_eq!(self.len, 0); self.reserve(mem::size_of::<pe::ImageDosHeader>() as u32, 1);
}
/// Write a custom DOS header. /// /// This must be at the start of the file. pubfn write_custom_dos_header(&mutself, dos_header: &pe::ImageDosHeader) -> Result<()> {
debug_assert_eq!(self.buffer.len(), 0);
/// Write the DOS header for a file without a stub. /// /// This must be at the start of the file. /// /// Uses default values for all fields. pubfn write_empty_dos_header(&mutself) -> Result<()> { self.write_custom_dos_header(&pe::ImageDosHeader {
e_magic: U16::new(LE, pe::IMAGE_DOS_SIGNATURE),
e_cblp: U16::new(LE, 0),
e_cp: U16::new(LE, 0),
e_crlc: U16::new(LE, 0),
e_cparhdr: U16::new(LE, 0),
e_minalloc: U16::new(LE, 0),
e_maxalloc: U16::new(LE, 0),
e_ss: U16::new(LE, 0),
e_sp: U16::new(LE, 0),
e_csum: U16::new(LE, 0),
e_ip: U16::new(LE, 0),
e_cs: U16::new(LE, 0),
e_lfarlc: U16::new(LE, 0),
e_ovno: U16::new(LE, 0),
e_res: [U16::new(LE, 0); 4],
e_oemid: U16::new(LE, 0),
e_oeminfo: U16::new(LE, 0),
e_res2: [U16::new(LE, 0); 10],
e_lfanew: U32::new(LE, self.nt_headers_offset),
})
}
/// Reserve a fixed DOS header and stub. /// /// Use `reserve_dos_header` and `reserve` if you need a custom stub. pubfn reserve_dos_header_and_stub(&mutself) { self.reserve_dos_header(); self.reserve(64, 1);
}
/// Write a fixed DOS header and stub. /// /// Use `write_custom_dos_header` and `write` if you need a custom stub. pubfn write_dos_header_and_stub(&mutself) -> Result<()> { self.write_custom_dos_header(&pe::ImageDosHeader {
e_magic: U16::new(LE, pe::IMAGE_DOS_SIGNATURE),
e_cblp: U16::new(LE, 0x90),
e_cp: U16::new(LE, 3),
e_crlc: U16::new(LE, 0),
e_cparhdr: U16::new(LE, 4),
e_minalloc: U16::new(LE, 0),
e_maxalloc: U16::new(LE, 0xffff),
e_ss: U16::new(LE, 0),
e_sp: U16::new(LE, 0xb8),
e_csum: U16::new(LE, 0),
e_ip: U16::new(LE, 0),
e_cs: U16::new(LE, 0),
e_lfarlc: U16::new(LE, 0x40),
e_ovno: U16::new(LE, 0),
e_res: [U16::new(LE, 0); 4],
e_oemid: U16::new(LE, 0),
e_oeminfo: U16::new(LE, 0),
e_res2: [U16::new(LE, 0); 10],
e_lfanew: U32::new(LE, self.nt_headers_offset),
})?;
fn nt_headers_size(&self) -> u32 { ifself.is_64 {
mem::size_of::<pe::ImageNtHeaders64>() as u32
} else {
mem::size_of::<pe::ImageNtHeaders32>() as u32
}
}
fn optional_header_size(&self) -> u32 { let size = ifself.is_64 {
mem::size_of::<pe::ImageOptionalHeader64>() as u32
} else {
mem::size_of::<pe::ImageOptionalHeader32>() as u32
};
size + self.data_directories.len() as u32 * mem::size_of::<pe::ImageDataDirectory>() as u32
}
/// Return the offset of the NT headers, if reserved. pubfn nt_headers_offset(&self) -> u32 { self.nt_headers_offset
}
/// Reserve the range for the NT headers. pubfn reserve_nt_headers(&mutself, data_directory_num: usize) {
debug_assert_eq!(self.nt_headers_offset, 0); self.nt_headers_offset = self.reserve(self.nt_headers_size(), 8); self.data_directories = vec![DataDirectory::default(); data_directory_num]; self.reserve(
data_directory_num as u32 * mem::size_of::<pe::ImageDataDirectory>() as u32, 1,
);
}
/// Set the virtual address and size of a data directory. pubfn set_data_directory(&mutself, index: usize, virtual_address: u32, size: u32) { self.data_directories[index] = DataDirectory {
virtual_address,
size,
}
}
for dir in &self.data_directories { self.buffer.write(&pe::ImageDataDirectory {
virtual_address: U32::new(LE, dir.virtual_address),
size: U32::new(LE, dir.size),
})
}
}
/// Reserve the section headers. /// /// The number of reserved section headers must be the same as the number of sections that /// are later reserved. // TODO: change this to a maximum number of sections? pubfn reserve_section_headers(&mutself, section_header_num: u16) {
debug_assert_eq!(self.section_header_num, 0); self.section_header_num = section_header_num; self.reserve(
u32::from(section_header_num) * mem::size_of::<pe::ImageSectionHeader>() as u32, 1,
); // Padding before sections must be included in headers_len. self.reserve_align(self.file_alignment); self.headers_len = self.len; self.reserve_virtual(self.len);
}
/// Write the section headers. /// /// This uses information that was recorded when the sections were reserved. pubfn write_section_headers(&mutself) {
debug_assert_eq!(self.section_header_num as usize, self.sections.len()); for section in &self.sections { let section_header = pe::ImageSectionHeader {
name: section.name,
virtual_size: U32::new(LE, section.range.virtual_size),
virtual_address: U32::new(LE, section.range.virtual_address),
size_of_raw_data: U32::new(LE, section.range.file_size),
pointer_to_raw_data: U32::new(LE, section.range.file_offset),
pointer_to_relocations: U32::new(LE, 0),
pointer_to_linenumbers: U32::new(LE, 0),
number_of_relocations: U16::new(LE, 0),
number_of_linenumbers: U16::new(LE, 0),
characteristics: U32::new(LE, section.characteristics),
}; self.buffer.write(§ion_header);
}
}
/// Reserve a section. /// /// Returns the file range and virtual address range that are reserved /// for the section. pubfn reserve_section(
&mutself,
name: [u8; 8],
characteristics: u32,
virtual_size: u32,
data_size: u32,
) -> SectionRange { let virtual_address = self.reserve_virtual(virtual_size);
// Padding after section must be included in section file size. let file_size = util::align_u32(data_size, self.file_alignment); let file_offset = if file_size != 0 { self.reserve(file_size, self.file_alignment)
} else { 0
};
let range = SectionRange {
virtual_address,
virtual_size,
file_offset,
file_size,
}; self.sections.push(Section {
name,
characteristics,
range,
});
range
}
/// Write the data for a section. pubfn write_section(&mutself, offset: u32, data: &[u8]) { if data.is_empty() { return;
} self.pad_until(offset); self.write(data); self.write_align(self.file_alignment);
}
/// Reserve an `.idata` section. /// /// Contains import tables. Note that it is permissible to store import tables in a different /// section. /// /// This also sets the `pe::IMAGE_DIRECTORY_ENTRY_IMPORT` data directory. pubfn reserve_idata_section(&mutself, size: u32) -> SectionRange { let range = self.reserve_section(
*b".idata\0\0",
pe::IMAGE_SCN_CNT_INITIALIZED_DATA | pe::IMAGE_SCN_MEM_READ | pe::IMAGE_SCN_MEM_WRITE,
size,
size,
); let dir = &mutself.data_directories[pe::IMAGE_DIRECTORY_ENTRY_IMPORT];
debug_assert_eq!(dir.virtual_address, 0);
*dir = DataDirectory {
virtual_address: range.virtual_address,
size,
};
range
}
/// Reserve an `.edata` section. /// /// Contains export tables. /// /// This also sets the `pe::IMAGE_DIRECTORY_ENTRY_EXPORT` data directory. pubfn reserve_edata_section(&mutself, size: u32) -> SectionRange { let range = self.reserve_section(
*b".edata\0\0",
pe::IMAGE_SCN_CNT_INITIALIZED_DATA | pe::IMAGE_SCN_MEM_READ,
size,
size,
); let dir = &mutself.data_directories[pe::IMAGE_DIRECTORY_ENTRY_EXPORT];
debug_assert_eq!(dir.virtual_address, 0);
*dir = DataDirectory {
virtual_address: range.virtual_address,
size,
};
range
}
/// Reserve a `.pdata` section. /// /// Contains exception information. /// /// This also sets the `pe::IMAGE_DIRECTORY_ENTRY_EXCEPTION` data directory. pubfn reserve_pdata_section(&mutself, size: u32) -> SectionRange { let range = self.reserve_section(
*b".pdata\0\0",
pe::IMAGE_SCN_CNT_INITIALIZED_DATA | pe::IMAGE_SCN_MEM_READ,
size,
size,
); let dir = &mutself.data_directories[pe::IMAGE_DIRECTORY_ENTRY_EXCEPTION];
debug_assert_eq!(dir.virtual_address, 0);
*dir = DataDirectory {
virtual_address: range.virtual_address,
size,
};
range
}
/// Reserve a `.rsrc` section. /// /// Contains the resource directory. /// /// This also sets the `pe::IMAGE_DIRECTORY_ENTRY_RESOURCE` data directory. pubfn reserve_rsrc_section(&mutself, size: u32) -> SectionRange { let range = self.reserve_section(
*b".rsrc\0\0\0",
pe::IMAGE_SCN_CNT_INITIALIZED_DATA | pe::IMAGE_SCN_MEM_READ,
size,
size,
); let dir = &mutself.data_directories[pe::IMAGE_DIRECTORY_ENTRY_RESOURCE];
debug_assert_eq!(dir.virtual_address, 0);
*dir = DataDirectory {
virtual_address: range.virtual_address,
size,
};
range
}
/// Add a base relocation. /// /// `typ` must be one of the `IMAGE_REL_BASED_*` constants. pubfn add_reloc(&mutself, mut virtual_address: u32, typ: u16) { let reloc = U16::new(LE, typ << 12 | (virtual_address & 0xfff) as u16);
virtual_address &= !0xfff; iflet Some(block) = self.reloc_blocks.last_mut() { if block.virtual_address == virtual_address { self.relocs.push(reloc);
block.count += 1; return;
} // Blocks must have an even number of relocations. if block.count & 1 != 0 { self.relocs.push(U16::new(LE, 0));
block.count += 1;
}
debug_assert!(block.virtual_address < virtual_address);
} self.relocs.push(reloc); self.reloc_blocks.push(RelocBlock {
virtual_address,
count: 1,
});
}
/// Return true if a base relocation has been added. pubfn has_relocs(&mutself) -> bool {
!self.relocs.is_empty()
}
/// Reserve a `.reloc` section. /// /// This contains the base relocations that were added with `add_reloc`. /// /// This also sets the `pe::IMAGE_DIRECTORY_ENTRY_BASERELOC` data directory. pubfn reserve_reloc_section(&mutself) -> SectionRange { iflet Some(block) = self.reloc_blocks.last_mut() { // Blocks must have an even number of relocations. if block.count & 1 != 0 { self.relocs.push(U16::new(LE, 0));
block.count += 1;
}
} let size = self.reloc_blocks.iter().map(RelocBlock::size).sum(); let range = self.reserve_section(
*b".reloc\0\0",
pe::IMAGE_SCN_CNT_INITIALIZED_DATA
| pe::IMAGE_SCN_MEM_READ
| pe::IMAGE_SCN_MEM_DISCARDABLE,
size,
size,
); let dir = &mutself.data_directories[pe::IMAGE_DIRECTORY_ENTRY_BASERELOC];
debug_assert_eq!(dir.virtual_address, 0);
*dir = DataDirectory {
virtual_address: range.virtual_address,
size,
}; self.reloc_offset = range.file_offset;
range
}
/// Write a `.reloc` section. /// /// This contains the base relocations that were added with `add_reloc`. pubfn write_reloc_section(&mutself) { ifself.reloc_offset == 0 { return;
} self.pad_until(self.reloc_offset);
letmut total = 0; for block in &self.reloc_blocks { self.buffer.write(&pe::ImageBaseRelocation {
virtual_address: U32::new(LE, block.virtual_address),
size_of_block: U32::new(LE, block.size()),
}); self.buffer
.write_slice(&self.relocs[total..][..block.count as usize]);
total += block.count as usize;
}
debug_assert_eq!(total, self.relocs.len());
self.write_align(self.file_alignment);
}
/// Reserve the certificate table. /// /// This also sets the `pe::IMAGE_DIRECTORY_ENTRY_SECURITY` data directory. // TODO: reserve individual certificates pubfn reserve_certificate_table(&mutself, size: u32) { let size = util::align_u32(size, 8); let offset = self.reserve(size, 8); let dir = &mutself.data_directories[pe::IMAGE_DIRECTORY_ENTRY_SECURITY];
debug_assert_eq!(dir.virtual_address, 0);
*dir = DataDirectory {
virtual_address: offset,
size,
};
}
/// Write the certificate table. // TODO: write individual certificates pubfn write_certificate_table(&mutself, data: &[u8]) { let dir = self.data_directories[pe::IMAGE_DIRECTORY_ENTRY_SECURITY]; self.pad_until(dir.virtual_address); self.write(data); self.pad_until(dir.virtual_address + dir.size);
}
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.