1. Introduction

There are two main pillars that make up a computer algorithm: data and the means to process it. Thus, along with the central processing unit (CPU), memory for storing data is vital to any computer system. Considering this, memory organization and memory addressing become important both for storage and during runtime.

In this tutorial, we’ll talk about memory randomization, how it works, and how to toggle it. First, we briefly discuss memory organization in general. After that, we explore when, why, and how to use memory address randomization. Next, we turn to toggling the mechanism locally. Finally, we check a way to disable and reenable memory randomization globally.

We tested the code in this tutorial on Debian 12 (Bookworm) with GNU Bash 5.1.4. It should work in most POSIX-compliant environments unless otherwise specified.

2. Memory Organization

Data is everything to a machine. Even code is data.

So, every computer system needs a way to reference parts of its memory for accessing relevant information. To do so, systems first organize memory and then address it:

+---+---+---+---+---+---+---+
| M | E | M | O | R | Y | ...
+---+---+---+---+---+---+---+

Here, we can see a visualization of six memory cells, each containing a single byte. In practice, single-byte addressing isn’t implemented by hardware, as it would require too many addresses:

+---+---+---+---+---+---+---+
| M | E | M | O | R | Y | ...
+---+---+---+---+---+---+---+
 001 002 003 004 005 006 ...

In any case, we can represent the address of each cell as above. This is valid for code, as well as dynamic and static data.

Of course, these aren’t physical but virtual addresses. Still, being able to easily guess the address space of a given process might make a system easier to compromise.

3. Address Space Layout Randomization (ASLR)

In general, memory can hold sensitive information:

  • private details
  • vital system data
  • executable code

Thus, any user with the proper privileges might be able to read and access potentially exploitable knowledge.

For example, let’s consider what the OS does when we try to run an executable:

  1. Find a free memory segment
  2. Copy the executable binary data to the free segment
  3. Run the instruction at the entry point of the code
  4. Allocate a memory segment for dynamic data, such as the stack and heap

Now, if we knew where the executable and all its components lie, we could attack this structure and perform different exploits, such as buffer overflows or simple memory extractions.

To prevent this, an operating system (OS) can employ address space layout randomization (ASLR), which affects several areas of a given process address space:

  • Base executable address
  • Library addresses
  • Heap and stack segments

In short, randomizing the addresses of these locations can be critical for security, so the kernel supports ASLR as a means to obfuscate memory.

To demonstrate, let’s use the ldd command on the /bin/bash executable file:

$ ldd /bin/bash
        linux-vdso.so.1 (0x00007ffda6df6000)
        libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007f507d701000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f507d520000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f507d884000)
$ ldd /bin/bash
        linux-vdso.so.1 (0x00007ffca9bd9000)
        libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007ff6373fc000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff63721b000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ff63757f000)

The ldd command shows any shared objects an executable needs with their current mapping address within parentheses. Consequently, running the same command twice produced different addresses. While this is secure, it might not be convenient when it comes to development, tracing, and debugging.

So, let’s see how to work with ASLR in Linux.

4. Local ASLR Testing

Since ASLR is critical to security, making global changes to its configuration isn’t recommended. Instead, we can create a local environment where we disable the feature.

4.1. Personality Flags and setarch

In Linux, process personality flags, also called execution domains, are modifiers that dictate the behavior of a process:

  • set architecture
  • address space characteristics
  • signal numbers to actions mapping
  • specific page and memory mapping
  • kernel version spoofing
  • custom platform code support

To modify these characteristics, we use the setarch utility:

$ uname --kernel-release
5.10.0-666-amd64
$ setarch --uname-2.6 uname --kernel-release
2.6.70-666-amd64

Here, we first run uname with its –kernel-release (-r) option to verify our kernel version as 5.10. After that, we run the same command in the context of version 2.6 spoofing via the –uname-2.6 flag. As a result, the output changes accordingly.

Additionally, we can get a –list of architectures via setarch:

$ setarch --list
uname26
linux32
linux64
i386
i486
i586
i686
athlon
x86_64

Support for each depends on the actual kernel version.

4.2. setarch Verbosity and Switches

To aid our efforts, we can add the –verbose (-v) flag of setarch:

$ setarch --verbose --3gb /bin/bash
Switching on ADDR_LIMIT_3GB.
Execute command `/bin/bash'.

Here, the –3gb (-3) flag turns on ADDR_LIMIT_3GB, which limits the address space of a process to 3GB. With the increased verbosity, we see which options got activated and for which process. This way, we know we’re in a new Bash session with the respective settings.

Most setarch flags exist mostly to maximize backward compatibility. This mechanism exists under different names in other operating systems as well.

4.3. setarch Address Space

One of the options to setarch is –addr-no-randomize (-R), which disables ASLR for the supplied process.

Let’s test this out with the Bash shell:

$ setarch --verbose --addr-no-randomize /bin/bash
Switching on ADDR_NO_RANDOMIZE.
Execute command `/bin/bash'.
$ ldd /bin/bash
        linux-vdso.so.1 (0x00007ffff7fc9000)
        libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007ffff7e42000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7c61000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ffff7fcb000)
$ ldd /bin/bash
        linux-vdso.so.1 (0x00007ffff7fc9000)
        libtinfo.so.6 => /lib/x86_64-linux-gnu/libtinfo.so.6 (0x00007ffff7e42000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7c61000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ffff7fcb000)

Had we not used –verbose, there would have been no output after the first command. However, we actually start a new Bash session with ASLR off, i.e., ADDR_NO_RANDOMIZE is on. To verify that, we again employ our ldd experiment and confirm that the addresses within parentheses are the same between both calls.

4.4. Programmatic Personality

In addition to the setarch command, we can also employ a raw C function to modify the personality of a new process:

#include <sys/personality.h>

void newProcess() {
  personality(ADDR_NO_RANDOMIZE);
  [...]
}

This way, we can spawn any other process that we want to test out without ASLR.

5. Disable or Enable ASLR Globally Using kernel.randomize_va_space

In Linux, many kernel features can be configured or checked in the /proc pseudo-filesystem. ASLR is no exception.

The global setting for ASLR in the kernel is kernel.randomize_va_space at /proc/sys/kernel/randomize_va_space:

$ sysctl kernel.randomize_va_space
kernel.randomize_va_space = 2
$ cat /proc/sys/kernel/randomize_va_space
2

In this case, we see both sysctl and the respective /proc file show a value of 2.

To interpret the value, we can use the relevant kernel documentation and find randomize_va_space:

Another way to test the results of this global setting is the /proc/self/maps file:

$ sysctl kernel.randomize_va_space
kernel.randomize_va_space = 2
$ cat /proc/self/maps
[...]
7ffe2f84b000-7ffe2f86c000 rw-p 00000000 00:00 0
         [stack]
$ cat /proc/self/maps
[...]
7ffcd8153000-7ffcd8174000 rw-p 00000000 00:00 0
         [stack]

As expected, the address changes when ASLR is on.

Now, let’s turn ASLR off and recheck:

$ $ sysctl kernel.randomize_va_space=0
kernel.randomize_va_space = 0
$ cat /proc/self/maps
[...]
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0
         [stack]
$ cat /proc/self/maps
[...]
7ffffffde000-7ffffffff000 rw-p 00000000 00:00 0
         [stack]

In the /proc path, self is the process identifier (PID) of the current process. This way, we don’t need to specify an actual PID to explore the current address space.

Again, disabling ASLR globally isn’t usually recommended.

6. Summary

In this article, we discussed memory randomization in Linux with the ASLR feature.

In conclusion, while we can toggle ASLR temporarily for a given shell session or permanently for the whole system, best practices advise against the latter.