hexdump callsites; need exports now

This commit is contained in:
🪞👃🪞 2025-02-22 01:50:29 +02:00
parent 27dec3a72c
commit cfd291b050
2 changed files with 104 additions and 49 deletions

View file

@ -1,9 +1,9 @@
#![feature(slice_split_once)]
mod util;
mod bang;
//mod load;
//mod show;
mod link;
mod bang;
//mod link;
pub(crate) use self::util::*;
fn main () -> Usually<()> {
@ -27,19 +27,59 @@ fn main () -> Usually<()> {
let path = rebuilder.find(path.to_str().expect("path must be unicode"), false)?
.unwrap_or_else(||panic!("Could not find: {path:?}"));
let main = rebuilder.load(&path, true)?;
//let main = rebuilder.dlls.get(&name).unwrap();
for (name, dll) in rebuilder.dlls.iter() {
println!("\n{name}");
println!("{:?}", dll.code.hex_conf(HexConfig {
println!("\n{main}");
let main = rebuilder.dlls.get(&main).unwrap();
//println!("{:?}", main.code[0..1024.min(main.code.len())].hex_conf(HexConfig {
//title: false,
//width: 16,
//group: 4,
//chunk: 1,
//display_offset: main.code_base as usize,
//..HexConfig::default()
//}));
for (addr, call) in main.calls_by_source.iter() {
let start = (addr - main.code_base) as usize;
let end = start + call.length;
let mut decoder = Decoder::with_ip(64, &main.code[start..end], 0, DecoderOptions::NONE);
let instruction = decoder.decode();
let cfg = HexConfig {
title: false,
width: 32,
width: 16,
group: 4,
chunk: 4,
display_offset: dll.code_start as usize,
chunk: 1,
display_offset: main.code_base as usize,
..HexConfig::default()
}));
};
let end = start + 16;
let snap = |x|(x/16)*16;
let a = snap(start - 32);
let b = start;
let c = snap(end);
let d = snap(c + 32);
println!("\n{BOLD}0x{addr:x}{RESET} {} {:?} {:?}\n{:?}\n{BOLD}{:?}{RESET}\n{:?}",
instruction,
call.module,
call.method,
&main.code[a..b].hex_conf(HexConfig { display_offset: main.code_base as usize + a, ..cfg }),
&main.code[b..c].hex_conf(HexConfig { display_offset: main.code_base as usize + b, ..cfg }),
&main.code[c..d].hex_conf(HexConfig { display_offset: main.code_base as usize + c, ..cfg }));
if let (Some(module_name), Some(method_name)) = (&call.module, &call.method) {
if let Some(module) = main.deps_by_library.get(module_name) {
if let Some(method) = module.get(method_name) {
println!("0x{method:>08x} {module_name} {method_name}");
if let Some(dll) = rebuilder.dlls.get(module_name) {
if let Some(address) = dll.exports.get(method_name) {
println!("# found");
}
}
}
}
}
}
//let mut decoder = iced_x86::Decoder::with_ip(64, &main.code, 0x1000, 0);
//println!("{:#?}", &main.calls_by_source);
for (name, dll) in rebuilder.dlls.iter() {
}
//let mut decoder = Decoder::with_ip(64, &main.code, 0x1000, 0);
//while decoder.can_decode() {
//let instruction = decoder.decode();
//if Dll::matches(&instruction) {
@ -61,18 +101,18 @@ struct Rebuilder {
#[derive(Debug)]
struct Dll {
/// Canonical name like `xxx.dll` (always lower case!)
name: Arc<str>,
/// Canonical name like `xxx.dll` (always lowercase)
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>,
/// Bytes of `.text` section
code: Arc<[u8]>,
code: Arc<[u8]>,
/// Start of `.text` section
code_start: u32,
code_base: u32,
/// Addresses of imported methods by library
deps_by_library: BTreeMap<Arc<str>, BTreeMap<Arc<str>, u32>>,
/// Imported methods by address
@ -81,6 +121,8 @@ struct Dll {
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>, u32>,
}
#[derive(Debug)]
@ -146,11 +188,10 @@ impl Rebuilder {
}
impl Dll {
fn new (
path: &Arc<PathBuf>,
verbose: bool
) -> Usually<Self> {
println!("\n(load {BOLD}{path:?}{RESET})");
fn new (path: &Arc<PathBuf>, verbose: bool) -> Usually<Self> {
if verbose {
println!("\n(load {BOLD}{path:?}{RESET})");
}
let name = path.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());
@ -161,8 +202,8 @@ impl Dll {
let text = &data[start..start+size];
let mut calls_by_source = Default::default();
let mut calls_by_target = Default::default();
let mut deps_by_library = Default::default();
let mut deps_by_address = Default::default();
let mut deps_by_library = Default::default();
let mut deps_by_address = Default::default();
let (modules_count, methods_count) = Self::deps(
&pe,
&mut deps_by_library,
@ -177,19 +218,23 @@ impl Dll {
Some(&deps_by_address),
&mut calls_by_source,
&mut calls_by_target,
true
false
)?;
Ok(Self {
name: name.clone(),
path: path.clone(),
bang,
pe,
code: Arc::from(text),
code_start: start as u32,
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,
},
deps_by_library: deps_by_library.clone(),
deps_by_address: deps_by_address.clone(),
calls_by_source,
calls_by_target,
exports: Default::default(),
pe,
})
}
fn deps (
@ -243,9 +288,9 @@ impl Dll {
}
}
}
println!(" (deps-modules {modules})");
println!(" (deps-methods {methods})");
if verbose {
println!(" (deps-modules {modules})");
println!(" (deps-methods {methods})");
for (module, methods) in deps_by_library.iter() {
print!(" ({module}");
for (method, addr) in methods.iter() {
@ -256,6 +301,16 @@ impl Dll {
}
Ok((modules, methods))
}
fn dep_name (deps: Option<&BTreeMap<u32, (Arc<str>, Arc<str>)>>, target: u32)
-> (Option<Arc<str>>, Option<Arc<str>>, Arc<str>)
{
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())
}
fn calls (
name: &Arc<str>,
pe: &VecPE,
@ -266,7 +321,7 @@ impl Dll {
calls_by_target: &mut BTreeMap<u32, Vec<Arc<Call>>>,
verbose: bool,
) -> Usually<usize> {
let mut decoder = iced_x86::Decoder::with_ip(64, data, 0x1000, 0);
let mut decoder = 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, deps, &mut decoder, false)? {
@ -278,8 +333,8 @@ impl Dll {
calls_by_target.get_mut(&call.target).unwrap().push(call);
}
}
println!(" (call-sites {calls})");
if verbose {
println!(" (call-sites {calls})");
for (target, sites) in calls_by_target.iter() {
let (_, _, external) = Self::dep_name(deps, *target);
println!(" ({:>5}x call 0x{target:08x} {external})", sites.len());
@ -292,32 +347,22 @@ impl Dll {
}
Ok(calls)
}
fn dep_name (deps: Option<&BTreeMap<u32, (Arc<str>, Arc<str>)>>, target: u32)
-> (Option<Arc<str>>, Option<Arc<str>>, Arc<str>)
{
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())
}
fn call (
name: &Arc<str>,
pe: &VecPE,
start: usize,
data: &[u8],
deps: Option<&BTreeMap<u32, (Arc<str>, Arc<str>)>>,
decoder: &mut iced_x86::Decoder,
decoder: &mut Decoder,
verbose: bool,
) -> Usually<Option<Arc<Call>>> {
let position = decoder.position();
let instruction = decoder.decode();
let opcodes = &data[position..position+instruction.len()];
if Self::matches(&instruction) && !Self::skip(opcodes) {
if Call::matches(&instruction) && !Call::skip(opcodes) {
let offset = (position + start) as u32;
let offset_rva = pe.offset_to_rva(Offset(offset))?.0;
if let Some(target) = Self::target(opcodes, offset_rva) {
if let Some(target) = Call::target(opcodes, offset_rva) {
let (module, method, external) = Self::dep_name(deps, target);
if verbose {
let external = format!("{}::{}",
@ -338,10 +383,13 @@ impl Dll {
}
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
}
impl Call {
fn matches (instruction: &Instruction) -> bool {
instruction.op0_kind() == OpKind::Memory && (
instruction.flow_control() == FlowControl::IndirectBranch ||
instruction.flow_control() == FlowControl::IndirectCall
)
}
fn skip (opcodes: &[u8]) -> bool {

View file

@ -15,6 +15,13 @@ pub(crate) use ::object::endian::LittleEndian;
pub(crate) use ::object::write::elf::Writer as ElfWriter;
pub(crate) use ::pretty_hex::*;
pub(crate) use ::exe::{Buffer, PE, VecPE, PtrPE, types::*, headers::*};
pub(crate) use ::iced_x86::{Encoder, Decoder, DecoderOptions, Instruction, OpKind, FlowControl};
pub(crate) type Usually<T> = Result<T, Box<dyn std::error::Error>>;
pub const RESET: &str = "\u{001b}[0m";
pub const BOLD: &str = "\u{001b}[1m";
pub enum Verbosity {
Silent,
Terse,
Brief,
Verbose,
}