Steps to Exploitation
Finding the Vulnerability
This is probably one of the hardest steps. There are various ways to find vulnerabilities, and none of them are quick and easy. This takes time normally on the order of months. But lets say you're doing this on your own so you write your own bad piece of code:
1 #include <stdio.h>
2 #include <string.h>
3
4 int function(char **argv) {
5 char buf[8];
6
7 strcpy(buf, argv[1]);
8
9 return 0;
10 }
11
12 int main(int argc, char **argv) {
13 return function(argv);
14 }
So here the vulnerability is copying an arbitrary string from the command-line arguments. Compile the code with stack mitigations turned off.
gcc overflow.c -o overflow -z execstack
Triggering the Vulnerability
Now you need to trigger the vulnerability. You know what it is. The strcpy
blindly copies whatever string is contained in argv[1]
. The buffer that the argument is copied into can only contain 8 bytes. Anything past this will overflow the stack. See this answer about how stack overflows work. So you can trigger the vulnerability by running it like so:
./overflow 01234567ABCD1234
Now if you read the link on how stack overflows work you'll know that you've overwritten the saved stack frame pointer with 0x41424344
("ABCD"
). And you've overwritten the return address of the function with 0x31323334
("1234"
). This is great right? You can now control the return address of a function which means you can now tell it where in the program to start executing.
This seems easy, but in reality triggering a vulnerability can be frustrating. Determining the exact path can be straight forward depending on the resources at hand. But nudging and poking execution to take the path that leads to a vulnerability to trigger it can be very difficult.
Exploiting the Vulnerability
You've triggered the vulnerability and the code segfaults. Now what? Now you need to write your shellcode to do something useful. This is purely up to you. Writing shellcode is an art in itself, but exploitation comes down to being able to write your shellcode into memory, and then executing it.
Back to our example. We're overwriting the stack. So keep on writing! After the return address is overwritten write your shellcode onto the stack. So now you have something like:
./overflow 01234567ABCD1234<SHELLCODE>
Remember how memory is oriented, and make sure the shellcode is aligned to the stack. You know the size of your shellcode. We're working in an example where program memory is not randomized so we know the address that we overwrite (You can find this by running your program in gdb
and witnessing the address for yourself). With the address and the size you can calculate the address at which your shellcode starts. Rather than using 0x31323334
use 0x<address of shellcode>
.
Now you have something like: ./overflow 01234567ABCD<address of shellcode><SHELLCODE>
The function will return to your shellcode, and begin execution.
Reality
That was fun, but in reality there are a lot of things you need to take into account.
Stack Execution
The program was compiled so that the stack was executable. In reality it might not be if DEP is turned on. This is where return oriented programming comes into play. ROPs help execute portions of code at a time in executable portions of memory. Generally a ROP's job is execute instructions that will mark the area of memory the shellcode is in to executable, and then jump to the shellcode.
ASLR
ASLR will randomize the address that a program is executed or a library is loaded. You can see how this works by running the piece of code in the previous link. You'll be able to see how each time a program is run the address changes. This means you can't rely on that pre-calculated address. Generally you'll need a separate vulnerability to leak an address or get the address of a library in order to calculate where you are running in memory.
Stack Overflow
In a stack overflow you just blew away the stack!! You possibly overwrote other return addresses, and key pieces of memory that the program needs to run. Yes the shellcode was run, but what do you do when your shellcode is finished? Cleaning up the stack correctly, or closing the program gracefully is another challenge. Or maybe you just don't care, because now you full access to their box anyway. Just something to keep in mind.
Lots of ways to skin a cat
Gaining execution can occur in lots of different ways. This post only goes over the one example. As you begin to grasp these concepts you can start to get into other techniques for gaining execution after triggering a vulnerability. But that would be well outside the scope of this answer.
I'm sure I'm missing something in all of this. At least this will give you an idea of how difficult exploitation is, and how much you need to take into consideration. Hopefully you can use this as a base, and perhaps later if you have more questions you can ask a more specific question.