From 2740b6c232d08bb575dc1bba76a7ff5689f0f54f Mon Sep 17 00:00:00 2001 From: unspeaker Date: Sun, 23 Feb 2025 19:20:10 +0200 Subject: [PATCH] flatten in preparation for big delet --- crates/vestal/src/call.rs | 147 ----------- crates/vestal/src/main.rs | 533 +++++++++++++++++++++++--------------- crates/vestal/src/show.rs | 51 ++-- 3 files changed, 358 insertions(+), 373 deletions(-) delete mode 100644 crates/vestal/src/call.rs diff --git a/crates/vestal/src/call.rs b/crates/vestal/src/call.rs deleted file mode 100644 index 69355a4..0000000 --- a/crates/vestal/src/call.rs +++ /dev/null @@ -1,147 +0,0 @@ -use crate::*; - -impl Dll { - - pub fn parse_call (&self, call: &Arc) -> Option { - self.deps_by_library.get(call.module.as_ref()?)?.get(call.method.as_ref()?).map(|x|*x) - } - - pub fn collect_calls (&mut self, verbose: bool) -> Usually { - let mut decoder = Decoder::with_ip(64, self.text_section.as_ref(), 0, 0); - let mut calls = 0; - while decoder.can_decode() { - if let Some(call) = self.collect_call(&mut decoder, verbose)? { - calls += 1; - self.calls_by_source.insert(call.source, call.clone()); - if !self.calls_by_target.contains_key(&call.target) { - self.calls_by_target.insert(call.target, Default::default()); - } - self.calls_by_target.get_mut(&call.target).unwrap().push(call); - } - } - if verbose { - println!(" (call-sites {calls})"); - for (target, sites) in self.calls_by_target.iter() { - let (_, _, external) = self.dep_name(*target); - println!(" ({:>5}x call 0x{target:08x} {external})", sites.len()); - //.map(|site|format!("0x{:08x}", site.offset)) - //.collect::>()); - //println!(" (call 0x{target:08x} {external}\n {:?})", sites.iter() - //.map(|site|format!("0x{:08x}", site.offset)) - //.collect::>()); - } - } - Ok(calls) - } - - pub fn collect_call (&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 Call::matches(&instruction) && !Call::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) = Call::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(Call { - offset: offset, - source: offset_rva, - length: opcodes.len(), - target, - module, - method, - }))) - } - } - Ok(None) - } - - 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()) - } -} - -#[derive(Debug)] -pub struct Call { - /// Address on disk - pub offset: u32, - /// Address in memory - pub source: u32, - /// Length of call in opcodes - pub length: usize, - /// Call trampoline address - pub target: u32, - /// Library being called - pub module: Option>, - /// Method being called - pub method: Option>, -} - -impl Call { - pub 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 { - 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 - } - pub 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 - } -} diff --git a/crates/vestal/src/main.rs b/crates/vestal/src/main.rs index 5be0262..b2ec785 100644 --- a/crates/vestal/src/main.rs +++ b/crates/vestal/src/main.rs @@ -1,6 +1,5 @@ #![feature(slice_split_once)] mod util; pub(crate) use self::util::*; -mod call; pub(crate) use self::call::*; mod show; pub(crate) use self::show::*; mod bang; @@ -24,8 +23,10 @@ fn main () -> Usually<()> { 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_recurse(&path)?; - rebuilder.resolve_recurse(&main)?; + let main = rebuilder.load_recursively(&path)?; + for (addr, link) in main.links_by_source.iter() { + rebuilder.resolve_recursively(&main, *addr, link)?; + } Ok(()) } @@ -64,17 +65,8 @@ impl Vestal { } Self { verbose, paths, ..Default::default() } } - /// Load all dependencies recursively, starting from the given path. - fn load_recurse (&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_recurse(&dep_path)?; - } - } - Ok(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) { @@ -91,6 +83,18 @@ impl Vestal { Ok(dll) } + /// 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) + } + fn find (&self, name: &str) -> Usually> { for base in self.paths.iter() { let mut path = base.as_ref().clone(); @@ -109,143 +113,108 @@ impl Vestal { Ok(None) } - fn resolve (&self, dll: &Dll) -> Usually<()> { - println!("{:11}{BOLD}{}{RESET}", "", dll.name); - for (addr, call) in dll.calls_by_source.iter() { - self.resolve_call(dll, *addr, call)?; - } - Ok(()) - } - - fn resolve_recurse (&self, dll: &Dll) -> Usually<()> { - println!("{:11}{BOLD}{}{RESET}", "", dll.name); - for (addr, call) in dll.calls_by_source.iter() { - self.resolve_call_recurse(dll, *addr, call)?; - } - Ok(()) - } - - fn resolve_call (&self, dll: &Dll, addr: u32, call: &Arc) -> Usually<()> { + fn resolve (&self, dll: &Dll, addr: u32, link: &Arc) -> Usually<()> { let addr = (addr - dll.code_base) as usize; - dll.show_call_site(addr, call.length, 1); - call.show_call(&dll.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 = name.to_str().map(Arc::from).expect("non-unicode filename"); - let dll = self.dlls.get(&name).unwrap_or_else(||panic!("# not found: {name}")); - } + 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}")); Ok(()) } - fn resolve_call_recurse (&self, dll: &Dll, addr: u32, call: &Arc) -> Usually<()> { - let addr = (addr - dll.code_base) as usize; - dll.show_call_site(addr, call.length, 1); - call.show_call(&dll.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 = 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); - addr += 1; - } - 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}"); - } + fn resolve_recursively (&self, dll: &Dll, 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) } Ok(()) } - fn resolve_call_recurse_into ( - &self, - //dll: &Dll, - //address: usize, - module_name: &Arc, - method_name: &Arc, - ) -> Usually<()> { - let module = self.dlls.get(module_name).unwrap_or_else(||panic!("# no dll {module_name}")); - let method = module.exports.get(method_name).unwrap_or_else(||panic!("# no export {method_name}")); - //println!("{} {} {:?}", module_name, method_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(call) = dll.collect_call(&mut decoder, true)? { - //println!("(call {call:#?}"); - //} - //if dll.text_section[address + position] == 0xc3 { - //break - //} - //} - Ok(()) - } - } struct Dll { /// Canonical name like `xxx.dll` (always lowercase) - name: Arc, + name: Arc, /// Path to DLL on host filesystem. - path: Arc, + path: Arc, /// Bytes of `#!`-instruction - bang: Arc<[u8]>, + bang: Arc<[u8]>, /// Parsed portable executable - pe: Arc, + pe: Arc, + /// Assumed address in memory + code_base: u32, /// Bytes of `.text` section - text_section: Arc<[u8]>, + text_section: Arc<[u8]>, /// Start of `.text` section text_section_start: usize, /// Size of `.text` section text_section_size: usize, - /// Assumed address in memory - code_base: u32, - /// Addresses of imported methods by library - deps_by_library: BTreeMap, BTreeMap, u32>>, - /// Imported methods by address - deps_by_address: BTreeMap, Arc)>, - /// Calls to dependencies by source address - calls_by_source: BTreeMap>, - /// Calls to dependencies by target address - calls_by_target: BTreeMap>>, /// Addresses of exported methods by name - exports: BTreeMap, ThunkData>, + 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>>, +} + +#[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>, } impl Dll { - fn new (path: &Arc, verbose: bool) -> Usually { + pub fn new (path: &Arc, verbose: bool) -> Usually { if verbose { println!("\n(load {BOLD}{path:?}{RESET})"); } @@ -266,17 +235,18 @@ impl Dll { exports: Default::default(), deps_by_library: Default::default(), deps_by_address: Default::default(), - calls_by_source: Default::default(), - calls_by_target: Default::default(), + links_by_source: Default::default(), + links_by_target: Default::default(), + call_sites: Default::default(), }; - let (_modules_count, _methods_count) = dll.collect_deps(verbose)?; - let _calls = dll.collect_calls(verbose)?; - let _exports = dll.collect_exports(verbose)?; + 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) } - fn read_pe (path: &Arc) -> Usually<(Arc, Arc, Arc<[u8]>, Arc<[u8]>)> { + 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()); @@ -284,7 +254,7 @@ impl Dll { Ok((name, pe, data, bang)) } - fn read_text_section (pe: &VecPE) -> Usually<(Arc<[u8]>, usize, usize)> { + 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; @@ -292,47 +262,26 @@ impl Dll { Ok((text.into(), start, size)) } - fn collect_exports (&mut self, _verbose: bool) -> Usually { + /// 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()) } - pub fn collect_deps (&mut self, verbose: bool) -> Usually<(usize, usize)> { + /// 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; - let unwrap_thunk = |thunk: &Thunk, name|match thunk { - Thunk::Thunk32(t) => panic!("32 bit {name}"), - Thunk::Thunk64(t) => t.0 - }; + let directory = ImportDirectory::parse(pe.as_ref())?; + let mut modules = 0; + let mut methods = 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 + 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; @@ -341,73 +290,247 @@ impl Dll { 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!(")"); - } + self.show_deps_by_library(); } 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, + /// 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; - 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 = module_name.clone().into(); - if !self.deps_by_library.contains_key(&module_name) { + 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 = method.clone().into(); - if module.contains_key(&method) { - panic!("duplicate method {method} in {module_name}"); + 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.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())); + module.insert(method_name.clone(), link_via); methods += 1; if verbose { - println!(" (import 0x{call_via:08x} {module_name}::{method})"); + if modules > 0 { + println!(" {BOLD}(import {module_name}){RESET}"); + } + if methods > 0 { + println!(" (import 0x{link_via:08x} {module_name}::{method_name})"); + } } - Ok((modules, methods)) + (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()))) } } +/// Collect an imported dependency's descriptor thunks into a temporary [Vec]. +/// +/// Most of these are currently discarded, but may be needed in the future +/// to resolve some of the trickier linkings. +fn import_collect (pe: &VecPE, descriptor: &ImageImportDescriptor) + -> Usually<(Arc, Vec<(usize, Arc)>)> +{ + let mut buffer = vec![]; + let name = descriptor.get_name(pe)?.as_str()?.to_lowercase(); + for (index, import) in descriptor.get_imports(pe)?.iter().enumerate() { + buffer.push((index, match import { + ImportData::Ordinal(x) => format!("___VESTAL_ORDINAL_{x}"), + ImportData::ImportByName(name) => format!("{name}"), + }.into())); + } + Ok((name.into(), buffer)) + //let imp = descriptor.get_imports(pe)?; + //let iat = descriptor.get_first_thunk(pe)?; + //let ilt = descriptor.get_original_first_thunk(pe)?; + //let lut = descriptor.get_lookup_thunks(pe)?; + //for (index, (import, thunk, orig, lookup)) in izip!( + //imp, + //iat.iter().map(|thunk|format!("0x{:08x}", Self::thunk_unwrap(thunk, "IAT thunk"))), + //ilt.iter().map(|thunk|format!("0x{:08x}", Self::thunk_unwrap(thunk, "ILT (orig) thunk"))), + //lut.iter().map(|thunk|format!("0x{:08x}", Self::thunk_unwrap(thunk, "lookup thunk"))), + //).enumerate() { + //buffer.push((index, import));//, thunk, orig, lookup)); + //} +} + +//fn thunk_unwrap (thunk: &Thunk, name: impl AsRef) -> u64 { + //match thunk { + //Thunk::Thunk32(t) => panic!("32 bit {}", name.as_ref()), + //Thunk::Thunk64(t) => t.0 + //} +//} + 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 calls = format!("(calls src {:>4} tgt {:>4})", - self.calls_by_source.len(), - self.calls_by_target.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} {calls} {exports})", + 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.flow_control() == FlowControl::IndirectBranch || + instruction.flow_control() == FlowControl::IndirectCall + ) + } + pub 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 + } + 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 a8330fa..b87b704 100644 --- a/crates/vestal/src/show.rs +++ b/crates/vestal/src/show.rs @@ -8,11 +8,20 @@ pub fn fmt_bytes (bytes: &[u8]) -> Arc { 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})") + } + println!(")"); + } + } pub fn show_instruction (&self, addr: usize) { let mut decoder = Decoder::with_ip(64, &self.text_section[addr..], 0x1000, DecoderOptions::NONE); let instruction = decoder.decode(); let is_stack = instruction.is_stack_instruction(); - let is_link = Call::matches(&instruction); + let is_link = Link::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()])); @@ -55,27 +64,27 @@ impl Dll { write!(&mut output, "{RESET}"); if byte % line == line - 1 { if snap(byte) == snap(addr) { - let dasm = Show::call_dasm(&self.text_section[addr..], addr); + let dasm = Show::link_dasm(&self.text_section[addr..], addr); write!(&mut output, " -> {dasm}"); } write!(&mut output, " \n"); } } - print!("\n{output}"); + print!("{output}"); } } -impl Call { - pub fn show_call (&self, name: &Arc) { +impl Link { + pub fn show (&self, caller_module_name: &Arc) { let module = self.module.as_ref(); let method = self.method.as_ref(); if module.is_some() || method.is_some() { - println!("╰--------> {name} -> {GREEN}{}{}{}{RESET}", + 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!("╰--------> {name} -> {RED}(unresolved){RESET} {self:?}"); + println!("╰--------> {caller_module_name} -> {RED}(unresolved){RESET} {self:?}"); } } } @@ -84,16 +93,16 @@ impl Show { pub fn num (n: usize) -> Arc { format!("0x{n:>08x}").into() } - pub fn call_target_addrs (call: Option<&Arc>) { + pub fn link_target_addrs (link: Option<&Arc>) { println!(" {}{}{}", - call.map(|call|format!(" (offset {})", Show::num(call.offset as usize))) + link.map(|link|format!(" (offset {})", Show::num(link.offset as usize))) .unwrap_or(String::new()), - call.map(|call|format!(" (source {})", Show::num(call.source as usize))) + link.map(|link|format!(" (source {})", Show::num(link.source as usize))) .unwrap_or(String::new()), - call.map(|call|format!(" (target {})", Show::num(call.target as usize))) + link.map(|link|format!(" (target {})", Show::num(link.target as usize))) .unwrap_or(String::new())); } - pub fn call_dasm (bytes: &[u8], rip: usize) -> Arc { + 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(); @@ -137,8 +146,8 @@ impl Show { //ep_off); //Ok(()) //} - //pub fn show_calls (&self, path: &PathBuf, verbose: bool) -> Usually<()> { - //let mut calls = 0; + //pub fn show_links (&self, path: &PathBuf, verbose: bool) -> Usually<()> { + //let mut links = 0; //let dll = self.path_to_pe.get(path).expect("no such library"); //let buf = dll.get_buffer(); //let section = dll.get_section_by_name(".text")?; @@ -152,7 +161,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::IndirectCall) + //|| instruction.flow_control() == iced_x86::FlowControl::IndirectLink) //&& instruction.op0_kind() == iced_x86::OpKind::Memory { //match opcodes[0] { //0xff => match opcodes[1] { @@ -173,7 +182,7 @@ impl Show { //} //let offset = (position + section_ptr) as u32; //let offset_rva = dll.offset_to_rva(Offset(offset))?.0; - //let call_target = match opcodes[0] { + //let link_target = match opcodes[0] { //0xff => match opcodes[1] { //0x15 | 0x25 => //offset_rva + opcodes.len() as u32 + u32::from_le_bytes([ @@ -201,8 +210,8 @@ impl Show { //}; //let unknown = (String::from("unknown"), String::from("unknown")); //let external = format!("{}::{}", - //self.addr_to_import.get(&call_target).unwrap_or(&unknown).0, - //self.addr_to_import.get(&call_target).unwrap_or(&unknown).1); + //self.addr_to_import.get(&link_target).unwrap_or(&unknown).0, + //self.addr_to_import.get(&link_target).unwrap_or(&unknown).1); //let dependent = path.file_name().unwrap(); //if verbose { //println!(" ({BOLD}{external}{RESET}\n Offset(0x{:08x}) RVA(R=0x{:08x})\n {:25} {:40} 0x{:08x}", @@ -210,13 +219,13 @@ impl Show { //offset_rva, //opcodes.iter().map(|x|format!("{x:>02x}")).collect::>().join(" "), //instruction, - //call_target, + //link_target, //); //} - //calls += 1; + //links += 1; //} //} - //println!(" (calls {calls})"); + //println!(" (links {links})"); //Ok(()) //} //}