Arbitrary Code execution due to memory corruption is not new. It’s been there since forever(to be more precise, probably since the 1980s) and still a major thing. For instance,70 percent of all security issues addressed in Microsoft products are caused by violations of memory safety. Similar proportion holds true for linux. However, a lot has changed over the years. The conventional attacks and exploits that were prevalent a decade ago no longer hold the same significance. Simultaneously, new protective measures have been introduced periodically to mitigate these attacks, but have also inadvertently led to the discovery of novel attack techniques to circumvent these mitigations. For a diligent security researcher or application developer, It’s hard to follow this ongoing cat and mouse race. While certain protective measures are widely recognized, there are many others that are not well-known yet possess intriguing narratives or implementation details. This realization has inspired me to document all these protections that I have encountered or become aware of throughout my years of study, and to ponder why I had not been acquainted with them earlier.
Document version for the series can be found here: https://github.com/shubham0d/memory-corruption-mitigations
How it all started (The chaos)
The initial instance of memory corruption was discovered in 1988 where Morris worm found to be exploiting the fingerd Unix application. This particular incident used a buffer overflow vulnerability, that leads to code execution. Subsequently, the awareness of memory corruption spread throughout the security community as a result of the publication of "Smashing the stack for fun and profit" in the 49th edition of Phrack in 1996. Prior to this publication, buffer overflows were acknowledged but not widely attended to. However, since then, researchers, regardless been red or blue, have begun to prioritize this issue. Consequently, memory corruption has wreaked chaos within the security community.
THE FIRST ORDER: The introduction of memory corruptions
Let's look at a traditional case of buffer overflow that will help to understand what makes memory corruption vulnerability and how code execution happens due to memory corruption.
#include <stdio.h>
#include <string.h>
int main(int argc, char **argv[])
{
char password[64];
strcpy(password, argv[1]);
if (strcmp(password, "secret") == 0) {
printf("Sucessfully login\n"); }
else{
printf("Password doesn't match. Unanble to login.\n"); }
return 0;
}
The above program presents a clear-cut problem of copying data into a stack buffer without specifying the number of bytes that should be copied. This vulnerability can enable malicious input to execute arbitrary shellcodes by modifying the return address to point it to the shellcode stored in the stack buffer, which will be passed as part of the password buffer. This can be better understood using below illustration:
Similar type of issue can occur in heap as well, where an overflow can cause arbitrary read/write primitives due to control of modification of heap chunk header. Although heap contain more complex issues than the above mentioned like use after free, double free that mostly occurred due to bad implementation of heap allocator.
This series is not about digging into memory safety issues, but here are the common types of cases you will see that are related to memory:
- Uninitialized memory
- Reading/ Writing freed memory (Use after free)
- Illegal read/write or Out of bound read/write
- Null pointer dereferences
- Function pointer modifications
- Memory leaks (usually not treated as security vulnerability)
Besides above there are certain bugs that cause memory corruptions like integer overflows, heap allocator implementation issues etc. Our work is not focusing on these bugs rather on the actual corruptions(main exploitation scenario).
The third order: Categorizing the work
I decided to categorize the mitigations that have been introduced over the years into three general groups:
Let’s quickly try to understand what each categories meant:
First generation mitigations:
These are the techniques that are mostly introduced in early years of protecting memory corruption issues until around 2010. Due to their long-standing presence, they have reached a state of predominantly being stable or deprecated, with no subsequent advancements taking place in these areas.
Second Generation mitigations:
Second generation mitigations are mostly introduced after 2010 and are focused on filling up the gaps that 1st gen mitigations left or for cases where 1st gen mitigations are bypassed. Most of the techniques mentioned here are introduced in different platforms in the last 5 years, hence are still under development or constant improvements.
Memory detection tools:
This category is different then above two but serves the common objective of detecting memory errors. This contains mostly tools (techniques in some cases) that are introduced by different vendors to detect memory error conditions during the testing or development stages, and are not intended for deployment in production environments.
Let us examine each part individually in the following three upcoming sections.
Section 1: https://nixhacker.com/nostalgic-memory-part-2/