homework picoMini by redpwn Solution

Published: April 2, 2026

Description

Do your homework.

Connect to the challenge server with netcat.

Download the binary for local analysis.

bash
nc <challenge_host> <PORT_FROM_INSTANCE>
bash
wget <challenge_url>/homework  # binary for local analysis
  1. Step 1Check binary protections and find the overflow
    Run checksec to 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.
    bash
    checksec --file=homework
    bash
    file homework
    python
    python3 -m pip install pwntools
    Learn 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" in rdi (the first argument register on x86-64 System V ABI) and then returning to system in libc. The gadget sequence is: pop rdi; ret → address of "/bin/sh" → address of system.

  2. Step 2Determine the overflow offset
    Use 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.txt
    bash
    gdb -q ./homework
    bash
    # Inside GDB: run < pattern.txt
    bash
    # 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.

  3. Step 3Find ROP gadgets and build the chain
    Use ROPgadget or pwntools&apos; ROP module to find a pop rdi; ret gadget in the binary. If no PIE, gadget addresses are static. Build the payload: padding + pop_rdi_ret + bin_sh_addr + system_addr.
    bash
    ROPgadget --binary homework | grep 'pop rdi'
    python
    python3 exploit.py
    Learn more

    ROPgadget scans a binary for all sequences ending in ret and lists them with their addresses. Search for pop rdi ; ret to 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 a call in modern glibc, so an extra ret gadget 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").

Want more picoMini by redpwn writeups?

Tools used in this challenge

Related reading

What to try next