From f5a4ce9116e55d5c5c7128e916be5f9ab13bced7 Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 21 Feb 2025 19:28:34 +0200 Subject: [PATCH] report load addr overlaps --- .gitignore | 1 + Cargo.lock | 44 ++++++++++ crates/vestal/Cargo.toml | 1 + crates/vestal/src/bang.rs | 15 ++++ crates/vestal/src/link.rs | 165 ++++++++++++++++++++++++++++++++++++++ crates/vestal/src/load.rs | 10 ++- crates/vestal/src/main.rs | 20 +++++ crates/vestal/src/show.rs | 2 +- crates/vestal/src/util.rs | 23 ++---- 9 files changed, 260 insertions(+), 21 deletions(-) create mode 100644 crates/vestal/src/bang.rs create mode 100644 crates/vestal/src/link.rs diff --git a/.gitignore b/.gitignore index e7e783a..3203204 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ target *.dll *.exe +bin/ diff --git a/Cargo.lock b/Cargo.lock index 3e674a8..04d32ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -88,6 +88,17 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "binary-layout" +version = "4.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66c7e8da2156abef3421f6226ef339ade8c0d157ec50932d5e624f1c6a5127b4" +dependencies = [ + "doc-comment", + "paste", + "thiserror", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -216,6 +227,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "either" version = "1.13.0" @@ -477,6 +494,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pkbuffer" version = "0.4.2" @@ -658,6 +681,26 @@ dependencies = [ "jack", ] +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.98", +] + [[package]] name = "twox-hash" version = "1.6.3" @@ -696,6 +739,7 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" name = "vestal" version = "0.1.0" dependencies = [ + "binary-layout", "clap", "exe", "hexy", diff --git a/crates/vestal/Cargo.toml b/crates/vestal/Cargo.toml index d5ac8f7..a1447a5 100644 --- a/crates/vestal/Cargo.toml +++ b/crates/vestal/Cargo.toml @@ -12,6 +12,7 @@ pretty-hex = "0.4.1" exe = "0.5.6" iced-x86 = "1.21.0" itertools = "0.14.0" +binary-layout = "4.0.2" #elf = "0.7.4" #goblin = "0.9.3" #lancelot = "0.9.7" diff --git a/crates/vestal/src/bang.rs b/crates/vestal/src/bang.rs new file mode 100644 index 0000000..0c2d27d --- /dev/null +++ b/crates/vestal/src/bang.rs @@ -0,0 +1,15 @@ +use crate::*; + +/// You can manually patch DLLs by prepending +/// a `#!/usr/bin/env vestal` line to them. +pub fn slice_shebang (buffer: &[u8]) -> (Arc<[u8]>, Arc<[u8]>) { + if buffer.get(0) == Some(&b'#') && buffer.get(1) == Some(&b'!') { + if let Some((bang, data)) = buffer.split_once(|x|*x==0x0a) { + (bang.to_vec().into(), data.to_vec().into()) + } else { + (buffer.to_vec().into(), vec![].into()) + } + } else { + (vec![].into(), buffer.to_vec().into()) + } +} diff --git a/crates/vestal/src/link.rs b/crates/vestal/src/link.rs new file mode 100644 index 0000000..2ea05d3 --- /dev/null +++ b/crates/vestal/src/link.rs @@ -0,0 +1,165 @@ +//! Create a minimal ELF file manually. +//! +//! Based on reading the [ELF-64 +//! standard](https://uclibc.org/docs/elf-64-gen.pdf) and the [x86-64 +//! architecture supplement](https://uclibc.org/docs/psABI-x86_64.pdf) (for the +//! value `EM_X86_64`, specific to x86-64). +//! +//! Also learned from the classic blog post "[A Whirlwind Tutorial on Creating +//! Really Teensy Elf Executables for +//! Linux](https://www.muppetlabs.com/~breadbox/software/tiny/teensy.html)" and +//! the code in . + +// from https://github.com/tchajed/minimal-elf/blob/main/src/lib.rs + +#![allow(non_camel_case_types)] + +use binary_layout::prelude::*; +use std::io::prelude::*; +use std::path::Path; +use std::{fs::OpenOptions, os::unix::prelude::OpenOptionsExt}; + +type Elf64_Addr = u64; +type Elf64_Off = u64; +type Elf64_Half = u16; +type Elf64_Word = u32; +// type Elf64_Sword = i32; +type Elf64_Xword = u64; +// type Elf64_Sxword = i64; + +pub const VADDR: u64 = 0x400000; +const PROGRAM_OFFSET: u64 = { + // XXX: manually implement unwrap since it isn't stable as a const fn + let sz1 = match elf64_hdr::SIZE { + Some(s) => s, + None => panic!("unsized"), + }; + let sz2 = match elf64_phdr::SIZE { + Some(s) => s, + None => panic!("unsized"), + }; + (sz1 + sz2) as u64 +}; + +define_layout!(elf64_file, LittleEndian, { + hdr: elf64_hdr::NestedView, + phdr: elf64_phdr::NestedView, + program: [u8], +}); +define_layout!(elf64_hdr, LittleEndian, { + ident: elf64_ident::NestedView, + _type: Elf64_Half, + machine: Elf64_Half, + version: Elf64_Word, + entry: Elf64_Addr, // virtual address of entry point + phoff: Elf64_Off, // program header + shoff: Elf64_Off, // section header + flags: Elf64_Word, // processor-specific + ehsize: Elf64_Half, + phentsize: Elf64_Half, + phnum: Elf64_Half, // number of program header entries + shentsize: Elf64_Half, // size of section header entry + shnum: Elf64_Half, // number of section header entries + shstrndx: Elf64_Half, // section name string table index +}); +define_layout!(elf64_phdr, LittleEndian, { + _type: Elf64_Word, + flags: Elf64_Word, + offset: Elf64_Off, + vaddr: Elf64_Addr, + paddr: Elf64_Addr, + filesz: Elf64_Xword, + memsz: Elf64_Xword, + align: Elf64_Xword, +}); +define_layout!(elf64_ident, LittleEndian, { + mag: [u8; 4], + class: u8, + data: u8, + version: u8, + os_abi: u8, + abi_version: u8, + pad: [u8; 7], +}); +fn set_ident + AsMut<[u8]>>(mut view: elf64_ident::View) { + view.mag_mut() + .copy_from_slice(&[0x7f, 'E' as u8, 'L' as u8, 'F' as u8]); + view.class_mut().write(2); // class: ELFCLASS64 + view.data_mut().write(1); // data encoding: ELFDATA2LSB + view.version_mut().write(1); // file version: EV_CURRENT + view.os_abi_mut().write(0); // OS/ABI identification: System V + view.abi_version_mut().write(0); // ABI version: System V third edition + view.pad_mut().copy_from_slice(&[0u8; 7]); +} +fn set_elf64_hdr + AsMut<[u8]>>(mut view: elf64_hdr::View) { + set_ident(view.ident_mut()); + view._type_mut().write(2); // ET_EXEC + view.machine_mut().write(62); // EM_X86_64 + view.version_mut().write(1); // EV_CURRENT + view.entry_mut().write(VADDR + PROGRAM_OFFSET); + view.phoff_mut().write(elf64_hdr::SIZE.unwrap() as u64); + view.flags_mut().write(0); // no processor-specific flags + view.ehsize_mut().write(elf64_hdr::SIZE.unwrap() as u16); + view.phentsize_mut().write(elf64_phdr::SIZE.unwrap() as u16); + view.phnum_mut().write(1); +} +fn set_elf64_phdr(mut view: elf64_phdr::View, program_size: u64) +where + S: AsRef<[u8]> + AsMut<[u8]>, +{ + view._type_mut().write(1); // PT_LOAD + view.flags_mut().write(0x1 | 0x2 | 0x4); // PF_X | PF_W | PF_R + + // location of segment in file + let offset = (elf64_hdr::SIZE.unwrap() + elf64_phdr::SIZE.unwrap()) as u64; + view.offset_mut().write(offset); + // virtual address of segment + view.vaddr_mut().write(VADDR + PROGRAM_OFFSET); + + view.filesz_mut().write(program_size); + view.memsz_mut().write(program_size); + view.align_mut().write(4096); +} +pub fn create_elf(program: &[u8]) -> Vec { + let hdr_sz = elf64_hdr::SIZE.unwrap(); + let phdr_sz = elf64_phdr::SIZE.unwrap(); + let mut buf = vec![0u8; hdr_sz + phdr_sz + program.len()]; + let mut file = elf64_file::View::new(&mut buf); + set_elf64_hdr(file.hdr_mut()); + set_elf64_phdr(file.phdr_mut(), program.len() as u64); + file.program_mut().copy_from_slice(program); + buf +} +//fn create_program() -> Vec { + //use iced_x86::code_asm::*; + //let f = || -> Result<_, IcedError> { + //let mut a = CodeAssembler::new(64)?; + //// push + pop is 2+1 bytes, which is slightly shorter than even mov(eax, 60) + //a.push(60)?; + //a.pop(rax)?; + //// a.mov(eax, 60)?; + //// zero edi in two bytes + //a.xor(edi, edi)?; + //a.syscall()?; + //let bytes = a.assemble(VADDR)?; + //Ok(bytes) + //}; + //f().unwrap() +//} +#[cfg(test)] #[test] fn test_ident_size_ok() { + // XXX: could be a static assertion but Option<>::unwrap() is not a + // const_fn + assert_eq!(16, elf64_ident::SIZE.unwrap()); +} +//#[cfg(test)] #[test] fn test_create_program() { + //let program = create_program(); + //assert_eq!(7, program.len()); +//} +//pub fn write_elf>(path: P) -> std::io::Result<()> { + //let buf = create_elf(&create_program()); + //let mut options = OpenOptions::new(); + //options.write(true).create(true).mode(0o755); + //let mut file = options.open(path)?; + //file.write_all(&buf)?; + //Ok(()) +//} diff --git a/crates/vestal/src/load.rs b/crates/vestal/src/load.rs index 1bff67b..c5e6281 100644 --- a/crates/vestal/src/load.rs +++ b/crates/vestal/src/load.rs @@ -67,8 +67,12 @@ impl Vestal { }, }; imports.push((thunk, orig, import)); - if self.addr_to_import.contains_key(&call_via) { - panic!("addr space overlap"); + if let Some(existing) = self.addr_to_import.get(&call_via) { + panic!("addr space overlap at 0x{call_via:x}: {}::{} vs {}::{}", + existing.0, + existing.1, + dep.to_string(), + name); } self.addr_to_import.insert(call_via, (dep.to_string(), name)); } @@ -84,7 +88,7 @@ impl Vestal { Ok(()) } pub fn load_bang_data (&mut self, path: &Arc) -> Usually> { - let (bang, data) = slice_shebang(read(path.as_path())?.as_slice()); + let (bang, data) = crate::bang::slice_shebang(read(path.as_path())?.as_slice()); self.path_to_bang.insert(path.clone(), bang.clone()); if bang.len() > 0 { println!(" (bang {path:?} {:x})", bang.len()) diff --git a/crates/vestal/src/main.rs b/crates/vestal/src/main.rs index 6ee589c..b658061 100644 --- a/crates/vestal/src/main.rs +++ b/crates/vestal/src/main.rs @@ -2,6 +2,8 @@ mod util; mod load; mod show; +mod link; +mod bang; pub(crate) use self::util::*; use clap::{arg, command, value_parser, ArgAction, Command}; fn main () -> Usually<()> { @@ -49,6 +51,24 @@ impl Vestal { let len = dll_data.len(); println!(" (bytes {len} 0x{len:x})"); self.show_calls(dll_path, dll_path.as_ref() == path)?; + if dll_path.as_ref() == path { + println!("{:?}", dll_data.hex_dump()); + let text_section = dll.get_section_by_name(".text")?; + println!("\n{:#?}", text_section); + let start = text_section.pointer_to_raw_data.0 as usize; + let size = text_section.size_of_raw_data as usize; + let vsize = text_section.virtual_size as usize; + println!("\n{:?}", &dll_data[start..start+size].hex_dump()); + println!("\n{:?}", &dll_data[start..start+vsize].hex_dump()); + let elf = crate::link::create_elf(&dll_data[start..start+vsize]); + println!("\n{:?}", &elf.hex_dump()); + println!("\n{:?}", &elf.len()); + let mut options = std::fs::OpenOptions::new(); + options.write(true).create(true).mode(0o755); + let mut file = options.open("output")?; + file.write_all(&elf)?; + println!("\nDone."); + } total += len; //println!("{:?}", dll_data.hex_dump()); } diff --git a/crates/vestal/src/show.rs b/crates/vestal/src/show.rs index e36fc21..46c3717 100644 --- a/crates/vestal/src/show.rs +++ b/crates/vestal/src/show.rs @@ -99,7 +99,7 @@ impl Vestal { self.addr_to_import.get(&call_target).unwrap_or(&unknown).1); let dependent = path.file_name().unwrap(); if verbose { - println!(" ({BOLD}{external:30}{RESET} O=0x{:08x} {BOLD}R=0x{:08x}{RESET} {:25} {:40} 0x{:08x}", + println!(" ({BOLD}{external}{RESET}\n Offset(0x{:08x}) RVA(R=0x{:08x})\n {:25} {:40} 0x{:08x}", offset, offset_rva, opcodes.iter().map(|x|format!("{x:>02x}")).collect::>().join(" "), diff --git a/crates/vestal/src/util.rs b/crates/vestal/src/util.rs index c217023..c9a8dca 100644 --- a/crates/vestal/src/util.rs +++ b/crates/vestal/src/util.rs @@ -1,9 +1,11 @@ +pub(crate) use std::collections::{BTreeMap, BTreeSet}; +pub(crate) use std::error::Error; +pub(crate) use std::fs::{read, canonicalize}; +pub(crate) use std::io::Write; +pub(crate) use std::os::unix::fs::OpenOptionsExt; +pub(crate) use std::path::{Path, PathBuf}; pub(crate) use std::pin::Pin; pub(crate) use std::sync::Arc; -pub(crate) use std::error::Error; -pub(crate) use std::path::{Path, PathBuf}; -pub(crate) use std::collections::{BTreeMap, BTreeSet}; -pub(crate) use std::fs::{read, canonicalize}; pub(crate) use itertools::izip; //pub(crate) use ::lancelot::loader::pe::{PE, reloc::apply_relocations}; //pub(crate) use ::goblin::{error, Object, pe::{import::Import, export::Export}}; @@ -16,16 +18,3 @@ pub(crate) use ::exe::{Buffer, PE, VecPE, PtrPE, types::*, headers::*}; pub(crate) type Usually = Result>; pub const RESET: &str = "\u{001b}[0m"; pub const BOLD: &str = "\u{001b}[1m"; -/// You can manually patch DLLs by prepending -/// a `#!/usr/bin/env vestal` line to them. -pub fn slice_shebang (buffer: &[u8]) -> (Arc<[u8]>, Arc<[u8]>) { - if buffer.get(0) == Some(&b'#') && buffer.get(1) == Some(&b'!') { - if let Some((bang, data)) = buffer.split_once(|x|*x==0x0a) { - (bang.to_vec().into(), data.to_vec().into()) - } else { - (buffer.to_vec().into(), vec![].into()) - } - } else { - (vec![].into(), buffer.to_vec().into()) - } -}