Buffer Overflow Image

Buffer Overflow

A buffer overflow is a runtime error that occurs when a program writes beyond the boundaries of a buffer or array, causing it to overwrite adjacent memory. This type of bug doesn't always present itself in every execution of the program; it usually emerges under specific conditions, like unusual user input.

Example Scenario:

The following code performs a simple password check and is vulnerable to a buffer overflow attack:

      
            #include 

            #include 
            
            #include 
            
            
            const char *PASSWORD = "azedev";
            
            
            int main()
            
            {
            
              char input[8];
            
              char password[8];
            
            
              std::sscanf(PASSWORD, "%s", password);
            
            
              std::cout << "Password: ";
            
              std::cin >> input;
            
              if (std::strncmp(password, input, 8) == 0)
            
                std::cout << "Access granted\n";
            
              else
            
                std::cout << "Access denied\n";
            
            
              return 0;
            
            }
      
    

The code promts the user to enter a password and compares it to the stored password. If they match the user is granted access.

This is what happens when you run the program:

        
            Password: azedev
            Access granted
        
    

Entering the correct password grants us access - as expected. Entering an incorrect password denies access:

        
            Password: 123
            Access denied
        
    

So far so good, the code is working as intended. However here comes the exploit:

        
            Password: firewallfirewall
            Access granted
        
    

Unexpectedly we entered something very different from the correct password - yet were still granted access.

The reason to why this happens is because we sucessfully executed a buffer overflow attack against the program. In order to understand how this happened let's add a couple of debug lines to the code:

        
            std::cout << "Address of input: " << &input << "\n";

            std::cout << "Address of password: " << &password << "\n";
          
            std::cout << "Input: " << input << "\n";
          
            std::cout << "Password: " << password << "\n";
        
    

Then we re-run the program, first with proper input:

        
            Password: azedev
            Address of input: 0x7ffdf027f988
            Address of password: 0x7ffdf027f990
            Input: azedev
            Password: azedev
            Access granted
        
    

There's nothing unusual about the output.

Let's try again with improper input:

        
            Password: 123
            Address of input: 0x7ffc75a2c298
            Address of password: 0x7ffc75a2c2a0
            Input: 123
            Password: azedev
            Access denied
        
    

Still nothing unusual about the output.

But notice what happens when we enter the third password "firewallfirewall"

        
            Password: firewallfirewall
            Address of input: 0x7ffd4288bd18
            Address of password: 0x7ffd4288bd20
            Input: firewallfirewall
            Password: firewall
            Access granted
        
    

As you can see the password is no longer "azedev" - it now contains "firewall"


How it works:

In our example, we use two 8-byte arrays: password holds the application password, and input captures the user input. In this instance, the GCC compiler places password eight bytes after input (0x7ffd4288bd20 - 0x7ffd4288bd18 = 8), meaning the arrays are adjacent in memory. Keep in mind that different compilers might result in varying memory layouts.

For passwords shorter than eight characters, the memory layout appears as follows:

But for our special password "firewallfirewall" it looks like this:

Here, the null terminator \0 is written beyond the end of the password array, potentially overriding other data on the stack.

This issue arises because std::cin does not enforce bounds checking. It continues reading input from the console until it encounters a newline, which occurs when the user presses Enter, without verifying that the buffer is adequately sized for the input.

Since we only compare the first eight characters of both password and input using std::strncmp to prevent reading beyond the end of either array, a match is mistakenly found where it shouldn't be.


How to prevent it:

To avoid buffer overflow, use std::string for user input, as it dynamically manages buffer size. This prevents overflow issues and enhances security. Ensure comparisons stay within bounds with functions like std::strncmp.

Like this:

        
            #include 

            #include 
                
            #include 
                
            #include 
                
                
            const char *PASSWORD = "azedev";
                
                
            int main()
                
            {
                
                std::string input;
                
                char password[8];
                
                
                std::sscanf(PASSWORD, "%s", password);
                
                
                std::cout << "Password: ";
                
                std::cin >> input;
                
                if (std::strncmp(password, input.c_str(), 8) == 0)
                
                std::cout << "Access granted\n";
                
                else
                
                std::cout << "Access denied\n";
                
                
                return 0;
                
            }
        
    

10-09-2024 by azedev

This blog post is intended for educational purposes only.