From 862159116c127b91af14edb26b806b8bac5200ae Mon Sep 17 00:00:00 2001 From: unspeaker Date: Fri, 21 Feb 2025 22:16:15 +0200 Subject: [PATCH] collect calls by target --- crates/vestal/src/bang.rs | 13 +++ crates/vestal/src/load.rs | 10 -- crates/vestal/src/main.rs | 233 ++++++++++++++++++++++++++++++++++++-- 3 files changed, 239 insertions(+), 17 deletions(-) diff --git a/crates/vestal/src/bang.rs b/crates/vestal/src/bang.rs index 0c2d27d..4888cbb 100644 --- a/crates/vestal/src/bang.rs +++ b/crates/vestal/src/bang.rs @@ -13,3 +13,16 @@ pub fn slice_shebang (buffer: &[u8]) -> (Arc<[u8]>, Arc<[u8]>) { (vec![].into(), buffer.to_vec().into()) } } + +impl Vestal { + pub fn load_bang_data (&mut self, path: &Arc) -> Usually> { + let (bang, data) = crate::bang::slice_shebang(read(path.as_path())?.as_slice()); + self.path_to_bang.insert(path.clone(), bang.clone()); + if bang.len() > 0 { + println!(" (bang {path:?} {:x})", bang.len()) + } + self.path_to_data.insert(path.clone(), data.clone()); + println!(" (buffer {:p} 0x{:08x} {path:?})", data.as_ptr(), data.len()); + Ok(data) + } +} diff --git a/crates/vestal/src/load.rs b/crates/vestal/src/load.rs index c5e6281..996f587 100644 --- a/crates/vestal/src/load.rs +++ b/crates/vestal/src/load.rs @@ -87,16 +87,6 @@ impl Vestal { pub fn load_exports (&mut self, path: &PathBuf, dll: &VecPE) -> Usually<()> { Ok(()) } - pub fn load_bang_data (&mut self, path: &Arc) -> Usually> { - let (bang, data) = crate::bang::slice_shebang(read(path.as_path())?.as_slice()); - self.path_to_bang.insert(path.clone(), bang.clone()); - if bang.len() > 0 { - println!(" (bang {path:?} {:x})", bang.len()) - } - self.path_to_data.insert(path.clone(), data.clone()); - println!(" (buffer {:p} 0x{:08x} {path:?})", data.as_ptr(), data.len()); - Ok(data) - } pub fn load_pe (&mut self, path: &Arc, data: &Arc<[u8]>) -> Usually> { let pe = Arc::new(VecPE::from_disk_data(data)); self.path_to_pe.insert(path.clone(), pe.clone()); diff --git a/crates/vestal/src/main.rs b/crates/vestal/src/main.rs index b658061..fbbdb98 100644 --- a/crates/vestal/src/main.rs +++ b/crates/vestal/src/main.rs @@ -5,8 +5,223 @@ mod show; mod link; mod bang; pub(crate) use self::util::*; -use clap::{arg, command, value_parser, ArgAction, Command}; + +#[derive(Debug)] +struct Rebuilder { + /// Paths visited + paths: BTreeSet>, + /// All DLLs in scope + dlls: BTreeMap, Arc>, +} + +#[derive(Debug)] +struct Dll { + /// Canonical name like `xxx.dll` (always lower case!) + name: Arc, + /// Path to DLL on host filesystem. + path: Arc, + /// Bytes of `#!`-instruction + bang: Arc<[u8]>, + /// Parsed portable executable + pe: Arc, + /// Bytes of `.text` section + code: Arc<[u8]>, + /// DLLs that this one depends on + deps: BTreeMap, Arc>, + /// Calls to deps by source address. + calls_by_source: BTreeMap>, + /// Calls to deps by target address. + calls_by_target: BTreeMap>>, +} + +#[derive(Debug)] +struct Call { + /// Address on disk + offset: u32, + /// Address in memory + source: u32, + /// Length of call in opcodes + length: usize, + /// Call trampoline address + target: u32, + /// Library being called + module: Option>, + /// Method being called + method: Option>, +} + +impl Rebuilder { + fn new (path: &impl AsRef) -> Usually { + let mut build = Self { paths: Default::default(), dlls: Default::default() }; + let dll = Dll::new(&mut build, &Arc::new(PathBuf::from(path.as_ref()))); + Ok(build) + } +} + +impl Dll { + fn new (build: &mut Rebuilder, path: &Arc) -> Usually> { + println!("\n(load {BOLD}{path:?}{RESET})"); + build.paths.insert(path.clone()); + let name = path.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())); + 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 calls_by_source = Default::default(); + let mut calls_by_target = Default::default(); + let _ = Self::calls( + &name, + &pe, + start, + text, + &mut calls_by_source, + &mut calls_by_target, + false + )?; + let dll = Arc::new(Self { + name: name.clone(), + path: path.clone(), + bang, + pe, + code: Arc::from(text), + deps: Default::default(), + calls_by_source, + calls_by_target, + }); + build.dlls.insert(name.clone(), dll.clone()); + Ok(dll) + } + fn calls ( + name: &Arc, + pe: &VecPE, + start: usize, + data: &[u8], + calls_by_source: &mut BTreeMap>, + calls_by_target: &mut BTreeMap>>, + verbose: bool, + ) -> Usually { + let mut decoder = iced_x86::Decoder::with_ip(64, data, 0x1000, 0); + let mut calls = 0; + while decoder.can_decode() { + if let Some(call) = Self::call(name, pe, start, data, &mut decoder, verbose)? { + calls += 1; + calls_by_source.insert(call.source, call.clone()); + if !calls_by_target.contains_key(&call.target) { + calls_by_target.insert(call.target, Default::default()); + } + calls_by_target.get_mut(&call.target).unwrap().push(call); + } + } + println!(" (call-sites {calls})"); + for (call, sites) in calls_by_target.iter() { + println!(" (0x{call:08x}\n {:?})", sites.iter() + .map(|call|format!("0x{:08x}", call.offset)) + .collect::>()); + } + Ok(calls) + } + fn call ( + name: &Arc, + pe: &VecPE, + start: usize, + data: &[u8], + decoder: &mut iced_x86::Decoder, + verbose: bool, + ) -> Usually>> { + let position = decoder.position(); + let instruction = decoder.decode(); + let opcodes = &data[position..position+instruction.len()]; + if Self::matches(&instruction) && !Self::skip(opcodes) { + let offset = (position + start) as u32; + let offset_rva = pe.offset_to_rva(Offset(offset))?.0; + if let Some(call_target) = Self::target(opcodes, offset_rva) { + if verbose { + print!(" ({BOLD}0x{offset:08x}{RESET} 0x{offset_rva:08x}"); + println!(" {BOLD}{instruction:30}{RESET} 0x{call_target:x})"); + } + //let unknown = (String::from("unknown"), String::from("unknown")); + //let target = self.addr_to_import.get(call_target).unwrap_or(&unknown).0; + //let method = self.addr_to_import.get(call_target).unwrap_or(&unknown).1; + //let external = format!("{}::{}", target, method); + //if verbose { + //println!(" ({BOLD}{external}{RESET}\n Offset(0x{:08x}) RVA(R=0x{:08x})\n {:25} {:40} 0x{:08x}", + //offset, offset_rva, + //opcodes.iter().map(|x|format!("{x:>02x}")).collect::>().join(" "), + //instruction, call_target, + //); + //} + return Ok(Some(Arc::new(Call { + offset: offset, + source: offset_rva, + length: opcodes.len(), + target: call_target, + module: None, + method: None, + }))) + } + } + Ok(None) + } + fn matches (instruction: &iced_x86::Instruction) -> bool { + instruction.op0_kind() == iced_x86::OpKind::Memory && ( + instruction.flow_control() == iced_x86::FlowControl::IndirectBranch || + instruction.flow_control() == iced_x86::FlowControl::IndirectCall + ) + } + fn skip (opcodes: &[u8]) -> bool { + match opcodes[0] { + 0x41 | 0x42 | 0x43 | 0x49 => match opcodes[1] { + 0xff => return true, + _ => {} + }, + 0x48 => match opcodes[2] { + 0x20 | 0x60 | 0x62 | 0xa0 | 0xa2 => return true, + _ => {} + }, + 0xff => match opcodes[1] { + 0x10 | 0x12 | 0x13 | + 0x50 | 0x51 | 0x52 | 0x53 | 0x54 | 0x55 | 0x56 | 0x57 | + 0x60 | 0x90 | 0x92 | 0x93 | 0x94 | 0x97 => return true, + _ => {} + }, + _ => {} + } + false + } + fn target (opcodes: &[u8], offset_rva: u32) -> Option { + match opcodes[0] { + 0xff => match opcodes[1] { + 0x15 | 0x25 => return Some(offset_rva + opcodes.len() as u32 + u32::from_le_bytes([ + opcodes[2], + opcodes[3], + opcodes[4], + opcodes[5] + ])), + _ => {} + }, + 0x48 => match opcodes[1] { + 0xff => match opcodes[2] { + 0x15 | 0x25 => return Some(offset_rva + opcodes.len() as u32 + u32::from_le_bytes([ + opcodes[3], + opcodes[4], + opcodes[5], + opcodes[6] + ])), + _ => {} + }, + _ => {} + } + _ => {} + } + None + } +} + fn main () -> Usually<()> { + use clap::{arg, command, value_parser, ArgAction, Command}; let matches = command!() .arg(arg!([path] "Path to VST DLL").value_parser(value_parser!(PathBuf))) .arg(arg!(-i --inspect "Show info, don't run").required(false)) @@ -20,7 +235,8 @@ fn main () -> Usually<()> { println!("(search {path:?})") } if let Some(path) = vestal.resolve(path.to_str().expect("path must be unicode"))? { - vestal.enter(&path)?; + let rebuilder = Rebuilder::new(&path)?; + //vestal.enter(&path)?; } else { panic!("Could not find: {path:?}") } @@ -29,6 +245,9 @@ fn main () -> Usually<()> { } Ok(()) } + +/////////////////////////////////////////////////////////////////////////////////////////////////// + #[derive(Default, Debug)] struct Vestal { search_paths: Vec, @@ -53,11 +272,11 @@ impl Vestal { self.show_calls(dll_path, dll_path.as_ref() == path)?; if dll_path.as_ref() == path { println!("{:?}", dll_data.hex_dump()); - let text_section = dll.get_section_by_name(".text")?; - println!("\n{:#?}", text_section); - let start = text_section.pointer_to_raw_data.0 as usize; - let size = text_section.size_of_raw_data as usize; - let vsize = text_section.virtual_size as usize; + let text = dll.get_section_by_name(".text")?; + println!("\n{:#?}", text); + let start = text.pointer_to_raw_data.0 as usize; + let size = text.size_of_raw_data as usize; + let vsize = text.virtual_size as usize; println!("\n{:?}", &dll_data[start..start+size].hex_dump()); println!("\n{:?}", &dll_data[start..start+vsize].hex_dump()); let elf = crate::link::create_elf(&dll_data[start..start+vsize]);