mirror of
https://codeberg.org/unspeaker/vestal.git
synced 2025-12-06 08:36:41 +01:00
This commit is contained in:
parent
6bc456c814
commit
07f6f82268
22 changed files with 298 additions and 108 deletions
20
core/Cargo.toml
Normal file
20
core/Cargo.toml
Normal 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
28
core/src/bang.rs
Normal 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
207
core/src/call_sites.rs
Normal 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
165
core/src/elf.rs
Normal 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
104
core/src/exec.rs
Normal 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
14
core/src/exports.rs
Normal 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
109
core/src/imports.rs
Normal 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
197
core/src/lib.rs
Normal 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
320
core/src/show.rs
Normal 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 = §ion_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
35
core/src/util.rs
Normal 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!(" ⋮ ");
|
||||
Loading…
Add table
Add a link
Reference in a new issue