npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
Overview
Heap spraying is an exploitation technique that fills large regions of a process's heap with attacker-controlled data (typically NOP sleds followed by shellcode) to increase the reliability of code execution exploits. This skill covers detecting heap spray artifacts in memory dumps using Volatility3's malfind, vadinfo, and memmap plugins, identifying suspicious contiguous memory allocations, scanning for NOP sled patterns (0x90, 0x0c0c0c0c), and extracting embedded shellcode for analysis.
When to Use
- When investigating security incidents that require analyzing heap spray exploitation
- When building detection rules or threat hunting queries for this domain
- When SOC analysts need structured procedures for this analysis type
- When validating security monitoring coverage for related attack techniques
Prerequisites
- Python 3.9+ with
volatility3framework installed - Memory dump file (.raw, .vmem, .dmp format)
- Understanding of virtual memory layout and VAD (Virtual Address Descriptor) trees
- Familiarity with common shellcode patterns and NOP sled encodings
Steps
Step 1: Identify Suspicious Processes
Use Volatility3 windows.malfind to scan for processes with executable injected memory regions.
Step 2: Analyze VAD Entries
Examine VAD tree entries using windows.vadinfo for large contiguous allocations with RWX permissions.
Step 3: Scan for NOP Sled Patterns
Search suspicious memory regions for NOP sled signatures (0x90 sequences, 0x0c0c0c0c patterns).
Step 4: Extract and Analyze Shellcode
Dump suspicious memory regions and identify shellcode using byte pattern analysis.
Expected Output
JSON report with suspicious processes, heap spray indicators, NOP sled locations, memory region sizes, and extracted shellcode hashes.
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 1
api-reference.md2.2 KB
API Reference: Analyzing Heap Spray Exploitation
Volatility3 Plugins for Heap Spray Analysis
| Plugin | Command | Purpose |
|---|---|---|
| malfind | vol -f dump.raw windows.malfind |
Find injected executable memory regions |
| vadinfo | vol -f dump.raw windows.vadinfo |
Virtual Address Descriptor details |
| memmap | vol -f dump.raw windows.memmap --pid PID --dump |
Dump process memory to files |
| pslist | vol -f dump.raw windows.pslist |
List running processes |
| handles | vol -f dump.raw windows.handles --pid PID |
List process handles |
Common Heap Spray NOP Sled Patterns
| Pattern | Hex | Description |
|---|---|---|
| x86 NOP | 0x90909090 | Classic NOP instruction |
| 0x0C landing | 0x0C0C0C0C | Common heap spray address target |
| 0x0D landing | 0x0D0D0D0D | Alternative spray address |
| 0x0A landing | 0x0A0A0A0A | Alternative spray address |
| 0x41 fill | 0x41414141 | "AAAA" padding fill |
Shellcode Signatures
| Bytes | Mnemonic | Context |
|---|---|---|
| FC E8 | CLD; CALL | Common shellcode prologue |
| 60 E8 | PUSHAD; CALL | Register-saving shellcode start |
| 31 C0 50 68 | XOR EAX; PUSH; PUSH | Stack setup for API call |
| E8 FF FF FF FF | CALL $+5 | Self-locating shellcode (GetPC) |
Detection Thresholds
| Indicator | Threshold | Meaning |
|---|---|---|
| Large allocation | >= 1 MB per region | Suspicious heap allocation |
| Total spray size | >= 50 MB per process | Strong heap spray indicator |
| NOP sled count | >= 20 repeated bytes | NOP sled detected |
| RWX permissions | PAGE_EXECUTE_READWRITE | Injected executable code |
Install Volatility3
pip install volatility3
# Or from source:
git clone https://github.com/volatilityfoundation/volatility3.git
cd volatility3 && pip install -e .References
- Volatility3 GitHub: https://github.com/volatilityfoundation/volatility3
- Volatility3 malfind: https://volatility3.readthedocs.io/en/latest/
- Heap Spray Techniques: https://www.corelan.be/index.php/2011/12/31/exploit-writing-tutorial-part-11-heap-spraying-demystified/
- DFRWS 2025 Workshop: https://webdiis.unizar.es/~ricardo/dfrws-eu-25-workshop/
Scripts 1
agent.py8.1 KB
#!/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()