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