Buffer Overflow 101: Exploring Exploits and Protection
Danushka Stanley
Senior DevOps Engineer | Ethical Hacker | Google Cybersecurity Certified | TryHackMe {MASTER} - TOP 2% Lv11
One of the most crucial vulnerabilities that has been exploited is the buffer overflow when it comes to cybersecurity. Buffer overflow can be defined as a condition created in programming when a running program attempts to write more data on a memory block, or buffer, than it actually is allocated to hold, therefore having the overflow written over the succeeding memory. This can result in erratic behavior, crashes, or even the execution of malicious code. Understanding buffer overflow is important for anybody working in software development or cybersecurity, as it still stands today as a very good potent weapon for attacks. This article exposes all there is about buffer overflow and the basic mechanism behind it and the risks it brings to light.
1. Understanding the Stack and Heap
1.1 The Stack
The stack is a primary part of computer system memory management that facilitates the execution of programs. It is simply a linear storage space to store data and recover it in a way called Last In, First Out (LIFO), that the very last data inserted is the first one extracted. It makes a very good structure to handle the function calls, local variables, and control flow. The return address is pushed onto the stack together with the parameters and local variables, for more difficult calls. Then, after return from the function, this data is popped off the stack, hence returning cleanly and efficiently back to the calling function.
The stack has several important characteristics that set it apart from other memory structures:
Stack grows in LIFO method (Last In, First Out)
The stack grows and shrinks as functions are called and return. When a function is invoked a stack frame is created and pushed on to the stack. Within this stack frame is the function's return address, its parameters, and its local variables. As more functions are called new stack frames are pushed on to the stack. When a function is done executing, its stack frame is popped off, returning the state of the calling function. This LIFO nature preserves the execution context of each function until it is complete, which makes nested and recursive function calls possible.
Example of Stack Operations of Various Operating Systems
Although the operations by different operating systems on a stack may be different, in principle, it remains constant.
The stack in Windows is managed by the operating system and is part of the context of each thread. Stack size can be specified at creation time of a thread and can be modified by either compiler directives or runtime settings. Guard pages are reserved by Windows to detect stack overflow; when the space used by a stack exceeds that which is allocated, Windows will throw an exception.
Linux also takes care of the stack at the kernel level. Each process and thread has its own stack. The default stack size may be made at system settings or even at run time it is changeable. A guard page similar to Windows is provided to avoid overflowing of the stack and when the stack crosses the limit, it will cause a segmentation fault.
In macOS, stacks are handled similarly to Linux. Each thread is given its stack by the kernel. The size of memory reserved for each thread's stack can be either manually specified by using system settings or programmatically set up using application level code. Guard pages are used in macOS so as to trap and treat appropriately stack overflow conditions.
1.2 The Heap
The heap is a core part of a computer's memory management system, targeted for dynamic memory allocation. While the stack automatically allocates and deallocates memory as functions are called and return, explicitly requesting and releasing memory at runtime can be done using the heap. This flexibility is key to supporting applications that have to deal with different amounts of data that are not predetermined until the time of execution. Data structures like linked lists, trees, or complex objects are usually dynamically allocated, for which heap support is efficient.
The main features of the heap are:
How the Heap Grows
When a program requests memory, the heap manager searches for a block of sufficient size from the pool of available memory. As memory gets allocated from the free memory pool, the heap would keep on growing. If a suitable block is found, it is allocated to the program. Otherwise, the heap manager has to increase the size of the heap by requesting more memory from the operating system. As soon as memory is de-allocated, it goes back into the pool, enabling it for reuse in future allocations. In this way, the heap grows and contracts dynamically to match the needs of the program.
There are many operating systems with different ways of handling the heap, each having its memory allocation mechanisms and strategies.
What is a Buffer Overflow?
Buffer overflow is one of the most prevalent and critical security vulnerabilities occurring in computer systems, characterized by a situation in which a program writes more data to a buffer than the buffer is allocated to hold. Buffers are basically continuous blocks of memory reserved for holding data; an example includes arrays or strings. If the data's volume is greater than the capacity of the buffer, it can lead to overwriting adjacent memory, risking unpredictable behavior, crashes, or security breaches. Because the concept is simple and the potential consequences are quite severe, buffer overflow vulnerabilities are a central topic in software development and cybersecurity.
What is a Buffer Overflow Attack?
The product of this flaw is a buffer overflow attack, allowing the program's execution to be exploited and unapproved code to be possibly run. This is how it often happens:
Buffer overflow attacks apply to both stacks and heaps. On a stack, attackers typically overwrite return addresses to hijack function calls. On the heap, they might corrupt memory management data to have it execute arbitrary code. With the number of results that a successful buffer overflow can give — from data corruption and application crashes to full system compromise and unauthorised access — the adversary is always looking for ways.
Example of a Buffer Overflow attack (Simulation only; no RCE will be triggered)
Vulnerable code
Once we compile and execute the application, we can use GDB (what I like to use) or any other disassembly tool to disassemble the machine code, identify the memory addresses, and then further initiate the exploitation of the vulnerability.
Now what we need to do is run our application using GDB to reverse engineer the machine code to find where the main function and checkpw function are stored in memory.
Now let's disassemble the checkpw function
Now we can see the granted function, and understand how we can manipulate the program to call it. By exploiting the buffer overflow vulnerability, we can overwrite the return address on the stack with the address of the granted function. This means we can successfully bypass the in-app authentication and invoke the granted function without entering the correct password.
Let's execute below command
gdb: run < <(python3 -c 'print("A" * 100 + "BBBB" + "CCCC" + "DDDD" + "EEEE")')
As you can see, the memory has been corrupted and our payload has been written to the return address, and because of that, the application crashed.
Now we will replace our code with the memory address of the granted function to see whether we can call it.
领英推荐
As you can see in the above example, despite the application crashing with a segmentation fault, the "Access granted.." message was displayed. This confirms that we have successfully redirected the program's execution flow to the granted function, bypassing the password check.
The crash occurred after the granted function was executed, likely because the stack's return address was overwritten with an invalid value that caused the segmentation fault when the function tried to return.
While this demonstration shows the potential of a buffer overflow attack to bypass authentication, further manipulation could allow an attacker to gain a shell. To achieve this, you would need to:
This process involves finding the appropriate memory addresses for the shellcode and the system function, which is beyond this basic demonstration but entirely feasible with further exploration and exploitation.
Feel free to experiment with these concepts and push the boundaries of this example to achieve a complete exploit. Happy hacking!
What are the known attacks caused by buffer overflows?
Buffer overflow vulnerabilities have been the root cause of many significant cybersecurity incidents over the years. These attacks exploit weaknesses in software where more data is written to a buffer than it can handle, leading to memory corruption. Here, we will discuss some of the most notorious attacks caused by buffer overflows, including their associated CVE (Common Vulnerabilities and Exposures) numbers.
1. The Morris Worm (1988) - No CVE
One of the earliest and most famous buffer overflow attacks was the Morris Worm. Released in 1988, it exploited a buffer overflow in the finger daemon (a network protocol for user information) on UNIX systems. This worm caused significant disruption across the early internet by propagating rapidly between vulnerable machines.
2. Code Red Worm (2001) - CVE-2001-0500
The Code Red worm targeted a buffer overflow vulnerability in the Microsoft IIS (Internet Information Services) web server. Specifically, it exploited a flaw in the indexing service (IDQ.dll). Once infected, the worm defaced websites and launched denial-of-service (DoS) attacks against various targets, including the White House.
3. SQL Slammer Worm (2003) - CVE-2002-0649
The SQL Slammer worm exploited a buffer overflow in Microsoft SQL Server's resolution service (ssnetlib.dll). This attack caused a rapid spread of the worm across the internet, leading to widespread network congestion and denial-of-service conditions.
4. Heartbleed (2014) - CVE-2014-0160
Although Heartbleed is technically a buffer over-read rather than a buffer overflow, it is worth mentioning due to its impact. Heartbleed exploited a vulnerability in the OpenSSL library, allowing attackers to read sensitive data from the memory of affected servers, including private keys and user credentials.
5. Ghost (2015) - CVE-2015-0235
The Ghost vulnerability affected the GNU C Library (glibc), specifically the gethostbyname functions. This buffer overflow could be exploited by attackers to execute arbitrary code on vulnerable systems, posing a significant risk to Linux-based servers and devices.
6. SigRed (2020) - CVE-2020-1350
SigRed is a critical buffer overflow vulnerability in the Windows DNS Server, discovered in 2020. This flaw, present for 17 years, allowed remote code execution (RCE) by sending malicious DNS queries to the server. Given the widespread use of Windows DNS Servers in enterprise environments, this vulnerability posed a severe risk of widespread exploitation.
7. EternalBlue (2017) - CVE-2017-0144
EternalBlue, a buffer overflow vulnerability in the SMBv1 protocol of Windows operating systems, was famously exploited by the WannaCry ransomware attack. This attack caused massive disruptions worldwide by encrypting user data and demanding ransom payments in Bitcoin.
Any mitigations against these types of attacks?
Buffer overflow attacks have been a persistent threat in the field of cybersecurity. To combat these vulnerabilities, various mitigation techniques have been developed and implemented in modern operating systems and compilers. Here are some of the most effective mitigation strategies:
1. Address Space Layout Randomization (ASLR)
ASLR is a memory protection technique that randomises the memory addresses used by system and application processes. By randomising the locations of stack, heaps, libraries, and other memory regions, ASLR makes it significantly harder for an attacker to predict the addresses required for successful exploitation. If an attacker does not know the exact location of the target code or data, their chances of successfully executing a buffer overflow attack are greatly reduced.
Implementation in Operating Systems:
2. Data Execution Prevention (DEP)
DEP is a security feature that marks certain areas of memory as non-executable. This prevents code from being executed in these regions, such as the stack and heap, which are typically used for storing data. By doing so, DEP mitigates the risk of executing malicious code injected through buffer overflows.
Implementation in Operating Systems:
3. Stack Canaries
Stack canaries are special values placed between the buffer and control data on the stack, such as return addresses and frame pointers. Before a function returns, the integrity of the stack canary is checked. If the canary value has been altered, it indicates a buffer overflow, and the programme can be terminated to prevent exploitation.
Implementation in Compilers:
Since this is more focused on offsec techniques, I will not talk about dev best practices (I am not a dev anyway :D)
Notes:
Let's discuss the below attack techniques in a new article later. see ya!
That's some serious knowledge drop on buffer overflows and mitigations. Impressive stuff, mate.