Buffer Overloading
Buffer Overloading
One of the most common and oldest security vulnerabilities in software are buffer overflow
vulnerabilities. Buffer overflow vulnerabilities occur in all kinds of software from operating
systems to client/server applications and desktop software. This often happens due to bad
programming and the lack of or poor input validation on the application side. In this article we
will look at what a buffer overflow exactly is, how they work and how they can become serious
security vulnerabilities. We will also look at what happens when a buffer overrun occurs and
mitigation techniques to minimize their harmful effects.
A buffer overflow is a situation where a running program attempts to write data outside the
memory buffer which is not intended to store this data. When this happens we are talking about a
buffer overflow or buffer overrun situation. A memory buffer is an area in the computer’s
memory (RAM) meant for temporarily storing data. This kind of buffers can be found in all
programs and are used to store data for input, output and processing.
An example of data stored in buffers are login credentials or the hostname for an FTP server.
Also other data temporarily stored before processing can be stored in buffers. This literally could
be anything from user input fields such as username and password fields to input files used to
import certain configuration files. When the amount of data written to the buffer exceeds the
expected amount of data, the memory buffer is overrun. This happens for example when a
username with a maximum of 8 bytes is expected and a username of 10 bytes is given and
written to the buffer. In this case the buffer is exceeded by 2 bytes and an overflow will occur
when it’s not prevented from happening. This often happens due to bad programming and the
lack of input sanitization.
An example of a buffer overflow when writing 10 bytes of data (username12) to an 8 byte buffer.
When a buffer overflow vulnerability is used to write malicious data in the memory and the
attacker is able to take control of the execution flow of a program, we are dealing with a serious
security vulnerability. Buffer overflows can then become serious security issues. These
security issues can be exploited by hackers to take (remote) control of a host, perform privilege
escalation or a lot more bad things as a result of arbitrary code execution. Arbitrary code
execution is the process of injecting code in the buffer and get it to execute.
Not all buffer overflow vulnerabilities can be exploited to gain arbitrary code execution. Also
(remote) Denial of Service attacks can be performed when they only crash the running program.
As buffer overflows vulnerabilities can occur in any software DoS attacks are not just limited to
services and computers. Also routers, firewalls IoT devices and anything else running an OS can
be targeted. An example of this situation is the recent Cisco ASA IKEv1 and IKEv2 Buffer
Overflow exploits lately. Some of these remote exploits only crash and force reboot the
firewall resulting in a couple minutes downtime.
Buffer overflows in software can be prevented or mitigated in several ways. Mitigation is the
process of minimizing the impact of a threat before or after the threat occurs. This is exactly
what we need to do when it comes to buffer overflows. They can be prevented from happening
before they occur (proactive). But, since buffer overflows keep occurring, despite the proactively
taken actions to avoid them, we also need mechanisms in place to minimize impact when they do
occur (reactive countermeasures). Let’s have a look at how buffer overflow prevention and
mitigation works.
Buffer overflow prevention
The best and most effective solution is to prevent buffer overflow conditions from happening in
the code. For example when a maximum of 8 bytes as input data is expected, than the amount of
data which can be written to the buffer to be limited to 8 bytes at any time. Also, programmers
should be using save functions, test code and fix bugs accordingly. Proactive methods for buffer
overflow prevention like these should be used whenever possible to limit buffer overflow
vulnerabilities.
Another way of safeguarding to buffer overflows is to detect them as they happen and mitigate
the situation. This is an reactive approach and focuses on minimizing the harmful impact. An
example of effective mitigation is a modern operating system which protects certain memory
areas from being written to or executed from. This will prevent an attacker from writing arbitrary
code to the memory when a buffer overflow occurred. Implementations like DEP, ASLR,
SEHOP and executable space and pointer protection try to minimize the negative impact of a
buffer overflow. This does not prevent the buffer overflow from occurring, but it does minimize
the impact.
Another way of passive buffer overflow detection is using intrusion detection systems (IDS) to
analyse network traffic. An IDS is capable of detecting signatures in network traffic which
are known to exploit buffer overflow vulnerabilities. The IDS can than mitigate the attack
and prevent the payload from executing on the targeted system.
Let’s have a look at how a buffer overflow actually works by looking at the program code. We
explain this process using a very known function vulnerable to buffer overflow is the strcopy()
function in the c library. This functions uses 2 pointers as parameters, the source which points to
the source array to copy from and the destination pointer to the character array to write to. When
the function is executed the source array of chars will be copied to the destination array and does
not have a check for bounds when it does so. When the source buffer is larger than the
destination buffer, than the buffer is overrun.
The follow image is an example of the strcpy() function using a source which is overrunning the
destination buffer.
The code would look like the following image in you IDE of choice:
In this example the buffer is overrun with 2 bytes containing a harmless 1 and 2. Since the
strcpy() function does not perform a bounds check we could write anything outside the buffer
space. Also malicious code like shellcode. In the following tutorials about buffer overflows we
will learn about overrunning buffers with shellcode instead of 1’s and 2’s. We will also learn
how to control the execution flow of a program and execute the malicious shellcode outside the
buffer.
Lessons learned
We have learned that a buffer overflow is caused by certain conditions where a running program
is writing data outside the memory buffer. By injecting (shell)code and redirecting the execution
flow of a running program to that code, an attacker is able to execute that code. This is called
arbitrary code execution. With arbitrary code execution an attacker is able to gain (remote)
control of a specific target, elevate privileges or cause a denial of service on the target.
Buffer overflows can be proactively prevented and mitigated with several techniques.
Programmers should write secure code and test it for buffer overflows. When a buffer overflow
is not prevented from happening it can still be mitigated with reactive methods like protecting
memory from being written to.
We have tried to explain buffer overflow basics without to many technical details. In the
following tutorials about this subject we will get into more details regarding stack based
buffer overflows, heap based buffer overflows and how to detect and exploit buffer overflows
vulnerabilities in software. We will also be learning about shellcode and writing our own basic
buffer overflow exploits.
Managed, or interpreted, programming languages (e.g. C#, Java, Python, etc.) have built in
buffer overflow protection, in the form of garbage collection/boundary checks and code
management. Most application’s written today use these languages, reducing the attack surface.
Buffer overflow vulnerabilities are still commonly found in operating systems (which are
primarily written in C/C++), browsers, browser extensions, and many Adobe applications.
Everything from Microsoft Edge to the Microsoft .NET framework (via remote code execution)
and Adobe Flash Playerfall prey to buffer overflow vulnerabilities.
This type of attack is related to inefficient memory management in that the stack (buffer) has
been allocated a certain amount of memory, and a user inputs data into the buffer that is greater
than the amount of memory that the stack was designed to hold. Both efficient memory
management and boundary checking (via the optimized GCC compiler at compile time for C/C+
+) can mitigate the issue and prevent cybercriminals from running custom code in the system
remotely.
Heap Based Buffer Overflow Attack
Dynamically allocated memory space is managed by the “heap”. Consider an application that
created a dynamically sized buffer on the heap at run time. Similar to stack-based attacks, this
vulnerability occurs because of insufficient bounds checking when writing data to the heap.
Writing too much data into the buffer can allow attackers to control program execution,
manipulate heap data structures, and overwrite function pointers to attacker controlled
instructions.
At the same time, in the event of an engineer not manually de-allocating objects or pointers to
memory addresses in the heap, a memory leak can result in a heap overflow in attempting to
write data to the heap that has no memory (storage space) left. Hence memory management and
boundary checks are the two primary reasons for stack or heap overflows. It is important to note,
however, that in terms of the heap, languages with automatic garbage collection (C/C++ don’t
come with GC included) will manage memory on the heap automatically by de-allocating unused
objects/data from memory addresses.
Though the size of the stack is determined upon execution (the memory is allocated at run-time)
and generally doesn’t change, the size of the heap can grow as the operator calls upon the OS for
more memory resources (hence being dynamic). This means that while the stack may be
overridden by data inputs (e.g. an endless loop call) creating a stack overflow, the heap can grow
to accommodate the system. However, common memory leaks associated with the heap can
essentially take up memory resources, which stops the heap from accommodating the system’s
needs, possibly resulting in a system crash.
What Systems Are Vulnerable to a Buffer Overflow Attack?
Typically systems built on languages that do not come packaged with garbage collection and are
unmanaged languages – C and C++ specifically. Applications and systems built on these
languages very commonly have buffer overflow vulnerabilities when engineers have not
managed memory correctly or perform boundary checking (via debugging and the use of
optimized GCC).
At the same time, certain systems are very difficult to attack via a buffer overflow. These include
systems built on languages that include automatic garbage collection (memory management),
and managed code languages. Managed code written by development teams runs inside a
protected virtual environment (Java Virtual Machine, .NET Common Language Runtime are
both examples) that provide memory management, array bounds checking, type safety, and
overflow protection. However, it is important to realize that managed frameworks (Java, .NET,
and Python) themselves call into operating system libraries that are vulnerable to buffer
overflow (C / C++). Buffer overflow vulnerabilities can occur when passing unvalidated data
into the framework’s APIs that interface with the operating system.
In line with above, even safe languages such as Java, Python, and the .NET framework have
published buffer overflow vulnerability bugs associated with their frameworks, according to
CVE Details. This can range from a heap-overflow in the JRE (Java Run-time environment), to
an integer overflow in Python, to a directory services protocol buffer overflow in the .NET
Framework.
What Damage Can be Caused by a Buffer Overflow Attack?
Writing too much data into the buffer can allow attackers to control program execution,
manipulate heap data structures, and overwrite function pointers to attacker controlled
instructions. In addition to this, system crashes, a DoS or even DDoS attack, and remote code
execution can be used in combination with malware to escalate privileges and open a reverse
shell. The end result is remote code execution, malware installation, and complete control of the
system running the vulnerable code.
Windows desktop applications, Adobe applications, many browsers, etc. often have buffer
overflow vulnerabilities associated with the use of C/C++. Microsoft Edge, the web browser
packaged with Windows 10, has a known scripting engine buffer overflow vulnerability. With
this bug, objects in memory are handled by MS Edge in such a way that a malicious person could
corrupt the memory and execute his/her own code, allowing him/her to gain the rights of an
authenticated user (root access to the system). With escalated privileges, the malicious person
could create additional accounts, install a backdoor, shut down the system, delete data, install
programs (e.g. malware), and more.
Different MS Windows Server software packages have also been susceptible to buffer overflow
attacks in the DHCP Service. This bug results in remote code execution when a malicious person
sends specially designed packets to the server, resulting in memory corruption. The end result is
control of the system and/or the crashing of the server.
Adobe applications are often susceptible to buffer overflow attacks and memory corruption via
malicious inputs of data. Some examples include an Adobe Acrobat vulnerability that could
result in a malicious PDF causing remote code execution, and an Adobe Flash Player
vulnerability that could result in remote code execution due to the use of both a stack-overflow
(associated with a bug in the application’s use of regular expressions) and social engineering.
Essentially, when a cybercriminal uses a buffer overflow against a corporate system and gains
the ability to run remote code, he/she has complete control of the system and can do almost
whatever he/she desires, assuming that such a person has root privileges.
Checking for Buffer Overflow Vulnerabilities
Conducting a code review is a good way to do a preliminary check for the existence of buffer
overflow vulnerabilities. In addition to this, compilers usually conduct boundary checks at
compile-time to ensure that buffer overflows cannot be performed. For example, the strict C++
compiler option provides applications using C++ with defense against stack overflow issues.
Typically, for optimized compilers will report an error if problems associated with a possible
buffer overflow exist.
Security engineers also conduct code reviews (as mentioned), vulnerability scans, and
penetration tests to determine if buffer overflow vulnerabilities exist. Vulnerability scans will
scan static code (based on parameters) for known buffer overflow coding patterns, while
penetration tests allow an ethical hacker to actively attempt to dynamically utilize buffer
overflow attacks on a system at run-time.
Preventing Buffer Overflow Attacks
Operating systems use Address Space Layout Randomization, or ASLR, which is a memory
protection feature that randomizes the application’s memory, making it difficult for an attacker to
predict its location. Operating systems also use Data Execution Prevention, or DEP, which is a
security feature that separates executable and non-executable data into different memory
locations. Only data placed into an executable location is allowed to run by the operating system.
Buffers placed in non-executable memory locations reduce the damage potential of an attack.
In addition to using ASLR and DEP – when it comes to other software applications – compiler
tools, such as StackShield and StackGuard, help with buffer overflow prevention. The use of safe
functions by engineers and the utilization of code audits, etc. can further ensure that buffer
overflow vulnerabilities are mitigated.
At the Application Layer Strict Input Validation on All dynamic Data
As has been noted, buffer overflows occur due to malicious user inputs, either over a
network/web application (from the client side to a server) or via a non-web software application.
Thus one of the most important methods of mitigation is via strict input validation. Some
additional processes associated with input validation that can be used are:
String Length Checks – before assigning string data to buffers, engineers should make
sure that the data is not larger than the buffer’s capacity
Length Checks – engineers should enforce strict length checks on dynamic data before
the data is parsed by the server
Bounds Checks – engineers should make sure that the size of inputs will fit into a buffer
before writing data into the buffer. Check array indexes and ensure they fall in the valid
range.
Overflow checks – When performing mathematical operations, validate the numeric
value ranges before the calculation and ensure that the return result fits into the variable it
is being assigned to.
Avoiding the use of dangerous functions (e.g. strcat, strcpy)
Languages with direct memory access (C/C++) have deprecated unsafe functions in favor
of secure implementations that are immune to buffer overflow.
Applications written in C should use the secure methods provided by the Safe C Runtime
library, also known as Safe CRT.
Applications written in C++ should use the Standard Template Library classes, often
called STL, which automatically detects overflow conditions and throws an exception.
The use of strict input validation on all dynamic data will ensure that only validated, safe inputs
are processed by the application, which will ensure that buffers are not overflowed with data.