reverse-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
103
reverse-skill-repo/scripts/pe_deep.py
Normal file
103
reverse-skill-repo/scripts/pe_deep.py
Normal file
@@ -0,0 +1,103 @@
|
||||
#!/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 <path.exe> [--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())
|
||||
Reference in New Issue
Block a user