Description
Do your homework.
Setup
Connect to the challenge server with netcat.
Download the binary for local analysis.
nc <challenge_host> <PORT_FROM_INSTANCE>wget <challenge_url>/homework # binary for local analysisSolution
Walk me through it- Step 1Check binary protections and find the overflowRun
checksecto see which mitigations are present. For a classic ROP chain challenge, look for disabled NX (rare) or more likely NX enabled with no PIE - this means code addresses are static. Find the vulnerable input function in Ghidra.bashchecksec --file=homeworkbashfile homeworkpythonpython3 -m pip install pwntoolsLearn more
Return-Oriented Programming (ROP) is a code-reuse attack technique used when NX (No-eXecute) prevents shellcode injection. Instead of injecting new code, the attacker chains together small existing code snippets ending in
ret- called gadgets- to build arbitrary computation. Each gadget pops a value into a register and returns to the next gadget's address.The classic goal is to call
system("/bin/sh")by placing the address of"/bin/sh"inrdi(the first argument register on x86-64 System V ABI) and then returning tosystemin libc. The gadget sequence is:pop rdi; ret→ address of"/bin/sh"→ address ofsystem. - Step 2Determine the overflow offsetUse a cyclic pattern (De Bruijn sequence) to find the exact number of bytes needed to reach the saved return address on the stack. This is the offset for your ROP chain payload.python
python3 -c "from pwn import *; print(cyclic(200))" > pattern.txtbashgdb -q ./homeworkbash# Inside GDB: run < pattern.txtbash# After crash: python3 -c "from pwn import *; print(cyclic_find(0x6161616b))"Learn more
A De Bruijn sequence of order n over an alphabet of size k is a cyclic sequence in which every possible subsequence of length n appears exactly once as a contiguous substring. pwntools'
cyclic()generates one - when the program crashes with a value from this pattern in the instruction pointer,cyclic_find()tells you the exact byte offset. This replaces manual trial-and-error to find the overflow offset.The saved return address sits at a predictable location relative to the buffer being overflowed: base of buffer + local variables + saved rbp + 8 bytes = saved rip. The cyclic pattern fills all of this and overwrites rip with a known 4-byte subsequence, revealing the exact offset.
- Step 3Find ROP gadgets and build the chainUse ROPgadget or pwntools' ROP module to find a
pop rdi; retgadget in the binary. If no PIE, gadget addresses are static. Build the payload: padding + pop_rdi_ret + bin_sh_addr + system_addr.bashROPgadget --binary homework | grep 'pop rdi'pythonpython3 exploit.pyLearn more
ROPgadget scans a binary for all sequences ending in
retand lists them with their addresses. Search forpop rdi ; retto control the first function argument. On 64-bit Linux, arguments go in registers (rdi,rsi,rdx,rcx,r8,r9) before the stack.pwntools' ROP module can automate gadget chaining:
rop = ROP(elf); rop.call(elf.plt.system, [next(elf.search(b'/bin/sh\x00'))]). This abstracts away manual gadget hunting and handles alignment requirements (the stack must be 16-byte aligned before acallin modern glibc, so an extraretgadget is sometimes needed as padding).
Flag
picoCTF{...}
Stack buffer overflow with ROP chain - find the overflow offset with a cyclic pattern, locate a pop rdi; ret gadget with ROPgadget, and chain to system("/bin/sh").