malware analysis

Analyzing Heap Spray Exploitation

Detect and analyze heap spray attacks in memory dumps using Volatility3 plugins to identify NOP sled patterns, shellcode landing zones, and suspicious large allocations in process virtual address space.

exploit-analysisheap-spraymalware-analysismemory-forensicsvolatility3
Install this skill
npx skills add mukul975/Anthropic-Cybersecurity-Skills
Framework mappings

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 volatility3 framework 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.

Source materials

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

Scripts 1

agent.py8.1 KB
Display-only source. This catalog never executes bundled scripts.
#!/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()
Keep exploring