What is a Buffer Overflow, Attack Examples and Prevention Methods

Igal Zeifman
Igal Zeifman

13  min read | min read | 27/03/2023

What is a Buffer Overflow, Attack Examples and Prevention Methods

What Is a Buffer Overflow

Buffer overflow is a type of security vulnerability that occurs when a computer program tries to write more data to a buffer (a temporary data storage area) than it was designed to hold. This can cause the program to crash or, in some cases, allow an attacker to execute malicious code on the system.

Buffer overflows can occur when a program does not properly validate the size or format of the input it receives, allowing an attacker to send a large amount of data that exceeds the buffer’s capacity. When this happens, the excess data can overwrite other parts of the program’s memory, potentially allowing the attacker to execute arbitrary code or take control of the system.

Buffer overflows are a common vulnerability, especially in older or poorly designed software. They can be difficult to prevent because they often involve unexpected or malicious input that the program is not intended to handle. To protect against buffer overflows, developers must carefully validate input and ensure that their programs are able to handle unexpected data without crashing or becoming vulnerable to attack.

How Significant Are Buffer Overflow Attacks?

Buffer overflow attacks can be a severe security threat because they can allow attackers to execute arbitrary code on a system, potentially giving them complete control over the system or enabling the theft of sensitive information. Buffer overflow consistently ranks in the SANS Top 20 Most Dangerous Software Errors.

The Common Weakness Enumeration (CWE), a dictionary of software security weaknesses, lists multiple weaknesses related to a buffer overflow. CWE-120, also known as “Buffer Copy without Checking Size of Input,” describes a scenario in which a program copies data from one buffer to another without adequately checking the input size, potentially leading to a buffer overflow vulnerability. Other weaknesses in the CWE that are related to buffer overflows include:

  • CWE-119: “Improper Restriction of Operations within the Bounds of a Memory Buffer”
  • CWE-121: “Stack-Based Buffer Overflow”
  • CWE-122: “Heap-based Buffer Overflow”
  • CWE-125: “Out-of-bounds Read”
  • CWE-131: “Incorrect Calculation of Buffer Size”

These weaknesses all involve problems with how a program handles data in memory buffers, which can lead to buffer overflows and other security vulnerabilities. To prevent these types of vulnerabilities, developers must carefully validate input and ensure that their programs can handle unexpected data without crashing or becoming vulnerable to attack.

General Attack Flow: How It Works

A buffer overflow attack involves several stages:

  1. Identifying a vulnerable program: The attacker must first identify a program vulnerable to buffer overflow attacks, typically by finding a program that does not properly validate the size or format of the input it receives.
  2. Crafting malicious input: The attacker must then create a carefully crafted input, often referred to as arbitrary code, designed to exploit the vulnerability in the program. This input is usually designed to exceed the buffer’s capacity and overwrite other parts of the program’s memory.
  3. Sending the malicious input: The attacker then sends the malicious input to the program, either by directly interacting with the program or by tricking a user into entering the input on their behalf.
  4. Overwriting memory: If the program does not correctly validate the input size, it will try to store the input in a buffer that is not large enough to hold it, causing the excess data to be written to the adjacent memory.
  5. Executing arbitrary code: If the attacker has successfully overwritten parts of the program’s memory, they can potentially set new values for the return pointer, which is the address to which the program should go next. By changing the return pointer to point to a location where the attacker’s malicious code is positioned, the attacker can alter the program’s execution path and transfer control to their code.

To perform remote code execution, the attacker must have a way to send the malicious input to the program and be able to determine the address where the malicious code will reside. In some cases, the attacker may pad their malicious code with “no operation” (NOP) instructions on both sides, allowing the code to be executed even if the exact memory location is unknown.

Different programming languages offer varying levels of protection against buffer overflow attacks. Some languages, like C and C++, have no built-in protection against accessing or overwriting data in any part of their memory, making them particularly vulnerable to these attacks. Other languages, like C#, Java, and Perl, have mechanisms in place that can help prevent or mitigate buffer overflows. However, they can still be affected if there are flaws in the program compiler, runtime libraries, or features of the language itself.

Simple Example of a Buffer Overflow Exploit

Sample program
The following C program allows users to enter their name and age, and then displays the information back to them:

#include <stdio.h>
#include <string.h>

int main() {
   char name[10];
   int age;

   printf("Enter your name: ");
   
   scanf(“%s”, &name);

   printf("Enter your age: ");
   scanf("%d", &age);

   printf("Your name is %s and you are %d years old.\n", name, age);

   return 0;
}

Unsafe input
The program is vulnerable to buffer overflow attacks because it uses the fgets() function to read user input into a fixed-size buffer (name) without properly checking the size of the input. This allows an attacker to enter a name that is longer than the size of the buffer, causing the excess data to be written to adjacent memory.

For example, if an attacker enters the following string as a name:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

The excess data will overwrite the value of age, which is stored in the adjacent memory location.

Result
When the program is run, and the attacker’s input is entered, the program will crash or produce unexpected results because the value of age has been overwritten. In some cases, the attacker may be able to use this type of exploit to execute arbitrary code or take control of the system.

Real-Life Examples of Buffer Overflow Weaknesses

BadAlloc

The BadAlloc vulnerability is a security vulnerability that can occur in systems that use the C++ Standard Template Library (STL). It is caused by a failure to allocate memory properly, which can lead to a buffer overflow or other types of memory-related vulnerabilities.

The vulnerability is often caused by using an STL container, such as a vector or map, that is not correctly initialized or resized, causing it to try to allocate more memory than is available. This can lead to a buffer overflow or other memory-related issues, potentially allowing an attacker to execute arbitrary code or take control of the system.

Code example:
Here is an example of code that is vulnerable to the BadAlloc vulnerability. Note that this code assumes the maximum vector size is < 1,000,000.

#include <stdio.h>
#include <stdlib.h>

int main() {
        int *v;
        int num_elements;

        num_elements = 100000000;

   v = (int*)calloc(num_elements, sizeof(short));

   // Try to add more elements than the vector can hold
   for (int i = 0; i < num_elements; i++) {
      v[i]=i*328;
   }
   return 0;
}

The output will look something like this:

badalloc code example

In this example, the vector v is not correctly initialized or resized, causing it to try to allocate more memory than is available when the push_back function is called. This can lead to a buffer overflow or other memory-related issues, potentially allowing an attacker to execute arbitrary code or take control of the system.

To prevent the BadAlloc vulnerability, developers should ensure that STL containers are properly initialized and resized and avoid allocating more memory than is available. They should also use proper memory management techniques, such as using smart pointers or manually releasing memory when it is no longer needed.

CWE-120: Buffer Copy without Checking Size of Input

CWE-120, also known as “Buffer Copy without Checking Size of Input,” is a vulnerability in the Common Weakness Enumeration (CWE) that describes a scenario in which a program copies data from one buffer to another without properly checking the size of the input, potentially leading to a buffer overflow vulnerability.

This type of vulnerability can occur when a program does not properly validate the size or format of the input it receives, allowing an attacker to send a large amount of data that exceeds the buffer’s capacity.

Code example:
Here is an example of code that is vulnerable to CWE-120:

#include <stdio.h>
#include <string.h>

int main(int argc, char *argv[]) {
   char buffer1[10];
   char buffer2[10];

   // Copy user input into buffer1 without checking the size
   strcpy(buffer1, argv[1]);

   // Copy the contents of buffer1 into buffer2 without checking the size
   strcpy(buffer2, buffer1);

   printf("%s\n", buffer2);

   return 0;
}

In this example, the program uses the strcpy function to copy user input (passed as an argument to the program) into a fixed-size buffer (buffer1) without properly checking the size of the input. It then copies the contents of buffer1 into another fixed-size buffer (buffer2) without checking the size again. If the user input is larger than the size of either buffer, the excess data will be written to the adjacent memory, potentially causing a buffer overflow vulnerability.

CWE-119: Inadequate Restriction of Operations in the Memory Buffer

Software operations are typically performed on a memory buffer, although the program can read and write to another location outside the buffer’s intended boundary. Some programming languages allow you to provide the addresses of other memory locations directly – they don’t automatically check if the locations are appropriate for the referenced memory buffer.

Thus, read and write operations could be performed on a memory location associated with different variables, internal program data, or data structures. In such cases, attackers can execute arbitrary code, read sensitive data, modify your control flow, or crash the system.

Attackers could take control of an IoT device to establish a foothold within the network, enabling further exploits such as actions-on-objective. For example, they could use the compromised device to launch a botnet attack against your internal network.

Code example:
Here is an example of a CWE-119 vulnerability in C:

#include <stdio.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>

#define BUFFER_SIZE 32

int main(int argc, char* argv[]) {
  char ip[16];
  char hostname[BUFFER_SIZE];

  printf("Enter an IP address: ");
  
  fgets(ip, sizeof(ip), stdin)

  struct in_addr addr;
  if (inet_pton(AF_INET, ip, &addr) != 1) {
    printf("Invalid IP address\n");
    return 1;
  }

  struct hostent* host = gethostbyaddr((const void*)&addr, sizeof(addr), AF_INET);
  if (host == NULL) {
    printf("Error looking up hostname\n");
    return 1;
  }

  strcpy(hostname, host->h_name);  // CWE-119: Buffer Overflow
  printf("Hostname: %s\n", hostname);

  return 0;
}

This code takes an IP address from the user, verifies that it is a well-formed IP address, and then looks up the hostname for the IP. However, the call to strcpy at the end of the program is vulnerable to CWE-119, because it does not check the size of the hostname before copying it to the hostname buffer. If the hostname is longer than BUFFER_SIZE, it will overwrite memory outside the bounds of the hostname buffer, potentially causing a buffer overflow.

To fix this vulnerability, the program should use a function like snprintf or strncpy to copy the hostname to the hostname buffer, ensuring that the size of the hostname does not exceed the size of the buffer.

CWE-121: Stack-Based Buffer Overflow

Stack-based buffer overflow refers to a situation where a buffer allocated on the stack is overwritten – for example, it might be a local variable or a parameter for a given function.

The execution stack usually includes sensitive data that could enable arbitrary code execution if accessed by unauthorized users. The most critical information includes stored return addresses – the memory addresses where the system should continue after completing the current function.

Attackers can overwrite these values with memory addresses they can access with write privileges. The attackers can then insert arbitrary code, which runs with the vulnerable program’s full privileges. Another option is to provide an address for important calls, such as POSIX system(), with the stack containing arguments to the calls. This approach is known as a “return into libc” exploit because the attackers make the program jump into a different routine at return time based on libc (the standard C library).

The frame pointer and stack pointer are other critical data commonly included in the stack. These values indicate the offsets to compute memory addresses. Modifying these values allows attackers to create write-what-where conditions.

Code example:
Here is an example of a CWE-121 vulnerability in C:

#include <stdio.h>
#include <string.h>

#define BUFFER_SIZE 16

void vulnerable_function(char* input) {
  char buffer[BUFFER_SIZE];  // Fixed-size buffer on the stack
  strcpy(buffer, input);  // CWE-121: Stack-Based Buffer Overflow
}

int main(int argc, char* argv[]) {
  char* user_input = "This input is larger than the buffer size";
  vulnerable_function(user_input);
  return 0;
}

This code defines a function vulnerable_function that takes a string as input and copies it to a fixed-size buffer on the stack. However, the call to strcpy is vulnerable to CWE-121, because it does not check the input size before copying it to the buffer. If the input is longer than BUFFER_SIZE, it will overwrite memory outside the bounds of the buffer, potentially causing a stack-based buffer overflow.

To fix this vulnerability, the program should use a function like snprintf or strncpy to copy the input to the buffer, ensuring that the size of the input does not exceed the size of the buffer. Alternatively, the program could dynamically allocate memory for the buffer using functions like malloc or calloc, and then free the memory when it is no longer needed.

Why IoT Devices Are at Greater Risk?

IoT (Internet of Things) devices have a higher risk of buffer overflow vulnerabilities for several reasons:

  • The need to use memory efficiently: IoT devices often have limited memory and processing power, so developers must carefully optimize their code to use resources efficiently. This can lead to using fixed-size buffers and other practices that can make the devices more vulnerable to buffer overflow attacks.
  • The use of C/C++: Many IoT devices are programmed in languages like C and C++, which have no built-in protection against accessing or overwriting data in any part of their memory. This makes them more vulnerable to buffer overflow attacks, as well as other types of memory-related vulnerabilities.
  • The use of standard programs across many IoT devices: Many IoT devices use standard programs or libraries shared across multiple devices. If one of these programs or libraries has a buffer overflow vulnerability, it can potentially affect all of the devices that use it, increasing the risk of a widespread attack.

 

Common C Functions That Cause Overflows

Buffer overflow attacks in IoT devices are often carried out by common C library functions that are designed to copy or manipulate data in memory buffers. These include:

Vulnerable C Function Description
memcpy This function copies data from one memory location to another. It does not perform any size checking, so it can be used to copy more data into a buffer than the buffer is designed to hold, potentially leading to a buffer overflow vulnerability.
strcpy This function copies a string from one buffer to another. Like memcpy, it does not perform any size checking, so it can be used to copy a string larger than the destination buffer into the buffer, potentially leading to a buffer overflow vulnerability.
strncpy This function is similar to strcpy, but it allows the programmer to specify the maximum number of characters to be copied. While this can help to prevent buffer overflows, it is important to ensure that the maximum number of characters is set correctly and that the destination buffer is large enough to hold the copied string
sprintf This function formats and stores a string in a buffer. Like strcpy, it does not perform any size checking, so it can be used to write a string larger than the buffer into the buffer, potentially leading to a buffer overflow vulnerability.
snprintf This function is similar to sprintf, but it allows the programmer to specify the maximum number of characters to be written to the buffer. While this can help to prevent buffer overflows, it is important to ensure that the maximum number of characters is set correctly and that the destination buffer is large enough.
strcat This function concatenates (joins) one string to the end of another string in a buffer. Like strcpy, it does not perform any size checking, so it can be used to append a string that is larger than the buffer to the buffer, potentially leading to a buffer overflow vulnerability.
strncat This function is similar to strcat, but it allows the programmer to specify the maximum number of characters to be concatenated to the end of the buffer. This can help to prevent buffer overflows if the number of characters is set correctly.
sscanf This function reads data from a string and stores it in a buffer. Like sprintf, it does not perform any size checking, so it can be used to read a string larger than the buffer into the buffer, potentially leading to a buffer overflow vulnerability.
fscanf This function is similar to sscanf, but it reads data from a file rather than a string. It can also be used to read data into a buffer without properly checking the size of the file, potentially leading to a buffer overflow vulnerability.

Preventing Buffer Overflow Attacks on IoT

If you are developing your own IoT software, several strategies can be used to prevent buffer overflow attacks:

  • Checking input sizes: One of the most effective ways to prevent buffer overflow attacks is to carefully validate the size and format of the input received by the device. This can help to ensure that the input fits within the size of the buffers and does not exceed their capacity.
  • Making memory non-executable: Many buffer overflow attacks involve injecting and executing malicious code in memory. To prevent this type of attack, developers can make the memory used by the device non-executable, which means that it cannot be used to execute code. This can be done through Data Execution Prevention (DEP) or Address Space Layout Randomization (ASLR).
  • Using ASLR: ASLR is a technique that randomizes the memory addresses used by a program, making it more difficult for an attacker to predict where their code will be stored in memory. This can help make buffer overflows and other memory-related vulnerabilities harder to exploit.

If you are using third-party software, use these best practices to mitigate buffer overflow attacks in your dependencies:

  • Patching: Regularly applying patches and updates to the software can help to fix known vulnerabilities and prevent attacks. It is important to ensure that patches are applied promptly, so that buffer overflow vulnerabilities are patched soon after they are discovered.
  • Security gateways: Using a security gateway or other network-level security measures can help to protect against buffer overflow attacks by blocking or filtering malicious traffic before it reaches the device. This can be effective in cases where the device is vulnerable to attack, and patching is not possible.
  • Authentication: Implementing strong authentication measures, such as two-factor authentication or passwordless authentication, can help to prevent the injection of buffer overflow inputs via man-in-the-middle (MitM) attacks, which is a common attack vector.

However, in reality, these best practices cannot be effectively applied to many IoT devices because their firmware or software cannot be updated or due to legacy designs that do not support modern security measures.

Deterministic Security for IoT

Most IoT software is written using C or C++, which are susceptible to buffer overflow vulnerabilities. When buffer overflow attacks are discovered after a device is deployed in the field, it might be difficult or even impossible to patch the vulnerability. Sternum can help.

Sternum is an IoT security and observability platform – embedded in the device itself, it provides deterministic security with runtime protection against known and unknown threats; complete observability that offers data about individual devices and the entire device fleet; and anomaly detection powered by AI to provide real-time operational intelligence.

Sternum operates at the binary level, making it universally compatible with any IoT device or operating system, including RTOS, Linux, OpenWrt, Zephyr, Micirum, and FreeRTOS. It has a low overhead of only 1-3%, even on legacy devices.

In the video below you can see Sternum is action, mitigating  a memory heap corruption attack from Ripple20 malware, out-of-the-box:

The benefits of using Sternum for IoT security include:

  • Agentless security – integrates directly into the firmware, making it a part of the core build. This ensures that the solution cannot be externally compromised and leveraged as a point of failure.
  • Automatic mitigation of known and zero-day threats – prevents 96.5% of attacks in benchmark (RIPE) security tests. Its vulnerability-agnostic approach makes it equally effective in dealing with known and zero-day threats. This not only improves security but can also cut security patch management costs by as much as 60%.
  • Supply chain protection – relies on binary instrumentation, enabling it to protect all running code. This extends to 3rd party and operating system libraries, effectively preventing all supply chain exploit attempts.
  • Protection of isolated devices – does not rely on external communication to secure devices, making it equally effective for connected and isolated devices.
  • Live attack information with zero false positives – real-time alert system notifies about all blocked attacks, providing – for each – detailed logs and attack path analysis. The deterministic nature of EIV’s integrity checks ensures that all alerts are always valid.
  • Streamlined compliance – helps meet the latest cyber regulations for IoT devices (IEC 62443, FDA, NIST, etc.) and the most current FBI recommendations for Internet of Medical Things (IoMT) endpoint protection.

Learn more about Sternum Runtime Protection >>

JUMP TO SECTION

Enter data to download case study

By submitting this form, you agree to our Privacy Policy.