report load addr overlaps

This commit is contained in:
🪞👃🪞 2025-02-21 19:28:34 +02:00
parent 5490fef835
commit f5a4ce9116
9 changed files with 260 additions and 21 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
target
*.dll
*.exe
bin/

44
Cargo.lock generated
View file

@ -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",

View file

@ -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"

15
crates/vestal/src/bang.rs Normal file
View file

@ -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())
}
}

165
crates/vestal/src/link.rs Normal file
View file

@ -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 <https://github.com/AjayBrahmakshatriya/minimal-elf/>.
// 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<S: AsRef<[u8]> + AsMut<[u8]>>(mut view: elf64_ident::View<S>) {
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<S: AsRef<[u8]> + AsMut<[u8]>>(mut view: elf64_hdr::View<S>) {
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<S>(mut view: elf64_phdr::View<S>, 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<u8> {
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<u8> {
//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<P: AsRef<Path>>(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(())
//}

View file

@ -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<PathBuf>) -> Usually<Arc<[u8]>> {
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())

View file

@ -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());
}

View file

@ -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::<Vec<_>>().join(" "),

View file

@ -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<T> = Result<T, Box<dyn std::error::Error>>;
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())
}
}