Assignment: ROP
Contents
Changelog:
- 26 March 2021: correct typo in link to ROPgadget-6.5.tar
- 26 March 2021: mention instructions for running gdb with ASLR disabled
- 26 March 2021: correct “even with ASLR enabled” to “even with ASLR disabled”
- 29 March 2021: add note about installing python3-pip; PATH
- 2 April 2021: advising always using
setarch
to run the debugger just in case addresses are different rather than making it seem more optional - 7 April 2021: include “that” in command in hint below to be consistent with “Your Task” section (but we will accept solutions that omit the “that”)
Your Task
-
Download the target executable program dumbledore-rop.exe, which is the same program that we used for the OVER assignment, compiled and linked differently. Your job is to exploit a buffer overflow using an ROP chain, most likely with assistance from the tool ROPgadget (linked below) to construct it.
Your goal is to construct a program input such that the program’s output ends with:
Thank you, YOUR NAME. I recommend that you get a grade of A on this assignment.
If your attack program is in a file called
attack.py3
orattack.py
, your exploit must work when run as follows, which is similar to OVER, but without the dependency on libc (dumbledore-rop.exe is statically linked, so it does not depend on libc):-
from the directory containing
dumbledore-rop.exe
, running the shell command:python3 attack.py3 > attack.txt setarch -RL env - ./dumbledore-rop.exe < attack.txt
or a similar program named
attack.c
orattack.cc
orattack.py2
(which we will run differently, as in the OVER assignment). -
Tools
- For this assignmentusing the tool ROPgadget, available here. Follow the installation instructions in “Install” section of the README.md.
Some notes about running/installing it:
-
You might want to install
pip
to do this (depending which version of the install instructions you use). On our VM, you could do this withsudo apt install python3-pip
and then usepip3
insteadpip
. -
If you are using our VM,
pip
will, by default, place executables in/home/student/.local/bin
; this isn’t in the default search path for executables on our VM (or many default Linux environemnts), but you can change this with a command likePATH=$PATH:$HOME/.local/bin
-
The
ROPgadget.py
file that comes with ROPgadget starts with#!/usr/bin/env python
to say it should be executed with a command calledpython
; this might work better as#!/usr/bin/env python3
on our VM.
Addt’l Resources
-
The slides on return-oriented programming.
-
The “definitive” article on return-oriented programming, especially section 4.
Hints
Using ROPgadget
-
Given a binary foo.exe, running
ROPgadget.py --binary foo.exe
will output a list of gadgets discovered in the executable.
ROPgadget.py also has many additional options which you can find by running
ROPgadget.py --help
The ROPchain option
-
ROPgadget has an
--ropchain
option that attempts to construct a generic ROP chain that executes a shell (that is, a program that accepts commands to run, like what you get when you open a terminal on Linux). So runningROPgadget.py --binary foo.exe --ropchain
will output the list of gadgets, followed by a Python 2 script given the gadgets found in foo.exe.
(In a Unix-like shell, you can use something like
some_command > output.txt
to run the command
some_command
, redirecting its output tooutput.txt
) -
Although you could construct a custom ROP chain that outputs the text you want, it would probably be easier to use this generic ROP chain, and then provide a shell command that will output the text you desire.
In the “executing a command with a shell” section below, we describe how to construct suitable input for the shell that this exploit would start.
-
The Python 2 script that ROPgadget constructs is incomplete; it places the chain in a variable
p
, but does not:- output that chain
- add padding such that the beginning of the chain will overwrite a return address
- add shell commands after the chain so some useful command will run afterwards
- add padding before the shell command to deal with stdio buffering (see hint below)
The executable is offset
-
The dumbledore-rop.exe we supply is position-independent, so it can be loaded at a different locations. (If we did not disable address space layout randomization (ASLR), this would allow the executable to fully take advantage of ASLR.)
By default, ROPgadget.py assuems that executables are loaded at the addresses encoded in them (the one shown by
objdump
), which isn’t the same as the address that’s chosen by the OS, even with ASLR disabled.You can change this default by giving
ROPgadget
an offset with the--offset
option:ROPgadget.py --binary foo.exe --offset 0x123456 ...
(where
0x123456
is the difference between the addresses shown inobjdump
and the actual address to which the executable is loaded). -
If the you run the debugger with the same settings as the executable (such as by running
gdb
withsetarch x86_64 -RL gdb
), you should get addresses in the debugger which are consistent with the addresses from testing.
Forbidden bytes
- It’s possible that some gadgets that ROPgadget.py finds will have an address which
when written out as bytes contain
0x10
, which is the newline character. Since this causes the program to stop reading input, you would not be able to input these addresses. You can have ROPgadget.py ignore these gadgets by using the--badbytes
option
Positioning the ROP chain
-
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 is 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.
- examine the
Executing a command with a shell
Padding before shell comamnd to handle buffers
-
To execute a command in the shell that ROPgadget.py’s exploit code would start, you will need to output a shell command after the exploit string. When you do this, 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 calls to the OS to get data. This data is buffered for futuregets
,getchar
, etc. calls (making them faster). But when the exploit code generated by ROPgadget.py starts the shell, these buffers are discarded. -
To work around stdio buffering, you can 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.
A suitable shell command
-
The echo command is useful for outputting text of your choice:
echo "Thank you, YOUR NAME." echo "I recommend that you get a grade of A on this assignment."
(Originally the command above missed the word that, which wasn’t consistent with the “Your Task” section. We’ll accept either output.)
No output?
-
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”.
See the “padding before shell command to handle buffers” section above.