mirror of
https://codeberg.org/unspeaker/vestal.git
synced 2025-12-06 08:36:41 +01:00
wip: resolve in breadth
This commit is contained in:
parent
88ee260cde
commit
8a5452f6a6
3 changed files with 312 additions and 244 deletions
|
|
@ -1,98 +0,0 @@
|
||||||
use crate::*;
|
|
||||||
|
|
||||||
impl Dll {
|
|
||||||
|
|
||||||
pub fn collect_deps (&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 = descriptor.get_name(pe.as_ref())?.as_str()?.to_lowercase();
|
|
||||||
let imp = descriptor.get_imports(pe.as_ref())?;
|
|
||||||
let iat = descriptor.get_first_thunk(pe.as_ref())?;
|
|
||||||
let ilt = descriptor.get_original_first_thunk(pe.as_ref())?;
|
|
||||||
let lut = descriptor.get_lookup_thunks(pe.as_ref())?;
|
|
||||||
let unwrap_thunk = |thunk: &Thunk, name|match thunk {
|
|
||||||
Thunk::Thunk32(t) => panic!("32 bit {name}"),
|
|
||||||
Thunk::Thunk64(t) => t.0
|
|
||||||
};
|
|
||||||
let mut buffer = vec![];
|
|
||||||
for (index, (import, thunk, orig, lookup)) in izip!(
|
|
||||||
imp,
|
|
||||||
iat.iter().map(|thunk|format!("0x{:08x}", unwrap_thunk(thunk, "IAT thunk"))),
|
|
||||||
ilt.iter().map(|thunk|format!("0x{:08x}", unwrap_thunk(thunk, "ILT (orig) thunk"))),
|
|
||||||
lut.iter().map(|thunk|format!("0x{:08x}", unwrap_thunk(thunk, "lookup thunk"))),
|
|
||||||
).enumerate() {
|
|
||||||
buffer.push((index, import, thunk, orig, lookup));
|
|
||||||
}
|
|
||||||
for (index, import, thunk, orig, lookup) in buffer {
|
|
||||||
let (new_modules, new_methods) = self.collect_dep(
|
|
||||||
module_name.as_str(),
|
|
||||||
descriptor,
|
|
||||||
index,
|
|
||||||
import,
|
|
||||||
thunk,
|
|
||||||
orig,
|
|
||||||
lookup,
|
|
||||||
verbose
|
|
||||||
)?;
|
|
||||||
modules += new_modules;
|
|
||||||
methods += new_methods;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if verbose {
|
|
||||||
println!(" (deps-modules {modules})");
|
|
||||||
println!(" (deps-methods {methods})");
|
|
||||||
for (module, methods) in self.deps_by_library.iter() {
|
|
||||||
print!(" ({module}");
|
|
||||||
for (method, addr) in methods.iter() {
|
|
||||||
print!("\n (0x{addr:08x} {method})")
|
|
||||||
}
|
|
||||||
println!(")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok((modules, methods))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn collect_dep (
|
|
||||||
&mut self,
|
|
||||||
module_name: &str,
|
|
||||||
descriptor: &ImageImportDescriptor,
|
|
||||||
index: usize,
|
|
||||||
import: ImportData,
|
|
||||||
thunk: String,
|
|
||||||
orig: String,
|
|
||||||
lookup: String,
|
|
||||||
verbose: bool,
|
|
||||||
) -> Usually<(usize, usize)> {
|
|
||||||
let mut modules = 0;
|
|
||||||
let mut methods = 0;
|
|
||||||
let call_via = descriptor.first_thunk.0 + index as u32 * 8;
|
|
||||||
let method = match import {
|
|
||||||
ImportData::Ordinal(x) => format!("___VESTAL_ORDINAL_{x}"),
|
|
||||||
ImportData::ImportByName(name) => format!("{name}"),
|
|
||||||
};
|
|
||||||
let module_name: Arc<str> = module_name.clone().into();
|
|
||||||
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();
|
|
||||||
let method: Arc<str> = method.clone().into();
|
|
||||||
if module.contains_key(&method) {
|
|
||||||
panic!("duplicate method {method} in {module_name}");
|
|
||||||
}
|
|
||||||
module.insert(method.clone(), call_via);
|
|
||||||
if self.deps_by_address.contains_key(&call_via) {
|
|
||||||
panic!("duplicate address {call_via} from {module_name}");
|
|
||||||
}
|
|
||||||
self.deps_by_address.insert(call_via, (module_name.clone(), method.clone()));
|
|
||||||
methods += 1;
|
|
||||||
if verbose {
|
|
||||||
println!(" (import 0x{call_via:08x} {module_name}::{method})");
|
|
||||||
}
|
|
||||||
Ok((modules, methods))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,87 +1,105 @@
|
||||||
#![feature(slice_split_once)]
|
#![feature(slice_split_once)]
|
||||||
mod util; pub(crate) use self::util::*;
|
mod util; pub(crate) use self::util::*;
|
||||||
mod deps; pub(crate) use self::deps::*;
|
|
||||||
mod call; pub(crate) use self::call::*;
|
mod call; pub(crate) use self::call::*;
|
||||||
mod show; pub(crate) use self::show::*;
|
mod show; pub(crate) use self::show::*;
|
||||||
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<()> {
|
fn main () -> Usually<()> {
|
||||||
use clap::{arg, command, value_parser, ArgAction, Command};
|
use clap::{arg, command, value_parser, ArgAction, Command};
|
||||||
let matches = command!()
|
// Parse command line arguments.
|
||||||
.arg(arg!([path] "Path to VST DLL").value_parser(value_parser!(PathBuf)))
|
let matches = cli().get_matches();
|
||||||
//.arg(arg!(-s --stub <VALUE> "Provide a stub import").required(false))
|
let path = matches.get_one::<PathBuf>("path").unwrap_or_else(||panic!("pass path to VST DLL"));
|
||||||
.arg(arg!(-i --inspect "Show info, don't run").required(false))
|
|
||||||
.arg(arg!(-v --verbose "Show a lot of info").required(false))
|
|
||||||
.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));
|
let verbose = *(matches.get_one::<bool>("verbose").unwrap_or(&false));
|
||||||
let mut rebuilder = Vestal::new(&[
|
// Construct a rebuilder.
|
||||||
|
let mut rebuilder = Vestal::new(verbose, &[
|
||||||
|
// TODO allow user to specify search paths, system path, wineprefix...
|
||||||
std::env::current_dir()?,
|
std::env::current_dir()?,
|
||||||
canonicalize(path.clone().parent().expect("invalid parent path"))?,
|
canonicalize(path.clone().parent().expect("invalid parent path"))?,
|
||||||
"/home/user/Lab/Cosmo/wineprefix/drive_c/windows/system32".into(),
|
"/home/user/Lab/Cosmo/wineprefix/drive_c/windows/system32".into(),
|
||||||
]);
|
]);
|
||||||
println!();
|
// Resolve input path.
|
||||||
for path in rebuilder.paths.iter() {
|
let path = path.to_str().expect("path must be unicode");
|
||||||
println!("(search {path:?})")
|
let path = rebuilder.find(path)?.unwrap_or_else(||panic!("# not found: {path:?}"));
|
||||||
}
|
// Load with dependencies.
|
||||||
let path = rebuilder.find(path.to_str().expect("path must be unicode"), false)?
|
let main = rebuilder.load_recurse(&path)?;
|
||||||
.unwrap_or_else(||panic!("Could not find: {path:?}"));
|
rebuilder.resolve_recurse(&main)?;
|
||||||
let name = rebuilder.load(&path, true, verbose)?;
|
|
||||||
let main = rebuilder.dlls.get(&name).unwrap();
|
|
||||||
rebuilder.resolve_calls(&main, true, verbose)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Define command line arguments.
|
||||||
|
fn cli () -> clap::Command {
|
||||||
|
use clap::{arg, command, value_parser, ArgAction, Command};
|
||||||
|
command!()
|
||||||
|
.arg(arg!([path] "Path to VST DLL").value_parser(value_parser!(PathBuf)))
|
||||||
|
//.arg(arg!(-s --stub <VALUE> "Provide a stub import").required(false))
|
||||||
|
.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)]
|
#[derive(Debug, Default)]
|
||||||
struct Vestal {
|
struct Vestal {
|
||||||
|
/// More detailed output.
|
||||||
|
verbose: bool,
|
||||||
/// Search paths
|
/// Search paths
|
||||||
paths: BTreeSet<Arc<PathBuf>>,
|
paths: BTreeSet<Arc<PathBuf>>,
|
||||||
/// All DLLs in scope
|
|
||||||
dlls: BTreeMap<Arc<str>, Arc<Dll>>,
|
|
||||||
/// Paths visited
|
/// Paths visited
|
||||||
visited: BTreeSet<Arc<PathBuf>>,
|
visited: BTreeSet<Arc<PathBuf>>,
|
||||||
|
/// All DLLs in scope
|
||||||
|
dlls: BTreeMap<Arc<str>, Arc<Dll>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Vestal {
|
impl Vestal {
|
||||||
fn new (paths: &[impl AsRef<Path>]) -> Self {
|
/// Construct a rebuilder
|
||||||
Self {
|
fn new (verbose: bool, paths: &[impl AsRef<Path>]) -> Self {
|
||||||
paths: paths.iter().map(|x|Arc::new(x.as_ref().into())).collect(),
|
let paths: BTreeSet<_> = paths.iter().map(|x|Arc::new(x.as_ref().into())).collect();
|
||||||
..Default::default()
|
if verbose {
|
||||||
|
// Report search paths
|
||||||
|
for path in paths.iter() {
|
||||||
|
println!("(search {path:?})")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Self { verbose, paths, ..Default::default() }
|
||||||
}
|
}
|
||||||
fn load (&mut self, path: &impl AsRef<Path>, recurse: bool, verbose: bool) -> Usually<Arc<str>> {
|
/// Load all dependencies recursively, starting from the given path.
|
||||||
|
fn load_recurse (&mut self, path: &impl AsRef<Path>) -> Usually<Arc<Dll>> {
|
||||||
|
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_recurse(&dep_path)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(dll)
|
||||||
|
}
|
||||||
|
fn load (&mut self, path: &impl AsRef<Path>) -> Usually<Arc<Dll>> {
|
||||||
let path: Arc<PathBuf> = Arc::from(PathBuf::from(path.as_ref()));
|
let path: Arc<PathBuf> = Arc::from(PathBuf::from(path.as_ref()));
|
||||||
if self.visited.contains(&path) {
|
if self.visited.contains(&path) {
|
||||||
let name = path.file_name().expect("no file 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 name: Arc<str> = name.to_str().map(Arc::from).expect("non-unicode filename");
|
||||||
return Ok(name)
|
return Ok(self.dlls.get(&name).unwrap().clone())
|
||||||
}
|
}
|
||||||
if verbose {
|
if self.verbose {
|
||||||
println!("(load {path:?})");
|
println!("(load {path:?})");
|
||||||
}
|
}
|
||||||
self.visited.insert(path.clone());
|
self.visited.insert(path.clone());
|
||||||
let dll = Arc::new(Dll::new(&Arc::new(PathBuf::from(path.as_ref())), verbose)?);
|
let dll = Arc::new(Dll::new(&Arc::new(PathBuf::from(path.as_ref())), self.verbose)?);
|
||||||
self.dlls.insert(dll.name.clone(), dll.clone());
|
self.dlls.insert(dll.name.clone(), dll.clone());
|
||||||
if recurse {
|
Ok(dll)
|
||||||
for dep in dll.deps_by_library.keys() {
|
|
||||||
if let Some(dep) = self.find(dep, verbose)? {
|
|
||||||
self.load(&dep, recurse, verbose)?;
|
|
||||||
} else {
|
|
||||||
panic!("not found: {dep:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(dll.name.clone())
|
|
||||||
}
|
}
|
||||||
fn find (&self, name: &str, verbose: bool) -> Usually<Option<PathBuf>> {
|
|
||||||
|
fn find (&self, name: &str) -> Usually<Option<PathBuf>> {
|
||||||
for base in self.paths.iter() {
|
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 verbose {
|
if self.verbose {
|
||||||
println!("# looking for {name} at {path:?}");
|
println!("# looking for {name} at {path:?}");
|
||||||
}
|
}
|
||||||
if std::fs::exists(&path)? {
|
if std::fs::exists(&path)? {
|
||||||
if verbose {
|
if self.verbose {
|
||||||
println!("# found {name} at {path:?}");
|
println!("# found {name} at {path:?}");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -90,67 +108,110 @@ impl Vestal {
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
fn resolve_calls (&self, dll: &Dll, recurse: bool, verbose: bool) -> Usually<()> {
|
|
||||||
|
fn resolve (&self, dll: &Dll) -> Usually<()> {
|
||||||
println!("{:11}{BOLD}{}{RESET}", "", dll.name);
|
println!("{:11}{BOLD}{}{RESET}", "", dll.name);
|
||||||
for (addr, call) in dll.calls_by_source.iter() {
|
for (addr, call) in dll.calls_by_source.iter() {
|
||||||
self.resolve_call(dll, *addr, call, recurse, verbose)?;
|
self.resolve_call(dll, *addr, call)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn resolve_call (
|
|
||||||
&self,
|
fn resolve_recurse (&self, dll: &Dll) -> Usually<()> {
|
||||||
dll: &Dll,
|
println!("{:11}{BOLD}{}{RESET}", "", dll.name);
|
||||||
addr: u32,
|
for (addr, call) in dll.calls_by_source.iter() {
|
||||||
call: &Arc<Call>,
|
self.resolve_call_recurse(dll, *addr, call)?;
|
||||||
recurse: bool,
|
}
|
||||||
verbose: bool,
|
Ok(())
|
||||||
) -> Usually<()> {
|
}
|
||||||
|
|
||||||
|
fn resolve_call (&self, dll: &Dll, addr: u32, call: &Arc<Call>) -> Usually<()> {
|
||||||
let addr = (addr - dll.code_base) as usize;
|
let addr = (addr - dll.code_base) as usize;
|
||||||
dll.show_call_site(addr, call.length, 1);
|
dll.show_call_site(addr, call.length, 1);
|
||||||
Show::call_module_method(Some(call));
|
call.show_call(&dll.name);
|
||||||
if let Some(method) = dll.parse_call(call) {
|
if let Some(method) = dll.parse_call(call) {
|
||||||
let module_name = call.module.as_ref().unwrap();
|
let module_name = call.module.as_ref().unwrap();
|
||||||
let method_name = call.method.as_ref().unwrap();
|
let method_name = call.method.as_ref().unwrap();
|
||||||
let path = self.find(module_name, false)?
|
let path = self.find(module_name)?.unwrap_or_else(||panic!("# not found: {module_name}"));
|
||||||
.unwrap_or_else(||panic!("# not found: {module_name}"));
|
let name = path.file_name().expect("no file name");
|
||||||
let name = path.file_name()
|
let name: Arc<str> = name.to_str().map(Arc::from).expect("non-unicode filename");
|
||||||
.expect("no file name");
|
let dll = self.dlls.get(&name).unwrap_or_else(||panic!("# not found: {name}"));
|
||||||
let name: Arc<str> = name.to_str().map(Arc::from)
|
}
|
||||||
.expect("non-unicode filename");
|
Ok(())
|
||||||
let dll = self.dlls.get(&name)
|
}
|
||||||
.unwrap_or_else(||panic!("# not found: {name}"));
|
|
||||||
match dll.exports.get(method_name) {
|
fn resolve_call_recurse (&self, dll: &Dll, addr: u32, call: &Arc<Call>) -> Usually<()> {
|
||||||
Some(ThunkData::Function(rva)) => {
|
let addr = (addr - dll.code_base) as usize;
|
||||||
let addr = (rva.0 - dll.code_base) as usize;
|
dll.show_call_site(addr, call.length, 1);
|
||||||
if recurse {
|
call.show_call(&dll.name);
|
||||||
self.resolve_call_recurse(dll, addr, module_name, method_name)?;
|
if let Some(method) = dll.parse_call(call) {
|
||||||
|
let module_name = call.module.as_ref().unwrap();
|
||||||
|
let method_name = call.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 dll = self.dlls.get(&name).unwrap_or_else(||panic!("# not found: {name}"));
|
||||||
|
let export = dll.exports.get(method_name);
|
||||||
|
if let Some(ThunkData::Function(rva)) = export {
|
||||||
|
self.resolve_call_recurse_into(
|
||||||
|
//dll,
|
||||||
|
//(rva.0 - dll.code_base) as usize,
|
||||||
|
module_name,
|
||||||
|
method_name,
|
||||||
|
)?;
|
||||||
|
} else if let Some(ThunkData::ForwarderString(rva)) = export {
|
||||||
|
let mut addr = (rva.0 - dll.code_base) as usize;
|
||||||
|
let mut forward = vec![];
|
||||||
|
while let Some(c) = dll.pe.as_slice().get(addr) {
|
||||||
|
if *c == 0x00 {
|
||||||
|
break
|
||||||
}
|
}
|
||||||
},
|
forward.push(*c);
|
||||||
None => panic!("# not found: {method_name}"),
|
addr += 1;
|
||||||
thunk => panic!("# unsupported {thunk:?}"),
|
}
|
||||||
|
let forward = String::from_utf8(forward)?;
|
||||||
|
if let Some((module, method)) = forward.as_str().split_once(".") {
|
||||||
|
self.resolve_call_recurse_into(
|
||||||
|
//dll,
|
||||||
|
//addr,
|
||||||
|
&format!("{}.dll", module.to_lowercase()).into(),
|
||||||
|
&method.into(),
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
panic!("# invalid forward {}", &forward);
|
||||||
|
}
|
||||||
|
} else if let Some(thunk) = export {
|
||||||
|
panic!("# unsupported {thunk:?}");
|
||||||
|
} else {
|
||||||
|
panic!("# not found: {method_name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn resolve_call_recurse (
|
|
||||||
|
fn resolve_call_recurse_into (
|
||||||
&self,
|
&self,
|
||||||
dll: &Dll,
|
//dll: &Dll,
|
||||||
address: usize,
|
//address: usize,
|
||||||
module_name: &Arc<str>,
|
module_name: &Arc<str>,
|
||||||
method_name: &Arc<str>,
|
method_name: &Arc<str>,
|
||||||
) -> Usually<()> {
|
) -> Usually<()> {
|
||||||
let mut decoder = Decoder::with_ip(64, &dll.text_section[address..], 0, DecoderOptions::NONE);
|
let module = self.dlls.get(module_name).unwrap_or_else(||panic!("# no dll {module_name}"));
|
||||||
while decoder.can_decode() {
|
let method = module.exports.get(method_name).unwrap_or_else(||panic!("# no export {method_name}"));
|
||||||
let position = decoder.position();
|
//println!("{} {} {:?}", module_name, method_name, method);
|
||||||
if let Some(call) = dll.collect_call(&mut decoder, true)? {
|
//let mut decoder = Decoder::with_ip(64, &dll.text_section[address..], 0, DecoderOptions::NONE);
|
||||||
println!("(call {call:#?}");
|
//while decoder.can_decode() {
|
||||||
}
|
//let position = decoder.position();
|
||||||
if dll.text_section[address + position] == 0xc3 {
|
//if let Some(call) = dll.collect_call(&mut decoder, true)? {
|
||||||
break
|
//println!("(call {call:#?}");
|
||||||
}
|
//}
|
||||||
}
|
//if dll.text_section[address + position] == 0xc3 {
|
||||||
|
//break
|
||||||
|
//}
|
||||||
|
//}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Dll {
|
struct Dll {
|
||||||
|
|
@ -182,15 +243,166 @@ struct Dll {
|
||||||
exports: BTreeMap<Arc<str>, ThunkData>,
|
exports: BTreeMap<Arc<str>, ThunkData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Dll {
|
||||||
|
|
||||||
|
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(),
|
||||||
|
calls_by_source: Default::default(),
|
||||||
|
calls_by_target: Default::default(),
|
||||||
|
};
|
||||||
|
let (_modules_count, _methods_count) = dll.collect_deps(verbose)?;
|
||||||
|
let _calls = dll.collect_calls(verbose)?;
|
||||||
|
let _exports = dll.collect_exports(verbose)?;
|
||||||
|
println!("{dll:?}");
|
||||||
|
Ok(dll)
|
||||||
|
}
|
||||||
|
|
||||||
|
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()));
|
||||||
|
Ok((name, pe, data, bang))
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn collect_exports (&mut self, _verbose: bool) -> 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())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collect_deps (&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;
|
||||||
|
let unwrap_thunk = |thunk: &Thunk, name|match thunk {
|
||||||
|
Thunk::Thunk32(t) => panic!("32 bit {name}"),
|
||||||
|
Thunk::Thunk64(t) => t.0
|
||||||
|
};
|
||||||
|
for descriptor in directory.descriptors.iter() {
|
||||||
|
let name = descriptor.get_name(pe.as_ref())?.as_str()?.to_lowercase();
|
||||||
|
let imp = descriptor.get_imports(pe.as_ref())?;
|
||||||
|
let iat = descriptor.get_first_thunk(pe.as_ref())?;
|
||||||
|
let ilt = descriptor.get_original_first_thunk(pe.as_ref())?;
|
||||||
|
let lut = descriptor.get_lookup_thunks(pe.as_ref())?;
|
||||||
|
let mut buffer = vec![];
|
||||||
|
for (index, (import, thunk, orig, lookup)) in izip!(
|
||||||
|
imp,
|
||||||
|
iat.iter().map(|thunk|format!("0x{:08x}", unwrap_thunk(thunk, "IAT thunk"))),
|
||||||
|
ilt.iter().map(|thunk|format!("0x{:08x}", unwrap_thunk(thunk, "ILT (orig) thunk"))),
|
||||||
|
lut.iter().map(|thunk|format!("0x{:08x}", unwrap_thunk(thunk, "lookup thunk"))),
|
||||||
|
).enumerate() {
|
||||||
|
buffer.push((index, import, thunk, orig, lookup));
|
||||||
|
}
|
||||||
|
for (index, import, thunk, orig, lookup) in buffer {
|
||||||
|
let (new_modules, new_methods) = self.collect_dep(
|
||||||
|
name.as_str(),
|
||||||
|
descriptor,
|
||||||
|
index,
|
||||||
|
import,
|
||||||
|
thunk,
|
||||||
|
orig,
|
||||||
|
lookup,
|
||||||
|
verbose
|
||||||
|
)?;
|
||||||
|
modules += new_modules;
|
||||||
|
methods += new_methods;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if verbose {
|
||||||
|
println!(" (deps-modules {modules})");
|
||||||
|
println!(" (deps-methods {methods})");
|
||||||
|
for (module, methods) in self.deps_by_library.iter() {
|
||||||
|
print!(" ({module}");
|
||||||
|
for (method, addr) in methods.iter() {
|
||||||
|
print!("\n (0x{addr:08x} {method})")
|
||||||
|
}
|
||||||
|
println!(")");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok((modules, methods))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collect_dep (
|
||||||
|
&mut self,
|
||||||
|
module_name: &str,
|
||||||
|
descriptor: &ImageImportDescriptor,
|
||||||
|
index: usize,
|
||||||
|
import: ImportData,
|
||||||
|
thunk: String,
|
||||||
|
orig: String,
|
||||||
|
lookup: String,
|
||||||
|
verbose: bool,
|
||||||
|
) -> Usually<(usize, usize)> {
|
||||||
|
let mut modules = 0;
|
||||||
|
let mut methods = 0;
|
||||||
|
let call_via = descriptor.first_thunk.0 + index as u32 * 8;
|
||||||
|
let method = match import {
|
||||||
|
ImportData::Ordinal(x) => format!("___VESTAL_ORDINAL_{x}"),
|
||||||
|
ImportData::ImportByName(name) => format!("{name}"),
|
||||||
|
};
|
||||||
|
let module_name: Arc<str> = module_name.clone().into();
|
||||||
|
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();
|
||||||
|
let method: Arc<str> = method.clone().into();
|
||||||
|
if module.contains_key(&method) {
|
||||||
|
panic!("duplicate method {method} in {module_name}");
|
||||||
|
}
|
||||||
|
module.insert(method.clone(), call_via);
|
||||||
|
if self.deps_by_address.contains_key(&call_via) {
|
||||||
|
panic!("duplicate address {call_via} from {module_name}");
|
||||||
|
}
|
||||||
|
self.deps_by_address.insert(call_via, (module_name.clone(), method.clone()));
|
||||||
|
methods += 1;
|
||||||
|
if verbose {
|
||||||
|
println!(" (import 0x{call_via:08x} {module_name}::{method})");
|
||||||
|
}
|
||||||
|
Ok((modules, methods))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Dll {
|
impl std::fmt::Debug for Dll {
|
||||||
fn fmt (&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
|
fn fmt (&self, f: &mut std::fmt::Formatter) -> std::result::Result<(), std::fmt::Error> {
|
||||||
let deps = format!("deps (lib {}) (addr {:3})",
|
let deps = format!("(deps lib {:>4} addr {:>4})",
|
||||||
self.deps_by_library.len(),
|
self.deps_by_library.len(),
|
||||||
self.deps_by_address.len());
|
self.deps_by_address.len());
|
||||||
let calls = format!("calls (src {:4}) (tgt {:4})",
|
let calls = format!("(calls src {:>4} tgt {:>4})",
|
||||||
self.calls_by_source.len(),
|
self.calls_by_source.len(),
|
||||||
self.calls_by_target.len());
|
self.calls_by_target.len());
|
||||||
let exports = format!("exp {}",
|
let exports = format!("(exp {:>5})",
|
||||||
self.exports.len());
|
self.exports.len());
|
||||||
write!(f, "(dll {BOLD}{UNDERLINE}{:15}{RESET} [0x{:>08x}] (img 0x{:>08x} -> mem 0x{:>08x}) {deps} {calls} {exports})",
|
write!(f, "(dll {BOLD}{UNDERLINE}{:15}{RESET} [0x{:>08x}] (img 0x{:>08x} -> mem 0x{:>08x}) {deps} {calls} {exports})",
|
||||||
&self.name,
|
&self.name,
|
||||||
|
|
@ -199,52 +411,3 @@ impl std::fmt::Debug for Dll {
|
||||||
self.code_base)
|
self.code_base)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Dll {
|
|
||||||
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 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 = &data[start..start+size];
|
|
||||||
let mut dll = Self {
|
|
||||||
bang,
|
|
||||||
name: name.clone(),
|
|
||||||
path: path.clone(),
|
|
||||||
exports: Default::default(),
|
|
||||||
deps_by_library: Default::default(),
|
|
||||||
deps_by_address: Default::default(),
|
|
||||||
calls_by_source: Default::default(),
|
|
||||||
calls_by_target: Default::default(),
|
|
||||||
text_section: Arc::from(text),
|
|
||||||
text_section_start: start,
|
|
||||||
text_section_size: 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,
|
|
||||||
};
|
|
||||||
let (_modules_count, _methods_count) = dll.collect_deps(verbose)?;
|
|
||||||
let _calls = dll.collect_calls(verbose)?;
|
|
||||||
println!("{dll:?}");
|
|
||||||
let _exports = dll.collect_exports(verbose)?;
|
|
||||||
Ok(dll)
|
|
||||||
}
|
|
||||||
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()));
|
|
||||||
Ok((name, pe, data, bang))
|
|
||||||
}
|
|
||||||
fn collect_exports (&mut self, _verbose: bool) -> Usually<usize> {
|
|
||||||
let directory = ImageExportDirectory::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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,22 @@ impl Dll {
|
||||||
write!(&mut output, " \n");
|
write!(&mut output, " \n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
print!("{output}");
|
print!("\n{output}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Call {
|
||||||
|
pub fn show_call (&self, name: &Arc<str>) {
|
||||||
|
let module = self.module.as_ref();
|
||||||
|
let method = self.method.as_ref();
|
||||||
|
if module.is_some() || method.is_some() {
|
||||||
|
println!("╰--------> {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!("╰--------> {name} -> {RED}(unresolved){RESET} {self:?}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -78,18 +93,6 @@ impl Show {
|
||||||
call.map(|call|format!(" (target {})", Show::num(call.target as usize)))
|
call.map(|call|format!(" (target {})", Show::num(call.target as usize)))
|
||||||
.unwrap_or(String::new()));
|
.unwrap_or(String::new()));
|
||||||
}
|
}
|
||||||
pub fn call_module_method (call: Option<&Arc<Call>>) {
|
|
||||||
let module = call.map(|call|call.module.as_ref()).flatten();
|
|
||||||
let method = call.map(|call|call.method.as_ref()).flatten();
|
|
||||||
if module.is_some() || method.is_some() {
|
|
||||||
println!("╰--------> {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!("╰--------> {RED}(unresolved){RESET} {call:?}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn call_dasm (bytes: &[u8], rip: usize) -> Arc<str> {
|
pub fn call_dasm (bytes: &[u8], rip: usize) -> Arc<str> {
|
||||||
let mut decoder = Decoder::with_ip(64, bytes, 0x1000 + rip as u64, DecoderOptions::NONE);
|
let mut decoder = Decoder::with_ip(64, bytes, 0x1000 + rip as u64, DecoderOptions::NONE);
|
||||||
while decoder.can_decode() {
|
while decoder.can_decode() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue