Common Bugs
Previous Reading:
Stack Frames, Calling a Function, and the Function Prologue
The Heap (NOT WRITTEN YET)
Key Terminology:
Integer Overflow
Integer Underflow
Off By One
Use After Free
Format String Vulnerability
Common Bugs:
Vulnerabilities arise when a bug occurs that leaves some opportunity that can be leveraged for an exploit. There are a few bus that are considered core items for any beginner, and occur commonly in the field of vulnerability research. These bugs repeatedly are used in practice CTF challenge sets and actual real world vulnerabilities alike. We will not do a deep dive into specific scenarios of implementing any of these in this tutorial, but we will take a look at multiple bugs, and understand how they function. Any code and concepts presented in this tutorial will be in C, but generally apply to programing languages and concepts more generally.
Integer Overflow And Underflow
Variables have limits to the data they can hold. For example, in C a signed char takes up one byte, this can represent any value between -128 and 127. Likewise with an unsigned char, where the most significant bit isn’t used to represent the sign, any value can be represented between 0 and 255. But what happens if we try to represent a value outside of these ranges.
What happens if we have an unsigned char used for counting, ‘i’, and we try to use it to count from 0 to 300? When the variable hits the limit of its range, so when our unsigned char tries to go from 255 to 256, it will actually wrap around. Instead of representing 256, the value will wrap around to being 0. This is called an integer overflow.
This also works in the inverse direction as well. If we start with the value 255 in an unsigned char, and we subtract 256 from it, it will wrap 0 and once again represent the largest value it can represent.
Both integer overflows and integer underflows have dramatic and serious security consequences. It causes variables to be bigger than or less than the program otherwise expected it to be. Among many things, this often leads to accepting more input than expected, writing to memory longer than expecting, passing different checks with smaller than expected values, and much more.
For example, here is a simple program that prints the values of an unsigned char as 1 is added to it 270 times. Watch for when the variable overflows past it’s range and wraps back around.
#include <stdio.h>
int main()
{
unsigned char i = 0;
int counter = 0;
while (counter < 270)
{
printf(“%d “, i);
counter++
i++;
}
return 0;
}
Off By One
Often times authors are sloppy in their math and how they use library functions. As the name implies, a common mistake is to not account for the 0th element, or to do so when that should not be the case. For example, if someone intends to loop 5 times, and sets their iterator to go from 0 until i is less then or equal to to 5, if unintended this is bad as it is actually looping 6 times.
While one byte might not seem like much, it often causes extreme security vulnerabilities. You may remember in the Buffer Overflow tutorial that overwriting one byte of int b caused it to go from equaling 2 to equaling 1090519042. Other severe cases involve overwriting the null terminator of a string, effectively removing the strings end signal. For example run the following code, notice it loops 6 times instead of 5.
#include <stdio.h>
int main()
{
char i = 0;
while (i <= 5)
{
printf(“%d “, i);
i++;
}
return 0;
}
Use After Free
Put simply this is using an out of date pointer, which is pointing somewhere it no longer should be, and due to this, this bug is often also referred to as a ‘dangling pointer’. This vulnerability happens when previously freed memory is used. Understanding how a use after free works is a bit hard without a concrete example. The next bit of text will attempt to explain the general case when use after free’s happen, but it is probably best to go google some concrete examples about how this bug might arise. A great concrete example is pwnable’s ‘UAF’ challenge. That, along with many other resources, can be found on this site’s Resources tab.
Use After Free bugs often happen when malloc() and free() are mismanaged. As a general example, we might have a sample program with 3 simple functions:
One that malloc()’s some memory and set a pointer to it
One that uses that memory
One that free()’s that memory
What the author here clearly intended is for malloc() to allocate the memory first, then the using function uses it, and finally the clean up function to free() it. But what happens when this order is mixed up? What happens if malloc() allocates the memory, then free() free’s it, and then the program that uses that memory, which is now free()’d, goes ahead and uses that memory. Long story short, it depends. This scenario is often dangerous and allows the program to be manipulated.
This type of bug is more common in large programs where it becomes more difficult to track allocating and freeing memory where there may be confusion about which part of the program is responsible for freeing memory.
Failure To Check Return Value
It is often the case that novice programmers just assume that functions will return as expected. An easy example of this is malloc(), or any function that usually can be expected to return an address for that matter. Most functions that would normally return an address instead return NULL in the case that they break. If a function relies on using the address returned by malloc(), but instead malloc() fails and just return’s NULL, if the function does not check for that possibility there could be serious consequences. In general, a program or function that fails to check that a returned values is as expected can usually be considered a bug.
Format String Vulnerability
The printf() and scanf() family of functions allow authors to specify how their input and output is formatted. Among many different formats, printf() can print a value as a decimal value (‘%d’), string (‘%s’), or hexadecimal (‘%x’) value. In certain situations, a printf() call that doesn’t specify any formatter can have its format manipulated. In a mundane case, this means a number could be printed as a botched ASCII string, or as a hexadecimal value.
In a more serious case this can be manipulated to print additional information that might give a malicious actor additional information to successfully implement an exploit. More serious yet is printf()’s ‘%n’ specifier, which counter-intuitively allows a user to write to memory. If manipulated correctly this can allow a user to write to any point in memory.
Summary: Common Bugs
There are a handful of common bugs that anyone learning vulnerability research should know. These bugs cause vulnerabilities in programs and commonly come up in both CTFs and real world cases.
Practice Exercise:
Compile this C code yourself using GCC and inspect the integer_overflow_example’ binray yourself in GDB. This is a simple program that makes sure a user doesn’t write a username so long that it might overwrite things on the stack. However, it only counts the length of the user’s input with an unsigned char, that means that 257 characters is just as valid as one character! Give this program way more input than it expects, and trick the length check. Inspect this happening in GDB. (The buffer to write to has been made big enough to accommodate this ‘too big’ string so that this exercise doesn’t break the program, but if you wanted to shrink it you could use this to overwrite and hijack the return address if you’d like!)
Compiler options:
gcc buffer_overflow_example.c -o buffer_overflow_example -m32 -fno-stack-protector -O0
Code:
#include <stdio.h>
void win(void)
{
printf("You win!\n");
}
int main()
{
unsigned char overflow_me = 0;
char buffer[300] = { 0 };
/*Take user input*/
printf(“Put in your ‘short’ username!”);
scanf(“%s”, buffer);
/*Count how many chars until null terminator*/
while (*overflow_me != ‘\0’)
{
overflow_me++;
}
/*Ensure the user didn’t put in more than 10 bytes*/
if (overflow_me < 20)
{
win();
}
return 0;
}