#!/usr/bin/env python3 """Disassemble a PE .text range by RVA and annotate direct branch targets.""" from __future__ import annotations import argparse import math import struct from pathlib import Path from capstone import Cs, CS_ARCH_X86, CS_MODE_64 from capstone.x86 import X86_OP_IMM, X86_OP_MEM, X86_REG_RIP from find_pe_string_xrefs import Section, parse_pe def find_section(sections: list[Section], rva: int) -> Section: for section in sections: if section.contains_rva(rva): return section raise ValueError(f"RVA 0x{rva:x} is not inside any PE section") def find_section_or_none(sections: list[Section], rva: int) -> Section | None: for section in sections: if section.contains_rva(rva): return section return None def parse_rva(text: str) -> int: return int(text, 16 if text.lower().startswith("0x") else 16) def format_memory_annotation(data: bytes, sections: list[Section], target_rva: int) -> str: section = find_section_or_none(sections, target_rva) if section is None: return f"rip_rva=0x{target_rva:x}" offset = section.rva_to_offset(target_rva) if offset < 0 or offset >= len(data): return f"rip_rva=0x{target_rva:x}" available = data[offset : min(len(data), offset + 8)] fields = [f"rip_rva=0x{target_rva:x}"] if len(available) >= 4: u32 = struct.unpack_from("= 8: u64 = struct.unpack_from(" int: parser = argparse.ArgumentParser() parser.add_argument("pe", type=Path) parser.add_argument("rva", type=parse_rva) parser.add_argument("--size", type=lambda value: int(value, 0), default=0x200) parser.add_argument("--before", type=lambda value: int(value, 0), default=0) args = parser.parse_args() data = args.pe.read_bytes() image_base, sections = parse_pe(data) start_rva = max(0, args.rva - args.before) section = find_section(sections, start_rva) end_rva = min(section.virtual_address + section.raw_size, start_rva + args.size) start_offset = section.rva_to_offset(start_rva) code = data[start_offset : start_offset + (end_rva - start_rva)] disassembler = Cs(CS_ARCH_X86, CS_MODE_64) disassembler.detail = True print(f"image_base=0x{image_base:x} section={section.name} range=0x{start_rva:x}..0x{end_rva:x}") for insn in disassembler.disasm(code, image_base + start_rva): rva = insn.address - image_base annotation = "" if insn.group(1) or insn.group(2): # jump/call for operand in insn.operands: if operand.type == X86_OP_IMM: annotation = f" ; target_rva=0x{operand.imm - image_base:x}" break elif insn.operands: memory_annotations = [] for operand in insn.operands: if operand.type == X86_OP_MEM and operand.mem.base == X86_REG_RIP: target_rva = (insn.address + insn.size + operand.mem.disp) - image_base memory_annotations.append(format_memory_annotation(data, sections, target_rva)) if memory_annotations: annotation = " ; " + " ; ".join(memory_annotations) marker = ">>" if rva == args.rva else " " print(f"{marker} 0x{rva:08x}: {insn.mnemonic:<8} {insn.op_str}{annotation}") return 0 if __name__ == "__main__": raise SystemExit(main())