From ed90cb0c68fc5891db2db9be16c2cfaa5ac0dbb4 Mon Sep 17 00:00:00 2001 From: Philip Craig Date: Thu, 30 Sep 2021 00:00:00 +0000 Subject: [PATCH] examples: add elftoefi (#388) Includes some changes to `write::pe::Writer. --- crates/examples/Cargo.toml | 4 + crates/examples/src/bin/elftoefi.rs | 347 ++++++++++++++++++++++++++++ crates/examples/src/bin/pecopy.rs | 6 +- src/write/pe.rs | 107 ++++++--- 4 files changed, 431 insertions(+), 33 deletions(-) create mode 100644 crates/examples/src/bin/elftoefi.rs diff --git a/crates/examples/Cargo.toml b/crates/examples/Cargo.toml index 722c925c..66989a09 100644 --- a/crates/examples/Cargo.toml +++ b/crates/examples/Cargo.toml @@ -26,6 +26,10 @@ required-features = ["object/read"] name = "elfcopy" required-features = ["object/read_core", "object/write_core", "object/elf"] +[[bin]] +name = "elftoefi" +required-features = ["object/read_core", "object/write_core", "object/elf", "object/pe"] + [[bin]] name = "objcopy" required-features = ["object/read", "object/write"] diff --git a/crates/examples/src/bin/elftoefi.rs b/crates/examples/src/bin/elftoefi.rs new file mode 100644 index 00000000..9ab3133d --- /dev/null +++ b/crates/examples/src/bin/elftoefi.rs @@ -0,0 +1,347 @@ +use std::error::Error; +use std::{env, fs, process}; + +use object::read::elf::{FileHeader, Rel, Rela, SectionHeader}; +use object::Endianness; +use object::{elf, pe, SectionIndex}; + +fn main() { + let mut args = env::args(); + if args.len() != 3 { + eprintln!("Usage: {} ", args.next().unwrap()); + process::exit(1); + } + + args.next(); + let in_file_path = args.next().unwrap(); + let out_file_path = args.next().unwrap(); + + let in_file = match fs::File::open(&in_file_path) { + Ok(file) => file, + Err(err) => { + eprintln!("Failed to open file '{}': {}", in_file_path, err,); + process::exit(1); + } + }; + let in_data = match unsafe { memmap2::Mmap::map(&in_file) } { + Ok(mmap) => mmap, + Err(err) => { + eprintln!("Failed to map file '{}': {}", in_file_path, err,); + process::exit(1); + } + }; + let in_data = &*in_data; + + let kind = match object::FileKind::parse(in_data) { + Ok(file) => file, + Err(err) => { + eprintln!("Failed to parse file: {}", err); + process::exit(1); + } + }; + let out_data = match kind { + object::FileKind::Elf32 => copy_file::>(in_data).unwrap(), + object::FileKind::Elf64 => copy_file::>(in_data).unwrap(), + _ => { + eprintln!("Not an ELF file"); + process::exit(1); + } + }; + if let Err(err) = fs::write(&out_file_path, out_data) { + eprintln!("Failed to write file '{}': {}", out_file_path, err); + process::exit(1); + } +} + +fn copy_file>( + in_data: &[u8], +) -> Result, Box> { + let in_elf = Elf::parse(in_data)?; + let endian = in_elf.endian()?; + let is_mips64el = in_elf.is_mips64el(endian); + let in_sections = in_elf.sections(endian, in_data)?; + + let text = in_sections.iter().find(|s| is_text(*s, endian)).unwrap(); + let alignment = text.sh_addralign(endian).into(); + + // Calculate text and data layout. + // For now, we use the ELF layout without any change, and require + // sections to be in order of address, and all text sections must + // appear before data sections. + let mut text_start = !0; + let mut text_end = 0; + let mut have_data = false; + let mut data_start = !0; + let mut data_end = 0; + for in_section in in_sections.iter() { + if !is_alloc(in_section, endian) { + continue; + } + assert_eq!(in_section.sh_addralign(endian).into(), alignment); + let start = in_section.sh_addr(endian).into() as u32; + let end = start + in_section.sh_size(endian).into() as u32; + if is_text(in_section, endian) { + assert!(text_end <= start); + if text_start > start { + text_start = start; + } + if text_end < end { + text_end = end; + } + } else if is_data(in_section, endian) { + assert!(data_end <= start); + have_data = true; + if data_start > start { + data_start = start; + } + if data_end < end { + data_end = end; + } + } else { + unreachable!(); + } + } + assert!(text_start <= text_end); + if have_data { + assert!(text_end <= data_start); + assert!(data_start <= data_end); + } + + let machine = match in_elf.e_machine(endian) { + elf::EM_ARM => pe::IMAGE_FILE_MACHINE_THUMB, + elf::EM_AARCH64 => pe::IMAGE_FILE_MACHINE_ARM64, + elf::EM_RISCV => { + if in_elf.is_class_64() { + pe::IMAGE_FILE_MACHINE_RISCV64 + } else { + pe::IMAGE_FILE_MACHINE_RISCV32 + } + } + _ => unimplemented!(), + }; + + let mut out_data = Vec::new(); + let mut writer = object::write::pe::Writer::new( + in_elf.is_type_64(), + alignment as u32, + alignment as u32, + &mut out_data, + ); + + // Add relocations + for in_section in in_sections.iter() { + if let Some((rels, _)) = in_section.rel(endian, in_data)? { + let info_index = SectionIndex(in_section.sh_info(endian) as usize); + let info = in_sections.section(info_index).unwrap(); + if !is_alloc(info, endian) { + continue; + } + for rel in rels { + let r_offset = rel.r_offset(endian).into() as u32; + let r_type = rel.r_type(endian); + match machine { + pe::IMAGE_FILE_MACHINE_THUMB => { + match r_type { + elf::R_ARM_PC24 + | elf::R_ARM_REL32 + | elf::R_ARM_THM_PC22 + | elf::R_ARM_CALL + | elf::R_ARM_JUMP24 + | elf::R_ARM_THM_JUMP24 => { + // Relative relocations can be ignored if relative offsets + // between sections are preserved. + } + elf::R_ARM_ABS32 => { + writer.add_reloc(r_offset, pe::IMAGE_REL_BASED_HIGHLOW); + } + _ => { + unimplemented!("relocation offset {:x}, type {}", r_offset, r_type); + } + } + } + _ => unimplemented!(), + } + } + } else if let Some((relas, _)) = in_section.rela(endian, in_data)? { + let info_index = SectionIndex(in_section.sh_info(endian) as usize); + let info = in_sections.section(info_index).unwrap(); + if !is_alloc(info, endian) { + continue; + } + for rela in relas { + let r_offset = rela.r_offset(endian).into() as u32; + let r_type = rela.r_type(endian, is_mips64el); + match machine { + pe::IMAGE_FILE_MACHINE_ARM64 => { + match r_type { + elf::R_AARCH64_PREL64 + | elf::R_AARCH64_PREL32 + | elf::R_AARCH64_PREL16 + | elf::R_AARCH64_LD_PREL_LO19 + | elf::R_AARCH64_ADR_PREL_LO21 + | elf::R_AARCH64_ADR_PREL_PG_HI21 + | elf::R_AARCH64_CONDBR19 + | elf::R_AARCH64_JUMP26 + | elf::R_AARCH64_CALL26 => { + // Relative relocations can be ignored if relative offsets + // between sections are preserved. + } + elf::R_AARCH64_ADD_ABS_LO12_NC + | elf::R_AARCH64_LDST8_ABS_LO12_NC + | elf::R_AARCH64_LDST16_ABS_LO12_NC + | elf::R_AARCH64_LDST32_ABS_LO12_NC + | elf::R_AARCH64_LDST64_ABS_LO12_NC + | elf::R_AARCH64_LDST128_ABS_LO12_NC => { + // ABS_LO12 relocations can be ignored if sections are aligned + // to pages. + assert!(alignment >= 0x1000); + } + elf::R_AARCH64_ABS64 => { + writer.add_reloc(r_offset, pe::IMAGE_REL_BASED_DIR64); + } + elf::R_AARCH64_ABS32 => { + writer.add_reloc(r_offset, pe::IMAGE_REL_BASED_HIGHLOW); + } + _ => { + unimplemented!("relocation offset {:x}, type {}", r_offset, r_type); + } + } + } + pe::IMAGE_FILE_MACHINE_RISCV64 => { + match r_type { + elf::R_RISCV_BRANCH + | elf::R_RISCV_JAL + | elf::R_RISCV_CALL + | elf::R_RISCV_RVC_BRANCH + | elf::R_RISCV_RVC_JUMP + | elf::R_RISCV_PCREL_HI20 + | elf::R_RISCV_PCREL_LO12_I => { + // Relative relocations can be ignored if relative offsets + // between sections are preserved. + } + elf::R_RISCV_64 => { + writer.add_reloc(r_offset, pe::IMAGE_REL_BASED_DIR64); + } + elf::R_RISCV_32 => { + writer.add_reloc(r_offset, pe::IMAGE_REL_BASED_HIGHLOW); + } + _ => { + unimplemented!("relocation offset {:x}, type {}", r_offset, r_type); + } + } + } + _ => unimplemented!(), + } + } + } + } + + let mut section_num = 1; + if have_data { + section_num += 1; + } + if writer.has_relocs() { + section_num += 1; + } + + // Reserve file ranges and virtual addresses. + writer.reserve_dos_header(); + writer.reserve_nt_headers(16); + writer.reserve_section_headers(section_num); + writer.reserve_virtual_until(text_start); + let text_range = writer.reserve_text_section(text_end - text_start); + assert_eq!(text_range.virtual_address, text_start); + let mut data_range = Default::default(); + if have_data { + writer.reserve_virtual_until(data_start); + // TODO: handle bss + data_range = writer.reserve_data_section(data_end - data_start, data_end - data_start); + assert_eq!(data_range.virtual_address, data_start); + } + if writer.has_relocs() { + writer.reserve_reloc_section(); + } + + // Start writing. + writer.write_empty_dos_header()?; + writer.write_nt_headers(object::write::pe::NtHeaders { + machine, + time_date_stamp: 0, + characteristics: if in_elf.is_class_64() { + pe::IMAGE_FILE_EXECUTABLE_IMAGE + | pe::IMAGE_FILE_LINE_NUMS_STRIPPED + | pe::IMAGE_FILE_LOCAL_SYMS_STRIPPED + | pe::IMAGE_FILE_LARGE_ADDRESS_AWARE + } else { + pe::IMAGE_FILE_EXECUTABLE_IMAGE + | pe::IMAGE_FILE_LINE_NUMS_STRIPPED + | pe::IMAGE_FILE_LOCAL_SYMS_STRIPPED + | pe::IMAGE_FILE_32BIT_MACHINE + }, + major_linker_version: 0, + minor_linker_version: 0, + address_of_entry_point: in_elf.e_entry(endian).into() as u32, + image_base: 0, + major_operating_system_version: 0, + minor_operating_system_version: 0, + major_image_version: 0, + minor_image_version: 0, + major_subsystem_version: 0, + minor_subsystem_version: 0, + subsystem: pe::IMAGE_SUBSYSTEM_EFI_APPLICATION, + dll_characteristics: 0, + size_of_stack_reserve: 0, + size_of_stack_commit: 0, + size_of_heap_reserve: 0, + size_of_heap_commit: 0, + }); + writer.write_section_headers(); + + writer.pad_until(text_range.file_offset); + for in_section in in_sections.iter() { + if !is_text(in_section, endian) { + continue; + } + let offset = (in_section.sh_addr(endian).into() as u32) + .checked_sub(text_range.virtual_address) + .unwrap(); + writer.pad_until(text_range.file_offset + offset); + writer.write(in_section.data(endian, in_data)?); + } + writer.pad_until(text_range.file_offset + text_range.file_size); + if have_data { + for in_section in in_sections.iter() { + if !is_data(in_section, endian) { + continue; + } + let offset = (in_section.sh_addr(endian).into() as u32) + .checked_sub(data_range.virtual_address) + .unwrap(); + writer.pad_until(data_range.file_offset + offset); + writer.write(in_section.data(endian, in_data)?); + } + writer.pad_until(data_range.file_offset + data_range.file_size); + } + writer.write_reloc_section(); + + debug_assert_eq!(writer.reserved_len() as usize, writer.len()); + + Ok(out_data) +} + +// Include both code and read only data in the text section. +fn is_text(s: &S, endian: S::Endian) -> bool { + let flags = s.sh_flags(endian).into() as u32; + flags & elf::SHF_ALLOC != 0 && (flags & elf::SHF_EXECINSTR != 0 || flags & elf::SHF_WRITE == 0) +} + +// Anything that is alloc but not text. +fn is_data(s: &S, endian: S::Endian) -> bool { + let flags = s.sh_flags(endian).into() as u32; + flags & elf::SHF_ALLOC != 0 && flags & elf::SHF_EXECINSTR == 0 && flags & elf::SHF_WRITE != 0 +} + +fn is_alloc(s: &S, endian: S::Endian) -> bool { + let flags = s.sh_flags(endian).into() as u32; + flags & elf::SHF_ALLOC != 0 +} diff --git a/crates/examples/src/bin/pecopy.rs b/crates/examples/src/bin/pecopy.rs index ec17c792..9eeaad40 100644 --- a/crates/examples/src/bin/pecopy.rs +++ b/crates/examples/src/bin/pecopy.rs @@ -71,8 +71,7 @@ fn copy_file(in_data: &[u8]) -> Result, Box(in_data: &[u8]) -> Result, Box Writer<'a> { /// The reserved length will be increased to match the section alignment. /// /// Returns the aligned offset of the start of the range. - fn reserve_virtual(&mut self, len: u32) -> u32 { + pub fn reserve_virtual(&mut self, 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. + pub fn reserve_virtual_until(&mut self, 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. pub fn reserved_len(&self) -> u32 { self.len @@ -133,6 +141,11 @@ impl<'a> Writer<'a> { self.reserve(len, self.file_alignment) } + /// Write data. + pub fn write(&mut self, data: &[u8]) { + self.buffer.write_bytes(data); + } + /// Reserve alignment padding bytes. pub fn reserve_align(&mut self, align_start: u32) { self.len = util::align_u32(self.len, align_start); @@ -143,13 +156,6 @@ impl<'a> Writer<'a> { util::write_align(self.buffer, align_start as usize); } - /// Write data. - /// - /// This is typically used to write section data. - pub fn write(&mut self, data: &[u8]) { - self.buffer.write_bytes(data); - } - /// Reserve the file range up to the given file offset. pub fn reserve_until(&mut self, offset: u32) { debug_assert!(self.len <= offset); @@ -162,20 +168,20 @@ impl<'a> Writer<'a> { self.buffer.resize(offset as usize); } - /// Reserve the range for the DOS header and stub. + /// 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`. pub fn reserve_dos_header(&mut self) { debug_assert_eq!(self.len, 0); self.reserve(mem::size_of::() as u32, 1); } - /// Write the DOS header. + /// Write a custom DOS header. /// /// This must be at the start of the file. - /// - /// Uses default values for all fields. - pub fn write_dos_header(&mut self) -> Result<()> { + pub fn write_custom_dos_header(&mut self, dos_header: &pe::ImageDosHeader) -> Result<()> { debug_assert_eq!(self.buffer.len(), 0); // Start writing. @@ -183,42 +189,73 @@ impl<'a> Writer<'a> { .reserve(self.len as usize) .map_err(|_| Error(String::from("Cannot allocate buffer")))?; - let dos_header = pe::ImageDosHeader { + self.buffer.write(dos_header); + Ok(()) + } + + /// 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. + pub fn write_empty_dos_header(&mut self) -> 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_cblp: U16::new(LE, 0), + e_cp: U16::new(LE, 0), e_crlc: U16::new(LE, 0), - e_cparhdr: U16::new(LE, 4), + e_cparhdr: U16::new(LE, 0), e_minalloc: U16::new(LE, 0), - e_maxalloc: U16::new(LE, 0xffff), + e_maxalloc: U16::new(LE, 0), e_ss: U16::new(LE, 0), - e_sp: U16::new(LE, 0xb8), + 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, 0x40), + 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), - }; - self.buffer.write(&dos_header); - Ok(()) + }) } - /// Reserve a fixed DOS stub. + /// Reserve a fixed DOS header and stub. /// - /// Use `reserve` if you need a custom stub. - pub fn reserve_dos_stub(&mut self) { + /// Use `reserve_dos_header` and `reserve` if you need a custom stub. + pub fn reserve_dos_header_and_stub(&mut self) { + self.reserve_dos_header(); self.reserve(64, 1); } - /// Write a fixed DOS stub. + /// Write a fixed DOS header and stub. /// - /// Use `write` if you need a custom stub. - pub fn write_dos_stub(&mut self) { + /// Use `write_custom_dos_header` and `write` if you need a custom stub. + pub fn write_dos_header_and_stub(&mut self) -> 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), + })?; + #[rustfmt::skip] self.buffer.write_bytes(&[ 0x0e, 0x1f, 0xba, 0x0e, 0x00, 0xb4, 0x09, 0xcd, @@ -230,6 +267,8 @@ impl<'a> Writer<'a> { 0x6d, 0x6f, 0x64, 0x65, 0x2e, 0x0d, 0x0d, 0x0a, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]); + + Ok(()) } fn nt_headers_size(&self) -> u32 { @@ -249,6 +288,11 @@ impl<'a> Writer<'a> { size + self.data_directories.len() as u32 * mem::size_of::() as u32 } + /// Return the offset of the NT headers, if reserved. + pub fn nt_headers_offset(&self) -> u32 { + self.nt_headers_offset + } + /// Reserve the range for the NT headers. pub fn reserve_nt_headers(&mut self, data_directory_num: usize) { debug_assert_eq!(self.nt_headers_offset, 0); @@ -648,6 +692,11 @@ impl<'a> Writer<'a> { }); } + /// Return true if a base relocation has been added. + pub fn has_relocs(&mut self) -> bool { + !self.relocs.is_empty() + } + /// Reserve a `.reloc` section. /// /// This contains the base relocations that were added with `add_reloc`.