#!/usr/bin/env python3 """Find simple direct x64 call/jump xrefs to code RVAs in a PE file.""" from __future__ import annotations import argparse import struct from pathlib import Path from find_pe_string_xrefs import Section, parse_pe def iter_rel32_xrefs(data: bytes, section: Section, image_base: int, target_vas: set[int]) -> dict[int, list[tuple[int, str]]]: hits: dict[int, list[tuple[int, str]]] = {target_va: [] for target_va in target_vas} start = section.raw_offset end = min(len(data), section.raw_offset + section.raw_size) opcodes = { 0xE8: "call", 0xE9: "jmp", } for offset in range(start, max(start, end - 5)): opcode = data[offset] mnemonic = opcodes.get(opcode) if mnemonic is None: continue disp = struct.unpack_from(" imports/thunks are not resolved # here. This helper intentionally stays small and deterministic. return hits def main() -> int: parser = argparse.ArgumentParser() parser.add_argument("pe", type=Path) parser.add_argument("targets", nargs="+", help="name=rva_hex pairs") args = parser.parse_args() data = args.pe.read_bytes() image_base, sections = parse_pe(data) code_sections = [section for section in sections if section.is_code] targets: list[tuple[str, int, int]] = [] target_vas: set[int] = set() for item in args.targets: name, _, rva_text = item.partition("=") if not name or not rva_text: raise ValueError(f"target must be name=rva_hex: {item}") target_rva = int(rva_text, 16) target_va = image_base + target_rva targets.append((name, target_rva, target_va)) target_vas.add(target_va) xrefs: dict[int, list[tuple[int, str]]] = {target_va: [] for target_va in target_vas} for section in code_sections: section_hits = iter_rel32_xrefs(data, section, image_base, target_vas) for target_va, hits in section_hits.items(): xrefs[target_va].extend(hits) print(f"image_base=0x{image_base:x}") for name, target_rva, target_va in targets: all_hits = sorted(xrefs[target_va]) print(f"{name}: target_rva=0x{target_rva:x} direct_xrefs={len(all_hits)}") for instr_rva, mnemonic in all_hits[:64]: print(f" {mnemonic}_rva=0x{instr_rva:x}") if len(all_hits) > 64: print(f" ... {len(all_hits) - 64} more") return 0 if __name__ == "__main__": raise SystemExit(main())