When you test the security of an application (let’s say for finding a buffer overflow vulnerability) you can have a variety of tools at your disposal:
Static code analysers: tools that allow the analysis of a program without actually executing it. They can check for Syntax errors, coding implementations that don’t adhere to the standard guidelines, security vulnerabilities. They are prone to a high number of false positive and false negative results. Normally they analyze the source code, in some cases some form of the object code, so it is a form of white box testing. It can be also included in the build system of the SDL (Software Development Life cycle.
Dynamic code analysers: tools that allow the analysis of a program executing it. The program can be executed by the real processor or in a virtual environment.
Fuzzers: tools that are able to generate random data or data based on a predefined pattern that can be fed into a program through the stdin, passed as an argument of the program or loaded into the program as a file. A buzzer is able to log any crashes, memory corruptions and memory leaks when coupled with a memory debugger.
What is a buffer and what is a buffer overflow?
A buffer in computer science is a region of a physical memory storage used to temporarily store data while it is being moved from one place to another.
A buffer can be for example a part of memory that contains the values of a variable or an array. A buffer overflow occurs when the content (value) is bigger than the container (variable).
Let’s see a very simple example in written in C:
char  input = ('aaaaaaaaaaa'); char buffer ; strcpy(buffer , input);
As you can see input (11) > buffer (10) so the last byte can’t be contained in the buffer and has to go somewhere, as a result, memory will be overwritten and there will be a buffer overflow.
Let’s take a look now at a safe implementation of the same code:
char  input = ('aaaaaaaaaaa'); char buffer ; ncpy(buffer , input , sizeof(buffer) );
In this case, a number of bytes corresponding to the size of the buffer will be copied and the exceeding 1 byte will be simply discarded without overwriting the memory.
If the EIP gets overwritten then an attacker is able to control the execution flow of the application.
Any application that uses unsafe Operations can be vulnerable to buffer overflow. It actually depends on how the function is implemented. The most common functions to look for when trying to test an applications for buffer overflow are:
char * strcpy ( char * destination, const char * source );
char * strcat ( char * destination, const char * source );
(Appends a copy of the source string to the destination string. The terminating null character in destination is overwritten by the first character of source, and a null-character is included at the end of the new string formed by the concatenation of both in destination. Destination and source shall not overlap.)
- gets / fgets:
char * gets ( char * str );
char * fgets ( char * str, int num, FILE * stream );
(Reads characters from the standard input (stdin) and stores them as a C string into str until a newline character or the end-of-file is reached. The newline character, if found, is not copied into str. A terminating null character is automatically appended after the characters copied to str. Notice that gets is quite different from fgets: not only gets uses stdin as source, but it does not include the ending newline character in the resulting string and does not allow to specify a maximum size for str (which can lead to buffer overflows).)
- scanf / fscanf:
int scanf ( const char * format, ... );
int fscanf ( FILE * stream, const char * format, ... );
(Reads data from stdin and stores them according to the parameter format into the locations pointed by the additional arguments. The additional arguments should point to already allocated objects of the type specified by their corresponding format specifier within the format string.)
int vsprintf (char * s, const char * format, va_list arg );
(Composes a string with the same text that would be printed if format was used on printf, but using the elements in the variable argument list identified by arg instead of additional function arguments and storing the resulting content as a C string in the buffer pointed by s. Internally, the function retrieves arguments from the list identified by arg as if va_arg was used on it, and thus the state of arg is likely to be altered by the call.
In any case, arg should have been initialized by va_start at some point before the call, and it is expected to be released by va_end at some point after the call.)
- printf :
int printf ( const char * format, ... );
(Writes the C string pointed by format to the standard output (stdout). If format includes format specifiers(subsequences beginning with %), the additional arguments following format are formatted and inserted in the resulting string replacing their respective specifiers.)
void * memcpy ( void * destination, const void * source, size_t num );
(Copies the values of num bytes from the location pointed to by source directly to the memory block pointed to by destination. The underlying type of the objects pointed to by both the source and destination pointers are irrelevant for this function; The result is a binary copy of the data. The function does not check for any terminating null character in source – it always copies exactly num bytes. To avoid overflows, the size of the arrays pointed to by both the destination and source parameters, shall be at least num bytes, and should not overlap (for overlapping memory blocks, memmove is a safer approach).)
Any function that fails to do proper input validation before operating can lead to buffer overflow. Buffer overflows are vulnerabilities that exists in languages like C/C++ that make use of pointers or allow access to raw memory. Generally interpreted languages like C#, Visual Basic, .NET and JAVA don’t suffer from this type of vulnerability because they perform automatically memory management and garbage collection.
Buffer overflows can be triggered by any of the following buffer Operations:
- User input
- Data loaded from disk
- Data from network
Null pointer dereference
In computing, a null pointer has a valuereserved for indicating that the pointer does not refer to a valid object. Programs routinely use null pointers to represent conditions such as the end of a list of unknown length or the failure to perform some action; this use of null pointers can be compared to nullable types and to the Nothing value in an option type.
Dereferencing the NULL pointer typically results in an attempted read or write from memory that is not mapped – triggering a segmentation fault or access violation. This may represent itself to the developer as a program crash, or be transformed into an exception that can be caught. If exceptions aren’t handled properly, it’s possible to execute arbitrary code on the machine.
int a, b, c; // some integers int *pi; // a pointer to an integer a = 5; pi = &a; // pi points to a b = *pi; // b is now 5 pi = NULL; c = *pi; // this is a NULL pointer dereference
Interesting article about exploiting null pointer dereference in kernel space: https://blogs.oracle.com/ksplice/entry/much_ado_about_null_exploiting1
Author: Fabio Baroni Date: 2016-02-17 18:17:21