Assignment: ROP

Changelog:

Purpose

In this assignment you will demonstrate a return-oriented programming attack.

Resources

  1. The slides on return-oriented programming. (27 Mar and 29 Mar).

  2. The “definitive” article on return-oriented programming, especially section 4.

Assignment details

  1. Like the buffer overflow assignment, we will test your solution to this assignment in the 64-bit Ubuntu 16.04 LTS environment of your VM.

  2. You will be attacking an supplied binary here. This binary has the same source code as the one used for the buffer overflow assignment. But it was compiled and linked very differently. Specifically, the mitigation of making the stack not executable is disabled and the binary is statically linked. Statically linking the binary ensures that it will be easy to find ROP “gadgets” in the executable itself, rather than relying on them being in the loaded C library.

    This will also allow us to avoid disabling ASLR. Although the addresses of the stack and any dynamically allocated memory will be randomized, the address of the code loaded for the executable will not be. This means that as long as we do not require any hard-coded stack addresses in our ROP chain, we can construct an ROP chain that does not depend on this randomization.

    As a further mitigation, a binary creator could have defeated this attack by building the program to randomzie the address of the executable’s code itself. On Linux with GCC, this can be done with the -pie -fPIE (position-independent executable) option. However, this can produce slightly slower executables, since some places where addresses would be hardcoded need to be replaced by sequences of code that compute it based on the current program counter.

  3. To help you construct your ROP attack, you will use the tool ROPgadget. You can download the tool here. Follow the installation instructions in “Install” section of the README.md.

    As part of installing this, you may need to install pip, which you should be able to do with sudo apt install python-setuptools python-pip.

    Note that ROPgadget requires Python 2 and will not work with Python 3.

  4. The ultimate goal of your attack will be the same: to cause the program to print out “I recommend you get a grade of A on this assignment.” But to do this, probably the easiest way is to cause the program to execute a shell. It will be easiest to use a shell (command line) because the tool you are using will construct the code to start a shell for you. It is also possible, but to encode writing the entire string in an ROP attack more directly, but that will take much more effort. Once the shell executes, you can run the echo command in the shell to produce the desired output. For example, the commands:

     echo "Thank you, YOUR NAME."
     echo "I recommend you get a grade of A on this assignment."
    
  5. Then, use ROPgadget to find an ROP chain to that will run a shell. ROPgadget does this automatically with the --ropchain option:

     python2 ./ROPgadget-5.4/ROPgadget.py --binary ./dumbledore_rop.exe --ropchain
    

    will output a python program that produces an ROP chain that is equivalent to the C code

     execve("/bin//sh", NULL, NULL);
    

    after listing all the “gadgets” found in the program (in Intel assembly syntax). /bin//sh is used instead of /bin/sh because it exactly 8 bytes long, allowing it to be loaded into a register with pop. This C code will replace the current program with the program /bin//sh, which is a shell (command line).

    ROPgadget also takes other options, notably:

    • You can specify bytes that must not appear in the found gadget’s addresses, including those gadgets that it uses in the python program output by --ropchain
    • You can search for gadgets that match specific patterns. (See the description of options from ROPgadget.py --help for examples.)
  6. The generated python code will have a space for “padding”. You will need to modify this to correctly place the ROP chain on the stack.

  7. To execute a command in the shell, you will need to modify the generated python code to output a shell command after the exploit string. You will need additional padding before this shell command to deal with gets’s buffering.

    stdio.h functions like gets default to reading a lot of extra data from a file to avoid making many system calls. This data is buffered for future gets, getchar, etc. calls (making them faster). But when our exploit code starts a shell with execve, these buffers are discarded.

    To avoid this problem, you should have some unused bytes between the attack string and the shell commands, such as several thousand newlines. (Note that the python expression "\n" * 10000 will produce a string containing 10000 newlines.) This will act similarly to a NOP sled.

    After this extra space, output the commands to run as in step 4.

  8. Modify the python program generated by ROPgadget.py to contain the appropriate padding and shell commands to output the text recommending a grade of A.

  9. If you are successful, then you should get output like the following:

     python chain.py >attack.data
     ./dumbledore_rop.exe <attack.data
     Thank you, Charles Reiss.
     I recommend that you get a grade of A on this assignment.
    

Submission

  1. Submit your chain.py file and its output as attack.data.

Hints

  1. You need to make sure the ROP chain is positioned properly over the return address. If it is not, you are likely to get a segfault, or the normal output.

    Positioning the beginning of the ROP chain should be exactly like positioning the overwritten return address in OVER (though the program may be slightly different). You can either:

    • examine the objdump output to determine what’s on the stack in the vulnerable function, and therefore the distance between the return address and the beginning of the buffer; or
    • try inputting different amounts of data and see what length is required to get a segfault. This should indicate where the return address starts relative to the buffer.
  2. If your ROP chain causes the program to not produce any output, this is probably because you need to add padding between your exploit and the shell commands. You can check if a shell is actually running by seeing if the debugger gives a message like “process 13977 is executing new program: /bin/dash”.

    An example of padding is a string with a lot of newlines. This is needed because gets will read part of the next lines into an internal buffer to speed up reading those future lines. This buffer will not be shared with the executed shell, as described in item 7 of the instructions.

  3. If you get an error about “[-] Error - Can’t find a writable section”, then you are probably using Python 3 instead of Python 2 to run ROPgadget.py. It is a Python 2 program that is silently incompatible with Python 3.