102 lines
3.7 KiB
Python
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())
|