Executive Summary
Memory Bombing is an evasion technique that exploits the dynamic memory management of the Windows operating system to bypass security mechanisms. By allocating an excessive amount of memory within a legitimate process, attackers can prevent security engines from detecting malicious code. This is achieved by using functions like calloc or malloc to allocate memory blocks, into which the attacker injects shellcode. The technique disrupts the dynamic scanning capabilities of security software, allowing the malicious code to be executed undetected. Memory Bombing is an advanced memory exploitation technique that can effectively bypass advanced security software like EDR and antivirus systems.
Memory Bombing is a new technique invented by a security researcher named Nir Yehoshua. It was popularized by Nir Yehoshua and Uriel Kosayev’s book Anti-Virus Bypass Techniques.
Introduction to Memory Bombing
Memory Bombing is a new technique for bypassing dynamic engines in security measures. It involves allocating a large amount of memory to a target process to manipulate its behavior with the intention of executing malicious code.
Because an existing process has a large amount of memory, the dynamic engines will avoid scanning the target process and thus not detect the malicious operations carried out by the target process. By overwhelming these defenses with excessive or unexpected memory usage, attackers can execute their code without being detected.
As a theoretical example of this technique, we have a legitimate process named Everything.exe. The operating system allocated the process 2.86MB of private bytes, which is equivalent to normal physical RAM. After that, a malicious binary increases the Everything.exe process’s private bytes to 800MB, overloading the memory.
Calloc Vs. Malloc
Calloc is a memory allocation function in the C language. It allocates memory for an array of elements and initializes them to zero. The function takes two arguments: the number of elements and the size of each element in bytes. For example, calloc(5, sizeof(int)) allocates memory for five integers, each initialized to zero. The allocated memory can be freed later using the free() function.
Malloc is a function in the C language used to allocate a single block of memory dynamically. It takes one argument, which is the size of memory, to be allocated in bytes. For example, malloc(5 * sizeof(int)) allocates memory for five integers but does not initialize this memory. The content of the allocated memory is left uninitialized, meaning it may contain garbage values from previous uses. Similar to calloc, memory allocated using malloc must be freed using the free() function to avoid memory leaks.
The main difference between calloc and malloc is how they handle memory initialization. When you use calloc, the allocated memory is automatically initialized to zero, while with malloc, the memory is left uninitialized. In addition, calloc requires two parameters: the number of elements and the size of each element, while malloc requires only one parameter: the total memory size in bytes. The zeroing of memory in calloc adds a small amount of processing time compared to malloc, which can make malloc faster if you don’t need the memory to be initialized. However, calloc is often a better choice when you need the memory to start with all values set to zero.
Introduction to Virtual Memory
When we execute programs on the Windows OS, the OS creates processes with their own private memory address space. The memory space is part of virtual memory, which, virtual memory is an illusion created by the Windows operating system's memory manager. Because of that illusion, each process thinks the Windows OS allocated its own private memory space. When a process runs, the memory manager uses the hardware’s abilities to translate virtual addresses into physical addresses in RAM. Some memory is paged to the disk, and when the process accesses the paged virtual address, the memory manager loads it from the disk back into the memory.
The diagram below shows two processes, process A and process B, with their own memory spaces allocated in physical memory while certain sections are paged out to disk.
Virtual memory is memory used by user applications. A process memory contains the following components:
Dynamic Linked Libraries: When the OS creates a process, all the associated DLL files get loaded to the process’s memory.
Process Environment Block: The PEB file contains a data structure about a specific executable file, such as process name, PID, executable’s full path on the disk, and loaded modules.
Process Heap: A Process Heap is a large area of memory available for use by processes. The process can request certain areas or blocks of memory to use.
Thread Stack: A thread stack represents a memory area dedicated to a single thread of execution within a program. Each thread has its own separate stack, utilized for maintaining the thread's state and tracking function calls.
Executable: When a user executes a program, a process is created, and the executable associated with the program is loaded into the process memory.
You can use System Informer (formerly Process Hacker) to observe the process's memory.
Stack Vs. Heap Memory
Stack memory is a region of memory used for storing local variables and function call data. It operates in a Last In, First Out (LIFO) manner, where memory is automatically managed. When a function is called, its local variables are pushed onto the stack, and once the function finishes execution, that memory is automatically freed. Stack memory is faster because the system tightly manages it.
Heap memory is used for dynamic memory allocation, meaning memory can be allocated and deallocated at runtime based on the needs of the program. Unlike stack memory, the heap allows for large blocks of memory. It is managed manually by the programmer using functions like malloc, calloc, and free() functions in C. Memory allocated on the heap stays in use until explicitly freed, which makes it flexible but also prone to issues like memory leaks if not properly managed.
The differences between stack and heap memory lie in how they are managed and used:
1. The memory management between the two is different. The stack is automatically managed by the CPU, which is responsible for what to put in the stack in order. In contrast, Heap its memory management can be dynamic; programmers can allocate and free memory.
2. Stack Memory is limited in size and usually contains a small amount of memory. It is used to store local variables and objects whose time is limited to the function or code area where they were defined. On the other hand, Heap Memory is larger and can be more dynamic in terms of storing larger objects or those that need to be preserved for a longer time while the same processor is running.
3. Accessing Stack Memory is faster because it's done directly, based on pointers, and closer to the processor. On the other hand, accessing Heap Memory is slower compared to Stack Memory because it takes time to find a suitable memory space and dynamically allocate it.
Memory-Level Access Permissions
Memory-level access permissions are an aspect of operating system security as they control how processes and applications can interact with memory regions. These permissions determine whether a certain area of memory can be read from, written to, or executed. Improperly configured permissions can lead to various security vulnerabilities, including code injection attacks and memory-based attacks.
There are usually three main types of memory access permissions:
Read (R):
The "read" permission enables a process to access and retrieve the contents of a memory region, including stored data, constants, and instructions for its operation.
Write (W):
The "write" permission allows a process to modify or update the contents of a memory region. This is often necessary when data needs to be adjusted dynamically, such as updating variables or buffers. However, uncontrolled write access to critical memory regions can lead to vulnerabilities, such as buffer overflow attacks, where an attacker overwrites memory beyond its intended bounds.
Execute (X):
The "execute" permission allows a process to run code stored in a specific memory area, typically memory sections with executable instructions. It's important not to mark memory regions containing non-executable data as executable to maintain security.
How Memory Bombing Works
For the first time, the malware will look for a specific process to manipulate. It will then use functions such as calloc or malloc to allocate large blocks of memory.
For the sake of the example, the malware uses the calloc function to allocate the memory in the specific process. In the end, the malware injects the shellcode into the allocated memory and is ready for the trigger to run.
When using the malloc or calloc functions, we are turning to the process’s Heap Memory.
When it comes to the technique of memory bombing, the calloc function asks the operating system to allocate memory in the process’s Heap memory and fill the same memory space with zero bits.
Memory Bombing in Action
The following code is a sample code for using the Memory Bombing technique in its basic configuration.
In the code, you can see that the main function was defined to run the calloc function with two parameters: the first defines the number of elements (200MB), and the second calculates the overall size of the elements.
After that, the if condition verifies that the value returned from the function is a valid pointer and not NULL.
At this point, the security products' dynamic engines lose the ability to scan the specific process dynamically due to excessive memory allocation.
Finally, the free() function frees the allocated memory to prevent a memory leak.
A memory leak means that after the memory allocation has been used, the allocation must be released to give the operating system the opportunity to use it if necessary. And if they don't, it can cause malfunctions in the operating system or the processes running in the system.
For red team, research, or penetration testing purposes, we’ll use Shellcode to utilize this technique.
In order for the process to execute the shellcode we will define, we will have to edit the permissions of the allocated memory to READ/WRITE with the VirtualProtect API function. Then, we will execute the shellcode directly from the allocated memory.
This is what the shellcode looks like.
Now, we can abuse the technique for research purposes; we can use the following code.
We allocate memory to the malicious process with a size of about 800MB and print the allocated memory address on the screen to know the allocated memory’s location for the specific process.
Define the shellcode in a variable and copy the code using memcpy() to the allocated memory.
The VirtualProtect function is then used to change the permissions of the allocated memory to READ/WRITE/EXECUTE.
Then, the program executes the shellcode, restores the old permissions, and frees the memory using the free() function.
Merging Memory Bombing with Heap Spraying for Stealthy Attack
Heap Spraying is a known technique on the offensive side of cybersecurity. It is often used to bypass security mechanisms such as antivirus and EDR software. While this method has huge and useful value on its own, combining both techniques increases the likelihood of successful exploitation. This is particularly useful when trying to execute shellcode without being detected by security mechanisms.
Heap spraying is a technique in which an attacker allocates numerous blocks of memory (the "heap") within a process and fills these blocks with identical shellcode. The goal of this technique is to "spray" a large portion of the heap with the same code, increasing the chance that the attacker's shellcode will be executed when exploiting memory bombing and memory-related attacks.
In fact, the Heap Spraying technique can be considered a brute-force approach to positioning the shellcode in predictable memory locations.
The combined use of Heap Spraying and Memory Bombing creates a much more effective evasion technique. The attacker first employs Memory Bombing to allocate a huge amount of memory within a legitimate process. This overwhelms the dynamic engine, reducing its effectiveness in scanning for malicious activity. Subsequently, the attacker employs Heap Spraying to fill numerous smaller memory blocks within this large memory area, each containing the same shellcode.
Practically, we can modify the memory bombing code and add the heap spraying technique for efficient exploitation.
First, we allocate an 800MB memory block using the calloc function.
Then, we coded the heap spraying. We added a loop that allocates several smaller blocks (4KB each), into each of which the shellcode is injected. This loop allocates multiple memory blocks, decrypts to XOR encryption, and injects the shellcode into each assigned heap memory block after changing the permissions using the VirtualProtect function.
Merging ROP with Memory Bombing
Return-Oriented Programming (ROP) is a technique used to bypass security mechanisms like Data Execution Prevention (DEP). Instead of directly injecting and executing shellcode in memory, the attacker utilizes "gadgets" - existing pieces of code within the memory of a legitimate process, such as DLLs or built-in functions.
What makes ROP unique is that the attacker uses these gadgets to create a sequence of operations, allowing them to carry out malicious operations without the necessity of executing code directly from memory.
Memory Bombing is in charge of allocating a large amount of memory in a legitimate process. It can disrupt security mechanisms that scan the process memory dynamically. When combined with ROP, the chances of a successful attack increase.
The following piece of code shows how to use the ROP technique to make memory bombing exploitation much stealthier. In the code, we’re using the previous memory bombing code we used for the Heap Spraying technique, in which we allocated 800MB of block memory.
For the ROP, we retrieved the VirtualProtect function address from the kernel32.dll using the GetProcAddress API to modify the memory permissions and allow the shellcode to execute.
Investigating Memory Bombing Exploitation Through Memory Analysis
Volatility is an open-source tool for memory analysis. It allows analysis of memory images, including the detection of suspicious processes and abnormal memory allocations. With Volatility, researchers can detect abnormal memory processes such as excessive memory usage (like Memory Bombing), code injection, changing memory permissions, and more.
When obtaining a memory image of an infected endpoint, we will start by listing the processes present in a specific Windows memory image.
vol.py -f memdump.mem windows.pslist.PsList
In the screenshot below, we can see two suspicious processes running when the memory was dumped. The first process is memoryBombing.exe, and its PID (Process ID) is 11536. The second process is cmd.exe, and its PPID (Parent Process ID) is 11536. These indicators suggest that the suspicious process memoryBombing.exe initiated a cmd.exe process.
Then, we’ll list process memory ranges that potentially contain injected code. The Malfind plugin will help detect malicious code injected into a process.
vol.py -f memdump.mem windows.malfind.Malfind
In the screenshot provided, we observed that a shellcode was injected into the memoryBombing.exe process. This can be determined based on the following indicators:
- The first four magic bytes are 4D 5A, which typically represent a shellcode, EXE, or DLL file.
- The use of memory protection PAGE_EXECUTE_READWRITE, which signifies read, write, and execute permissions.
To strengthen our claim that the attacker injected a shellcode to gain initial access, such as a reverse shell, we will list the network objects in a specific Windows memory image to identify any connections related to the suspicious process.
vol.py -f memdump.mem windows.netscan.NetScan
In the screenshot below, we noticed that the memoryBombing.exe process established a connection from the victim’s endpoint to the attacker’s IP address on port 443.
Mitigating Memory Bombing
ASLR (Address Space Layout Randomization): This mechanism randomizes memory locations, making it difficult for attackers to predict where to inject malicious code.
DEP (Data Execution Prevention): This mechanism prevents code execution from memory regions that are meant only for data storage, like the stack and heap.
NX (No-eXecute): This is a Hardware-level protection mechanism that ensures code cannot run in non-executable regions, such as writable areas.
Heap Integrity Checks: Identifies abnormal memory allocations or corruption in the heap, flagging suspicious behavior.
Conclusion
Memory Bombing represents an advancement in stealth techniques used by attackers to bypass security measures. By leveraging the inherent properties of virtual memory and exploiting the limitations of dynamic engines, this method can successfully evade detection and execution prevention mechanisms, such as EDR and antivirus software.
When combined with methods like Heap Spraying and Return-Oriented Programming (ROP), the attack becomes even more stealthy and effective. Heap Spraying increases the chance of successful shellcode execution by spreading it across large portions of memory, while ROP manipulates existing code within the process to bypass protections like DEP.
To mitigate the risks associated with Memory Bombing, security researchers must implement advanced memory management techniques, integrate continuous monitoring of memory usage patterns, and use security mechanisms like ASLR, DEP, and NX.