mirror of
https://codeberg.org/unspeaker/vestal.git
synced 2025-12-06 10:46:42 +01:00
flatten in preparation for big delet
This commit is contained in:
parent
8a5452f6a6
commit
2740b6c232
3 changed files with 358 additions and 373 deletions
|
|
@ -1,147 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
impl Dll {
|
||||
|
||||
pub fn parse_call (&self, call: &Arc<Call>) -> Option<u32> {
|
||||
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<usize> {
|
||||
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::<Vec<_>>());
|
||||
//println!(" (call 0x{target:08x} {external}\n {:?})", sites.iter()
|
||||
//.map(|site|format!("0x{:08x}", site.offset))
|
||||
//.collect::<Vec<_>>());
|
||||
}
|
||||
}
|
||||
Ok(calls)
|
||||
}
|
||||
|
||||
pub fn collect_call (&self, decoder: &mut Decoder, verbose: bool) -> Usually<Option<Arc<Call>>> {
|
||||
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<Arc<str>>, Option<Arc<str>>, Arc<str>) {
|
||||
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<Arc<str>>,
|
||||
/// Method being called
|
||||
pub method: Option<Arc<str>>,
|
||||
}
|
||||
|
||||
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<u32> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
@ -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<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)
|
||||
}
|
||||
|
||||
/// Load a library from a path.
|
||||
fn load (&mut self, path: &impl AsRef<Path>) -> Usually<Arc<Dll>> {
|
||||
let path: Arc<PathBuf> = 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<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_recursively(&dep_path)?;
|
||||
}
|
||||
}
|
||||
Ok(dll)
|
||||
}
|
||||
|
||||
fn find (&self, name: &str) -> Usually<Option<PathBuf>> {
|
||||
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<Call>) -> Usually<()> {
|
||||
fn resolve (&self, dll: &Dll, addr: u32, link: &Arc<Link>) -> 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<str> = 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<str> = 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<Call>) -> 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<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);
|
||||
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<Link>) -> 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<str>,
|
||||
method_name: &Arc<str>,
|
||||
) -> 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<str>,
|
||||
name: Arc<str>,
|
||||
/// Path to DLL on host filesystem.
|
||||
path: Arc<PathBuf>,
|
||||
path: Arc<PathBuf>,
|
||||
/// Bytes of `#!`-instruction
|
||||
bang: Arc<[u8]>,
|
||||
bang: Arc<[u8]>,
|
||||
/// Parsed portable executable
|
||||
pe: Arc<VecPE>,
|
||||
pe: Arc<VecPE>,
|
||||
/// 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<Arc<str>, BTreeMap<Arc<str>, u32>>,
|
||||
/// Imported methods by address
|
||||
deps_by_address: BTreeMap<u32, (Arc<str>, Arc<str>)>,
|
||||
/// Calls to dependencies by source address
|
||||
calls_by_source: BTreeMap<u32, Arc<Call>>,
|
||||
/// Calls to dependencies by target address
|
||||
calls_by_target: BTreeMap<u32, Vec<Arc<Call>>>,
|
||||
/// Addresses of exported methods by name
|
||||
exports: BTreeMap<Arc<str>, ThunkData>,
|
||||
exports: BTreeMap<Arc<str>, ThunkData>,
|
||||
/// Locations in `.text` section that need to be patched
|
||||
call_sites: BTreeMap<u32, Link>,
|
||||
/// Addresses of imported methods by library
|
||||
deps_by_library: BTreeMap<Arc<str>, BTreeMap<Arc<str>, u32>>,
|
||||
/// Imported methods by callable address
|
||||
deps_by_address: BTreeMap<u32, (Arc<str>, Arc<str>)>,
|
||||
/// Links to dependencies by source address
|
||||
links_by_source: BTreeMap<u32, Arc<Link>>,
|
||||
/// Links to dependencies by callable address
|
||||
links_by_target: BTreeMap<u32, Vec<Arc<Link>>>,
|
||||
}
|
||||
|
||||
#[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<Arc<str>>,
|
||||
/// Method being called
|
||||
pub method: Option<Arc<str>>,
|
||||
}
|
||||
|
||||
impl Dll {
|
||||
|
||||
fn new (path: &Arc<PathBuf>, verbose: bool) -> Usually<Self> {
|
||||
pub fn new (path: &Arc<PathBuf>, verbose: bool) -> Usually<Self> {
|
||||
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<PathBuf>) -> Usually<(Arc<str>, Arc<VecPE>, Arc<[u8]>, Arc<[u8]>)> {
|
||||
pub 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());
|
||||
|
|
@ -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<usize> {
|
||||
/// Collect all exported methods.
|
||||
pub fn exports_collect (&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)> {
|
||||
/// 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<str>, method_name: &Arc<str>, 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<str>, method_name: &Arc<str>, 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<str> = 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<str> = 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<str>, method_name: &Arc<str>
|
||||
) {
|
||||
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<Arc<str>>, Option<Arc<str>>, Arc<str>) {
|
||||
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<usize> {
|
||||
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<Option<Arc<Link>>> {
|
||||
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<Link>) -> Option<u32> {
|
||||
Some(*self.deps_by_library
|
||||
.get(link.module.as_ref()?)?
|
||||
.get(link.method.as_ref()?)?)
|
||||
}
|
||||
|
||||
fn resolve_forward (&self, rva: &RVA) -> Usually<Option<(Arc<str>, Arc<str>)>> {
|
||||
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<str>, Vec<(usize, Arc<str>)>)>
|
||||
{
|
||||
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<str>) -> 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<u32> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,20 @@ pub fn fmt_bytes (bytes: &[u8]) -> Arc<str> {
|
|||
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<str>) {
|
||||
impl Link {
|
||||
pub fn show (&self, caller_module_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}",
|
||||
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<str> {
|
||||
format!("0x{n:>08x}").into()
|
||||
}
|
||||
pub fn call_target_addrs (call: Option<&Arc<Call>>) {
|
||||
pub fn link_target_addrs (link: Option<&Arc<Link>>) {
|
||||
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<str> {
|
||||
pub fn link_dasm (bytes: &[u8], rip: usize) -> Arc<str> {
|
||||
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::<Vec<_>>().join(" "),
|
||||
//instruction,
|
||||
//call_target,
|
||||
//link_target,
|
||||
//);
|
||||
//}
|
||||
//calls += 1;
|
||||
//links += 1;
|
||||
//}
|
||||
//}
|
||||
//println!(" (calls {calls})");
|
||||
//println!(" (links {links})");
|
||||
//Ok(())
|
||||
//}
|
||||
//}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue