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:
- Memory disclosure (or information leakage)
- Brute force attacks
- Partial overwrites
- Return-oriented programming (ROP)
- Ret2libc (return-to-libc)
- ASLR on 32-bit systems
- Heap spraying
- Non-randomized regions
- 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.

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 address0xB7E00000
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
libcfunctions likesystem()to execute commands.
- Beneficial in bypassing non-executable stack protections (e.g., DEP) by using return-to-libc techniques, calling pre-existing
🌸👋🏻 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:
And macOS:
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 address0xB3C50000
Run 2
- Stack starts at address
0x97AB4000 - Heap starts at address
0x71098000 - Library (e.g.,
libc) starts at address0xC2D30000
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:
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.
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.


You must be logged in to post a comment.