#!/usr/bin/env python3 """Behavior-focused static reverse: resolve the real main from the CRT stub, map every import call-site in .text to its API, dump resource directory + carve embedded PE/streams. No IDA. x64 only for call-site RIP resolution. Usage: python pe_behavior.py """ from __future__ import annotations import sys, struct import pefile from capstone import Cs, CS_ARCH_X86, CS_MODE_64 from capstone.x86 import X86_OP_MEM, X86_OP_IMM, X86_REG_RIP def main(): path = sys.argv[1] pe = pefile.PE(path, fast_load=False) base = pe.OPTIONAL_HEADER.ImageBase md = Cs(CS_ARCH_X86, CS_MODE_64); md.detail = True # IAT slot VA -> name iat = {} for entry in getattr(pe, "DIRECTORY_ENTRY_IMPORT", []) or []: 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.split('.')[0]}!{nm}" text = [s for s in pe.sections if s.Name.rstrip(b'\x00')==b'.text'][0] code = text.get_data() text_va = base + text.VirtualAddress # Count import call-sites across whole .text (linear sweep). from collections import Counter calls = Counter() callsites = {} # name -> [addrs] for insn in md.disasm(code, text_va): if insn.mnemonic in ("call","jmp") and insn.operands: op = insn.operands[-1] tgt = None if op.type == X86_OP_MEM and op.mem.base in (0, X86_REG_RIP): tgt = insn.address + insn.size + op.mem.disp elif op.type == X86_OP_IMM: tgt = op.imm if tgt in iat: nm = iat[tgt]; calls[nm]+=1 callsites.setdefault(nm, []).append(insn.address) print(f"# {path}") print(f"imagebase={hex(base)} .text_va={hex(text_va)} size={len(code)}") print("\n== import call-site frequency (behavior surface) ==") for nm,c in calls.most_common(): addrs = ",".join(hex(a) for a in callsites[nm][:4]) print(f" {c:3}x {nm:<32} @ {addrs}") # Resource directory dump + carve print("\n== resources ==") if hasattr(pe,"DIRECTORY_ENTRY_RESOURCE"): for t in pe.DIRECTORY_ENTRY_RESOURCE.entries: tn = pefile.RESOURCE_TYPE.get(t.id, t.id) for r in getattr(t,"directory",{}).entries if hasattr(t,"directory") else []: for l in getattr(r,"directory",{}).entries if hasattr(r,"directory") else []: d = l.data.struct print(f" type={tn} id={r.id} size={d.Size} rva={hex(d.OffsetToData)}") # Whole-file embedded PE scan (extractable child?) b = open(path,"rb").read() print("\n== embedded PE scan ==") cnt=0 for i in range(0, len(b)-0x100): if b[i:i+2]==b'MZ': e = i + int.from_bytes(b[i+0x3c:i+0x40],'little') if 0