#!/usr/bin/env python3 """Deep PE static reverse without IDA: capstone linear+entry disasm, IAT thunk resolution, call-site mapping to imports, and payload/container carving. Usage: python pe_deep.py [--disasm-entry N] [--carve] """ from __future__ import annotations import argparse, sys import pefile from capstone import Cs, CS_ARCH_X86, CS_MODE_64, CS_MODE_32 def rva_to_off(pe, rva): return pe.get_offset_from_rva(rva) def build_iat_map(pe): """Map IAT slot VA -> imported name (for call [rip+x] / call [iat] resolution).""" m = {} if not hasattr(pe, "DIRECTORY_ENTRY_IMPORT"): return m base = pe.OPTIONAL_HEADER.ImageBase for entry in pe.DIRECTORY_ENTRY_IMPORT: dll = entry.dll.decode("latin1", "replace") for imp in entry.imports: nm = imp.name.decode("latin1", "replace") if imp.name else f"{dll}#ord{imp.ordinal}" if imp.address is not None: m[imp.address] = f"{dll}!{nm}" # absolute VA of the IAT slot user # imp.address is the VA where the pointer is used; also map the thunk RVA return m def main(): ap = argparse.ArgumentParser() ap.add_argument("path") ap.add_argument("--disasm-entry", type=int, default=120, help="instructions from entrypoint") ap.add_argument("--carve", action="store_true") args = ap.parse_args() pe = pefile.PE(args.path, fast_load=False) is64 = hex(pe.OPTIONAL_HEADER.Magic) == "0x20b" md = Cs(CS_ARCH_X86, CS_MODE_64 if is64 else CS_MODE_32) md.detail = True base = pe.OPTIONAL_HEADER.ImageBase ep_rva = pe.OPTIONAL_HEADER.AddressOfEntryPoint ep_va = base + ep_rva print(f"# {args.path}") print(f"imagebase={hex(base)} entry_va={hex(ep_va)} bits={'64' if is64 else '32'}") # Build a name map for IAT slots keyed by the pointer VA the code dereferences. # pefile: imp.address = VA of the IAT entry (where the resolved pointer lives). iat = {} if hasattr(pe, "DIRECTORY_ENTRY_IMPORT"): for entry in pe.DIRECTORY_ENTRY_IMPORT: dll = entry.dll.decode("latin1", "replace") for imp in entry.imports: nm = imp.name.decode("latin1", "replace") if imp.name else f"ord{imp.ordinal}" if imp.address: iat[imp.address] = f"{dll}!{nm}" # Disassemble from entrypoint. ep_off = rva_to_off(pe, ep_rva) code = pe.get_memory_mapped_image()[ep_rva: ep_rva + args.disasm_entry * 16] print(f"\n== entrypoint disasm ({args.disasm_entry} insns) ==") n = 0 for insn in md.disasm(code, ep_va): annot = "" # resolve call/jmp [rip+disp] against IAT if insn.mnemonic in ("call", "jmp") and insn.operands: op = insn.operands[-1] try: from capstone.x86 import X86_OP_MEM, X86_OP_IMM if op.type == X86_OP_MEM and op.mem.base in (0,) and is64: # rip-relative: target = next_insn + disp tgt = insn.address + insn.size + op.mem.disp if tgt in iat: annot = f" ; -> {iat[tgt]}" elif op.type == X86_OP_MEM: if op.mem.disp in iat: annot = f" ; -> {iat[op.mem.disp]}" elif op.type == X86_OP_IMM and op.imm in iat: annot = f" ; -> {iat[op.imm]}" except Exception: pass print(f" {insn.address:#0x}: {insn.mnemonic:<7}{insn.op_str}{annot}") n += 1 if n >= args.disasm_entry: break if args.carve: print("\n== carve scan ==") rd = [s for s in pe.sections if s.Name.rstrip(b'\x00') == b'.rdata'] if rd: data = rd[0].get_data() print(f".rdata off={hex(rd[0].PointerToRawData)} size={len(data)}") # leading dword table import struct vals = struct.unpack_from("<8I", data, 0) print("leading dwords:", [hex(v) for v in vals]) if __name__ == "__main__": sys.exit(main())