Binary Exploitation


Pwndamentals.


Challenges

The classic stack buffer overflow. Overwrite the saved return address with win(), and it reads the flag for you.

The stack is executable and the program prints the address of your buffer. Write shellcode into the buffer, overflow the saved return address, and jump back to the buffer to run it.

The win() function is back but is now "more secure." Return into the middle of win_authed() to skip its check.

The bounds check uses a signed int, but fread() takes a size_t. A negative count slips past the check, then turns huge when the read happens.

You only control the low byte of the saved return address. PIE is on, but that byte is still enough to land in win().

The stack canary blocks a straight overwrite, so leak it first and put it back before you return into win().

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

Partial RELRO leaves the GOT writable. Use the write primitive to replace one function pointer, then let the program call through 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.

A single byte past the buffer is enough when it lands on the low byte of the saved frame pointer. Let leave; ret do the rest.

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