Binary Exploitation


Pwndamentals.


Challenges

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

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

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.

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

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

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

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.

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

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

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

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

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

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

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

Partial RELRO leaves the GOT writable. Use the write primitive to replace one function pointer, then let the program call through it.

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

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.

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

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.

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

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.

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

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.

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

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

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

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().

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

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

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

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.

Connect with SSH

Link your SSH key, then connect with: ssh [email protected]

30-Day Scoreboard:

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

Rank Hacker Badges Score