ROP ROP #4

ROP ROP #4

Before you proceed any further, make sure you have all the requirements fulfilled.

  • ASM knowledge
  • Debugger familiarity
  • GDB
  • Basic ROP knowledge
  • Brain

Setup

In this post, we will try to learn ROP (Return Oriented Programming) by using ropemporium write4 32bit binary. Lets setup our machine for debugging by installing PEDA and downloading the target binary.

We have downloaded the binary and extracted it, time to start gdb.

Crash

And we crashed.

We can see that our session crashed while trying to access an invalid memory address 0x41414141 pointed to by EIP register and the stack also contains alot of our payload. We need to to determine the exact offset from the start of your payload at which we will actually control the 4bytes of our EIP and to do that we will create a unique set of characters by PEDA.

Copy and paste the generated pattern in input prompt this time, upon crash the EIP will be holding a unique 4byte value which can be easily calculated

We found the offset at 44 which means that the EIP will hold the next 4bytes. We will use a custom boiler plate to ease our exploit development for this binary which can be found below.

 

Control

What should we do now? We control the EIP but where should we point it at? Lets try to use different approaches in order to solve this challenge. We saw that the binary imports fgets(), printf(), puts() and system() from GLIBC library. Also the checksec reports that this binary is not compiled with -fPIC flag (PIE: Disabled) which means that the binary will always be loaded at the same place in memory. Checkec also reports that this binary is NX enabled which means we can’t just execute anything from stack so we probably need to utilize ROP in order to bypass DEP/ASLR. How we can use all this info to attempt a successful exploitation in order to get our flag?

It looks like we need to leak some memory data from our target binary in order to craft working payload. Lets start by examining the execution flow carefully and monitor the above mentioned function calls. In GDB, type pdisas pwnme to disassemble the pwnme function

Lets put breakpoints on few addresses which are of interest in gdb.

Type run and press enter, gdb will stop the program at first breakpoint i.e. 0x08048627. Now we should carefully have a look at the execution flow.

printf takes an argument which is a pointer to the data that is to be printed on the screen, so if we have to leak some memory info then we need to pass an argument which is a pointer to another pointer. The current function is imported from GLIBC library, this linking done by PLT and GOT. The next instruction is call 0x8048400 but if you do a single step by pressing ‘s’ inside gdb it will directly take you inside printf. To understand this better, we will be using a simple flow chart

The GOT entry for our printf@plt function has been updated now and it will remain the same throughout the process lifecycle, this linking of procedures is handled by ldd linker. Now our original pointer 0x804a00c has been updated with a new pointer 0xb7e47930 which points inside of libc library. So perhaps it is a good idea now to point our return pointer to printf and pass 0x804a00c as an argument? Lets update our script to make our first payload

This will create a file called write_payload which we will use as our testing payload in order to create a working exploit. Now again head to gdb, and this time type run < write_payload and for gdb to hit our first breakpoint, press ‘c’ twice to continue till our third breakpoint and closely look at the return address.

The return address is what we have used in our payload which confirms that our identified offset was indeed correct. But why we are using print@plt as return address and not 0x08048627 where we set our first breakpoint? We can also do that but then we will lose control of our ROP chain and it is a better idea that the reader should try and identify the reason himself (did i just assumed your ……?) themselves.

We will modify our code once again to pass a new argument to printf function.

This should work, lets run the binary outside of gdb with this payload now but first lets disable ASLR on our machine so that our gdb results matches and then run the binary

This matches our earlier results, we have a successful memory leak. Now its about time we do some maths and tweak our exploit in order to gain a successful shell prompt.

The Bogus Shell

We will use the last address in the memory leak and the above ‘/bin/sh‘ address for our calculations.

Why we are calculating the address for ‘/bin/sh’ ? Simple, we will pass this pointer to our system function to spawn a shell ;). But our application crashed after sending first payload right? Lets trace the crash

The crash happened while accessing invalid address 0x42424242 which is ‘BBBB‘ in our payload right between printf_plt and printf_got which means that our return pointer needs to be updated to something which can restart our program once again to accept more input data. In order to do start, we will use a bash script to start out program as a listening server with strace so that we can also debug it a bit.

And we need to modify our payload a bit with all the gathered information.

Lets run our exploit and see the results

Superb, lets modify our exploit and create a second payload to spawn a system shell.

Excellent, we have our shell and we can do anything.

system(*arg)

The problem above is different libc.so.6 being supplied with various distros. They are updated frequently to improve and add functionality due to which the offsets doesn’t remains the same. We can try to scan host to identify OS version and then try with different libc.so.6 versions by calculating offsets but there is another way out which is more reliable. We can modify our exploit to accept an input at our arbitrary memory location which we can reference later on to system() without needing to worry about calculating offsets. We will modify our second payload to use fgets() to copy our input buffer to our decided memory location but in order to do that, we need need to modify our first payload to leak the file pointer which is a required argument for fgets(). Lets debug our program once again

We can see that fgets() accepts 3 arugments i.e pointer to memory location where our input will be stored, size of input and pointer to a FILE object that identifies the stream where characters are read from. We need this pointer and locating it from the stack is going to be a hassle. Instead we find it in our application itself which is updated at runtime by dynamic linker. So here is our final exploit using all the information we have

Enable ASLR again and test the final exploit.

Look, no leaky!

In both methods above, we relied on leaking the memory information first and then working out our payload by doing some simple maths or by overwriting the data. Its all good, although we can be a bit more creative with our imagination and try to make a single payload to achieve the same. This time we will be using gadgets (nothing but simple instructions followed by return) to achieve the same results and spawn a shell. So here we go one more time, run the binary inside gdb

We will use pop2ret gadget and another gadget located at 0x804866f+1 = 0x8048670 which is mov [edi], ebp; ret. This instruction will move data from the EBP register into pointer located at EDI register. We know the location where we can write the data, so lets craft our exploit.

Lets run our exploit and see whether we can spawn a shell and retrieve our flag.

—–END—–

7 Replies to “ROP ROP #4”

    1. We know that the address space in the ELF file is loaded in memory at the same address space, so we can reference any address from binary in our payload. We can use stack/heap address if we are able to leak address (due to ASLR) and calculate the offsets. The address 0x804a060 contains a file stream pointer required by fgets “char *fgets(char *str, int n, FILE *stream)” which we need to leak to later use in our payload so that fgets can accept the next input at the desired memory address

    1. Think I figured it out from disassembling the binary.

      stdin@@GLIBC_2_0 is what is the file pointer argument to the original call to fgets.
      Your post did explain how to find that dynamically in gdb too, didn’t make sense until I reread that.

      0x804a028 is the start of the data section so should be good to write to in this example as we aren’t overwriting anything.

Leave a Reply

Your email address will not be published. Required fields are marked *