diff --git a/Justfile b/Justfile index 8b2dfe8..4a38737 100644 --- a/Justfile +++ b/Justfile @@ -1,6 +1,8 @@ hello: 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: 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 diff --git a/crates/vestal/src/.scratch.rs b/crates/vestal/src/.scratch.rs index 397cc98..3767e0f 100644 --- a/crates/vestal/src/.scratch.rs +++ b/crates/vestal/src/.scratch.rs @@ -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, decoder: &mut Decoder, verbose: bool) -> Usually>> { + //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::default().into(); + //let method: Arc = "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>, Option>, Arc) { + //(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 { + //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)) + //} diff --git a/crates/vestal/src/call_sites.rs b/crates/vestal/src/call_sites.rs new file mode 100644 index 0000000..36045d0 --- /dev/null +++ b/crates/vestal/src/call_sites.rs @@ -0,0 +1,63 @@ +use crate::*; +impl Module { + /// Collect all calls that point to imports. + pub fn load_call_sites (&mut self) -> Usually { + 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 { + 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) -> Option { + None + //Some(*self.deps_by_library + //.get(link.module.as_ref()?)? + //.get(link.method.as_ref()?)?) + } + + pub fn resolve_forward (&self, rva: &RVA) -> Usually, Arc)>> { + 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()))) + } +} diff --git a/crates/vestal/src/exports.rs b/crates/vestal/src/exports.rs new file mode 100644 index 0000000..deae667 --- /dev/null +++ b/crates/vestal/src/exports.rs @@ -0,0 +1,10 @@ +use crate::*; +impl Module { + /// Collect all exported methods. + pub fn load_exports (&mut self) -> Usually { + 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()) + } +} diff --git a/crates/vestal/src/imports.rs b/crates/vestal/src/imports.rs new file mode 100644 index 0000000..b03eaf2 --- /dev/null +++ b/crates/vestal/src/imports.rs @@ -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, Vec<(usize, Arc)>)> + { + 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, method_name: &Arc + ) { + 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, + method_name: &Arc, + ) -> (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) + } +} diff --git a/crates/vestal/src/main.rs b/crates/vestal/src/main.rs index b2ec785..b55cd3b 100644 --- a/crates/vestal/src/main.rs +++ b/crates/vestal/src/main.rs @@ -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)] 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; - -/// 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::("path").unwrap_or_else(||panic!("pass path to VST DLL")); - let verbose = *(matches.get_one::("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. fn cli () -> clap::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!(-v --verbose "Show a lot of info").required(false)) } - -/// Application state -#[derive(Debug, Default)] -struct Vestal { +/// A DLL that will be linked into the output. +pub struct Module { + /// Scope. + pub namespace: Arc, Arc>>>, + /// Search paths for resolving DLL names. + pub search_paths: Arc>>>, + /// Canonical name like `xxx.dll` (always lowercase) + pub name: Arc, + /// Path to DLL on host filesystem. + pub path: Arc, + /// Bytes of `#!`-instruction + pub bang: Arc<[u8]>, + /// Parsed portable executable + pub pe: Arc, + /// 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>, + /// Call targets to methods from imported modules. + pub imports: BTreeMap, Arc)>, + /// Addresses of exported methods by name + pub exports: BTreeMap, ThunkData>, + /// Locations in `.text` section that need to be patched + pub call_sites: BTreeMap>, /// More detailed output. - verbose: bool, - /// Search paths - paths: BTreeSet>, - /// Paths visited - visited: BTreeSet>, - /// All DLLs in scope - dlls: BTreeMap, Arc>, + pub verbose: bool, } - -impl Vestal { - /// Construct a rebuilder - fn new (verbose: bool, paths: &[impl AsRef]) -> Self { - let paths: BTreeSet<_> = paths.iter().map(|x|Arc::new(x.as_ref().into())).collect(); - if verbose { - // Report search paths - for path in paths.iter() { - println!("(search {path:?})") - } - } - Self { verbose, paths, ..Default::default() } +/// A call from one DLL to another. +#[derive(Debug)] +pub struct CallSite { + /// Module that is making the call + pub caller: Arc, + /// Address in memory + pub source: u32, + /// Address on disk + 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, + /// Name of method that is being called + pub method: Arc, +} +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::("path") { + Module::from_path(path, *(args.get_one::("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"); } - - /// Load a library from a path. - fn load (&mut self, path: &impl AsRef) -> Usually> { - let path: Arc = Arc::from(PathBuf::from(path.as_ref())); - if self.visited.contains(&path) { - let name = path.file_name().expect("no file name"); - let name: Arc = name.to_str().map(Arc::from).expect("non-unicode filename"); - return Ok(self.dlls.get(&name).unwrap().clone()) - } - if self.verbose { - println!("(load {path:?})"); - } - self.visited.insert(path.clone()); - let dll = Arc::new(Dll::new(&Arc::new(PathBuf::from(path.as_ref())), self.verbose)?); - self.dlls.insert(dll.name.clone(), dll.clone()); - Ok(dll) + Ok(()) +} +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. + fn from_path (path: impl AsRef, verbose: bool) -> Usually { + 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, + }) } - - /// Load all dependencies recursively, starting from the given path. - fn load_recursively (&mut self, path: &impl AsRef) -> Usually> { - let dll = self.load(path)?; - 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) + /// Add a search path + fn search (mut self, path: impl AsRef) -> Self { + Log::add(self.verbose, &path); + self.search_paths.write().unwrap().insert(path.as_ref().to_path_buf().into()); + self } - - fn find (&self, name: &str) -> Usually> { - for base in self.paths.iter() { + /// Load the dependency tree, starting from this module. + fn load (mut self) -> Usually { + let _ = self.load_imports()?; + let _ = self.load_exports()?; + let _ = self.load_call_sites()?; + println!("{self:?}"); + Ok(self) + } + /// Search for DLL by name in search paths. + fn find (&self, name: &impl AsRef) -> Usually> { + 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()); if self.verbose { @@ -112,321 +144,85 @@ impl Vestal { } Ok(None) } - - fn resolve (&self, dll: &Dll, addr: u32, link: &Arc) -> Usually<()> { + /// Relink a VST. + //fn run (&mut self, path: impl AsRef) -> 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) -> Usually> { + //let name = to_dll_name(path); + //let path: Arc = 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) -> Usually<()> { let addr = (addr - dll.code_base) as usize; dll.show_call_site(addr, link.length, 1); - link.show(&dll.name); - //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 = name.to_str().map(Arc::from).expect("non-unicode filename"); - //let linked_dll = self.dlls.get(&name).unwrap_or_else(||panic!("# not found: {name}")); + link.show(); Ok(()) } - - fn resolve_recursively (&self, dll: &Dll, addr: u32, link: &Arc) -> Usually<()> { + fn resolve_recursively (&self, dll: &Module, addr: u32, link: &Arc) -> Usually<()> { self.resolve(dll, addr, link)?; - let module_name = link.module.as_ref().unwrap(); - let export_name = link.method.as_ref().unwrap(); - if let Some((module_name, export_name)) = match self.dlls - .get(module_name) - .map(|module|module.exports.get(export_name)) - { - Some(Some(ThunkData::Function(rva))) => Some((module_name.clone(), export_name.clone())), - Some(Some(ThunkData::ForwarderString(rva))) => dll.resolve_forward(&rva)?, - Some(Some(thunk)) => panic!("# unsupported {thunk:?}"), - Some(None) => panic!("# export not resolved: {export_name}"), - None => panic!("# module not resolved: {module_name}"), - } { - let module = self.dlls.get(&module_name) - .unwrap_or_else(||panic!("# no module {module_name}")); - let method = module.exports.get(&export_name) - .unwrap_or_else(||panic!("# no method {export_name}")); - println!("{module_name} {export_name} {method:?}"); - //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) - } + let module = &link.target.name; + let export = &link.method; + //if let Some((module, export)) = match self.modules + //.get(module) + //.map(|module|module.exports.get(export)) + //{ + //Some(Some(ThunkData::Function(rva))) => Some((module.clone(), export.clone())), + //Some(Some(ThunkData::ForwarderString(rva))) => dll.resolve_forward(&rva)?, + //Some(Some(thunk)) => panic!("# unsupported {thunk:?}"), + //Some(None) => panic!("# export not resolved: {export}"), + //None => panic!("# module not resolved: {module}"), + //} { + //let module = self.modules.get(&module).unwrap_or_else(||panic!("# no module {module}")); + //let method = module.exports.get(&export).unwrap_or_else(||panic!("# no method {export}")); + //println!("{module:?} {export} {method:?}"); + //} else { + //panic!("# unresolved link at {:?} [{addr}]", &dll.name) + //} Ok(()) } - +} +impl Module { } -struct Dll { - /// Canonical name like `xxx.dll` (always lowercase) - name: Arc, - /// Path to DLL on host filesystem. - path: Arc, - /// Bytes of `#!`-instruction - bang: Arc<[u8]>, - /// Parsed portable executable - pe: Arc, - /// 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, ThunkData>, - /// Locations in `.text` section that need to be patched - call_sites: BTreeMap, - /// Addresses of imported methods by library - deps_by_library: BTreeMap, BTreeMap, u32>>, - /// Imported methods by callable address - deps_by_address: BTreeMap, Arc)>, - /// Links to dependencies by source address - links_by_source: BTreeMap>, - /// Links to dependencies by callable address - links_by_target: BTreeMap>>, +fn to_dll_name (path: &impl AsRef) -> Arc { + Arc::from(path.as_ref() + .file_name() + .expect("no file name") + .to_str() + .expect("non-unicode filename")) } -#[derive(Debug)] -pub struct Link { - /// 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>, - /// Method being called - pub method: Option>, +fn read_pe (path: &impl AsRef) -> Usually<(Arc, 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)) } -impl Dll { - - pub fn new (path: &Arc, verbose: bool) -> Usually { - 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) -> Usually<(Arc, Arc, Arc<[u8]>, Arc<[u8]>)> { - let name = path.as_ref().file_name().expect("no file name"); - let name: Arc = 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())); - Ok((name, pe, data, bang)) - } - - pub fn read_text_section (pe: &VecPE) -> Usually<(Arc<[u8]>, usize, usize)> { - 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)) - } - - /// Collect all exported methods. - pub fn exports_collect (&mut self, _verbose: bool) -> Usually { - 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()) - } - - /// 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, method_name: &Arc, 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, method_name: &Arc, 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, method_name: &Arc - ) { - 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>, Option>, Arc) { - 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 { - 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>> { - 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) -> Option { - Some(*self.deps_by_library - .get(link.module.as_ref()?)? - .get(link.method.as_ref()?)?) - } - - fn resolve_forward (&self, rva: &RVA) -> Usually, Arc)>> { - 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()))) - } +fn read_code (pe: &VecPE) -> Usually<(Arc<[u8]>, usize, usize)> { + 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)) +} +fn base_of_code (pe: &VecPE) -> Usually { + Ok(match pe.get_valid_nt_headers()? { + NTHeaders::NTHeaders32(h32) => panic!("32 bit headers"), + NTHeaders::NTHeaders64(h64) => h64.optional_header.base_of_code.0, + }) } /// 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 { - fn fmt (&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> { - 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 { +impl CallSite { + 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 { + fn skip (opcodes: &[u8]) -> bool { match opcodes[0] { 0x41 | 0x42 | 0x43 | 0x49 => match opcodes[1] { 0xff => return true, @@ -511,26 +289,4 @@ impl Link { } false } - pub fn target (opcodes: &[u8], offset_rva: u32) -> Option { - 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 - } } diff --git a/crates/vestal/src/show.rs b/crates/vestal/src/show.rs index b87b704..9d0242c 100644 --- a/crates/vestal/src/show.rs +++ b/crates/vestal/src/show.rs @@ -5,26 +5,87 @@ pub fn fmt_bytes (bytes: &[u8]) -> Arc { bytes.iter().map(|x|format!("{x:02x}")).join(" ").into() } +pub fn fmt_num (n: usize) -> Arc { + format!("0x{n:>08x}").into() +} + pub struct Show; -impl Dll { - pub fn show_deps_by_library (&self) { - for (module, methods) in self.deps_by_library.iter() { - print!(" ({module}"); - for (method, addr) in methods.iter() { - print!("\n (0x{addr:08x} {method})") +pub struct Log; + +impl Log { + pub fn add (show: bool, path: &impl AsRef) { + if show { + println!("(search {:?})", path.as_ref()) + } + } + pub fn load (show: bool, path: &impl AsRef) { + if show { + println!("(load {:?})", path.as_ref()); + } + } + pub fn import (show: bool, module: &Arc, exports: &[(usize, Arc)]) { + if show { + println!("(import {:?} {})", module, exports.len()); + } + } +} + +impl Show { + pub fn link_target_addrs (link: Option<&Arc>) { + 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 { + 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!(")"); } } 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 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 }; - print!("{style}{} {:20}{RESET}", Show::num(addr), &self.name); - print!(" {style}{:26}{RESET}", fmt_bytes(&self.text_section[addr..addr+instruction.len()])); + 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) { @@ -60,11 +121,11 @@ impl Dll { write!(&mut output, "{}", " "); } } - write!(&mut output, "{:02x}", self.text_section[byte]); + 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.text_section[addr..], addr); + let dasm = Show::link_dasm(&self.code[addr..], addr); write!(&mut output, " -> {dasm}"); } write!(&mut output, " \n"); @@ -74,43 +135,18 @@ impl Dll { } } -impl Link { - pub fn show (&self, caller_module_name: &Arc) { - let module = self.module.as_ref(); +impl CallSite { + pub fn show (&self) { + let caller = self.caller.name.as_ref(); + let module = self.target.name.as_ref(); let method = self.method.as_ref(); - if module.is_some() || method.is_some() { - println!("╰--------> {caller_module_name} -> {GREEN}{}{}{}{RESET}", - module.map(|x|x.as_ref()).unwrap_or(&""), - if module.is_some() && method.is_some() { "::" } else { "" }, - method.map(|x|x.as_ref()).unwrap_or(&"")); - } else { - println!("╰--------> {caller_module_name} -> {RED}(unresolved){RESET} {self:?}"); - } - } -} - -impl Show { - pub fn num (n: usize) -> Arc { - format!("0x{n:>08x}").into() - } - pub fn link_target_addrs (link: Option<&Arc>) { - 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 { - 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() + let style = GREEN; + println!("╰--------> {caller} -> {style}{module}::{method}{RESET}"); + //module.map(|x|x.as_ref()).unwrap_or(&""), + //method.map(|x|x.as_ref()).unwrap_or(&"")); + //} else { + //println!("╰--------> {caller} -> {RED}(unresolved){RESET} {self:?}"); + //} } } @@ -161,7 +197,7 @@ impl Show { //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::IndirectLink) + //|| instruction.flow_control() == iced_x86::FlowControl::IndirectCallSite) //&& instruction.op0_kind() == iced_x86::OpKind::Memory { //match opcodes[0] { //0xff => match opcodes[1] { diff --git a/crates/vestal/src/util.rs b/crates/vestal/src/util.rs index 8c72160..aa874c3 100644 --- a/crates/vestal/src/util.rs +++ b/crates/vestal/src/util.rs @@ -5,7 +5,7 @@ pub(crate) use std::io::Write; pub(crate) use std::os::unix::fs::OpenOptionsExt; pub(crate) use std::path::{Path, PathBuf}; pub(crate) use std::pin::Pin; -pub(crate) use std::sync::Arc; +pub(crate) use std::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}};