mirror of
https://codeberg.org/unspeaker/vestal.git
synced 2025-12-06 08:36:41 +01:00
gut it out
This commit is contained in:
parent
2740b6c232
commit
380d64c892
8 changed files with 514 additions and 477 deletions
6
Justfile
6
Justfile
|
|
@ -1,6 +1,8 @@
|
||||||
hello:
|
hello:
|
||||||
clear; tmux clear-history || true; cargo build && target/debug/vestal bin/hello-msg.exe 2>&1
|
clear; tmux clear-history || true; cargo build && target/debug/vestal bin/hello-msg.exe 2>&1
|
||||||
kotel:
|
|
||||||
clear; tmux clear-history || true; cargo build && target/debug/vestal bin/kotel.dll 2>&1
|
|
||||||
hello-v:
|
hello-v:
|
||||||
clear; tmux clear-history || true; cargo build && target/debug/vestal -v bin/hello-msg.exe 2>&1
|
clear; tmux clear-history || true; cargo build && target/debug/vestal -v bin/hello-msg.exe 2>&1
|
||||||
|
kotel:
|
||||||
|
clear; tmux clear-history || true; cargo build && target/debug/vestal bin/kotel.dll 2>&1
|
||||||
|
kotel-v:
|
||||||
|
clear; tmux clear-history || true; cargo build && target/debug/vestal -v bin/kotel.dll 2>&1
|
||||||
|
|
|
||||||
|
|
@ -290,3 +290,99 @@ impl Default for AEffect {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//if let Some(link) = self.decode_link(&mut decoder, verbose)? {
|
||||||
|
//links += 1;
|
||||||
|
//self.call_sites.insert(link.source, link.clone());
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
|
||||||
|
//fn decode_link (self: Arc<Self>, decoder: &mut Decoder, verbose: bool) -> Usually<Option<Arc<CallSite>>> {
|
||||||
|
//let position = decoder.position();
|
||||||
|
//let instruction = decoder.decode();
|
||||||
|
//let opcodes = &self.code[position..position+instruction.len()];
|
||||||
|
//if CallSite::matches(&instruction) && !CallSite::skip(opcodes) {
|
||||||
|
//let offset = (position + self.code_start) as u32;
|
||||||
|
//let source = self.pe.offset_to_rva(Offset(offset))?.0;
|
||||||
|
//if let Some(target) = Self::target(opcodes, source) {
|
||||||
|
//let module: Arc<Module> = Module::default().into();
|
||||||
|
//let method: Arc<str> = "TODO".into();
|
||||||
|
//if verbose {
|
||||||
|
//let offset = format!("0x{offset:08x}");
|
||||||
|
//let source = format!("0x{source:08x}");
|
||||||
|
//let instruct = format!("{BOLD}{instruction:30}{RESET}");
|
||||||
|
//let target = format!("0x{target:08x}");
|
||||||
|
//let external = format!("{module}::{method}");
|
||||||
|
//println!("({BOLD}{offset}{RESET} {source} {instruct} {target} {external}");
|
||||||
|
//}
|
||||||
|
//return Ok(Some(Arc::new(CallSite {
|
||||||
|
//caller: self.clone(),
|
||||||
|
//offset,
|
||||||
|
//source,
|
||||||
|
//length: opcodes.len(),
|
||||||
|
//target,
|
||||||
|
//module,
|
||||||
|
//method,
|
||||||
|
//})))
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//Ok(None)
|
||||||
|
//}
|
||||||
|
|
||||||
|
//fn dep_name (&self, target: u32) -> (Option<Arc<str>>, Option<Arc<str>>, Arc<str>) {
|
||||||
|
//(None, None, "".into())
|
||||||
|
//let deps = Some(&self.deps_by_address);
|
||||||
|
//let module = deps.and_then(|deps|deps.get(&target)).map(|dep|dep.0.clone());
|
||||||
|
//let method = deps.and_then(|deps|deps.get(&target)).map(|dep|dep.1.clone());
|
||||||
|
//let external = format!("{}::{}",
|
||||||
|
//module.as_ref().map(|x|x.as_ref()).unwrap_or("???"),
|
||||||
|
//method.as_ref().map(|x|x.as_ref()).unwrap_or("???"));
|
||||||
|
//(module, method, external.into())
|
||||||
|
//}
|
||||||
|
|
||||||
|
//fn links_collect (&mut self, verbose: bool) -> Usually<usize> {
|
||||||
|
//let mut decoder = Decoder::with_ip(64, self.code.as_ref(), 0, 0);
|
||||||
|
//let mut links = 0;
|
||||||
|
//while decoder.can_decode() {
|
||||||
|
//if let Some(link) = self.decode_link(&mut decoder, verbose)? {
|
||||||
|
//links += 1;
|
||||||
|
////self.links_by_source.insert(link.source, link.clone());
|
||||||
|
////if !self.links_by_target.contains_key(&link.target) {
|
||||||
|
////self.links_by_target.insert(link.target, Default::default());
|
||||||
|
////}
|
||||||
|
////self.links_by_target.get_mut(&link.target).unwrap().push(link);
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//if verbose {
|
||||||
|
//println!(" (link-sites {links})");
|
||||||
|
//for (target, sites) in self.links_by_target.iter() {
|
||||||
|
//let (_, _, external) = self.dep_name(*target);
|
||||||
|
//println!(" ({:>5}x link 0x{target:08x} {external})", sites.len());
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//Ok(links)
|
||||||
|
//}
|
||||||
|
/// Collect all imported dependencies.
|
||||||
|
//fn deps_collect (&mut self, verbose: bool) -> Usually<(usize, usize)> {
|
||||||
|
//let pe = self.pe.clone();
|
||||||
|
//let directory = ImportDirectory::parse(pe.as_ref())?;
|
||||||
|
//let mut modules = 0;
|
||||||
|
//let mut methods = 0;
|
||||||
|
//for descriptor in directory.descriptors.iter() {
|
||||||
|
//let (module_name, buffer) = import_collect(pe.as_ref(), descriptor)?;
|
||||||
|
//for (index, import_name) in buffer {
|
||||||
|
//let link_via = descriptor.first_thunk.0 + index as u32 * 8;
|
||||||
|
//let (new_modules, new_methods) = self.dep_collect(
|
||||||
|
//link_via, &module_name, &import_name, verbose
|
||||||
|
//)?;
|
||||||
|
//modules += new_modules;
|
||||||
|
//methods += new_methods;
|
||||||
|
//}
|
||||||
|
//}
|
||||||
|
//if verbose {
|
||||||
|
//println!(" (deps-modules {modules})");
|
||||||
|
//println!(" (deps-methods {methods})");
|
||||||
|
//self.show_deps_by_library();
|
||||||
|
//}
|
||||||
|
//Ok((modules, methods))
|
||||||
|
//}
|
||||||
|
|
|
||||||
63
crates/vestal/src/call_sites.rs
Normal file
63
crates/vestal/src/call_sites.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
use crate::*;
|
||||||
|
impl Module {
|
||||||
|
/// Collect all calls that point to imports.
|
||||||
|
pub fn load_call_sites (&mut self) -> Usually<usize> {
|
||||||
|
let mut decoder = Decoder::with_ip(64, self.code.as_ref(), 0, 0);
|
||||||
|
let mut links = 0;
|
||||||
|
while decoder.can_decode() {
|
||||||
|
let position = decoder.position();
|
||||||
|
let instruction = decoder.decode();
|
||||||
|
let opcodes = &self.code[position..position+instruction.len()];
|
||||||
|
if CallSite::matches(&instruction) && !CallSite::skip(opcodes) {
|
||||||
|
let offset = (position + self.code_start) as u32;
|
||||||
|
let source = self.pe.offset_to_rva(Offset(offset))?.0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(links)
|
||||||
|
}
|
||||||
|
fn target (opcodes: &[u8], offset_rva: u32) -> Option<u32> {
|
||||||
|
let rip = offset_rva + 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
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_link (&self, link: &Arc<CallSite>) -> Option<u32> {
|
||||||
|
None
|
||||||
|
//Some(*self.deps_by_library
|
||||||
|
//.get(link.module.as_ref()?)?
|
||||||
|
//.get(link.method.as_ref()?)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve_forward (&self, rva: &RVA) -> Usually<Option<(Arc<str>, Arc<str>)>> {
|
||||||
|
let mut addr = (rva.0 - self.code_base) as usize;
|
||||||
|
let mut forward = vec![];
|
||||||
|
while let Some(c) = self.pe.as_slice().get(addr) {
|
||||||
|
if *c == 0x00 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
forward.push(*c);
|
||||||
|
addr += 1;
|
||||||
|
}
|
||||||
|
Ok(String::from_utf8(forward)?
|
||||||
|
.as_str()
|
||||||
|
.split_once(".")
|
||||||
|
.map(|(x, y)|(x.into(), y.into())))
|
||||||
|
}
|
||||||
|
}
|
||||||
10
crates/vestal/src/exports.rs
Normal file
10
crates/vestal/src/exports.rs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
use crate::*;
|
||||||
|
impl Module {
|
||||||
|
/// Collect all exported methods.
|
||||||
|
pub fn load_exports (&mut self) -> Usually<usize> {
|
||||||
|
let directory = ExportDirectory::parse(self.pe.as_ref())?;
|
||||||
|
let export_map = directory.get_export_map(self.pe.as_ref())?;
|
||||||
|
self.exports = export_map.into_iter().map(|(k, v)|(k.into(), v)).collect();
|
||||||
|
Ok(self.exports.len())
|
||||||
|
}
|
||||||
|
}
|
||||||
74
crates/vestal/src/imports.rs
Normal file
74
crates/vestal/src/imports.rs
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
use crate::*;
|
||||||
|
impl Module {
|
||||||
|
/// Collect all imported modules and methods.
|
||||||
|
pub fn load_imports (&mut self) -> Usually<()> {
|
||||||
|
let pe = self.pe.clone();
|
||||||
|
let directory = ImportDirectory::parse(pe.as_ref())?;
|
||||||
|
for descriptor in directory.descriptors.iter() {
|
||||||
|
let (module, imports) = Self::parse_import(pe.as_ref(), descriptor)?;
|
||||||
|
Log::import(self.verbose, &module, &imports);
|
||||||
|
for (index, method) in imports {
|
||||||
|
let call_via = descriptor.first_thunk.0 + index as u32 * 8;
|
||||||
|
self.load_dependency(call_via, &module, &method);
|
||||||
|
self.load_import(call_via, &module, &method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.verbose {
|
||||||
|
self.show_dependencies();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Collect an imported dependency's descriptor thunks into a temporary [Vec].
|
||||||
|
fn parse_import (pe: &VecPE, descriptor: &ImageImportDescriptor)
|
||||||
|
-> Usually<(Arc<str>, Vec<(usize, Arc<str>)>)>
|
||||||
|
{
|
||||||
|
let mut imports = vec![];
|
||||||
|
let module = descriptor.get_name(pe)?.as_str()?.to_lowercase();
|
||||||
|
for (index, import) in descriptor.get_imports(pe)?.iter().enumerate() {
|
||||||
|
imports.push((index, match import {
|
||||||
|
ImportData::Ordinal(x) => format!("___VESTAL_ORDINAL_{x}"),
|
||||||
|
ImportData::ImportByName(name) => format!("{name}"),
|
||||||
|
}.into()));
|
||||||
|
}
|
||||||
|
Ok((module.into(), imports))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_import (
|
||||||
|
&mut self, call_via: u32, module_name: &Arc<str>, method_name: &Arc<str>
|
||||||
|
) {
|
||||||
|
if self.imports.contains_key(&call_via) {
|
||||||
|
panic!("duplicate address {call_via} from {module_name}");
|
||||||
|
}
|
||||||
|
self.imports.insert(call_via, (module_name.clone(), method_name.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_dependency (
|
||||||
|
&mut self,
|
||||||
|
call_via: u32,
|
||||||
|
module_name: &Arc<str>,
|
||||||
|
method_name: &Arc<str>,
|
||||||
|
) -> (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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,35 +1,13 @@
|
||||||
|
//! 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)]
|
#![feature(slice_split_once)]
|
||||||
mod util; pub(crate) use self::util::*;
|
mod util; pub(crate) use self::util::*;
|
||||||
mod show; pub(crate) use self::show::*;
|
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;
|
mod bang;
|
||||||
|
|
||||||
/// Takes a 64-bit VST2 in PE format and attempts to
|
|
||||||
/// relink it with Wine and a custom shim to make it
|
|
||||||
/// into a standalone, self-contained 64-bit ELF JACK device.
|
|
||||||
fn main () -> Usually<()> {
|
|
||||||
use clap::{arg, command, value_parser, ArgAction, Command};
|
|
||||||
// Parse command line arguments.
|
|
||||||
let matches = cli().get_matches();
|
|
||||||
let path = matches.get_one::<PathBuf>("path").unwrap_or_else(||panic!("pass path to VST DLL"));
|
|
||||||
let verbose = *(matches.get_one::<bool>("verbose").unwrap_or(&false));
|
|
||||||
// Construct a rebuilder.
|
|
||||||
let mut rebuilder = Vestal::new(verbose, &[
|
|
||||||
// TODO allow user to specify search paths, system path, wineprefix...
|
|
||||||
std::env::current_dir()?,
|
|
||||||
canonicalize(path.clone().parent().expect("invalid parent path"))?,
|
|
||||||
"/home/user/Lab/Cosmo/wineprefix/drive_c/windows/system32".into(),
|
|
||||||
]);
|
|
||||||
// Resolve input path.
|
|
||||||
let path = path.to_str().expect("path must be unicode");
|
|
||||||
let path = rebuilder.find(path)?.unwrap_or_else(||panic!("# not found: {path:?}"));
|
|
||||||
// Load with dependencies.
|
|
||||||
let main = rebuilder.load_recursively(&path)?;
|
|
||||||
for (addr, link) in main.links_by_source.iter() {
|
|
||||||
rebuilder.resolve_recursively(&main, *addr, link)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Define command line arguments.
|
/// Define command line arguments.
|
||||||
fn cli () -> clap::Command {
|
fn cli () -> clap::Command {
|
||||||
use clap::{arg, command, value_parser, ArgAction, Command};
|
use clap::{arg, command, value_parser, ArgAction, Command};
|
||||||
|
|
@ -39,64 +17,118 @@ fn cli () -> clap::Command {
|
||||||
.arg(arg!(-i --inspect "Show info, don't run").required(false))
|
.arg(arg!(-i --inspect "Show info, don't run").required(false))
|
||||||
.arg(arg!(-v --verbose "Show a lot of info").required(false))
|
.arg(arg!(-v --verbose "Show a lot of info").required(false))
|
||||||
}
|
}
|
||||||
|
/// A DLL that will be linked into the output.
|
||||||
/// Application state
|
pub struct Module {
|
||||||
#[derive(Debug, Default)]
|
/// Scope.
|
||||||
struct Vestal {
|
pub namespace: Arc<RwLock<BTreeMap<Arc<str>, Arc<Module>>>>,
|
||||||
|
/// 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: BTreeMap<Arc<str>, Arc<Self>>,
|
||||||
|
/// Call targets to methods from imported modules.
|
||||||
|
pub imports: BTreeMap<u32, (Arc<str>, Arc<str>)>,
|
||||||
|
/// Addresses of exported methods by name
|
||||||
|
pub exports: BTreeMap<Arc<str>, ThunkData>,
|
||||||
|
/// Locations in `.text` section that need to be patched
|
||||||
|
pub call_sites: BTreeMap<u32, Arc<CallSite>>,
|
||||||
/// More detailed output.
|
/// More detailed output.
|
||||||
verbose: bool,
|
pub verbose: bool,
|
||||||
/// Search paths
|
|
||||||
paths: BTreeSet<Arc<PathBuf>>,
|
|
||||||
/// Paths visited
|
|
||||||
visited: BTreeSet<Arc<PathBuf>>,
|
|
||||||
/// All DLLs in scope
|
|
||||||
dlls: BTreeMap<Arc<str>, Arc<Dll>>,
|
|
||||||
}
|
}
|
||||||
|
/// A call from one DLL to another.
|
||||||
impl Vestal {
|
#[derive(Debug)]
|
||||||
/// Construct a rebuilder
|
pub struct CallSite {
|
||||||
fn new (verbose: bool, paths: &[impl AsRef<Path>]) -> Self {
|
/// Module that is making the call
|
||||||
let paths: BTreeSet<_> = paths.iter().map(|x|Arc::new(x.as_ref().into())).collect();
|
pub caller: Arc<Module>,
|
||||||
if verbose {
|
/// Address in memory
|
||||||
// Report search paths
|
pub source: u32,
|
||||||
for path in paths.iter() {
|
/// Address on disk
|
||||||
println!("(search {path:?})")
|
pub offset: u32,
|
||||||
|
/// Length of link in opcodes
|
||||||
|
pub length: usize,
|
||||||
|
/// CallSite trampoline address
|
||||||
|
pub address: u32,
|
||||||
|
/// Module that is being called
|
||||||
|
pub target: Arc<Module>,
|
||||||
|
/// Name of method that is being called
|
||||||
|
pub method: Arc<str>,
|
||||||
}
|
}
|
||||||
|
fn main () -> Usually<()> {
|
||||||
|
use clap::{arg, command, value_parser, ArgAction, Command};
|
||||||
|
// Parse command line arguments.
|
||||||
|
let args = cli().get_matches();
|
||||||
|
if let Some(path) = args.get_one::<PathBuf>("path") {
|
||||||
|
Module::from_path(path, *(args.get_one::<bool>("verbose").unwrap_or(&false)))?
|
||||||
|
.search(std::env::current_dir()?)
|
||||||
|
.search(canonicalize(path.clone().parent().expect("invalid parent path"))?)
|
||||||
|
.search("/home/user/Lab/Cosmo/wineprefix/drive_c/windows/system32")
|
||||||
|
.load()?;
|
||||||
|
} else {
|
||||||
|
println!("Pass a path to a VST DLL");
|
||||||
}
|
}
|
||||||
Self { verbose, paths, ..Default::default() }
|
Ok(())
|
||||||
}
|
}
|
||||||
|
impl Module {
|
||||||
/// Load a library from a path.
|
/// Construct a cross-linkable library.
|
||||||
fn load (&mut self, path: &impl AsRef<Path>) -> Usually<Arc<Dll>> {
|
///
|
||||||
let path: Arc<PathBuf> = Arc::from(PathBuf::from(path.as_ref()));
|
/// Loads the PE but does not
|
||||||
if self.visited.contains(&path) {
|
/// extract any imports/exports/callsites.
|
||||||
let name = path.file_name().expect("no file name");
|
/// For that, invoke the [Module::load] method.
|
||||||
let name: Arc<str> = name.to_str().map(Arc::from).expect("non-unicode filename");
|
fn from_path (path: impl AsRef<Path>, verbose: bool) -> Usually<Self> {
|
||||||
return Ok(self.dlls.get(&name).unwrap().clone())
|
Log::load(verbose, &path);
|
||||||
|
let (pe, data, bang) = read_pe(&path)?;
|
||||||
|
let (code, code_start, code_size) = read_code(&pe)?;
|
||||||
|
Ok(Self {
|
||||||
|
bang,
|
||||||
|
name: to_dll_name(&path),
|
||||||
|
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(),
|
||||||
|
exports: Default::default(),
|
||||||
|
call_sites: Default::default(),
|
||||||
|
pe,
|
||||||
|
verbose,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if self.verbose {
|
/// Add a search path
|
||||||
println!("(load {path:?})");
|
fn search (mut self, path: impl AsRef<Path>) -> Self {
|
||||||
|
Log::add(self.verbose, &path);
|
||||||
|
self.search_paths.write().unwrap().insert(path.as_ref().to_path_buf().into());
|
||||||
|
self
|
||||||
}
|
}
|
||||||
self.visited.insert(path.clone());
|
/// Load the dependency tree, starting from this module.
|
||||||
let dll = Arc::new(Dll::new(&Arc::new(PathBuf::from(path.as_ref())), self.verbose)?);
|
fn load (mut self) -> Usually<Self> {
|
||||||
self.dlls.insert(dll.name.clone(), dll.clone());
|
let _ = self.load_imports()?;
|
||||||
Ok(dll)
|
let _ = self.load_exports()?;
|
||||||
|
let _ = self.load_call_sites()?;
|
||||||
|
println!("{self:?}");
|
||||||
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
/// Search for DLL by name in search paths.
|
||||||
/// Load all dependencies recursively, starting from the given path.
|
fn find (&self, name: &impl AsRef<str>) -> Usually<Option<PathBuf>> {
|
||||||
fn load_recursively (&mut self, path: &impl AsRef<Path>) -> Usually<Arc<Dll>> {
|
let name = name.as_ref();
|
||||||
let dll = self.load(path)?;
|
for base in self.search_paths.read().unwrap().iter() {
|
||||||
for dep in dll.deps_by_library.keys() {
|
|
||||||
let dep_path = self.find(dep)?.unwrap_or_else(||panic!("not found: {dep:?}"));
|
|
||||||
if !self.visited.contains(&dep_path) {
|
|
||||||
self.load_recursively(&dep_path)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(dll)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find (&self, name: &str) -> Usually<Option<PathBuf>> {
|
|
||||||
for base in self.paths.iter() {
|
|
||||||
let mut path = base.as_ref().clone();
|
let mut path = base.as_ref().clone();
|
||||||
path.push(name.to_lowercase());
|
path.push(name.to_lowercase());
|
||||||
if self.verbose {
|
if self.verbose {
|
||||||
|
|
@ -112,149 +144,73 @@ impl Vestal {
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
/// Relink a VST.
|
||||||
fn resolve (&self, dll: &Dll, addr: u32, link: &Arc<Link>) -> Usually<()> {
|
//fn run (&mut self, path: impl AsRef<Path>) -> Usually<()> {
|
||||||
|
//let path = path.as_ref().to_str().expect("path must be unicode");
|
||||||
|
//let path = self.find(path)?.unwrap_or_else(||panic!("# not found: {path:?}"));
|
||||||
|
//let main = self.load(&path)?;
|
||||||
|
//Ok(())
|
||||||
|
//}
|
||||||
|
/// Load all dependencies recursively, starting from the given path.
|
||||||
|
//fn load_all (&mut self, path: &impl AsRef<Path>) -> Usually<Arc<Module>> {
|
||||||
|
//let name = to_dll_name(path);
|
||||||
|
//let path: Arc<PathBuf> = Arc::from(PathBuf::from(path.as_ref()));
|
||||||
|
//if self.modules.contains_key(&name) {
|
||||||
|
//let module = self.modules.get(&name).unwrap().clone();
|
||||||
|
//return Ok(module)
|
||||||
|
//}
|
||||||
|
//Log::load(self.verbose, &path);
|
||||||
|
//let module = Arc::new(Module::new(&path, self.verbose)?.load(self.verbose)?);
|
||||||
|
//self.modules.insert(module.name.clone(), module.clone());
|
||||||
|
//Ok(module)
|
||||||
|
//}
|
||||||
|
fn resolve (&self, dll: &Module, addr: u32, link: &Arc<CallSite>) -> Usually<()> {
|
||||||
let addr = (addr - dll.code_base) as usize;
|
let addr = (addr - dll.code_base) as usize;
|
||||||
dll.show_call_site(addr, link.length, 1);
|
dll.show_call_site(addr, link.length, 1);
|
||||||
link.show(&dll.name);
|
link.show();
|
||||||
//let module_name = link.module.as_ref().unwrap();
|
|
||||||
//let export_name = link.method.as_ref().unwrap();
|
|
||||||
//let path = self.find(module_name)?.unwrap_or_else(||panic!("# not found: {module_name}"));
|
|
||||||
//let name = path.file_name().expect("no file name");
|
|
||||||
//let name: Arc<str> = name.to_str().map(Arc::from).expect("non-unicode filename");
|
|
||||||
//let linked_dll = self.dlls.get(&name).unwrap_or_else(||panic!("# not found: {name}"));
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
fn resolve_recursively (&self, dll: &Module, addr: u32, link: &Arc<CallSite>) -> Usually<()> {
|
||||||
fn resolve_recursively (&self, dll: &Dll, addr: u32, link: &Arc<Link>) -> Usually<()> {
|
|
||||||
self.resolve(dll, addr, link)?;
|
self.resolve(dll, addr, link)?;
|
||||||
let module_name = link.module.as_ref().unwrap();
|
let module = &link.target.name;
|
||||||
let export_name = link.method.as_ref().unwrap();
|
let export = &link.method;
|
||||||
if let Some((module_name, export_name)) = match self.dlls
|
//if let Some((module, export)) = match self.modules
|
||||||
.get(module_name)
|
//.get(module)
|
||||||
.map(|module|module.exports.get(export_name))
|
//.map(|module|module.exports.get(export))
|
||||||
{
|
//{
|
||||||
Some(Some(ThunkData::Function(rva))) => Some((module_name.clone(), export_name.clone())),
|
//Some(Some(ThunkData::Function(rva))) => Some((module.clone(), export.clone())),
|
||||||
Some(Some(ThunkData::ForwarderString(rva))) => dll.resolve_forward(&rva)?,
|
//Some(Some(ThunkData::ForwarderString(rva))) => dll.resolve_forward(&rva)?,
|
||||||
Some(Some(thunk)) => panic!("# unsupported {thunk:?}"),
|
//Some(Some(thunk)) => panic!("# unsupported {thunk:?}"),
|
||||||
Some(None) => panic!("# export not resolved: {export_name}"),
|
//Some(None) => panic!("# export not resolved: {export}"),
|
||||||
None => panic!("# module not resolved: {module_name}"),
|
//None => panic!("# module not resolved: {module}"),
|
||||||
} {
|
//} {
|
||||||
let module = self.dlls.get(&module_name)
|
//let module = self.modules.get(&module).unwrap_or_else(||panic!("# no module {module}"));
|
||||||
.unwrap_or_else(||panic!("# no module {module_name}"));
|
//let method = module.exports.get(&export).unwrap_or_else(||panic!("# no method {export}"));
|
||||||
let method = module.exports.get(&export_name)
|
//println!("{module:?} {export} {method:?}");
|
||||||
.unwrap_or_else(||panic!("# no method {export_name}"));
|
//} else {
|
||||||
println!("{module_name} {export_name} {method:?}");
|
//panic!("# unresolved link at {:?} [{addr}]", &dll.name)
|
||||||
//self.resolve_recursively(module)?;
|
|
||||||
//println!("{} {} {:?}", module_name, export_name, method);
|
|
||||||
//let mut decoder = Decoder::with_ip(64, &dll.text_section[address..], 0, DecoderOptions::NONE);
|
|
||||||
//while decoder.can_decode() {
|
|
||||||
//let position = decoder.position();
|
|
||||||
//if let Some(link) = dll.decode_link(&mut decoder, true)? {
|
|
||||||
//println!("(link {link:#?}");
|
|
||||||
//}
|
//}
|
||||||
//if dll.text_section[address + position] == 0xc3 {
|
|
||||||
//break
|
|
||||||
//}
|
|
||||||
//}
|
|
||||||
} else {
|
|
||||||
panic!("# unresolved link at {:?} [{addr}]", &dll.name)
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
impl Module {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Dll {
|
fn to_dll_name (path: &impl AsRef<Path>) -> Arc<str> {
|
||||||
/// Canonical name like `xxx.dll` (always lowercase)
|
Arc::from(path.as_ref()
|
||||||
name: Arc<str>,
|
.file_name()
|
||||||
/// Path to DLL on host filesystem.
|
.expect("no file name")
|
||||||
path: Arc<PathBuf>,
|
.to_str()
|
||||||
/// Bytes of `#!`-instruction
|
.expect("non-unicode filename"))
|
||||||
bang: Arc<[u8]>,
|
|
||||||
/// Parsed portable executable
|
|
||||||
pe: Arc<VecPE>,
|
|
||||||
/// Assumed address in memory
|
|
||||||
code_base: u32,
|
|
||||||
/// Bytes of `.text` section
|
|
||||||
text_section: Arc<[u8]>,
|
|
||||||
/// Start of `.text` section
|
|
||||||
text_section_start: usize,
|
|
||||||
/// Size of `.text` section
|
|
||||||
text_section_size: usize,
|
|
||||||
/// Addresses of exported methods by name
|
|
||||||
exports: BTreeMap<Arc<str>, ThunkData>,
|
|
||||||
/// Locations in `.text` section that need to be patched
|
|
||||||
call_sites: BTreeMap<u32, Link>,
|
|
||||||
/// Addresses of imported methods by library
|
|
||||||
deps_by_library: BTreeMap<Arc<str>, BTreeMap<Arc<str>, u32>>,
|
|
||||||
/// Imported methods by callable address
|
|
||||||
deps_by_address: BTreeMap<u32, (Arc<str>, Arc<str>)>,
|
|
||||||
/// Links to dependencies by source address
|
|
||||||
links_by_source: BTreeMap<u32, Arc<Link>>,
|
|
||||||
/// Links to dependencies by callable address
|
|
||||||
links_by_target: BTreeMap<u32, Vec<Arc<Link>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
fn read_pe (path: &impl AsRef<Path>) -> Usually<(Arc<VecPE>, Arc<[u8]>, Arc<[u8]>)> {
|
||||||
pub struct Link {
|
let (bang, data) = crate::bang::slice_shebang(read(path.as_ref())?.as_slice());
|
||||||
/// Address on disk
|
|
||||||
pub offset: u32,
|
|
||||||
/// Address in memory
|
|
||||||
pub source: u32,
|
|
||||||
/// Length of link in opcodes
|
|
||||||
pub length: usize,
|
|
||||||
/// Link trampoline address
|
|
||||||
pub target: u32,
|
|
||||||
/// Library being called
|
|
||||||
pub module: Option<Arc<str>>,
|
|
||||||
/// Method being called
|
|
||||||
pub method: Option<Arc<str>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Dll {
|
|
||||||
|
|
||||||
pub fn new (path: &Arc<PathBuf>, verbose: bool) -> Usually<Self> {
|
|
||||||
if verbose {
|
|
||||||
println!("\n(load {BOLD}{path:?}{RESET})");
|
|
||||||
}
|
|
||||||
let (name, pe, data, bang) = Self::read_pe(path)?;
|
|
||||||
let (text_section, text_section_start, text_section_size) = Self::read_text_section(&pe)?;
|
|
||||||
let mut dll = Self {
|
|
||||||
bang,
|
|
||||||
name: name.clone(),
|
|
||||||
path: path.clone(),
|
|
||||||
text_section,
|
|
||||||
text_section_start,
|
|
||||||
text_section_size,
|
|
||||||
code_base: match pe.get_valid_nt_headers()? {
|
|
||||||
NTHeaders::NTHeaders32(h32) => panic!("32 bit headers"),
|
|
||||||
NTHeaders::NTHeaders64(h64) => h64.optional_header.base_of_code.0,
|
|
||||||
},
|
|
||||||
pe,
|
|
||||||
exports: Default::default(),
|
|
||||||
deps_by_library: Default::default(),
|
|
||||||
deps_by_address: Default::default(),
|
|
||||||
links_by_source: Default::default(),
|
|
||||||
links_by_target: Default::default(),
|
|
||||||
call_sites: Default::default(),
|
|
||||||
};
|
|
||||||
let (_modules_count, _methods_count) = dll.deps_collect(verbose)?;
|
|
||||||
let _links = dll.links_collect(verbose)?;
|
|
||||||
let _exports = dll.exports_collect(verbose)?;
|
|
||||||
println!("{dll:?}");
|
|
||||||
Ok(dll)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_pe (path: &Arc<PathBuf>) -> Usually<(Arc<str>, Arc<VecPE>, Arc<[u8]>, Arc<[u8]>)> {
|
|
||||||
let name = path.as_ref().file_name().expect("no file name");
|
|
||||||
let name: Arc<str> = name.to_str().map(Arc::from).expect("non-unicode filename");
|
|
||||||
let (bang, data) = crate::bang::slice_shebang(read(path.as_path())?.as_slice());
|
|
||||||
let pe = Arc::new(VecPE::from_disk_data(data.clone()));
|
let pe = Arc::new(VecPE::from_disk_data(data.clone()));
|
||||||
Ok((name, pe, data, bang))
|
Ok((pe, data, bang))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_text_section (pe: &VecPE) -> Usually<(Arc<[u8]>, usize, usize)> {
|
fn read_code (pe: &VecPE) -> Usually<(Arc<[u8]>, usize, usize)> {
|
||||||
let 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 start = code.pointer_to_raw_data.0 as usize;
|
||||||
let size = code.size_of_raw_data as usize;
|
let size = code.size_of_raw_data as usize;
|
||||||
|
|
@ -262,171 +218,11 @@ impl Dll {
|
||||||
Ok((text.into(), start, size))
|
Ok((text.into(), start, size))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collect all exported methods.
|
fn base_of_code (pe: &VecPE) -> Usually<u32> {
|
||||||
pub fn exports_collect (&mut self, _verbose: bool) -> Usually<usize> {
|
Ok(match pe.get_valid_nt_headers()? {
|
||||||
let directory = ExportDirectory::parse(self.pe.as_ref())?;
|
NTHeaders::NTHeaders32(h32) => panic!("32 bit headers"),
|
||||||
let export_map = directory.get_export_map(self.pe.as_ref())?;
|
NTHeaders::NTHeaders64(h64) => h64.optional_header.base_of_code.0,
|
||||||
self.exports = export_map.into_iter().map(|(k, v)|(k.into(), v)).collect();
|
})
|
||||||
Ok(self.exports.len())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Collect all imported dependencies.
|
|
||||||
pub fn deps_collect (&mut self, verbose: bool) -> Usually<(usize, usize)> {
|
|
||||||
let pe = self.pe.clone();
|
|
||||||
let directory = ImportDirectory::parse(pe.as_ref())?;
|
|
||||||
let mut modules = 0;
|
|
||||||
let mut methods = 0;
|
|
||||||
for descriptor in directory.descriptors.iter() {
|
|
||||||
let (module_name, buffer) = import_collect(pe.as_ref(), descriptor)?;
|
|
||||||
for (index, import_name) in buffer {
|
|
||||||
let link_via = descriptor.first_thunk.0 + index as u32 * 8;
|
|
||||||
let (new_modules, new_methods) = self.dep_collect(
|
|
||||||
link_via, &module_name, &import_name, verbose
|
|
||||||
)?;
|
|
||||||
modules += new_modules;
|
|
||||||
methods += new_methods;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if verbose {
|
|
||||||
println!(" (deps-modules {modules})");
|
|
||||||
println!(" (deps-methods {methods})");
|
|
||||||
self.show_deps_by_library();
|
|
||||||
}
|
|
||||||
Ok((modules, methods))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Collect an imported dependency's exported methods.
|
|
||||||
pub fn dep_collect (
|
|
||||||
&mut self, link_via: u32, module_name: &Arc<str>, method_name: &Arc<str>, verbose: bool
|
|
||||||
) -> Usually<(usize, usize)> {
|
|
||||||
let (modules, methods) = self.dep_collect_by_library(
|
|
||||||
link_via, module_name, method_name, verbose
|
|
||||||
);
|
|
||||||
self.dep_collect_by_address(
|
|
||||||
link_via, module_name, method_name
|
|
||||||
);
|
|
||||||
Ok((modules, methods))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dep_collect_by_library (
|
|
||||||
&mut self, link_via: u32, module_name: &Arc<str>, method_name: &Arc<str>, verbose: bool
|
|
||||||
) -> (usize, usize) {
|
|
||||||
let mut modules = 0;
|
|
||||||
let mut methods = 0;
|
|
||||||
if !self.deps_by_library.contains_key(module_name) {
|
|
||||||
self.deps_by_library.insert(module_name.clone(), Default::default());
|
|
||||||
modules += 1;
|
|
||||||
}
|
|
||||||
let module = self.deps_by_library.get_mut(module_name).unwrap();
|
|
||||||
if module.contains_key(method_name) {
|
|
||||||
panic!("duplicate method {method_name} in {module_name}");
|
|
||||||
}
|
|
||||||
module.insert(method_name.clone(), link_via);
|
|
||||||
methods += 1;
|
|
||||||
if verbose {
|
|
||||||
if modules > 0 {
|
|
||||||
println!(" {BOLD}(import {module_name}){RESET}");
|
|
||||||
}
|
|
||||||
if methods > 0 {
|
|
||||||
println!(" (import 0x{link_via:08x} {module_name}::{method_name})");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(modules, methods)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dep_collect_by_address (
|
|
||||||
&mut self, link_via: u32, module_name: &Arc<str>, method_name: &Arc<str>
|
|
||||||
) {
|
|
||||||
if self.deps_by_address.contains_key(&link_via) {
|
|
||||||
panic!("duplicate address {link_via} from {module_name}");
|
|
||||||
}
|
|
||||||
self.deps_by_address.insert(link_via, (module_name.clone(), method_name.clone()));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dep_name (&self, target: u32) -> (Option<Arc<str>>, Option<Arc<str>>, Arc<str>) {
|
|
||||||
let deps = Some(&self.deps_by_address);
|
|
||||||
let module = deps.and_then(|deps|deps.get(&target)).map(|dep|dep.0.clone());
|
|
||||||
let method = deps.and_then(|deps|deps.get(&target)).map(|dep|dep.1.clone());
|
|
||||||
let external = format!("{}::{}",
|
|
||||||
module.as_ref().map(|x|x.as_ref()).unwrap_or("???"),
|
|
||||||
method.as_ref().map(|x|x.as_ref()).unwrap_or("???"));
|
|
||||||
(module, method, external.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn links_collect (&mut self, verbose: bool) -> Usually<usize> {
|
|
||||||
let mut decoder = Decoder::with_ip(64, self.text_section.as_ref(), 0, 0);
|
|
||||||
let mut links = 0;
|
|
||||||
while decoder.can_decode() {
|
|
||||||
if let Some(link) = self.decode_link(&mut decoder, verbose)? {
|
|
||||||
links += 1;
|
|
||||||
self.links_by_source.insert(link.source, link.clone());
|
|
||||||
if !self.links_by_target.contains_key(&link.target) {
|
|
||||||
self.links_by_target.insert(link.target, Default::default());
|
|
||||||
}
|
|
||||||
self.links_by_target.get_mut(&link.target).unwrap().push(link);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if verbose {
|
|
||||||
println!(" (link-sites {links})");
|
|
||||||
for (target, sites) in self.links_by_target.iter() {
|
|
||||||
let (_, _, external) = self.dep_name(*target);
|
|
||||||
println!(" ({:>5}x link 0x{target:08x} {external})", sites.len());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(links)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn decode_link (&self, decoder: &mut Decoder, verbose: bool) -> Usually<Option<Arc<Link>>> {
|
|
||||||
let position = decoder.position();
|
|
||||||
let instruction = decoder.decode();
|
|
||||||
let opcodes = &self.text_section[position..position+instruction.len()];
|
|
||||||
if Link::matches(&instruction) && !Link::skip(opcodes) {
|
|
||||||
let offset = (position + self.text_section_start) as u32;
|
|
||||||
let offset_rva = self.pe.offset_to_rva(Offset(offset))?.0;
|
|
||||||
if let Some(target) = Link::target(opcodes, offset_rva) {
|
|
||||||
let (module, method, external) = self.dep_name(target);
|
|
||||||
if verbose {
|
|
||||||
let external = format!("{}::{}",
|
|
||||||
module.as_ref().map(|x|x.as_ref()).unwrap_or("???"),
|
|
||||||
method.as_ref().map(|x|x.as_ref()).unwrap_or("???"));
|
|
||||||
println!(" ({BOLD}0x{:08x}{RESET} 0x{:08x} {BOLD}{:30}{RESET} 0x{:x} {}",
|
|
||||||
offset, offset_rva, instruction, target, external);
|
|
||||||
}
|
|
||||||
return Ok(Some(Arc::new(Link {
|
|
||||||
offset: offset,
|
|
||||||
source: offset_rva,
|
|
||||||
length: opcodes.len(),
|
|
||||||
target,
|
|
||||||
module,
|
|
||||||
method,
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_link (&self, link: &Arc<Link>) -> Option<u32> {
|
|
||||||
Some(*self.deps_by_library
|
|
||||||
.get(link.module.as_ref()?)?
|
|
||||||
.get(link.method.as_ref()?)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn resolve_forward (&self, rva: &RVA) -> Usually<Option<(Arc<str>, Arc<str>)>> {
|
|
||||||
let mut addr = (rva.0 - self.code_base) as usize;
|
|
||||||
let mut forward = vec![];
|
|
||||||
while let Some(c) = self.pe.as_slice().get(addr) {
|
|
||||||
if *c == 0x00 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
forward.push(*c);
|
|
||||||
addr += 1;
|
|
||||||
}
|
|
||||||
Ok(String::from_utf8(forward)?
|
|
||||||
.as_str()
|
|
||||||
.split_once(".")
|
|
||||||
.map(|(x, y)|(x.into(), y.into())))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Collect an imported dependency's descriptor thunks into a temporary [Vec].
|
/// Collect an imported dependency's descriptor thunks into a temporary [Vec].
|
||||||
|
|
@ -466,32 +262,14 @@ fn import_collect (pe: &VecPE, descriptor: &ImageImportDescriptor)
|
||||||
//}
|
//}
|
||||||
//}
|
//}
|
||||||
|
|
||||||
impl std::fmt::Debug for Dll {
|
impl CallSite {
|
||||||
fn fmt (&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
|
fn matches (instruction: &Instruction) -> bool {
|
||||||
let deps = format!("(deps lib {:>4} addr {:>4})",
|
|
||||||
self.deps_by_library.len(),
|
|
||||||
self.deps_by_address.len());
|
|
||||||
let links = format!("(links src {:>4} tgt {:>4})",
|
|
||||||
self.links_by_source.len(),
|
|
||||||
self.links_by_target.len());
|
|
||||||
let exports = format!("(exp {:>5})",
|
|
||||||
self.exports.len());
|
|
||||||
write!(f, "(dll {BOLD}{UNDERLINE}{:15}{RESET} [0x{:>08x}] (img 0x{:>08x} -> mem 0x{:>08x}) {deps} {links} {exports})",
|
|
||||||
&self.name,
|
|
||||||
self.text_section_size,
|
|
||||||
self.text_section_start,
|
|
||||||
self.code_base)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Link {
|
|
||||||
pub fn matches (instruction: &Instruction) -> bool {
|
|
||||||
instruction.op0_kind() == OpKind::Memory && (
|
instruction.op0_kind() == OpKind::Memory && (
|
||||||
instruction.flow_control() == FlowControl::IndirectBranch ||
|
instruction.flow_control() == FlowControl::IndirectBranch ||
|
||||||
instruction.flow_control() == FlowControl::IndirectCall
|
instruction.flow_control() == FlowControl::IndirectCall
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
pub fn skip (opcodes: &[u8]) -> bool {
|
fn skip (opcodes: &[u8]) -> bool {
|
||||||
match opcodes[0] {
|
match opcodes[0] {
|
||||||
0x41 | 0x42 | 0x43 | 0x49 => match opcodes[1] {
|
0x41 | 0x42 | 0x43 | 0x49 => match opcodes[1] {
|
||||||
0xff => return true,
|
0xff => return true,
|
||||||
|
|
@ -511,26 +289,4 @@ impl Link {
|
||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
pub fn target (opcodes: &[u8], offset_rva: u32) -> Option<u32> {
|
|
||||||
let rip = offset_rva + 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,26 +5,87 @@ pub fn fmt_bytes (bytes: &[u8]) -> Arc<str> {
|
||||||
bytes.iter().map(|x|format!("{x:02x}")).join(" ").into()
|
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 Show;
|
||||||
|
|
||||||
impl Dll {
|
pub struct Log;
|
||||||
pub fn show_deps_by_library (&self) {
|
|
||||||
for (module, methods) in self.deps_by_library.iter() {
|
impl Log {
|
||||||
print!(" ({module}");
|
pub fn add (show: bool, path: &impl AsRef<Path>) {
|
||||||
for (method, addr) in methods.iter() {
|
if show {
|
||||||
print!("\n (0x{addr:08x} {method})")
|
println!("(search {:?})", path.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn load (show: bool, path: &impl AsRef<Path>) {
|
||||||
|
if show {
|
||||||
|
println!("(load {:?})", path.as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn import (show: bool, module: &Arc<str>, exports: &[(usize, Arc<str>)]) {
|
||||||
|
if show {
|
||||||
|
println!("(import {:?} {})", module, exports.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.address 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> {
|
||||||
|
let deps = "";// format!("(deps lib {:>4} addr {:>4})",
|
||||||
|
//self.dependencies.len(),
|
||||||
|
//self.deps_by_address.len());
|
||||||
|
let links = "";// format!("(links src {:>4} tgt {:>4})",
|
||||||
|
//self.links_by_source.len(),
|
||||||
|
//self.links_by_target.len());
|
||||||
|
let exports = format!("(exp {:>5})",
|
||||||
|
self.exports.len());
|
||||||
|
write!(f, "(dll {BOLD}{UNDERLINE}{:15}{RESET} [0x{:>08x}] (img 0x{:>08x} -> mem 0x{:>08x}) {deps} {links} {exports})",
|
||||||
|
&self.name,
|
||||||
|
self.code_size,
|
||||||
|
self.code_start,
|
||||||
|
self.code_base)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Module {
|
||||||
|
pub fn show_dependencies (&self) {
|
||||||
|
for (name, module) in self.dependencies.iter() {
|
||||||
|
print!(" ({module:?}");
|
||||||
|
for (method, addr) in module.exports.iter() {
|
||||||
|
//print!("\n (0x{addr:08x} {method})")
|
||||||
}
|
}
|
||||||
println!(")");
|
println!(")");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn show_instruction (&self, addr: usize) {
|
pub fn show_instruction (&self, addr: usize) {
|
||||||
let mut decoder = Decoder::with_ip(64, &self.text_section[addr..], 0x1000, DecoderOptions::NONE);
|
let mut decoder = Decoder::with_ip(64, &self.code[addr..], 0x1000, DecoderOptions::NONE);
|
||||||
let instruction = decoder.decode();
|
let instruction = decoder.decode();
|
||||||
let is_stack = instruction.is_stack_instruction();
|
let is_stack = instruction.is_stack_instruction();
|
||||||
let is_link = Link::matches(&instruction);
|
let is_link = CallSite::matches(&instruction);
|
||||||
let style = if is_stack || is_link { BOLD } else { DIM };
|
let style = if is_stack || is_link { BOLD } else { DIM };
|
||||||
print!("{style}{} {:20}{RESET}", Show::num(addr), &self.name);
|
print!("{style}{} {:20}{RESET}", fmt_num(addr), &self.name);
|
||||||
print!(" {style}{:26}{RESET}", fmt_bytes(&self.text_section[addr..addr+instruction.len()]));
|
print!(" {style}{:26}{RESET}", fmt_bytes(&self.code[addr..addr+instruction.len()]));
|
||||||
println!(" {style}{instruction}{RESET}");
|
println!(" {style}{instruction}{RESET}");
|
||||||
}
|
}
|
||||||
pub fn show_call_site (&self, addr: usize, length: usize, context: usize) {
|
pub fn show_call_site (&self, addr: usize, length: usize, context: usize) {
|
||||||
|
|
@ -60,11 +121,11 @@ impl Dll {
|
||||||
write!(&mut output, "{}", " ");
|
write!(&mut output, "{}", " ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
write!(&mut output, "{:02x}", self.text_section[byte]);
|
write!(&mut output, "{:02x}", self.code[byte]);
|
||||||
write!(&mut output, "{RESET}");
|
write!(&mut output, "{RESET}");
|
||||||
if byte % line == line - 1 {
|
if byte % line == line - 1 {
|
||||||
if snap(byte) == snap(addr) {
|
if snap(byte) == snap(addr) {
|
||||||
let dasm = Show::link_dasm(&self.text_section[addr..], addr);
|
let dasm = Show::link_dasm(&self.code[addr..], addr);
|
||||||
write!(&mut output, " -> {dasm}");
|
write!(&mut output, " -> {dasm}");
|
||||||
}
|
}
|
||||||
write!(&mut output, " \n");
|
write!(&mut output, " \n");
|
||||||
|
|
@ -74,43 +135,18 @@ impl Dll {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Link {
|
impl CallSite {
|
||||||
pub fn show (&self, caller_module_name: &Arc<str>) {
|
pub fn show (&self) {
|
||||||
let module = self.module.as_ref();
|
let caller = self.caller.name.as_ref();
|
||||||
|
let module = self.target.name.as_ref();
|
||||||
let method = self.method.as_ref();
|
let method = self.method.as_ref();
|
||||||
if module.is_some() || method.is_some() {
|
let style = GREEN;
|
||||||
println!("╰--------> {caller_module_name} -> {GREEN}{}{}{}{RESET}",
|
println!("╰--------> {caller} -> {style}{module}::{method}{RESET}");
|
||||||
module.map(|x|x.as_ref()).unwrap_or(&""),
|
//module.map(|x|x.as_ref()).unwrap_or(&""),
|
||||||
if module.is_some() && method.is_some() { "::" } else { "" },
|
//method.map(|x|x.as_ref()).unwrap_or(&""));
|
||||||
method.map(|x|x.as_ref()).unwrap_or(&""));
|
//} else {
|
||||||
} else {
|
//println!("╰--------> {caller} -> {RED}(unresolved){RESET} {self:?}");
|
||||||
println!("╰--------> {caller_module_name} -> {RED}(unresolved){RESET} {self:?}");
|
//}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Show {
|
|
||||||
pub fn num (n: usize) -> Arc<str> {
|
|
||||||
format!("0x{n:>08x}").into()
|
|
||||||
}
|
|
||||||
pub fn link_target_addrs (link: Option<&Arc<Link>>) {
|
|
||||||
println!(" {}{}{}",
|
|
||||||
link.map(|link|format!(" (offset {})", Show::num(link.offset as usize)))
|
|
||||||
.unwrap_or(String::new()),
|
|
||||||
link.map(|link|format!(" (source {})", Show::num(link.source as usize)))
|
|
||||||
.unwrap_or(String::new()),
|
|
||||||
link.map(|link|format!(" (target {})", Show::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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,7 +197,7 @@ impl Show {
|
||||||
//let opcodes = §ion_data[position..position+instruction.len()];
|
//let opcodes = §ion_data[position..position+instruction.len()];
|
||||||
////println!("0x{position:08x} {opcodes:32} {instruction}");
|
////println!("0x{position:08x} {opcodes:32} {instruction}");
|
||||||
//if (instruction.flow_control() == iced_x86::FlowControl::IndirectBranch
|
//if (instruction.flow_control() == iced_x86::FlowControl::IndirectBranch
|
||||||
//|| instruction.flow_control() == iced_x86::FlowControl::IndirectLink)
|
//|| instruction.flow_control() == iced_x86::FlowControl::IndirectCallSite)
|
||||||
//&& instruction.op0_kind() == iced_x86::OpKind::Memory {
|
//&& instruction.op0_kind() == iced_x86::OpKind::Memory {
|
||||||
//match opcodes[0] {
|
//match opcodes[0] {
|
||||||
//0xff => match opcodes[1] {
|
//0xff => match opcodes[1] {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ pub(crate) use std::io::Write;
|
||||||
pub(crate) use std::os::unix::fs::OpenOptionsExt;
|
pub(crate) use std::os::unix::fs::OpenOptionsExt;
|
||||||
pub(crate) use std::path::{Path, PathBuf};
|
pub(crate) use std::path::{Path, PathBuf};
|
||||||
pub(crate) use std::pin::Pin;
|
pub(crate) use std::pin::Pin;
|
||||||
pub(crate) use std::sync::Arc;
|
pub(crate) use std::sync::{Arc, RwLock};
|
||||||
pub(crate) use itertools::{Itertools, izip};
|
pub(crate) use itertools::{Itertools, izip};
|
||||||
//pub(crate) use ::lancelot::loader::pe::{PE, reloc::apply_relocations};
|
//pub(crate) use ::lancelot::loader::pe::{PE, reloc::apply_relocations};
|
||||||
//pub(crate) use ::goblin::{error, Object, pe::{import::Import, export::Export}};
|
//pub(crate) use ::goblin::{error, Object, pe::{import::Import, export::Export}};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue