Memory Corruption Vulnerabilities
In the previous post, some brief information on analyzing memory corruption issues was given. In the following post, a few prevalent issues in this category that can lead to security impacts will be addressed.
Introduction Memory Corruption Vulnerability
It all starts with a runtime error leading to a crash, but in the end, it all boils down to one question: whether a memory corruption issue can be exploited or not. The exploitability of a memory corruption vulnerability depends on lot of factors and exploit developers generally face a lot of challenges (and frustrations due to exploit mitigations and countermeasures) while developing a `reliable` exploit. Sometimes a vulnerability that is not exploitable at the moment may become exploitable in the upcoming releases; sometimes a reliable exploit can be developed by chaining multiple low-level vulnerabilities. The price of a `reliable` exploit normally ranges from $10,000 to $1 Million or more; depending on the target and respective `market’s` business model. Hence, such vulnerabilities are usually treated as exploitable by default.
Key Takeaways
- Memory corruption vulnerabilities manifest as runtime crashes (e.g., SIGSEGV/SIGABRT) from invalid memory access like out-of-bounds operations or double frees.
- Common types include buffer overflows, double frees, integer overflows, and use-after-free, often due to poor boundary checks.
- Exploitability depends on memory location (stack, heap, BSS), attacker control over overwritten data, and ability to overwrite critical pointers.
- Fuzzing tools like AFL effectively discover these vulnerabilities in C/C++ and similar languages, despite challenges in manual detection.
Got SIGSEGV / SIGABRT?
The memory corruption issues in a software are identified if a crash has occurred during its `runtime` when accessing the contents at an arbitrary memory location that was not programmatically intended. Such vulnerabilities in the open source software we’ve analyzed (in C, C++, Ruby, Golang packages) were identified primarily through fuzzing (mostly AFL) the application binary - basically, a new issue in its source code repositories was created with the respective crash reports as attachments.
Manually identifying them is quite challenging due to the fact that a range of typical scenarios or boundary conditions have to addressed just by reading the source code. Some vulnerabilities are easy to find at the assembly level but not at the source code level. For example, integer errors like sign extensions can be identified easily using disassemblers by observing `movsx` instructions (in Intel platforms); however, it is usually hard to interpret the value changing type conversions causing sign-extensions (eg: signed char to unsigned int) that are performed by the compiler, just by reading the source code.
A few scenarios addressing a software’s operations that leads to memory corruptions are mentioned below.
- A read/write operation from/to a memory location that is outside of the intended boundary of the buffer.
- A write operation exceeding the destination buffer’s length, thereby overwriting other variables and stack pointers present on the stack.
- A free() is called using the same pointer which has already been free()’d.
- An arithmetic operation or a condition, resulting the integer value to be incremented to a huge value and wraparound.
Exploitability Factors:
There are number of factors that are considered while developing a fully functional and reliable exploit. However, we’ll address a few while tackling the vulnerable scenarios mentioned in the previous section that are essential for a developer to contextualize the impact of a memory corruption in her/his code.
- Whether the buffer is present in stack, heap or BSS (sometimes, unprotected buffers in BSS can allow important pointers to be overwritten)
- Whether the data used to overwrite memory can be controlled directly by the attacker i.e whether the pointers that are overwritten can be replaced with a pointer to attacker controlled data.
- What data is corrupted - whether the variable in adjacent data structure that is not used in the current execution context. (unexploitable)
- Whether over-write is possible to multiple memory locations at once. (eg: leveraging format string vulnerabilities). Which usually increases the likelihood of exploit.
There are complex cases as well, for example:
- If the overflow is of only one byte and the attacker don’t have enough liberty to choose what data in the memory can be overwritten with.
- If the data supplied by the attacker to a memory location is being freed in the subsequent instructions.
- Controlling the overwritten data in the multithreaded applications assuming concurrency and synchronization.
Summary:
A motivated attacker can compromise the software with a multitude of techniques. For example, if the buffer’s size is not enough to hold the required shell code or if the stack segments are non-executable (NX), or even if the program is dropping the privileges upon executing an argument (`/bin/sh`) to `system()` function call; the attacker still find a way by chaining up the required trampolines and conduct a ret2libc style attacks, even restoring the privileges by invoking `setreuid()`.
Hence, memory corruption issues shouldn’t be regarded as low priority issues even if they cannot be exploited at that moment. The developers must treat all memory corruption issues as exploitable by default.
Credit: ACE Team - Loginsoft
FAQ
Q1. What is a memory corruption vulnerability?
A memory corruption vulnerability occurs when a program unintentionally modifies memory in unintended ways, potentially allowing attackers to alter execution flow or execute malicious code.
Q2. What causes memory corruption vulnerabilities?
They are commonly caused by buffer overflows, out-of-bound reads/writes, use-after-free issues, double-free errors, and integer overflows in low-level languages like C and C++.
Q3. Why are memory corruption vulnerabilities dangerous?
They are highly dangerous because attackers can exploit them to crash applications, execute arbitrary code, escalate privileges, or even take complete control of systems.
Q4. How are memory corruption vulnerabilities detected?
They are identified using fuzzing, debugging crash dumps, static and dynamic analysis techniques, and secure testing practices in development pipelines.
Q5. How can organizations reduce the risk of memory corruption issues?
Organizations can reduce risk by enforcing secure coding practices, enabling protections like ASLR, DEP, and stack canaries, using memory-safe languages when possible, and performing continuous security testing.
Get Notified
BLOGS AND RESOURCES
.jpg)
.png)
