wip: baby's first (hex) dump

This commit is contained in:
🪞👃🪞 2025-02-22 20:34:14 +02:00
parent ed01876426
commit 099d0f3e92
2 changed files with 107 additions and 52 deletions

View file

@ -10,11 +10,12 @@ fn main () -> Usually<()> {
use clap::{arg, command, value_parser, ArgAction, Command}; use clap::{arg, command, value_parser, ArgAction, Command};
let matches = command!() let matches = command!()
.arg(arg!([path] "Path to VST DLL").value_parser(value_parser!(PathBuf))) .arg(arg!([path] "Path to VST DLL").value_parser(value_parser!(PathBuf)))
.arg(arg!(-i --inspect "Show info, don't run").required(false))
//.arg(arg!(-s --stub <VALUE> "Provide a stub import").required(false)) //.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))
.get_matches(); .get_matches();
let path = matches.get_one::<PathBuf>("path") let path = matches.get_one::<PathBuf>("path").unwrap_or_else(||panic!("pass path to VST DLL"));
.unwrap_or_else(||panic!("Pass a path to a VST DLL.")); let verbose = *(matches.get_one::<bool>("verbose").unwrap_or(&false));
let mut rebuilder = Rebuilder::new(&[ let mut rebuilder = Rebuilder::new(&[
std::env::current_dir()?, std::env::current_dir()?,
canonicalize(path.clone().parent().expect("invalid parent path"))?, canonicalize(path.clone().parent().expect("invalid parent path"))?,
@ -26,9 +27,9 @@ fn main () -> Usually<()> {
} }
let path = rebuilder.find(path.to_str().expect("path must be unicode"), false)? let path = rebuilder.find(path.to_str().expect("path must be unicode"), false)?
.unwrap_or_else(||panic!("Could not find: {path:?}")); .unwrap_or_else(||panic!("Could not find: {path:?}"));
let name = rebuilder.load(&path, true)?; let name = rebuilder.load(&path, true, verbose)?;
let main = rebuilder.dlls.get(&name).unwrap(); let main = rebuilder.dlls.get(&name).unwrap();
rebuilder.resolve_calls(&main, true, true)?; rebuilder.resolve_calls(&main, true, verbose)?;
Ok(()) Ok(())
} }
@ -49,7 +50,7 @@ impl Rebuilder {
..Default::default() ..Default::default()
} }
} }
fn load (&mut self, path: &impl AsRef<Path>, recurse: bool) -> Usually<Arc<str>> { fn load (&mut self, path: &impl AsRef<Path>, recurse: bool, verbose: bool) -> Usually<Arc<str>> {
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");
@ -57,12 +58,12 @@ impl Rebuilder {
return Ok(name) return Ok(name)
} }
self.visited.insert(path.clone()); self.visited.insert(path.clone());
let dll = Arc::new(Dll::new(&Arc::new(PathBuf::from(path.as_ref())), false)?); let dll = Arc::new(Dll::new(&Arc::new(PathBuf::from(path.as_ref())), verbose)?);
self.dlls.insert(dll.name.clone(), dll.clone()); self.dlls.insert(dll.name.clone(), dll.clone());
if recurse { if recurse {
for dep in dll.deps_by_library.keys() { for dep in dll.deps_by_library.keys() {
if let Some(dep) = self.find(dep, false)? { if let Some(dep) = self.find(dep, verbose)? {
self.load(&dep, recurse)?; self.load(&dep, recurse, verbose)?;
} else { } else {
panic!("not found: {dep:?}"); panic!("not found: {dep:?}");
} }
@ -97,43 +98,45 @@ impl Rebuilder {
&self, dll: &Dll, addr: u32, call: &Arc<Call>, recurse: bool, verbose: bool, &self, dll: &Dll, addr: u32, call: &Arc<Call>, recurse: bool, verbose: bool,
) -> Usually<()> { ) -> Usually<()> {
let addr = (addr - dll.code_base) as usize; let addr = (addr - dll.code_base) as usize;
if verbose { //if verbose {
println!("--------------------------------"); println!("--------------------------------");
dll.print_call(addr, call.module.as_ref(), call.method.as_ref()); dll.print_call(addr, call.module.as_ref(), call.method.as_ref());
dll.print_hex(addr, 1); dll.print_hex(addr, 1);
} //}
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();
println!("0x{method:>08x} {module_name:20} {method_name}"); println!("{BOLD}0x{method:>08x}{RESET} {module_name:20} {method_name}");
if let Some(path) = self.find(module_name, false)? { if let Some(path) = self.find(module_name, false)? {
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");
if let Some(dll) = self.dlls.get(&name) { if let Some(dll) = self.dlls.get(&name) {
if let Some(thunk) = dll.exports.get(method_name) { if let Some(thunk) = dll.exports.get(method_name) {
if let ThunkData::Function(rva) = thunk { if let ThunkData::Function(rva) = thunk {
println!("# found {:?}::{} at 0x{:>08x}", &dll.name, method_name, rva.0);
let addr = (rva.0 - dll.code_base) as usize; let addr = (rva.0 - dll.code_base) as usize;
dll.print_call(addr, None, None); println!("");
println!("{BOLD}0x{:>08x}{RESET} {}::{} found", rva.0, &dll.name, method_name);
dll.print_hex(addr, 1); dll.print_hex(addr, 1);
if recurse { if recurse {
let mut decoder = Decoder::with_ip( let mut decoder = Decoder::with_ip(
64, &dll.code[addr..], 0x1000, DecoderOptions::NONE 64, &dll.code[addr..], 0, DecoderOptions::NONE
); );
while decoder.can_decode() { while decoder.can_decode() {
let position = decoder.position(); let position = decoder.position();
let instruction = decoder.decode(); let instruction = decoder.decode();
//println!("...");
dll.print_call(addr + position, None, None); dll.print_call(addr + position, None, None);
//dll.print_hex(addr, 0); let opcodes = &dll.code[position..position+instruction.len()];
if Call::matches(&instruction) && !Call::skip(opcodes) {
let start = 0x0;
let offset = (position + start) as u32;
let offset_rva = dll.pe.offset_to_rva(Offset(offset))?.0 as u32;
println!(" └--> {:?}", Call::target(opcodes, 0));
}
if dll.code[addr + position] == 0xc3 {
break
}
} }
} }
//println!("{:?}", &dll.code[addr..addr + 128].hex_dump());
//if let Some(path) = self.find(&dll.name, true)? {
//println!("# at {path:?}");
//} else {
//panic!("# not found {:?}", &dll.name);
//}
} else { } else {
panic!("# unsupported {thunk:?}"); panic!("# unsupported {thunk:?}");
} }
@ -190,12 +193,15 @@ impl Dll {
let mut calls_by_target = Default::default(); let mut calls_by_target = Default::default();
let mut deps_by_library = Default::default(); let mut deps_by_library = Default::default();
let mut deps_by_address = Default::default(); let mut deps_by_address = Default::default();
let exports = collect_exports(&pe).unwrap_or_default(); let exports = collect_exports(
&pe,
verbose
).unwrap_or_default();
let (modules_count, methods_count) = collect_deps( let (modules_count, methods_count) = collect_deps(
&mut deps_by_library, &mut deps_by_library,
&mut deps_by_address, &mut deps_by_address,
&pe, &pe,
false verbose,
)?; )?;
let calls = collect_calls( let calls = collect_calls(
&mut calls_by_source, &mut calls_by_source,
@ -205,7 +211,7 @@ impl Dll {
&pe, &pe,
start, start,
text, text,
false verbose
)?; )?;
Ok(Self { Ok(Self {
name: name.clone(), name: name.clone(),
@ -276,7 +282,7 @@ fn collect_deps (
deps_by_address.insert(call_via, (module_name.clone(), method.clone())); deps_by_address.insert(call_via, (module_name.clone(), method.clone()));
methods += 1; methods += 1;
if verbose { if verbose {
println!(" ({index:5} 0x{call_via:08x} {module_name:>20} {method})"); println!(" (import 0x{call_via:08x} {module_name}::{method})");
} }
} }
} }
@ -295,7 +301,8 @@ fn collect_deps (
} }
fn collect_exports ( fn collect_exports (
pe: &VecPE pe: &VecPE,
_verbose: bool,
) -> Usually<BTreeMap<Arc<str>, ThunkData>> { ) -> Usually<BTreeMap<Arc<str>, ThunkData>> {
Ok(ImageExportDirectory::parse(pe)? Ok(ImageExportDirectory::parse(pe)?
.get_export_map(pe)? .get_export_map(pe)?

View file

@ -1,34 +1,82 @@
use crate::*; use crate::*;
use std::fmt::Write;
impl Dll { impl Dll {
pub fn print_call (&self, addr: usize, module: Option<&Arc<str>>, method: Option<&Arc<str>>) { pub fn print_call (&self, addr: usize, module: Option<&Arc<str>>, method: Option<&Arc<str>>) {
let mut decoder = Decoder::with_ip(64, &self.code[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 opcodes = &self.code[addr..addr+instruction.len()].iter() let is_stack = instruction.is_stack_instruction();
.map(|x|format!("{x:02x}")) let is_link = Call::matches(&instruction);
.join(" "); let style = if is_stack || is_link { BOLD } else { DIM };
println!("{BOLD}0x{addr:>08x}{RESET} {:20} {DIM}{opcodes}{RESET} {BOLD}{instruction}{RESET} {module:?} {method:?}", &self.name); print!("{style}0x{addr:>08x} {:20}{RESET}", &self.name);
print!(" {style}{:26}{RESET}", Self::fmt_bytes(&self.code[addr..addr+instruction.len()]));
println!(" {style}{instruction}{RESET}");
if module.is_some() || method.is_some() {
println!(" └--> {}{}{}",
module.map(|x|x.as_ref()).unwrap_or(&""),
if module.is_some() && method.is_some() { "::" } else { "" },
method.map(|x|x.as_ref()).unwrap_or(&""));
}
} }
pub fn print_hex (&self, addr: usize, n: usize) { pub fn print_hex (&self, addr: usize, context: usize) {
let cfg = HexConfig {title: false, width: 16, group: 4, chunk: 1, ..HexConfig::default()}; let base = self.code_base as usize;
let snap = |x|(x/16)*16; let line = 16;
let a = snap(addr - 16*n); let group = 2;
let b = addr; let snap = |x|(x/line)*line;
let c = snap(addr + 16); let byte_start = snap(addr).saturating_sub(context*line);
let d = snap(c + 16*n); let byte_end = snap(addr) + (context + 1) * line;
if n > 0 { let mut output = String::new();
println!("{DIM}{:?}{RESET}", &self.code[a..b].hex_conf(HexConfig { for (index, byte) in (byte_start..byte_end).enumerate() {
display_offset: self.code_base as usize + a, ..cfg write!(&mut output, "{DIM}");
})); if byte % line == 0 {
} if (byte >= snap(addr)) && (byte < snap(addr) + line) {
println!("{BOLD}{:?}{RESET}", &self.code[b..c].hex_conf(HexConfig { write!(&mut output, "{RESET}{BOLD}");
display_offset: self.code_base as usize + b, ..cfg }
})); write!(&mut output, " {byte:08x}");
if n > 0 { }
println!("{DIM}{:?}{RESET}", &self.code[c..d].hex_conf(HexConfig { write!(&mut output, "{DIM}");
display_offset: self.code_base as usize + c, ..cfg if byte % group == 0 {
})); write!(&mut output, " ");
}
if (byte >= addr) && (byte < addr + 8) {
write!(&mut output, "{RESET}{BOLD}");
}
write!(&mut output, "{:02x}", self.code[byte]);
if byte % line == line - 1 {
write!(&mut output, "\n");
}
} }
println!("{output}");
//let cfg = HexConfig {title: false, width: 16, group: 4, chunk: 1, ..HexConfig::default()};
//if n > 0 {
//println!(" {DIM}{}{RESET}", Self::fmt_hex_line(a, &self.code[a..b]));
//}
//println!(" {BOLD}{}{RESET}", Self::fmt_hex_line(b, &self.code[b..c]));
//if n > 0 {
//println!(" {DIM}{}{RESET}", Self::fmt_hex_line(c, &self.code[c..d]));
//}
}
//pub fn fmt_hex_block (data: &[u8], addr: usize, length: usize, context: usize) -> Arc<str> {
//let start = addr;
//let end = addr + length;
//let line = 16;
//let snap = |x|(x/line)*line;
//let addr = snap(addr);
//let prev = addr - line * context;
//let next = addr + line * (context + 1);
//if n > 0 {
//println!(" {DIM}{}{RESET}", Self::fmt_hex_line(prev, &self.code[prev..addr]));
//}
//println!(" {BOLD}{}{RESET}", Self::fmt_hex_line(b, &self.code[addr..addr+length]));
//if n > 0 {
//println!(" {DIM}{}{RESET}", Self::fmt_hex_line(c, &self.code[addr+length..next]));
//}
//}
pub fn fmt_hex_line (addr: usize, data: &[u8]) -> Arc<str> {
format!("{:>08x} {}", addr, Self::fmt_bytes(data)).into()
}
pub fn fmt_bytes (bytes: &[u8]) -> Arc<str> {
bytes.iter().map(|x|format!("{x:02x}")).join(" ").into()
} }
} }