Welcome finally, to a tutorial on buffer overflows! At last we have reached an exciting part of this series where I will dedicate the entire article on explaining and exploiting the notorious vulnerability. Grab some popcorn, sit back and enjoy the show.
A buffer overflow occurs when a buffer's contents spill into the data of the memory around it and essentially overwriting the affected addresses. You can imagine yourself pouring a glass of water. When the glass cannot hold any more, it will overflow and spill everywhere onto the table and floor. From tutorial 0x08 on memory, we observed the data in the char array and the other data around it. If we allocated more items into the array, eventually, the data of the array will begin to overwrite data in front of it.
Remember the previous tutorial on user input? Let's see what would happen if we gave the first example more than just 20 characters...?
As we can see clearly, the string array has been allocated 21 bytes of space for 20 characters and 1 extra for the null terminator. Let's bring up terminal, run the program and try to give it more data than it can hold...
What the heck happened? It gave me a segmentation fault! Not just that, it tore up my num variable too! Let's run a memory analysis for further examination.
Wow! Look at all of that damage! The hexadecimal "0x41" represents the character "A" and look at all of the data around it! It has been completely overrun and it is absolute chaos! I located our num variable and look what happened to it! Utterly destroyed... What a disaster! Yet somehow satisfying to look at... So much power... Such evil... Muahahahahaha! * cough *... Pardon me... Anyway! Let's see what happens when we continue running the program...
So there we get our seg fault because what happened was that when main tried to execute its return, it returned into an unknown memory location and said, "Where am I? There's nothing here!" The return location is actually stored on the stack but when we overflowed our buffer, it had overwritten it. A read access error occurred in the memory location 0x41414141 which caused the program to crash. I will clarify the exact details of the return in another tutorial. For now, just understand that the return failed.
Let's try it on the second example with the gets function.
Yep! Same result, of course. I will not go into the memory analysis for this example because it is basically the same thing. If you wish to do it yourselves, please, be my guest!
Okay, I'll admit that I did do some editing to cut a part out when going over the gets function. Here it is, see it, know it, understand it and keep it engraved in your brain:
The reason I showed this function is because I wanted you to know the consequences of unsafe functions like this. Never EVER under ANY circumstance should you use the gets function to read strings. If you do, I will find you and I will hunt you down!
The solution to gets? It's a function called fgets. Whenever you are reading a string for input, you should always use fgets because one of its required parameters is that you must specify a length limit. When that limit is reached, it will ignore everything else that is trying to get in.
Alright, I'm going to write up a piece of code and I will demonstrate an example of what could happen if buffer overflows are exploited. Of course, this code won't be sophisticated - it should be simple enough for you guys to understand and I will explain the code, so don't worry!
Note: The following is not the only thing you can do with buffer overflows as you can see with a few articles which already exist that detail into other methods of exploitation. I will cover those methods in other tutorials once we have gone over the necessary content.
What we have here is a sort of login system. There is a set password and to be able to successfully spawn the shell, the the user must type in the password and have it match. When this program is run, main calls the function login (you can see the function defined). It prompts for an input from the user with scanf. Once the user input has been placed into the userInput array, it will proceed to compare it with the password using the strcmp function. If the two match, it will set the authorized variable to 1, otherwise it will print a string to inform the user that the password failed to match. The login function then returns the value of authorized back into the main function. Main will pick up where it left off at where it called login and compare the returned value to 1. If the condition is satisfied, it will spawn a shell.
Can you figure out a way to run the shell without knowing the password?
The following has no tricks or sorcery to it, no "hacking tool" was used and no script kiddie was harmed in the making. This is the real deal, a hands-on and front row seat experience on basic buffer overflow exploitation.
We first have to know the details about the program we are running, so, we will use GNU's GDB to help us find out where data lies in memory. We begin by setting breakpoints so that we can pause and assess the situation while the program is running.
We have set two breakpoints, one before it requests for an input and one after reading input. We want to analyze the data on the stack so that we know where the userInput and authorized variables lie in relation to each other.
We let the program run and it stops at the first breakpoint.
Here, we print out the stack and attempt to find the variables. We can find the address of authorized (p &authorized) and userInput (p &userInput). Fantastic! The authorized variable is located after the userInput address! That means we can overflow the array and "inject" whatever we want into the authorized variable. Not only that but it's located directly adjacent to it! Sweet! Let's continue!
Okay, so we've calculated the string length we require to fill the data in the userInput array to reach authorized (51). We are then presented to give an input to the program.
We type in whatever 51 characters we want followed by 0x01 which represents the character for the decimal 1. The problem with just entering the number 1 instead is because it will be read as a character and that would give it the value 49, which is incorrect! We need it to be 1 otherwise the condition to spawn the shell would not be satisfied. Printing out the stack after giving the input, we can see that we have successfully "injected" a decimal 1 into the target variable. Continuing the program, we see that the string we gave it was obviously incorrect however, we still satisfied the condition! As a result, we have forced execution to spawn the shell!
50/\/\3 1337 57uff h3r3 0r \/\/h473\/3r...
There is a way to prevent this from happening. You can use fgets...
Okay, if you really need to use scanf, you can set a limit to how many characters to read with the string format specifier. All you need is to type in the maximum length in the form of %ns where n is the length (don't forget to account for the null terminator!). For example, scanf (%50s, userInput);
And there you have it. It's not really that nice because you cannot put a variable in place of the max length, unlike fgets, so every time you change the size of your buffer, you will need to manually change your scanf's format specifier.
Take care of your input! Set a maximum size so your buffers don't explode and leak everywhere! That's pretty much it... for now... Hope you've enjoyed this presentation!
Learn How To Code with Null Byte's Beginner's Python Course.