Initial disclaimer: please check the link below, as it will be necessary when following along the pdf.
Hi, again! Ready for some more low-level code goodness?
Today we'll take a look at some very simple, yet purposefully flawed C programs in order to learn a bit more about buffer overflows, grasping control of the return address and disrupting a program's flow.
In the first example, we'll see that the program will result in a segmentation fault, and understand why that's happening exactly, by looking at the disassembled code under the GDB debugger. We'll then talk a little about registers, the return address, its importance, and how to grab control of it.
In the second example, we'll take a look at the basics, which will let us finally take advantage of the return address through a buffer overflow.
But, without much ado, let's jump to our first example. Remember that these two first programs are directly taken from Smashing The Stack For Fun And Profit, which you can find here (revised edition! - please follow this link to disable defenses. Alternatively check this. If you don't do this, you won't be able to take advantage of these methods).
This program is creating a char array named large_string which can take up to 256 characters, and then it's filling large_string with A's. After that's done, the program is calling a function named function, using large_string as the argument. The problem becomes immediately obvious since, as we can see, inside that function, we're filling a char array entitled buffer with our A's. But our buffer can only take in 16 characters. Ergo, we have our buffer overflow.
Function was created and its local variable buffer can only hold so many of our A's. But, since we're using strcpy, which has no control for how many values we can enter, we'll just keep on writing A's until we reach the null character (at the end of large_string).
But let's open our debugger and actually see what's happening here.
So, we're looking at main(). Can you see our loop? We're moving 'A' into eax and advancing the counter at ebp-12. A is the ASCII representation of number 41:
0x000011ea <+50>: mov BYTE PTR [eax],0x41
Looking at the memory addresses, it's obvious what happened: these locations were filled with 0x41414141 or, in plain text, AAAA.
It's important to note that the A's are being written from higher memory positions to lower ones. This is crucial because one of the last things overwritten is the return address, which will cause our segmentation fault.
As we move along, at a certain point, the return address will also be filled with A's, and at that moment we won't have a valid return address any longer. As a result, our program will suffer a segmentation fault.
And that's it. We're going nowhere fast. This program has just died on our hands. If we were trying to crack this program, we would have wanted, instead, to take control of the return address stored next to EBP. We'd use that value and point towards some other function we wanted, for example, thus altering the program's flow in our favor.
Yes, we're slowly creeping towards true shellcode. We'll get there eventually, don't worry.
But before we do that, we might as well talk about other interesting tidbits that can prove helpful when using a debugger and watching our shellcode or buffer overflow in action.
I've already shown quite a few pics, so I won't give you another one, but here's the very first function that appears in our "Smashing the Stack" doc. It's pretty simple, but it hopefully shines a light on the function we've been analyzing so far:
void function(int a, int b, int c) {
char buffer1[5];
char buffer2[10];
}
void main() {
function(1,2,3);
}
If we look at the memory locations, as we did before with the other program, we'll see:
I have put in bold important addresses and their contents. In order, from left to right, going down...
ESP
- Points to the current top of the stack.
At 0xffffd09c
(the lowest memory address in this snapshot)- ESP is showing the exact spot in memory where new data are pushed onto the stack
Saved EBP
- At 0xffffd0bc
- The base pointer of the caller function
- This marks the base of the previous stack frame before the current function was called
Return Address
- At 0xffffd0c0
- This is the address the program will jump back to after the current function completes
- overwriting this location with a malicious address can cause the program to "return" to an arbitrary memory location
Variables
- Just below the saved EBP and return address are the local variables and parameters
- At 0xffffd0c4 (
0x00000001
), 0xffffd0c8 (0x00000002
) and 0xffffd0cc (0x00000003)
Buffer
- Just below these local variables we can see the space that has been assigned to our buffer1 and buffer2 local variables
- It's not 5+10 bytes in size. Instead, because of padding and alignment, it will be 8+12 bytes in size, for a total of 20 bytes.
Next up: We'll learn how to control the return address and force the program to do our bidding. This will set the stage for mastering the art of shellcoding!
No comments:
Post a Comment