Training session 16: Buffer Overflows
Learn the basic concepts that used to Exploit Buffer Overflows
For some reason, whenever I have gone into a irc channel and talked about buffer overflows, the newbies in the channel seem to go quiet, and ask next to no questions. Although if I was to bring up a topic such as telnet hijacking I would be bombarded with questions asking about it. This leads me to believe very few people know how a buffer overflow works, or even what it is, or why it is of any use. This lesson is made to give you a very basic insight into how buffer overflows work and why they are of any use.
Buffer overflows are generally an unintended way to control the flow of a program. An example of this is if you use a 'bug' in a game to give you more shields than was initially intended and you endup with near invincibility. A buffer Overflow is much the same in the way you control the program at a much deeper level.
The name buffer overflow is just as it is written, you are attempting to overflow a buffer with more data than it can hold. This may seem rather pointless until you understand what this will do. The following is how the stack is structured:
The stack is much like a stack of plates, you put one on top and you cant take the bottom plate without first taking the rest, so for example:
Items to be placed on the stack:
The items are moved onto the stack:
So if you wanted to get to Item 2, you would first have to remove Item 3 and you would just be left with Item 1 on the stack. The reason that the computer handles data like this is because it does not use 'variables' like most languages do, it relies on 'pushing' and 'popping' items on and off the stack, or placing them in specified memory addresses.
Let us look at how this is done when for example we call the win32 api GetFileTime:
The GetFileTime function retrieves the date and time that a file was created, last accessed, and last modified.
HANDLE hFile, // identifies the file
LPFILETIME lpCreationTime, // address of creation time
LPFILETIME lpLastAccessTime, // address of last access time
LPFILETIME lpLastWriteTime // address of last write time
Ok, so what does this all mean? Well he BOOL means that the function returns whether the command was a success or not, the rest are just paramaters of the command, the command might be executed as follows:
Success = GetFileTime("c:\config.sys",8008F6C,8004A14,8005DD3)
This would endup on the stack like this:
And this would placed on the stack as follows:
By now you should either be really confused, or have a good idea of what the stack is and how it works. Dont concern yourself with the data, just how the stack basically works.
So exactly how does this help us to control our program? Well as i said before, everything is pushed to the stack, including the return address. After a function has been called, the return address tells the computer where it was called from, and returns to that place. Let us take another look at the stack:
So when we put the buffer on the stack, it is placed BEFORE our return address, so if our buffer were say 8 bytes, our SFP is 4 bytes, our return address would be 12 bytes from the start of the stack. If we were to place 16 bytes of code into our buffer, instead of the allocated 8 then we would overwrite the return address and our program would probably crash. Always remember a buffer's size is always a multiple of 4. By now you should be getting a good idea of how we may be able to exploit this problem.
The following is a simple program made in C that has the ability to be exploited:
main(int argc, char **argv)
Now let us first realise that this program has been compiled, and that the program has SUID permissions so it is if we were to spawn a shell, we would gain the privledges of the owner, which for this example will be root. The program basically takes whatever arguement it was sent, and copies it into a 10 byte buffer. the_shell() is never actually called, but the code is already existent in it to spawn our shell. If the program was compiled to 'system' and we were to execute 'system aaaaaaaaaaaaaaaaaaaa' it would crash the program and cause a dump. But if we were to instead of using 20 a's, we only used 10 a's nothing would happen and the program wouldnt crash. So our aim to exploit this would be to push enough data onto the stack to overwrite our return address to call our shell. So basically we would have to send 12 bytes to fill our buffer (remember that the buffer is a multiple of 4), 4 bytes to overwrite our SFP and then 4 bytes containing the address of the_shell() so we can spawn a shell. Our stack would endup looking something like the following:
[aaaaaaaaaaaa] 12 bytes
[aaaa] 4 bytes
[address of the_shell()] 4 bytes
If we didnt exploit the code it would look more normal and something like:
[test_data] 12 bytes
[SFP] 4 bytes
[Return Address] 4 bytes
The a's represent random data as it has little or no effect on the outcome, but is required to fill the buffer until we reach our required address.
When the compiled program is dumped it looks like this:
assembler code for function main:
Dump of : push %ebp
0x8048501 : mov %esp,%ebp
0x8048503 : sub $0x18,%esp
0x8048506 : cmpl $0x1,0x8(%ebp)
0x804850a : jg 0x804851c
0x804850c : add $0xfffffff4,%esp
0x804850f : push $0x0
0x8048511 : call 0x80483e4
0x8048516 : add $0x10,%esp
0x8048519 : lea 0x0(%esi),%esi
0x804851c : add $0xfffffff8,%esp
0x804851f : mov 0xc(%ebp),%eax
0x8048522 : add $0x4,%eax
0x8048525 : mov (%eax),%edx
0x8048527 : push %edx
0x8048528 : lea 0xfffffff4(%ebp),%eax
0x804852b : push %eax
0x804852c : call 0x80483b4
0x8048531 : add $0x10,%esp
0x8048534 : leave
0x8048535 : ret
0x8048536 : mov %esi,%esi
End of assembler dump.
Dump of : push %ebp
0x8048539 : mov %esp,%ebp
0x804853b : sub $0x8,%esp
0x804853e : add $0xfffffff4,%esp
0x8048541 : push $0x8048587
0x8048546 : call 0x80483c4
0x804854b : add $0x10,%esp
0x804854e : leave
0x804854f : ret
End of assembler dump.
By dumping the code of the program we find that the address of the_shell() is 0x8048538, with the 0x obviously standing for hexidecimal. Now if we were to push this address onto our return address, the program would happily return to the_shell(), and spawn a nice shell for us. To exploit the stack we would want it to look something similar to the following with '\x' meaning hexidecimal:
[\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61] 12 bytes
[\x61\x61\x61\x61] 4 bytes
[\x38\x85\x04\x08] 4 bytes
I thought our memory address was 0x8048538 not, 0x38850408? Well this is because we have to push each byte onto the stack seperately, so 08 04 85 38 would be pushed onto the stack as 38 85 04 08. Now if our program was to be executed with the arguement '\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x61\x38\x85\x04\x08' we would spawn a shell. Although a C or Perl program would have to be used to push the data onto the stack as data such as \x04 cannot be typed correctly on the keyboard and is interpretted by the shell as being something diferent.
In this situation we are lucky the code to spawn a shell is already in the program, in others you may have to place the shell code inside our blanketting data, and return our program to the start of the buffer, in this case the buffer may look something similar to the following:
[address of shell code]
We would have to return the call to the exact start of the buffer. Since we cannot necessarily tell what the address of 'shell code' is going to be, we have to put some guess work into finding the code, but an example like the following may speed up the process if the buffer is large:
[around 1000 \x90's][shell code]
[rough address of the start of the 1000 \x90's]
You may ask why we would do it like this? Well in assembly language a hex value of 90 stands for 'NOP' which is 'No Operation', meaning if we were to return our function to anywhere inside the large buffer of NOP's, the code would be followed until it reaches our shell code, and then our shell code would happily be executed.
I hope that by now you should have a basic concept of how the stack works, how a buffer is made, how it can be exploited, and possibly some general tecniques used to exploit it. This is only theory and therefore is not indepth or totally correct, but is only made to convey the basic idea behind buffer overflows. Buffer overflows are also possibly the most used exploit used on unix based systems these days to gain elevated privledges.