Files
2077-gps-mod/contrib/re/tools/disassemble_pe_range.py
T

102 lines
3.7 KiB
Python

#!/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("<I", available)[0]
f32 = struct.unpack_from("<f", available)[0]
fields.append(f"u32=0x{u32:08x}")
if math.isfinite(f32) and abs(f32) < 1.0e12:
fields.append(f"f32={f32:.9g}")
if len(available) >= 8:
u64 = struct.unpack_from("<Q", available)[0]
fields.append(f"u64=0x{u64:016x}")
return " ".join(fields)
def main() -> 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())