ayman@portfolio:~$

Understanding Address Space Randomization

March 6, 2025
securitylow-levelC/C++memory

Randomizing memory layouts: a deep dive into how modern systems protect against memory exploitation attacks

The Problem: Predictable Memory

Before Address Space Layout Randomization (ASLR) became standard in operating systems, memory layouts were highly predictable. In a non-ASLR world, when you run a program, its components—the executable code, libraries, stack, and heap—would consistently load at the same memory addresses each time.

$ cat stack_demo.c
#include <stdio.h>

void vulnerable_function(char* input) {
    char buffer[16];
    strcpy(buffer, input);  // Classic buffer overflow vulnerability
    printf("Buffer content: %s\n", buffer);
}

int main(int argc, char** argv) {
    if (argc > 1) {
        vulnerable_function(argv[1]);
    }
    return 0;
}

This predictability was a gift to attackers. If you knew where a particular function or buffer resided in memory, you could craft exploits with surgical precision. Buffer overflows, return-to-libc attacks, and other memory corruption vulnerabilities relied on this predictability.

Enter ASLR: Entropy as Defense

ASLR introduces randomness into the equation. Every time a program launches, the OS shuffles the deck—randomizing the base addresses of key memory regions:

Without ASLR

Executable: 0x08048000
Libraries: 0xb7e21000
Stack: 0xbffdf000
Heap: 0x08072000

With ASLR

Executable: 0x55e839742000
Libraries: 0x7f6e21f81000
Stack: 0x7ffd83e41000
Heap: 0x5633a2172000

The result? An attacker can no longer hardcode memory addresses in exploits. Each execution presents a completely new memory landscape, turning what was once a deterministic attack into a probabilistic one.

How ASLR Works: Behind the Scenes

At a technical level, ASLR works during the program loading process:

  1. The OS's loader identifies relocatable components of the program
  2. It generates random offsets for each memory region (within architectural constraints)
  3. The loader applies these offsets when mapping the program into virtual memory
  4. Page tables are configured to reflect these randomized addresses

Let's observe ASLR in action on Linux using a simple command:

for i in {1..3}; do 
  ldd /bin/ls | grep libc
  echo "---" 
done

# Output:
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x7f46b6841000)
# ---
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x7f2b4e9a1000)
# ---
# libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x7f85e2c41000)
# ---

Implementation Details: Bits of Entropy

The effectiveness of ASLR depends on how many bits of entropy are used for randomization:

PlatformStackHeapLibraries/PIE
Linux (x86_64)~28 bits~23 bits~28 bits
Windows 10 (x86_64)~17 bits~16 bits~8 bits
macOS (ARM64)~33 bits~33 bits~33 bits

More bits of entropy mean more possible address combinations, making attacks exponentially harder. With 28 bits of entropy, there are over 268 million possible address combinations!

ASLR Bypass Techniques: Nothing Is Perfect

While ASLR raises the bar significantly, it's not impenetrable. Several techniques have emerged to bypass or weaken ASLR:

  • Memory disclosure vulnerabilities: If an attacker can leak memory addresses, the randomization becomes known
  • Brute force: For systems with low entropy, repeated attempts may eventually succeed
  • Side-channel attacks: Timing and cache-based attacks can sometimes reveal memory layout information
  • Return-Oriented Programming (ROP): A sophisticated technique that chains together existing code fragments

Memory Disclosure Example

// This vulnerability leaks a pointer, potentially defeating ASLR
void format_string_vulnerability(char* input) {
    printf(input);  // Classic format string vulnerability
    // Should be printf("%s", input);
}

If an attacker can supply "%p %p %p" as input, the function will print pointers from the stack, potentially revealing randomized addresses and breaking ASLR.

ASLR's Evolution: Modern Enhancements

Modern systems have evolved ASLR with additional protections:

Position Independent Executables (PIE)

Extends ASLR protection to the main executable code section, randomizing even more of the address space.

High-entropy ASLR

Uses more bits for randomization, exponentially increasing the number of possible memory layouts.

Implementing ASLR-aware Code

As a low-level programmer, there are key principles to follow when writing code in an ASLR world:

  • Never rely on fixed memory addresses in your code
  • Use relative addressing rather than absolute addressing
  • Compile with PIE and stack protection flags
  • Be cautious when working with function pointers or callbacks
  • Always validate pointer arithmetic to prevent out-of-bounds access

Compiling with ASLR-friendly flags in GCC

# Full protection with PIE and stack protector
gcc -o secure_binary source.c -fstack-protector-all -fpie -pie

# Check if binary is PIE-enabled
readelf -h secure_binary | grep Type
# Output should show: Type: DYN (Shared object file)

Conclusion: Security Through Unpredictability

ASLR represents a fundamental shift in system security philosophy: introducing controlled randomness as a defense mechanism. While not perfect, it has significantly raised the bar for memory-based attacks, forcing attackers to develop more sophisticated techniques.

As a low-level programmer, understanding ASLR isn't just academic—it's essential knowledge for building resilient systems. The cat-and-mouse game between security engineers and attackers continues, with ASLR playing a central role in modern defense strategies.

Want to learn more? Check out these resources:

  • USENIX Security Symposium papers on ASLR implementations
  • The PaX Team's original ASLR documentation
  • Linux kernel source code for memory randomization