Table of Contents

ASLR, bypass techniques, and circumvention impacts

FOSS breakfast. Used on a post about ASLR bypasses.


Address space layout randomization (ASLR) randomizes memory addresses used by system and application processes, making it more difficult for me to predict the location of specific code (e.g., functions or stack variables). Despite its effectiveness, several techniques can bypass ASLR. This blog post documents some of them:

  1. Memory disclosure (or information leakage)
  2. Brute force attacks
  3. Partial overwrites
  4. Return-oriented programming (ROP)
  5. Ret2libc (return-to-libc)
  6. ASLR on 32-bit systems
  7. Heap spraying
  8. Non-randomized regions
  9. Just-In-Time spraying (JIT-spray)

Note that this post is not highly technical, and is primarily for beginners in the low-level security space. This post functions as a rough overview of some of the possible techniques used in ASLR bypassing. 


🌸👋🏻 Join 10,000+ followers! Let’s take this to your inbox. You’ll receive occasional emails about whatever’s on my mind—offensive security, open source, boats, reversing, software freedom, you get the idea.


What is ASLR? 

But first! What is ASLR? Skip to 1. Memory disclosure (or information leakage) if you do not need this refresher. 

ASLR randomizes the memory addresses used by system and application processes, making it difficult for me to predict the locations of the stack, heap, and libraries (e.g., libc). By doing this, ASLR reduces the likelihood of a successful buffer overflow or other memory corruption attack.

Here’s an example to illustrate how ASLR works and how it helps prevent basic stack-based buffer overflows. 

Scenario without ASLR

Imagine a vulnerable program that has a stack-based buffer overflow. I can exploit this vulnerability by injecting malicious code (payload) into the stack and then overwriting the return address to point to the start of my payload.

Illustration of overwriting buffer with return address. Used on a post about ASLR bypasses.

Source – Fabulous Video 

In systems without ASLR, memory addresses for important regions (e.g., the stack, heap, and libraries) are fixed. Every time the program runs, these components will always be loaded at the same memory addresses.

Example:

  • Stack starts at address 0xBFFF0000
  • Heap starts at address 0x08048000
  • Library (e.g., libc) starts at address 0xB7E00000

Here, I know where these memory regions are located from previous runs (or from other victims’ systems in rare cases where the setup is identical), so I can craft an exploit for these regions. I overwrite the return address on the stack with a known address of my payload.

Regions

For reference, here is when I would want to target each region: 

  • Stack
    • When attempting to overwrite return addresses or function pointers, allowing for control over the execution flow.
  • Heap
    • When exploiting dynamically allocated memory vulnerabilities to manipulate or overwrite metadata and potentially control program flow.
  • libc
    • Beneficial in bypassing non-executable stack protections (e.g., DEP) by using return-to-libc techniques, calling pre-existing libc functions like system() to execute commands.

🌸👋🏻 Join 10,000+ followers! Let’s take this to your inbox. You’ll receive occasional emails about whatever’s on my mind—offensive security, open source, boats, reversing, software freedom, you get the idea.


Scenario with ASLR

Now, imagine the same vulnerable program is running with ASLR enabled.

Here, the memory layout is randomized each time the program runs. Here is an example on Linux: 

# ASLR randomizes mem addrs of certain memory segments (e.g., stack, heap, and shared libs) every time a process is executed or sometimes during its execution.
# this command runs three times and puts results in textfile, illustrating how the mem segments of self change each time.
for i in {1..3}; do cat /proc/self/maps >> text.txt; echo "—-" >> text.txt; sleep 1; done

And macOS: 

# run three times.
# vmmap shows virtual mem map of a process.
# $$ represents PID of curr shell process.
for i in {1..3}; do vmmap $$ >> text.txt; echo "—-" >> text.txt; sleep 1; done

Critical regions (again: stack, heap, and libraries) are loaded at different random addresses on every execution. 

For example, if the program is run multiple times, the memory addresses might look like this:

Run 1

  • Stack starts at address 0xA1FF2000
  • Heap starts at address 0x5F010000
  • Library (e.g., libc) starts at address 0xB3C50000

Run 2

  • Stack starts at address 0x97AB4000
  • Heap starts at address 0x71098000
  • Library (e.g., libc) starts at address 0xC2D30000

Now, I can no longer predict the memory addresses for these critical components. Even if I manage to inject malicious code, I don’t know where to point the return address to execute my payload. 

Since the stack, heap, and libraries are loaded at random locations on every run, the attack will likely crash the program or behave unexpectedly, preventing my exploit from working. 

With ASLR, I have no knowledge of the current memory addresses, and guessing the correct address becomes exponentially harder (often requiring brute force attempts to locate specific memory segments).

Limitations

The main limitation of ASLR is that it can be bypassed through information leaks—where I gather memory layout details through static, dynamic, and/or binary analysis—or through brute-force attacks: pwntools, debuggers (GDB and Peda), scripting, and/or memory debugging (Valgring or ASan) often within a controlled VM environment. 

Additionally, ASLR’s effectiveness is reduced if other system defenses (e.g., stack canaries or control flow integrity (CFI)) are not in place, leaving systems vulnerable to exploitation despite memory randomization.

Now that we understand ASLR, its benefits, and its limitations, let’s examine some ASLR bypasses! 


🌸👋🏻 Join 10,000+ followers! Let’s take this to your inbox. You’ll receive occasional emails about whatever’s on my mind—offensive security, open source, boats, reversing, software freedom, you get the idea.


1. Memory disclosure (or information leakage)

I could exploit a vulnerability that leaks memory addresses from a running process. Memory disclosure arises by exploiting format string vulnerabilities, buffer overflows, or using a read primitive to disclose memory contents.

Below is an example of a read primitive in a buffer overflow. Here, I can read data beyond the allocated space: 

void read_from_buffer(char *input) {
char buffer[8];
memcpy(buffer, input, strlen(input)); // no bounds check
printf("buffer content: %s\n", buffer); // leaks data
}

Impact

Once I know the base address of a library or a specific part of the memory layout, I can adjust my payload to target specific addresses, effectively bypassing ASLR.

2. Brute force attacks

ASLR doesn’t provide an infinite number of possible memory addresses. In some cases, the entropy (randomization range) may be low, making it feasible to brute force the correct address by trying multiple times until the correct one is found.

Impact

This technique is more feasible when I have the ability to restart the target process multiple times or when only a few bits are randomized. 

3. Partial overwrites

Another ASLR bypass is potential/partial overwrites. 

Instead of trying to guess the entire address, I can partially overwrite an address so that it points to a predictable memory location. 

For example, the least significant bytes of an address might be overwritten, while the most significant bytes remain the same. Here, the most significant bytes might (potentially) still be within the range of valid addresses.

Impact

Partial overwrites reduce the complexity of guessing the entire address, especially when I can control some of the address’s bits.


🌸👋🏻 Join 10,000+ followers! Let’s take this to your inbox. You’ll receive occasional emails about whatever’s on my mind—offensive security, open source, boats, reversing, software freedom, you get the idea.


4. Return-oriented programming (ROP)

Even with ASLR, I might be able to use ROP, which involves chaining together small sequences of existing instructions (“gadgets”) that end in a return instruction. If I can find enough gadgets within a randomized module, I can perform arbitrary operations without needing to inject new code.

Read more: How to use ROP to bypass security mechanisms – Olivia A. Gallucci 

Impact

This technique leverages existing code within a process and is more challenging to prevent, especially if I have some knowledge of the memory layout.

5. Ret2libc (return-to-libc)

Similar to ROP, ret2libc attacks leverage the fact that certain libraries (e.g., libc) might be loaded at predictable locations or partially known addresses. Even if the base address is randomized, certain functions may reside at predictable offsets from the base.

Impact

ret2libc attacks allow me to bypass ASLR by calling existing functions in a library (e.g., system() in libc) to execute arbitrary commands.

6. ASLR on 32-bit systems

On 32-bit systems, the amount of addressable memory is significantly less, leading to lower entropy in ASLR. Lower entropy makes it easier to guess or brute-force memory addresses.

Impact

Due to the reduced randomness, ASLR is less effective on 32-bit systems, and I could theoretically bypass it more easily compared to 64-bit systems. (Whether I can do this in practice is a skill-issue). 

7. Heap spraying

Another ASLR bypass is heap spraying. Here, I might fill the heap with a large amount of data (typically NOP sleds followed by shellcode) in an attempt to increase the likelihood that execution will jump to my payload. 

# Code by hg8
# Source: https://hg8.sh/posts/binary-exploitation/buffer-overflow-code-execution-by-shellcode-injection/#Crafting-our-exploit
import sys
shellcode = b"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
eip = b"\x43\x43\x43\x43" * 10
nop = b"\x90" * 447
buff = nop + shellcode + eip
sys.stdout.buffer.write(buff)
# Our NOP sled is being repeated 447 times since we need to write 512 bytes to overwrite the return address:
# 512 – (4 * 10) – 25 = 447
# Total size – eip – shellcode = nop sled

Above is an example of “NOP sleds followed by shellcode.” Code by hg8 in this great blog post: buffer overflow: code execution by shellcode injection

Impact

Although ASLR tries to prevent me from knowing exact memory addresses, heap spraying increases the chances that my code is executed by filling the memory with large amounts of predictable data.


🌸👋🏻 Join 10,000+ followers! Let’s take this to your inbox. You’ll receive occasional emails about whatever’s on my mind—offensive security, open source, boats, reversing, software freedom, you get the idea.


8. Non-randomized regions

Some memory regions may not be randomized or may be predictable across different instances or reboots. These regions can include global variables, static buffers, and other areas not protected by ASLR.

Impact

Here, I can target these non-randomized areas to gain a foothold, despite ASLR being in place.

9. Just-In-Time spraying (JIT-Spray)

Learn more: pwn.college – Exploitation Scenarios – JIT Spray. Note: I wish I had a good visualization of this. If someone has a GIF, or an idea of how I could visualize this, please let me know. I am at a loss. 

Lastly, we have JIT-spraying. This technique involves spraying a large amount of malicious code into memory that is generated dynamically by a JIT compiler. 

JIT compilers compile (no duh) code during program execution, translating the code from an intermediate form (e.g., bytecode) into machine code on-the-fly. They optimize performance by executing native code directly.

With JIT-spraying, I can try to force the JIT compiler to place code at predictable addresses.

Impact

JIT-spraying can bypass ASLR since I control where the JIT-generated code is placed, allowing me to execute my payload despite memory randomization.

ASLR bypasses 

Each of these techniques exploits weaknesses in ASLR or its implementation. Combining these methods with other vulnerabilities or techniques can increase the likelihood of a successful ASLR bypass. However, these bypasses can be mitigated through improved ASLR implementations and other mechanisms like CFI and data execution prevention (DEP), which I will cover in the future! 

If you enjoyed this post on ASLR, consider reading how the OS affects binary exploitation.

Portrait of Olivia Gallucci in garden, used in LNP article.

Written by Olivia Gallucci

Olivia is senior security engineer, certified personal trainer, and freedom software advocate. She writes about offensive security, open source software, and professional development.

Discover more from [ret]2read

An OS Internals Newsletter by Olivia Gallucci. Subscribe now to keep reading and get access to the full archive.

Continue reading