Buffer overflow and format string attacks: the basics
I have come across various analysts who want to learn how buffer overflow and format string attacks actually occur. Articles we read on the web are usually at a very advanced level with a start by dancing down the stack. However, this article series will serve as the basic building blocks for those advanced articles. This article is Part One of the series, and will focus on Buffer Overflow attacks only. Not only are we going to cover how buffer overflow occurs but also how to defend against these attacks.
What is buffer overflow?
Buffer overflow attacks are considered to be the most insidious attacks in Information Security. Buffer overflow attacks are analogous to the problem of water in a bucket. For example, when more water is added than a bucket can hold, water overflows and spills. It is the same case with buffer overflow, which occurs when more data is added than a variable can hold. It will then move out into the adjacent memory locations. Now you must be asking, "So what?" Only data is being spilled, after all. Now imagine that someone has issued a command and the data is spilling on it. Before going a step deeper in the program, let's refresh our concept of how a program runs on the computer.
What should you learn next?
When a program runs, the CPU fetches instructions from memory one by one. How does the CPU fetch the next instruction? It does so through the use of an Instruction Pointer that tells the CPU where to grab the next instruction from memory. With each fetch, this Instruction Pointer is incremented and new memory location is fetched. Whenever the CPU encounters a branch or a jump statement, the IP changes its value to a completely new memory location and then it will begin incrementing from the new memory location.
Now let's consider a simple program like the one below and its stack representation
"" main()
{
Buffer_overflow_function();
printf("testedn");
}
Buffer_overflow_function()
{
Local variable 1[10];
Local variable 2[10];
printf("Inside Buffer Overflow function");
return;
}Stack Representation
Below is the stack representation of a normal stack and a buffer-overflowed stack.
Normal Stack
Before going into the Buffer Overflow stack, few important points about above stack are:
Buffer Overflow Stack
In this case what happens is that the user-supplied input is not properly handled by the function and under the buffer flow. The attacker usually sends machine-specific bytecode, such as /bin/sh in this case, and a new address forf the address pointer. Where will this new pointer point to? You guessed right. This new pointer will point to the new address where attacker code will be executed. But as you can see, this attack has to be very precise to overwrite the variables and return pointer. What are the ways in which attacker does exploit buffer? We will see in the next section.
Now as we have seen, to overwrite return pointer and let the program execute malicious code, the attacker has to precisely pass input. But how does an attacker do it? There are some steps in which the attacker finds ands exploit the buffer overflow vulnerability.
- The very first step to exploit the buffer overflow vulnerability is to discover it. If the attacker has the binary executable they can search for weak function calls. Remember that the buffer overflow attack gets started with the input provided by user and any other function which is used to copy. Attackers will usually look out for functions such as:
- strcpy
- strncpy
- strcat
- sprint
- scanf
- fgets
- gets
- getws
- memcpy
-
memmove
All these functions are used for moving data between memory locations and are usually mishandled by the developer.
- After the attacker locates these weak functions, they try to see what amount of input is needed to overwrite IP or return pointer. To do this, the attacker passes some amount of similar input like a series of 'A' into the input field. They will then check which 'A' has overwritten the return pointer.
- After this step, the attacker will push the exploit code into memory. Usually the attacker will try to invoke a shell and execute arbitrary commands. The exploit code will run with the program permissions. Because of this, exploiting an SUID root program is very useful since it will run with root privileges. On Unix systems, attackers normally target programs with UID 0 and in Windows, attackers normally target programs that run as SYSTEM.
- Attacker should also make sure that the exploit code will fit into the buffer and does not contain characters that are filtered out.
So the above steps describe how buffer overflow actually occurs. Now we should explore how to defend against Buffer Overflow attacks
- Check input size wherever applicable, and truncate if it's too big
-
During the build, make sure that the non-executable system stack is implemented. Stacks are used to store function call arguments, return parameters, local variables but not executable code. So if we can implement a stack which is non-executable stack, a majority of buffer overflow attacks can be controlled. To implement this feature, windows even have a feature called "Data Execution Prevention" which is used to make stack non-executable. DEP settings are available at Systems >Advanced>Performance >Settings>DEP.
FREE role-guided training plans
Get 12 cybersecurity training plans — one for each of the most common roles requested by employers. - For compile time protection, some compilers calculate the key hash of return pointer when the RP is being pushed onto the stack. This keyed hash value is known as the canary. Then the canary and RP is pushed onto the stack and when the function needs to return, system checks whether the RP and canary has the same value. If they do not, program never return from function (which means that the evil code cannot be executed) and the program will end gracefully.
- Apply patches whenever released by vendor and wherever applicable.
We have learned the basics about buffer flow. In the next part of the series, we will learn how format string attacks happen.