mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 06:04:56 +03:00
220 lines
8.1 KiB
Python
220 lines
8.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for analyzing heap spray exploitation in memory dumps.
|
|
|
|
Detects heap spray artifacts using Volatility3 by scanning for
|
|
NOP sled patterns, large contiguous allocations, and injected
|
|
executable regions in process virtual address space.
|
|
"""
|
|
# For authorized forensic analysis only
|
|
|
|
import argparse
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
from collections import defaultdict
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
NOP_PATTERNS = {
|
|
"x86_nop": b"\x90" * 16,
|
|
"heap_spray_0c": b"\x0c" * 16,
|
|
"heap_spray_0d": b"\x0d" * 16,
|
|
"heap_spray_0a": b"\x0a" * 16,
|
|
"heap_spray_04": b"\x04" * 16,
|
|
"heap_spray_41": b"\x41" * 16,
|
|
}
|
|
|
|
SHELLCODE_MARKERS = [
|
|
b"\xfc\xe8", # CLD; CALL
|
|
b"\x60\xe8", # PUSHAD; CALL
|
|
b"\xeb\x10\x5a", # JMP SHORT; POP EDX
|
|
b"\x31\xc0\x50\x68", # XOR EAX; PUSH; PUSH
|
|
b"\xe8\xff\xff\xff\xff", # CALL $+5 (self-locating)
|
|
]
|
|
|
|
SUSPICIOUS_ALLOC_THRESHOLD = 0x100000 # 1 MB
|
|
|
|
|
|
class HeapSprayAnalyzer:
|
|
"""Detects heap spray exploitation artifacts in memory dumps."""
|
|
|
|
def __init__(self, memory_dump, output_dir="./heap_spray_analysis"):
|
|
self.memory_dump = memory_dump
|
|
self.output_dir = Path(output_dir)
|
|
self.output_dir.mkdir(parents=True, exist_ok=True)
|
|
self.findings = []
|
|
|
|
def _run_vol3(self, plugin, extra_args=None):
|
|
"""Run a Volatility3 plugin and return stdout."""
|
|
cmd = ["vol", "-f", self.memory_dump, plugin]
|
|
if extra_args:
|
|
cmd.extend(extra_args)
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
|
return result.stdout
|
|
except (FileNotFoundError, subprocess.TimeoutExpired):
|
|
return ""
|
|
|
|
def run_malfind(self):
|
|
"""Run windows.malfind to detect injected executable memory."""
|
|
output = self._run_vol3("windows.malfind")
|
|
entries = []
|
|
current = {}
|
|
for line in output.splitlines():
|
|
parts = line.split()
|
|
if len(parts) >= 6 and parts[0].isdigit():
|
|
if current:
|
|
entries.append(current)
|
|
current = {
|
|
"pid": int(parts[0]),
|
|
"process": parts[1],
|
|
"start_addr": parts[2],
|
|
"end_addr": parts[3],
|
|
"protection": parts[5] if len(parts) > 5 else "",
|
|
}
|
|
elif current and line.strip().startswith("0x"):
|
|
hex_match = re.findall(r"[0-9a-fA-F]{2}", line.split(" ")[0] if " " in line else line)
|
|
if "hex_bytes" not in current:
|
|
current["hex_bytes"] = ""
|
|
current["hex_bytes"] += "".join(hex_match)
|
|
if current:
|
|
entries.append(current)
|
|
return entries
|
|
|
|
def run_vadinfo(self):
|
|
"""Run windows.vadinfo to find large suspicious allocations."""
|
|
output = self._run_vol3("windows.vadinfo")
|
|
large_allocs = []
|
|
for line in output.splitlines():
|
|
parts = line.split()
|
|
if len(parts) >= 5 and parts[0].isdigit():
|
|
try:
|
|
pid = int(parts[0])
|
|
start = int(parts[2], 16) if parts[2].startswith("0x") else 0
|
|
end = int(parts[3], 16) if parts[3].startswith("0x") else 0
|
|
size = end - start
|
|
if size >= SUSPICIOUS_ALLOC_THRESHOLD:
|
|
large_allocs.append({
|
|
"pid": pid, "process": parts[1],
|
|
"start": hex(start), "end": hex(end),
|
|
"size_bytes": size, "size_mb": round(size / (1024 * 1024), 2),
|
|
})
|
|
except (ValueError, IndexError):
|
|
continue
|
|
return large_allocs
|
|
|
|
def scan_dump_for_patterns(self, dump_path):
|
|
"""Scan a memory dump file for NOP sled and shellcode patterns."""
|
|
matches = {"nop_sleds": [], "shellcode_markers": []}
|
|
try:
|
|
with open(dump_path, "rb") as f:
|
|
data = f.read()
|
|
except (FileNotFoundError, PermissionError):
|
|
return matches
|
|
|
|
for name, pattern in NOP_PATTERNS.items():
|
|
offset = 0
|
|
count = 0
|
|
while True:
|
|
idx = data.find(pattern, offset)
|
|
if idx == -1:
|
|
break
|
|
count += 1
|
|
offset = idx + len(pattern)
|
|
if count > 100:
|
|
break
|
|
if count > 0:
|
|
matches["nop_sleds"].append({"pattern": name, "occurrences": count})
|
|
|
|
for marker in SHELLCODE_MARKERS:
|
|
idx = data.find(marker)
|
|
if idx != -1:
|
|
context = data[idx:idx + 64]
|
|
matches["shellcode_markers"].append({
|
|
"offset": hex(idx),
|
|
"bytes": context.hex()[:128],
|
|
"sha256": hashlib.sha256(context).hexdigest(),
|
|
})
|
|
return matches
|
|
|
|
def dump_process_memory(self, pid):
|
|
"""Dump a process's memory using Volatility3 memmap."""
|
|
dump_dir = self.output_dir / f"pid_{pid}"
|
|
dump_dir.mkdir(exist_ok=True)
|
|
self._run_vol3("windows.memmap", ["--pid", str(pid), "--dump",
|
|
"--output-dir", str(dump_dir)])
|
|
dumps = list(dump_dir.glob("*.dmp"))
|
|
return [str(d) for d in dumps]
|
|
|
|
def analyze(self):
|
|
"""Run full heap spray analysis pipeline."""
|
|
malfind_results = self.run_malfind()
|
|
large_allocs = self.run_vadinfo()
|
|
|
|
spray_candidates = defaultdict(list)
|
|
for alloc in large_allocs:
|
|
spray_candidates[alloc["pid"]].append(alloc)
|
|
|
|
for pid, allocs in spray_candidates.items():
|
|
total_mb = sum(a["size_mb"] for a in allocs)
|
|
if total_mb > 50:
|
|
self.findings.append({
|
|
"severity": "high", "type": "Heap Spray Indicator",
|
|
"detail": f"PID {pid}: {total_mb:.1f} MB in {len(allocs)} large allocations",
|
|
})
|
|
|
|
for entry in malfind_results:
|
|
hex_bytes = entry.get("hex_bytes", "")
|
|
if hex_bytes.count("90") > 20 or hex_bytes.count("0c") > 20:
|
|
self.findings.append({
|
|
"severity": "critical", "type": "NOP Sled in Injected Region",
|
|
"detail": f"PID {entry['pid']} ({entry['process']}): "
|
|
f"NOP sled at {entry['start_addr']}",
|
|
})
|
|
|
|
return {
|
|
"malfind_entries": malfind_results,
|
|
"large_allocations": large_allocs,
|
|
"spray_candidate_pids": list(spray_candidates.keys()),
|
|
}
|
|
|
|
def generate_report(self):
|
|
analysis = self.analyze()
|
|
|
|
report = {
|
|
"report_date": datetime.utcnow().isoformat(),
|
|
"memory_dump": self.memory_dump,
|
|
"malfind_count": len(analysis["malfind_entries"]),
|
|
"large_allocation_count": len(analysis["large_allocations"]),
|
|
**analysis,
|
|
"findings": self.findings,
|
|
"total_findings": len(self.findings),
|
|
}
|
|
out = self.output_dir / "heap_spray_report.json"
|
|
with open(out, "w") as f:
|
|
json.dump(report, f, indent=2, default=str)
|
|
print(json.dumps(report, indent=2, default=str))
|
|
return report
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Analyze memory dumps for heap spray exploitation artifacts"
|
|
)
|
|
parser.add_argument("memory_dump", help="Path to memory dump file (.raw, .vmem, .dmp)")
|
|
parser.add_argument("--output-dir", default="./heap_spray_analysis",
|
|
help="Output directory for report and dumps")
|
|
parser.add_argument("--alloc-threshold", type=int, default=0x100000,
|
|
help="Minimum allocation size in bytes to flag (default: 1MB)")
|
|
args = parser.parse_args()
|
|
|
|
os.makedirs(args.output_dir, exist_ok=True)
|
|
analyzer = HeapSprayAnalyzer(args.memory_dump, output_dir=args.output_dir)
|
|
analyzer.generate_report()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|