chore: restructure and update deps
Some checks failed
/ build (push) Has been cancelled

This commit is contained in:
🪞👃🪞 2025-04-06 01:16:30 +03:00
parent 6bc456c814
commit 07f6f82268
22 changed files with 298 additions and 108 deletions

20
core/Cargo.toml Normal file
View file

@ -0,0 +1,20 @@
[package]
name = "vestal"
version = { workspace = true }
edition = { workspace = true }
[dependencies]
tengri = { git = "https://codeberg.org/unspeaker/tengri", rev = "877b344765" }
binary-layout = "4.0.2"
exe = "0.5.6"
hexy = "0.1.4"
iced-x86 = "1.21.0"
itertools = "0.14.0"
object = { version = "0.36.7", features = [ "read_core", "write_core", "elf", "pe" ] }
pretty-hex = "0.4.1"
syscalls = "0.6.18"
#elf = "0.7.4"
#goblin = "0.9.3"
#lancelot = "0.9.7"
#falcon = "0.5.5"

28
core/src/bang.rs Normal file
View file

@ -0,0 +1,28 @@
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())
}
}
//impl Vestal {
//pub fn load_bang_data (&mut self, path: &Arc<PathBuf>) -> Usually<Arc<[u8]>> {
//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())
//}
//self.path_to_data.insert(path.clone(), data.clone());
//println!(" (buffer {:p} 0x{:08x} {path:?})", data.as_ptr(), data.len());
//Ok(data)
//}
//}

207
core/src/call_sites.rs Normal file
View file

@ -0,0 +1,207 @@
use crate::*;
impl Module {
/// Collect all calls that point to imports.
pub fn load_call_sites (self: Arc<Self>, recurse: bool) -> Usually<Arc<Self>> {
self.load_call_sites_slice(0, recurse.then_some(0))?;
Ok(self)
}
/// Collect all calls that point to imports, starting from an address.
fn load_call_sites_slice (
self: &Arc<Self>,
start: usize,
recurse: Option<usize>,
) -> Usually<()> {
let group = false;
//if self.verbose {
//println!(" {DIM}(load-call-sites {} {} {} {RESET}",
//fmt_num(start),
//recurse.unwrap_or(0),
//self.name);
//}
let code = &self.code.as_ref()[start..];
//println!("{:x?}", &code[..64]);
let rip = self.code_base as u64 + start as u64;
let mut decoder = Decoder::with_ip(64, code, rip, 0);
while decoder.can_decode() {
let position = decoder.position();
let instruction = decoder.decode();
let opcodes = &code[position..position+instruction.len()];
let address = self.code_base + start as u32 + position as u32;
//println!("{:15} {} {instruction} {}", self.name, fmt_num(address as usize), fmt_bytes(opcodes));
// Ascend on RET
if let Some(depth) = recurse {
if depth > 0 && opcodes[0] == 0xc3 {
break
}
if depth > 0 && opcodes == &[0x66, 0x90] {
break
}
if depth > 0 && opcodes == &[0x90] {
break
}
}
// Descend on CALL/JMP
if CallSite::matches(&instruction) && !CallSite::skip(opcodes) {
let call_site = self.call_site(start + position, opcodes)?;
//if !self.targets.read().unwrap().contains_key(&call_site.target) {
Log::call_site(self.verbose && !group, &call_site, Some(opcodes), recurse);
//}
self.load_call_site(&call_site);
if let Some(depth) = recurse {
self.load_call_site_recurse(&call_site, depth)?;
}
}
}
//Log::call_sites(self.verbose && group, &self.name, &self.targets.read().unwrap(), recurse);
Ok(())
}
/// Annotate a given instruction in the code section as a [CallSite].
fn call_site (self: &Arc<Self>, position: usize, opcodes: &[u8]) -> Usually<Arc<CallSite>> {
let group = false;
let offset = (position + self.code_start) as u32;
let source = self.pe.offset_to_rva(Offset(offset))?.0;
let target = CallSite::target(source, opcodes).unwrap_or(0);
let import = self.imports.read().unwrap().get(&target).cloned();
let call_site = Arc::new(CallSite {
caller: self.clone(),
source,
offset,
target,
length: opcodes.len(),
method: import.clone().map(|x|x.1),
module: if let Some(import) = import {
self.dependencies.read().unwrap().get(&import.0).cloned()
} else {
None
}
});
Ok(call_site)
}
/// Add a [CallSite] under the appropriate target
fn load_call_site (&self, call_site: &Arc<CallSite>) {
let mut targets = self.targets.write().unwrap();
if !targets.contains_key(&call_site.target) {
targets.insert(call_site.target, vec![]);
}
targets.get_mut(&call_site.target).unwrap().push(call_site.clone());
let mut call_sites = self.call_sites.write().unwrap();
call_sites.insert(call_site.source, call_site.clone());
}
/// Follow the call site and resolve the next call site found in the dependency.
fn load_call_site_recurse (self: &Arc<Self>, call_site: &Arc<CallSite>, depth: usize) -> Usually<()> {
let CallSite { module, method, .. } = call_site.as_ref();
if let (Some(module), Some(method)) = (module, method) {
if let Some(method) = module.exports.read().unwrap().get(method) {
module.load_call_site_recurse_export(method, depth)?;
}
}
Ok(())
}
/// Follow a function call thunk.
fn load_call_site_recurse_export (self: &Arc<Self>, method: &ThunkData, depth: usize) -> Usually<()> {
match method {
ThunkData::Function(rva) => {
self.load_call_site_recurse_function(rva, depth + 1)?;
},
ThunkData::ForwarderString(rva) => {
self.load_call_site_recurse_forward(rva, depth + 1)?;
},
x => {
unimplemented!("{x:?}");
}
}
Ok(())
}
/// Follow a function call site.
fn load_call_site_recurse_function (self: &Arc<Self>, rva: &RVA, depth: usize) -> Usually<()> {
let index = (rva.0 - self.code_base) as usize;
let slice = &self.code[index..index+64];
self.load_call_sites_slice(index, Some(depth))?;
Ok(())
}
/// Follow a forwarded call site.
fn load_call_site_recurse_forward (self: &Arc<Self>, rva: &RVA, depth: usize) -> Usually<()> {
if let Some((mut module_name, method_name)) = self.resolve_forward(rva)? {
let mut name = module_name.to_lowercase();
if !name.ends_with(".dll") {
name = format!("{name}.dll");
}
if let Some(module) = self.dependencies.read().unwrap().get(name.as_str()) {
if let Some(method) = module.exports.read().unwrap().get(&method_name) {
module.load_call_site_recurse_export(method, depth)?;
}
}
//panic!("{} {name}::{method_name}", self.name);
} else {
panic!("unresolved fwd {rva:x?} {}", self.read_forward(rva)?);
}
Ok(())
}
pub fn read_forward (&self, rva: &RVA) -> Usually<Arc<str>> {
let mut address = self.pe.rva_to_offset(*rva)?.0 as usize;
let mut forward = vec![];
while let Some(c) = self.pe.as_slice().get(address) {
if *c == 0x00 {
break
}
forward.push(*c);
address += 1;
}
Ok(String::from_utf8(forward)?.as_str().into())
}
pub fn resolve_forward (&self, rva: &RVA) -> Usually<Option<(Arc<str>, Arc<str>)>> {
Ok(self.read_forward(rva)?.split_once(".").map(|(x, y)|(x.into(), y.into())))
}
}
impl CallSite {
pub fn matches (instruction: &Instruction) -> bool {
instruction.op0_kind() == OpKind::Memory && (
instruction.flow_control() == FlowControl::IndirectBranch ||
instruction.flow_control() == FlowControl::IndirectCall
)
}
pub fn skip (opcodes: &[u8]) -> bool {
match opcodes[0] {
0x41 | 0x42 | 0x43 | 0x49 => match opcodes[1] {
0xff => return true,
_ => {}
},
0x48 => match opcodes[2] {
0x20 | 0x60 | 0x62 | 0xa0 | 0xa2 => return true,
_ => {}
},
0xff => match opcodes[1] {
0x10 | 0x12 | 0x13 | 0x16 |
0x50 | 0x51 | 0x52 | 0x53 | 0x54 | 0x55 | 0x56 | 0x57 |
0x60 | 0x90 | 0x92 | 0x93 | 0x94 | 0x97 => return true,
_ => {}
},
_ => {}
}
false
}
pub fn target (address: u32, opcodes: &[u8]) -> Option<u32> {
let rip = address + opcodes.len() as u32;
match opcodes[0] {
0xff => match opcodes[1] {
0x15 | 0x25 => return Some(rip + u32::from_le_bytes([
opcodes[2], opcodes[3], opcodes[4], opcodes[5]
])),
_ => {}
},
0x48 => match opcodes[1] {
0xff => match opcodes[2] {
0x15 | 0x25 => return Some(rip + u32::from_le_bytes([
opcodes[3], opcodes[4], opcodes[5], opcodes[6]
])),
_ => {}
},
_ => {}
}
_ => {}
}
None
}
}

165
core/src/elf.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(())
//}

104
core/src/exec.rs Normal file
View file

@ -0,0 +1,104 @@
use crate::*;
use elf::file::Elf64_Ehdr;
use std::fmt::{Debug, Display};
use syscalls::{Sysno, Errno, syscall};
pub fn relink (context: &crate::dll::PEContext) -> Usually<()> {
Ok(())
}
static EMPTY: &'static [u8] = &[b'\0'];
static CMD: &'static [u8] = &[b'/',b'p',b'r',b'o',b'c',b'/',b's',b'e',b'l',b'f',b'/',b'f',b'd',b'/',b'3',b'\0'];
static ARGS: &'static [u8] = &[b'\0',b'\0'];
static ENVS: &'static [u8] = &[b'\0'];
pub struct MemRun(usize);
#[derive(Debug)]
pub enum MemRunError {
MemfdCreateFailed(Errno),
ExecveFailed(Errno),
}
impl MemRun {
pub fn new () -> Result<Self, MemRunError> {
unsafe { syscall!(Sysno::memfd_create, EMPTY.as_ptr(), 0x0001, 0) }
.map_err(MemRunError::MemfdCreateFailed)
.map(Self)
}
pub fn run (code: &[u8]) -> Result<(), MemRunError> {
let cmd = b"/proc/self/fd/3\0";
let arg = [b"custom process name\0"];
let env = [b"\0"];
unsafe { syscall!(Sysno::execve, CMD.as_ptr(), ARGS.as_ptr(), ENVS.as_ptr()) }
.map_err(MemRunError::ExecveFailed)
.map(|_|())
}
}
// From https://github.com/guitmz/memrun/
fn get_fd () -> usize {
match unsafe { syscall!(Sysno::memfd_create, EMPTY.as_ptr(), 0x0001, 0) } {
Err(no) => panic!("memfd_create failed: {no}"),
Ok(fd) => fd,
}
}
// From https://github.com/guitmz/memrun/
fn write (fd: usize, buffer: &[u8]) {
match unsafe { syscall!(Sysno::write, fd, buffer.as_ptr()) } {
Err(no) => panic!("write failed: {no}"),
Ok(_) => (),
}
}
// From https://github.com/guitmz/memrun/
fn run (fd: usize) {
println!("fd = {fd}");
let cmd = b"/proc/self/fd/3\0";
let arg = [b"it is i, leclerc\0"];
let env = [b"\0"];
match unsafe { syscall!(Sysno::execve, cmd.as_ptr(), arg.as_ptr(), env.as_ptr()) } {
Err(no) => panic!("write failed: {no}"),
Ok(_) => (),
}
}
fn make_elf () -> Vec<u8> {
let mut buffer = vec![0;1024*1024*1024];
// https://wiki.osdev.org/ELF#ELF_Header
buffer[0x00..0x40].copy_from_slice(any_as_u8_slice(&Elf64_Ehdr {
e_ehsize: 0x40, // elf header size,
e_ident: [ // identification data
0x7f, b'E', b'L', b'F', // magic bytes
0x02, // 64-bit, 0x01 is 32-bit
0x01, // little-endian
0x01, // ELF header version
0x00, // SysV ABI
0x00, 0x00, 0x00, 0x00, // unused
0x00, 0x00, 0x00, 0x00, // unused
],
e_version: 0x01, // ELF version
e_type: 0x02, // executable
e_machine: 0x3e, // x86_64, 0x03 = x86
e_flags: 0x00, // TODO machine flags,
e_entry: 0x00, // TODO entry point (from wrapper?)
e_phnum: 0x00, // TODO program headers
e_phentsize: 0x00, // TODO why is there phent in my elf?
e_phoff: 0x00, // TODO program header table offset
e_shnum: 0x00, // TODO section headers
e_shentsize: 0x00, // TODO section header entry size
e_shoff: 0x00, // TODO section header table offset
e_shstrndx: 0x00, // TODO section header table index something something
}));
buffer
}
/// From https://stackoverflow.com/a/42186553
pub fn any_as_u8_slice<T: Sized>(p: &T) -> &[u8] {
unsafe {
::core::slice::from_raw_parts(
(p as *const T) as *const u8,
::core::mem::size_of::<T>(),
)
}
}

14
core/src/exports.rs Normal file
View file

@ -0,0 +1,14 @@
use crate::*;
impl Module {
/// Collect all exported methods.
pub fn load_exports (self: Arc<Self>) -> Usually<Arc<Self>> {
if self.verbose {
println!(" {DIM}(load-exports){RESET}");
}
let directory = ExportDirectory::parse(self.pe.as_ref())?;
let export_map = directory.get_export_map(self.pe.as_ref())?;
let exports = export_map.into_iter().map(|(k, v)|(k.into(), v)).collect();
*self.exports.write().unwrap() = exports;
Ok(self)
}
}

109
core/src/imports.rs Normal file
View file

@ -0,0 +1,109 @@
use crate::*;
impl Module {
/// Collect all imported modules and methods.
pub fn load_imports (self: Arc<Self>, recurse: bool) -> Usually<Arc<Self>> {
if self.verbose {
println!(" {DIM}(load-imports){RESET}");
}
let pe = self.pe.clone();
let directory = ImportDirectory::parse(pe.as_ref());
if let Err(e) = directory {
println!("failed to parse import directory of {}: {e:?}", &self.name);
return Ok(self)
}
let mut new_modules = vec![];
for descriptor in directory.unwrap().descriptors.iter() {
let (module_name, imports) = Self::parse_import(pe.as_ref(), descriptor)?;
let create = !self.namespace.read().unwrap().contains_key(&module_name);
if create {
Log::dep(false, &module_name, &imports);
let path = self.find(&module_name)?.expect("not found");
let module = Arc::new(Self {
namespace: self.namespace.clone(),
search_paths: self.search_paths.clone(),
..Self::from_path(&path, self.verbose)?
});
assert_eq!(module_name, module.name);
self.namespace.write().unwrap().insert(module.name.clone(), module.clone());
}
let module = self.namespace.read().unwrap().get(&module_name).unwrap().clone();
self.dependencies.write().unwrap().insert(module.name.clone(), module.clone());
for (call_via, method_name) in imports {
self.load_import(call_via, &module_name, &method_name);
}
if create {
new_modules.push(module.clone());
}
if self.verbose {
if create {
println!(" (dep {:?})", &module.name);
} else {
println!(" {DIM}(dep {:?}){RESET}", &module.name);
}
}
}
if recurse {
for module in new_modules {
module.load(recurse)?;
}
}
Ok(self)
}
fn load_import (
&self, call_via: u32, module_name: &Arc<str>, method_name: &Arc<str>
) {
if self.imports.read().unwrap().contains_key(&call_via) {
panic!("duplicate address {call_via} from {module_name}");
}
self.imports.write().unwrap().insert(call_via, (
module_name.clone(),
method_name.clone(),
));
}
/// Collect an imported dependency's descriptor thunks into a temporary [Vec].
fn parse_import (pe: &VecPE, descriptor: &ImageImportDescriptor)
-> Usually<(Arc<str>, Vec<(u32, Arc<str>)>)>
{
Ok((
descriptor.get_name(pe)?.as_str()?.to_lowercase().into(),
descriptor.get_imports(pe)?.iter().enumerate().map(|(index, import)|(
descriptor.first_thunk.0 + index as u32 * 8,
match import {
ImportData::Ordinal(x) => format!("___VESTAL_ORDINAL_{x}"),
ImportData::ImportByName(name) => format!("{name}"),
}.into()
)).collect()
))
}
fn load_dependency (
&mut self,
module_name: &Arc<str>,
method_name: &Arc<str>,
call_via: u32,
) -> (usize, usize) {
let mut modules = 0;
let mut methods = 0;
//if !self.dependencies.contains_key(module_name) {
//self.dependencies.insert(module_name.clone(), Default::default());
//modules += 1;
//}
//let dependency = self.dependencies.get_mut(module_name).unwrap();
//if dependency.exports.contains_key(method_name) {
//panic!("duplicate method {method_name} in {module_name}");
//}
//dependency.insert(method_name.clone(), call_via);
//methods += 1;
//if verbose {
//if modules > 0 {
//println!(" {BOLD}(import {module_name}){RESET}");
//}
//if methods > 0 {
//println!(" (import 0x{call_via:08x} {module_name}::{method_name})");
//}
//}
(modules, methods)
}
}

197
core/src/lib.rs Normal file
View file

@ -0,0 +1,197 @@
//! Takes a 64-bit VST2 in PE format and attempts to
//! relink it with Wine and a custom shim, resulting in
//! a standalone, self-contained 64-bit ELF JACK device.
#![feature(slice_split_once)]
mod util; pub(crate) use self::util::*;
mod show; pub(crate) use self::show::*;
mod imports; pub(crate) use self::imports::*;
mod exports; pub(crate) use self::exports::*;
mod call_sites; pub(crate) use self::call_sites::*;
mod bang;
/// A DLL that will be linked into the output.
pub struct Module {
/// Scope.
pub namespace: Arc<RwLock<BTreeMap<Arc<str>, Arc<Self>>>>,
/// Search paths for resolving DLL names.
pub search_paths: Arc<RwLock<BTreeSet<Arc<PathBuf>>>>,
/// Canonical name like `xxx.dll` (always lowercase)
pub name: Arc<str>,
/// Path to DLL on host filesystem.
pub path: Arc<PathBuf>,
/// Bytes of `#!`-instruction
pub bang: Arc<[u8]>,
/// Parsed portable executable
pub pe: Arc<VecPE>,
/// Assumed address in memory
pub code_base: u32,
/// Bytes of `.text` section
pub code: Arc<[u8]>,
/// Start of `.text` section
pub code_start: usize,
/// Size of `.text` section
pub code_size: usize,
/// Modules that this one depends on.
pub dependencies: RwLock<BTreeMap<Arc<str>, Arc<Self>>>,
/// Call targets to methods from imported modules.
pub imports: RwLock<BTreeMap<u32, (Arc<str>, Arc<str>)>>,
/// Mapping of call target to its invocations.
pub targets: RwLock<BTreeMap<u32, Vec<Arc<CallSite>>>>,
/// Addresses of exported methods by name
pub exports: RwLock<BTreeMap<Arc<str>, ThunkData>>,
/// Locations in `.text` section that need to be patched
pub call_sites: RwLock<BTreeMap<u32, Arc<CallSite>>>,
/// More detailed output.
pub verbose: bool,
}
/// A call from one DLL to another.
#[derive(Debug)]
pub struct CallSite {
/// Module that is making the call
pub caller: Arc<Module>,
/// Address in memory
pub source: u32,
/// Address on disk
pub offset: u32,
/// Length of link in opcodes
pub length: usize,
/// CallSite trampoline address
pub target: u32,
/// Module that is being called
pub module: Option<Arc<Module>>,
/// Name of method that is being called
pub method: Option<Arc<str>>,
}
impl Module {
/// Construct a cross-linkable library.
///
/// Loads the PE but does not
/// extract any imports/exports/callsites.
/// For that, invoke the [Module::load] method.
pub fn from_path (path: impl AsRef<Path>, verbose: bool) -> Usually<Self> {
Log::load(verbose, &path);
let (pe, data, bang) = read_pe(&path)?;
let (code, code_start, code_size) = read_code(&pe)?;
Ok(Self {
bang,
path: path.as_ref().to_path_buf().into(),
code,
code_start,
code_size,
code_base: base_of_code(&pe)?,
namespace: Default::default(),
search_paths: Default::default(),
dependencies: Default::default(),
imports: Default::default(),
targets: Default::default(),
exports: Default::default(),
call_sites: Default::default(),
pe,
verbose,
name: Arc::from(path.as_ref()
.file_name()
.expect("no file name")
.to_str()
.expect("non-unicode filename")),
})
}
/// Add a search path
pub fn search (self: Arc<Self>, path: impl AsRef<Path>) -> Arc<Self> {
Log::add(self.verbose, &path);
self.search_paths.write().unwrap().insert(path.as_ref().to_path_buf().into());
self
}
/// Search for DLL by name in search paths.
pub fn find (&self, name: &impl AsRef<str>) -> Usually<Option<PathBuf>> {
let name = name.as_ref();
for base in self.search_paths.read().unwrap().iter() {
let mut path = base.as_ref().clone();
path.push(name.to_lowercase());
Log::find(false, &name, &path);
if std::fs::exists(&path)? {
Log::found(false, &name, &path);
return Ok(Some(canonicalize(&path)?))
}
}
Ok(None)
}
/// Load the dependency tree reachable from this module.
pub fn load (self: Arc<Self>, recurse: bool) -> Usually<Arc<Self>> {
let module = self.load_exports()?;
let module = module.load_imports(recurse)?;
if module.verbose {
println!("{:?}", &module);
}
Ok(module)
}
/// Identify call sites and try to match them with their new locations.
pub fn resolve (self: Arc<Self>, recurse: bool) -> Usually<Arc<Self>> {
let module = self.load_call_sites(recurse)?;
if module.verbose {
println!("{:?}", &module);
}
Ok(module)
}
/// Build an ELF executable out of the DLL
pub fn relink (self: Arc<Self>) -> Usually<Arc<Self>> {
let page = 32*1024;
let mut size = 0;
for module in self.namespace.read().unwrap().values() {
size += module.code_size.div_ceil(page);
}
println!("\n{}", fmt_num(size*page));
let mut layout: BTreeMap<usize, Arc<Self>> = Default::default();
let mut index = 0;
let mut append = |module: &Arc<Self>|{
layout.insert(index, self.clone());
index += module.code_size.div_ceil(page) * page;
};
append(&self);
for module in self.namespace.read().unwrap().values() {
if module.name == self.name {
continue;
}
append(module);
}
for (address, module) in layout.iter() {
println!("{BOLD}{}-{} {:16}{RESET}",
fmt_num(index),
fmt_num(index + module.code_size),
module.name);
for (addr, call) in module.call_sites.read().unwrap().iter() {
let addr = *addr as usize;
println!("{} {DIM}{} ╰->{RESET} {}::{}",
fmt_num(index + addr),
fmt_num(addr),
call.module.as_ref().map(|m|m.name.clone()).unwrap_or("???".into()),
call.method.as_ref().map(|m|m.clone()).unwrap_or("???".into()));
}
}
let mut output = vec![0x90;size];
Ok(self)
}
}
fn read_pe (path: &impl AsRef<Path>) -> Usually<(Arc<VecPE>, Arc<[u8]>, Arc<[u8]>)> {
let (bang, data) = crate::bang::slice_shebang(read(path.as_ref())?.as_slice());
let pe = Arc::new(VecPE::from_disk_data(data.clone()));
Ok((pe, data, bang))
}
fn read_code (pe: &VecPE) -> Usually<(Arc<[u8]>, usize, usize)> {
if let Ok(code) = pe.get_section_by_name(".text") {
let code = pe.get_section_by_name(".text")?;
let start = code.pointer_to_raw_data.0 as usize;
let size = code.size_of_raw_data as usize;
let text = &pe.as_slice()[start..start+size];
Ok((text.into(), start, size))
} else {
Ok(([].into(), 0, 0))
}
}
fn base_of_code (pe: &VecPE) -> Usually<u32> {
Ok(match pe.get_valid_nt_headers()? {
NTHeaders::NTHeaders32(h32) => panic!("32 bit headers"),
NTHeaders::NTHeaders64(h64) => h64.optional_header.base_of_code.0,
})
}

320
core/src/show.rs Normal file
View file

@ -0,0 +1,320 @@
use crate::*;
use std::fmt::Write;
pub fn fmt_bytes (bytes: &[u8]) -> Arc<str> {
bytes.iter().map(|x|format!("{x:02x}")).join(" ").into()
}
pub fn fmt_num (n: usize) -> Arc<str> {
format!("0x{n:>08x}").into()
}
pub struct Show;
pub struct Log;
impl Log {
pub fn add (show: bool, path: &impl AsRef<Path>) {
if show {
println!("(search {:?})", path.as_ref())
}
}
pub fn load (show: bool, path: &impl AsRef<Path>) {
if show {
println!("{DIM}(load {:?}){RESET}", path.as_ref());
}
}
pub fn dep (show: bool, name: &impl AsRef<str>, exports: &[(u32, Arc<str>)]) {
if show {
println!("(dep {:?} {})", name.as_ref(), exports.len());
}
}
pub fn find (show: bool, name: &impl AsRef<str>, path: &impl AsRef<Path>) {
if show {
println!("(find? {} {:?})", name.as_ref(), path.as_ref());
}
}
pub fn found (show: bool, name: &impl AsRef<str>, path: &impl AsRef<Path>) {
if show {
println!("(found {} {:?})", name.as_ref(), path.as_ref());
}
}
pub fn call_site (
show: bool,
call: &CallSite,
opcodes: Option<&[u8]>,
depth: Option<usize>,
) {
if show {
call.show(depth.unwrap_or(0), opcodes);
}
}
pub fn call_sites (
show: bool,
name: &impl AsRef<str>,
targets: &BTreeMap<u32, Vec<Arc<CallSite>>>,
depth: Option<usize>
) {
for (target, call_sites) in targets.iter() {
//if let Some(depth) = depth {
//for i in 0..depth {
//print!(" ");
//}
//}
//println!(" (call {:15} {})", name.as_ref(), fmt_num(*target as usize));
for call_site in call_sites.iter() {
call_site.show(depth.unwrap_or(0), None);
}
}
}
}
impl Show {
pub fn link_target_addrs (link: Option<&Arc<CallSite>>) {
println!(" {}{}{}",
link.map(|link|format!(" (offset {})", fmt_num(link.offset as usize))).unwrap_or(String::new()),
link.map(|link|format!(" (source {})", fmt_num(link.source as usize))).unwrap_or(String::new()),
link.map(|link|format!(" (target {})", fmt_num(link.target as usize))).unwrap_or(String::new()));
}
pub fn link_dasm (bytes: &[u8], rip: usize) -> Arc<str> {
let mut decoder = Decoder::with_ip(64, bytes, 0x1000 + rip as u64, DecoderOptions::NONE);
while decoder.can_decode() {
let position = decoder.position();
let instruction = decoder.decode();
let opcodes = &bytes[position..position+instruction.len()];
return format!("{BOLD}{instruction}{RESET} ({DIM}{}{RESET})", fmt_bytes(opcodes)).into()
}
Default::default()
}
}
impl std::fmt::Debug for Module {
fn fmt (&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
write!(f, "(dll {} [0x{:>08x}] {} (img 0x{:>08x} -> mem 0x{:>08x}))",
format!("{BOLD}{UNDERLINE}{:15}{RESET}", self.name),
self.code_size,
format!("({:>5})", self.exports.read().unwrap().len()),
self.code_start,
self.code_base)
}
}
impl Module {
pub fn show_layout (&self) {
println!("\n█ = 64k");
for module in self.namespace.read().unwrap().values() {
print!("\n{} ", module.name);
let page_size = 256;
for i in 0..(module.code_start + module.code_size) / page_size {
if i % 64 == 0 {
print!("\n");
}
if i * page_size < module.code_start {
print!("");
} else {
print!("");
}
}
print!("\n");
}
}
pub fn show_instruction (&self, addr: usize) {
let mut decoder = Decoder::with_ip(64, &self.code[addr..], 0x1000, DecoderOptions::NONE);
let instruction = decoder.decode();
let is_stack = instruction.is_stack_instruction();
let is_link = CallSite::matches(&instruction);
let style = if is_stack || is_link { BOLD } else { DIM };
print!("{style}{} {:20}{RESET}", fmt_num(addr), &self.name);
print!(" {style}{:26}{RESET}", fmt_bytes(&self.code[addr..addr+instruction.len()]));
println!(" {style}{instruction}{RESET}");
}
pub fn show_call_site (&self, addr: usize, length: usize, context: usize) -> Usually<()> {
let base = self.code_base as usize;
let line = 16;
let group = 2;
let snap = |x|(x/line)*line;
let byte_start = snap(addr).saturating_sub(context*line);
let byte_end = snap(addr) + (context + 1) * line;
let mut output = String::new();
for (index, byte) in (byte_start..byte_end).enumerate() {
write!(&mut output, "{DIM}")?;
if byte % line == 0 {
if (byte >= snap(addr)) && (byte < snap(addr) + line) {
write!(&mut output, "{RESET}╭─")?;
} else if byte >= snap(addr) + line {
write!(&mut output, "")?;
} else {
write!(&mut output, " ")?;
}
write!(&mut output, "{:08x}", byte + base)?;
}
write!(&mut output, "{DIM}")?;
if (byte >= addr) && (byte < addr + length) {
write!(&mut output, "{RESET}{BOLD}{INVERT}")?;
}
if byte % group == 0 {
if byte == addr {
write!(&mut output, "{}", "")?;
} else if byte == addr + length {
write!(&mut output, "{RESET}{BOLD}{INVERT}▐{RESET}{DIM}")?;
} else {
write!(&mut output, "{}", " ")?;
}
}
write!(&mut output, "{:02x}", self.code[byte])?;
write!(&mut output, "{RESET}")?;
if byte % line == line - 1 {
if snap(byte) == snap(addr) {
let dasm = Show::link_dasm(&self.code[addr..], addr);
write!(&mut output, " -> {dasm}");
}
write!(&mut output, " \n")?;
}
}
print!("{output}");
Ok(())
}
}
impl CallSite {
pub fn show (&self, depth: usize, opcodes: Option<&[u8]>) {
let mut call = String::new();
for i in 0..depth {
write!(&mut call, " ");
}
let label = self.caller.imports.read().unwrap().get(&self.target)
.map(|(module, method)|format!("{GREEN}{}{module}::{method}{RESET}", if depth > 0 { DIM } else { "" }))
.unwrap_or_else(||format!("{RED}unresolved{RESET}"));
write!(&mut call, "{DIM}╰->{RESET} (call {}", &self.caller.name);
println!("{call:40} {} {} {DIM}{:20}{RESET} {} {BOLD}{}{RESET})",
fmt_num(self.offset as usize),
fmt_num(self.source as usize),
fmt_bytes(opcodes.unwrap_or(&[])),
fmt_num(self.target as usize),
label);
//let caller = self.caller.name.as_ref();
//let module = self.module.as_ref();
//let method = self.method.as_ref();
//println!("╰--------> {caller} -> {label}{RESET}");
//module.map(|x|x.as_ref()).unwrap_or(&""),
//method.map(|x|x.as_ref()).unwrap_or(&""));
//} else {
//println!("╰--------> {caller} -> {RED}(unresolved){RESET} {self:?}");
//}
}
}
//impl Vestal {
//pub fn show_addr_to_import (&self) {
//for (addr, (dll, export)) in self.addr_to_import.iter() {
//println!("{BOLD}0x{addr:>08x}{RESET} {dll:>20} {export:<40}");
//}
//}
//pub fn show_vst_entrypoint (&self, path: &PathBuf) {
////let exports = self.path_to_exports.get(path).expect("no exports");
////for export in exports.iter() {
////if export.name_string() == Some("VSTPluginMain".to_string()) {
////println!("{export:?}");
////println!("{}", export.name_string().unwrap());
////let addr = (export.addr() as usize);
////println!();
////println!();
////return Ok(())
////}
////}
////panic!("no main");
////println!("{:#?}", &self.addr_to_import);
//}
//pub fn show_dll (&self, path: &PathBuf) -> Usually<()> {
//let dll = self.path_to_pe.get(path).expect("no such library");
//let ep_rva = dll.get_entrypoint()?;
//let ep_off = dll.rva_to_offset(ep_rva)?;
//println!("\n({:p} 0x{:x?} {:x?} {:x?}\n {path:?})",
//dll.as_ptr(),
//dll.get_image_base()?,
//ep_rva,
//ep_off);
//Ok(())
//}
//pub fn show_links (&self, path: &PathBuf, verbose: bool) -> Usually<()> {
//let mut links = 0;
//let dll = self.path_to_pe.get(path).expect("no such library");
//let buf = dll.get_buffer();
//let section = dll.get_section_by_name(".text")?;
//let section_ptr = section.pointer_to_raw_data.0 as usize;
//let section_len = section.size_of_raw_data as usize;
//let section_data = &buf[section_ptr..section_ptr+section_len];
//let mut decoder = iced_x86::Decoder::with_ip(64, section_data, 0x1000, 0);
//while decoder.can_decode() {
//let position = decoder.position();
//let instruction = decoder.decode();
//let opcodes = &section_data[position..position+instruction.len()];
////println!("0x{position:08x} {opcodes:32} {instruction}");
//if (instruction.flow_control() == iced_x86::FlowControl::IndirectBranch
//|| instruction.flow_control() == iced_x86::FlowControl::IndirectCallSite)
//&& instruction.op0_kind() == iced_x86::OpKind::Memory {
//match opcodes[0] {
//0xff => match opcodes[1] {
//0x10 | 0x12 | 0x13 |
//0x50 | 0x51 | 0x52 | 0x53 | 0x54 | 0x55 | 0x56 | 0x57 |
//0x60 | 0x90 | 0x92 | 0x93 | 0x94 | 0x97 => continue,
//_ => {},
//},
//0x41 | 0x42 | 0x43 | 0x49 => match opcodes[1] {
//0xff => continue,
//_ => {},
//},
//0x48 => match opcodes[2] {
//0x20 | 0x60 | 0x62 | 0xa0 | 0xa2 => continue,
//_ => {},
//},
//_ => {}
//}
//let offset = (position + section_ptr) as u32;
//let offset_rva = dll.offset_to_rva(Offset(offset))?.0;
//let link_target = match opcodes[0] {
//0xff => match opcodes[1] {
//0x15 | 0x25 =>
//offset_rva + opcodes.len() as u32 + u32::from_le_bytes([
//opcodes[2],
//opcodes[3],
//opcodes[4],
//opcodes[5]
//]),
//_ => 0x0
//},
//0x48 => match opcodes[1] {
//0xff => match opcodes[2] {
//0x15 | 0x25 =>
//offset_rva + opcodes.len() as u32 + u32::from_le_bytes([
//opcodes[3],
//opcodes[4],
//opcodes[5],
//opcodes[6]
//]),
//_ => 0x0
//},
//_ => 0x0
//}
//_ => 0x0
//};
//let unknown = (String::from("unknown"), String::from("unknown"));
//let external = format!("{}::{}",
//self.addr_to_import.get(&link_target).unwrap_or(&unknown).0,
//self.addr_to_import.get(&link_target).unwrap_or(&unknown).1);
//let dependent = path.file_name().unwrap();
//if verbose {
//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(" "),
//instruction,
//link_target,
//);
//}
//links += 1;
//}
//}
//println!(" (links {links})");
//Ok(())
//}
//}

35
core/src/util.rs Normal file
View file

@ -0,0 +1,35 @@
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, RwLock};
pub(crate) use itertools::{Itertools, izip};
//pub(crate) use ::lancelot::loader::pe::{PE, reloc::apply_relocations};
//pub(crate) use ::goblin::{error, Object, pe::{import::Import, export::Export}};
pub(crate) use ::object::endian::LittleEndian;
//pub(crate) use ::object::pe::ImageNtHeaders64;
//pub(crate) use ::object::read::pe::{PeFile, ExportTarget};
pub(crate) use ::object::write::elf::Writer as ElfWriter;
pub(crate) use ::pretty_hex::*;
pub(crate) use ::exe::{Buffer, PE, VecPE, PtrPE, types::*, headers::*};
pub(crate) use ::iced_x86::{Encoder, Decoder, DecoderOptions, Instruction, OpKind, FlowControl};
pub(crate) type Usually<T> = Result<T, Box<dyn std::error::Error>>;
pub const ESC: &str = "\u{001b}";
pub const RESET: &str = "\u{001b}[0m";
pub const BOLD: &str = "\u{001b}[1m";
pub const DIM: &str = "\u{001b}[2m";
pub const ITALIC: &str = "\u{001b}[3m";
pub const UNDERLINE: &str = "\u{001b}[4m";
pub const INVERT: &str = "\u{001b}[7m";
pub const RED: &str = "\u{001b}[1;31m";
pub const GREEN: &str = "\u{001b}[1;32m";
pub enum Verbosity {
Silent,
Terse,
Brief,
Verbose,
}
//println!(" ⋮ ");