Return-Oriented Programming


Pwndamentals.


Challenges

Import system() through the PLT, stage your own command in writable memory, and call it.

Use the libc leak to find system(), /bin/sh, and a pop rdi gadget inside libc, then chain them for a shell.


Common gotcha: the shell drops privileges like /bin/sh without -p, so you need to call setuid(0) first to keep root.

There is no helpful PLT target for a shell. Stage your own pathname, then use gadgets to invoke open(), read(), and write() as raw syscalls.

One pop rdi is not enough when you also need rdx. The flag is already sitting in a global buffer, so use the two CSU gadgets to set rdi, rsi, and rdx at once.


The gadgets live in __libc_csu_init, which every dynamically-linked binary used to carry. glibc 2.34 removed __libc_csu_init/__libc_csu_fini, so on a modern system they are gone.

There is not enough room on the stack for a full ROP chain. Put the real chain somewhere writable, then pivot rsp onto it.

The gadget set is too small for normal ROP, but one rt_sigreturn can restore every register at once. Build a fake signal frame, then use it to invoke execve().

NX is on, but mprotect() is only a ROP chain away. Mark a writable page executable, stage shellcode there, and jump to it.

No libc leak and no system() import. Stage a fake relocation, a symbol table entry, and your own command in writable memory, then bounce through the dynamic resolver to call system() anyway.


30-Day Scoreboard:

This scoreboard reflects solves for challenges in this module after the module launched in this dojo.

Rank Hacker Badges Score