The Blue Report 2024
Get a comprehensive analysis of over 136 million cyber attacks and understand the state of threat exposure management.
Process injection is a technique employed by threat actors to enhance their ability to remain undetected, persist within a victim's system, and potentially access higher levels of privileges. This method involves the insertion of malicious code into a legitimate process, thereby enabling the attacker to run their code in the context of that process. The strategy effectively masks the malicious activity, helping it to evade basic detection mechanisms.
In the Red Report 2024, this technique has emerged as the most prevalent MITRE ATT&CK Technique due to its extensive array of advantages for adversaries.
|
The Red Report 2024
|
Adversary Use of Process Injection
Adversaries may use Process Injection for various purposes, including evading detection, maintaining presence within a system, and accessing process resources such as memory and network.
It is a typical security practice to list all the processes running on a system and identify the malicious processes among the legitimate ones that are part of the operating system or installed software with recognizable names and file paths. Security mechanisms scan for processes that exhibit unusual characteristics, such as non-standard file paths or abnormal behavior, which may indicate a potential threat. Such processes are swiftly flagged as suspicious and can be killed to protect the system.
However, when adversaries embed their malicious code into an existing, trusted process, they create a challenge for detection efforts. This stealth tactic, known as Process Injection, allows the intrusive code to run unnoticed within the memory space of another process, making it particularly difficult for security defenses to detect and neutralize the threat.
Process injection provides two significant benefits for adversaries:
1. Privilege Escalation
If the target process has elevated privileges, the injected code will also have access to those privileges, allowing the adversary to gain greater control over the system and potentially escalate their privileges even further. For instance, if a target process has access to network resources, then the malicious code encapsulated within this process may allow an adversary to communicate over the Internet or with other computers on the same network. This privilege can enable the adversary to carry out various malicious activities, such as downloading next-stage payloads or tools, exfiltrating sensitive data, spreading malware to other systems, or launching attacks against the network.
2. Defense Evasion
An adversary can evade security controls designed to detect and block known threats by executing their malicious code under the privileges of a legitimate process. As the malicious code is hidden within the legitimate process, which is typically allow-listed, the target process acts as a camouflage for the malicious code, allowing the malicious code to evade detection and run without being noticed. Since the code is typically run directly in the memory of the legitimate process, it is difficult for disk forensics tools to detect the code, as it is not written to the disk.
Legitimate Processes Used for Process Injection
Security controls may quickly detect custom processes with unfamiliar names. Therefore, attackers use common native built-in Windows processes, such as:
- AppLaunch.exe - Application Launcher,
- arp.exe - Address Resolution Protocol Utility,
- cmd.exe - Command Prompt,
- conhost.exe - Console Window Host,
- csrss.exe - Client/Server Runtime Subsystem,
- ctfmon.exe - CTF Loader
- cvtres.exe - Microsoft Resource File To COFF Object Conversion Utility,
- dllhost.exe - COM Surrogate,
- dwm.exe - Desktop Window Manager,
- explorer.exe - Windows Explorer,
- lsass.exe - Local Security Authority Subsystem Service,
- msbuild.exe - Microsoft Build Engine,
- PowerShell.exe - Windows PowerShell,
- regsvr32.exe - Register Server,
- RegAsm.exe - Assembly Registration Tool,
- rundll32.exe - Run a DLL as an App,
- services.exe - Services Control Manager,
- smss.exe - Session Manager Subsystem,
- spoolsv.exe - Print Spooler Service,
- svchost.exe - Service Host,
- System - System Process,
- taskhost.exe - Host Process for Windows Tasks,
- vbc.exe - Visual Basic Command Line Compiler,
- wininit.exe - Windows Start-Up Application,
- winlogon.exe - Windows Logon Process,
- wmiprvse.exe - WMI Provider Host,
- wscntfy.exe - Windows Security Center Notification App,
- wuauclt.exe - Windows Update AutoUpdate Client.
Attackers also use processes of commonly used software, such as browsers, antiviruses, office tools, and utilities. Examples:
- acrobat.exe - Adobe Acrobat,
- avg.exe - AVG AntiVirus,
- chrome.exe - Google Chrome,
- dropbox.exe - Dropbox,
- excel.exe - Microsoft Excel,
- firefox.exe - Mozilla Firefox,
- ieuser.exe - Internet Explorer User,
- iexplore.exe - Internet Explorer,
- jucheck.exe - Java Update Checker,
- mcafee.exe - McAfee Antivirus,
- notepad.exe - Notepad,
- opera.exe - Opera Browser,
- outlook.exe - Microsoft Outlook,
- photoshop.exe - Adobe Photoshop,
- vmwaretray.exe - VMware Tray,
- winword.exe - Microsoft Word,
- wordpad.exe - Wordpad.
Methods of Target Process Selection
Adversaries use the following methods when picking their target process for malicious code injection:
1. Hardcoded Targeting
In the first scenario, an adversary can hardcode a particular target process in the malicious code, and only this process is used to host the injected code. explorer.exe and rundll32.exe are the two most commonly leveraged processes for this type of attack. For instance, RedLine Stealer malware is known to target the Visual Basic Compiler used with the .NET Framework. The malware injects its payload into the vbc.exe to evade detection [2].
An attacker can also define a list of target processes in the code, and the injected code is executed in the first process on the list that is found to be running on the system. These lists typically include native Windows and browser processes.
2. Dynamic Targeting
In this attack scenario, an adversary does not define the target process beforehand and instead locates a suitable host process at runtime. It is common for adversaries to use Windows API functions to enumerate the list of all currently active processes and to get a handle on each target process in attack campaigns. The specific API functions that are used will depend on the goals of the attack and the capabilities of the adversary, but some common examples include EnumProcesses(), EnumProcessModules(), CreateToolhelp32Snapshot(), and OpenProcess().
Sub-techniques of Process Injection
There are 12 sub-techniques under the Process Injection technique in ATT&CK v14:
ID |
Name |
T1055.001 |
Dynamic-link Library Injection |
T1055.002 |
Portable Executable Injection |
T1055.003 |
Thread Execution Hijacking |
T1055.004 |
Asynchronous Procedure Call |
T1055.005 |
Thread Local Storage |
T1055.008 |
Ptrace System Calls |
T1055.009 |
Proc Memory |
T1055.011 |
Extra Window Memory Injection |
T1055.012 |
Process Hollowing |
T1055.013 |
Process Doppelgänging |
T1055.014 |
VDSO Hijacking |
T1055.015 |
ListPlanting |
Each of these sub-techniques will be explained in the next sections.
#1.1. T1055.001 Dynamic-link Library Injection
Dynamic-link libraries (DLLs) are a fundamental concept in the Windows operating system. DLLs are files that contain compiled code and data used by multiple programs and processes on a computer. When a process calls a function in a DLL, the operating system loads the DLL into memory and jumps to the function in the DLL. DLLs save users' time and effort by allowing them to use the same code in multiple programs without recompiling all of the code every time any change is made.
DLLs promote modular architecture by allowing software developers to compartmentalize functionalities into different DLL files. This feature also makes adding new functionalities and maintaining existing ones easier. When developers want to use a DLL in your program, they typically include a header file that declares the functions in the DLL and links their program to the DLL at runtime. The #include directive in C and C++, and the import statement in Python and Java are common examples of declaring DLLs in programs.
Adversary Use of Dynamic-link Library (DLL) Injection
The main feature of DLLs can be a security risk in the wrong hands as they allow programs to use code from other programs. If a DLL contains malicious code, it can execute it when loaded into memory, which can compromise the security of your program.
Adversaries can manipulate DLLs in different ways to execute malicious actions on the target system. The most common method is injecting malicious code into a DLL that is already loaded in memory. This technique is called DLL injection, and it allows adversaries to execute their malicious code in the context of the program that is using the DLL, effectively masquerading the malicious activities as legitimate operations of the host application.
Once the adversary has successfully injected a malicious DLL into a process, they can perform a variety of actions depending on the nature of the injected code. For example, if the application has access to credentials, the malicious DLL may be able to capture and transmit these credentials. Moreover, malicious DLLs can hook into system calls and modify them to bypass security controls. To persist in the compromised system, injected DLLs can be used to ensure the adversary maintains access to the system even after reboots or updates.
A typical DLL injection attack follows these steps:
1- Identifying the target process: DLL injection starts with identifying the process to inject the malicious DLL. Adversaries search for processes on the system using various APIs:
- CreateToolhelp32Snapshot - provides a snapshot of all running processes, threads, loaded modules, and heaps associated with processes.
- Process32First - provides a way to access information about the first process encountered in the snapshot of all active processes on the system. Since a snapshot of all processes is a complex set of data, the Process32First is a useful function to retrieve information about each individual process.
- Process32Next - helps in iterating through the list of processes, one by one, after the initial process has been accessed using Process32First.
These APIs allow adversaries to enumerate the list of processes currently running on the system and gather information about each process, such as its name, ID, and path.
2- Attaching to the process: After identifying the target process, adversaries use the OpenProcess function to obtain the target process's handle. This handle can then be used to perform various operations on the process, such as reading from or writing to its memory or querying for information.
3- Allocating memory within the process: Adversaries then call the VirtualAllocEx function with the target process's handle and allocate memory in the virtual address space of the process. The output of VirtualAllocEx is a pointer to the start of a block of memory allocated in another process's virtual address space. This pointer is a crucial handle for further operations on the allocated memory, enabling processes to interact with and manipulate memory in other processes within the security and operational confines set by the Windows operating system.
4- Copying DLL or the DLL path into process memory: To write into the allocated memory, adversaries use the WriteProcessMemory function and write the path to their malicious DLL. Adversaries also use the LoadLibraryA function in the kernel32.dll library to load a DLL at runtime. LoadLibraryA allows adversaries to write the DLL path or determine offset for writing full DLL. It accepts a filename as a parameter and returns a handle to the loaded module.
5- Executing the injected DLL: Instead of managing threads within the target process, adversaries often create their own threads using the CreateRemoteThread function. Additionally, the NtCreateThreadEx or RtlCreateUserThread API functions can be utilized to execute code in another process' memory. The method usually consists of passing the LoadLibrary address to one of these two APIs, which requires a remote process to execute the DLL on the malware's behalf [3].
Since the LoadLibrary function registers the loaded DLL with the program, security controls can detect malicious activity, presenting a challenge for adversaries. To avoid being detected, some adversaries load the entire DLL into memory and determine the offset to the DLL's entry point. This action may allow adversaries to inject the DLL into a process without registering it and remain hidden on the target system.
The Reflective DLL Injection is an alternative technique that allows adversaries to inject DLLs into processes. Instead of using standard Windows API functions like LoadLibrary() and GetProcAddress(), the DLL loads and executes itself within the target process using techniques like parsing the Export Address Table (EAT) to locate the addresses of key API functions like LoadLibraryA() and GetProcAddress(). With the Reflective DLL Injection technique, adversaries inject DLLs into the process without the need to call these functions directly.
DLL injection is commonly employed by adversaries in the wild. For example, the North Korean threat group UNC2970 developed and used a malware named LidShift in various campaigns. LidShift is capable of injecting encrypted DLL into memory using the reflective DLL injection technique. When executed, it injects a DLL disguised as a Notepad++ plugin and loads another malware named LidShot. It is a malware downloader that can perform system enumeration and deploy other malicious payloads on the compromised system [4].
In some cases, adversaries were observed to combine shellcode execution and reflective DLL injection. This method is called the Shellcode Reflective DLL Injection (sDRI) technique, and it allows adversaries to execute a DLL within the memory of a target process without having to rely on the standard Windows loading mechanisms. Rhadamanthys Stealer V0.5.0 uses the sRDI technique to inject its main module into another process [5].
#1.2. T1055.002 Portable Executable Injection
Portable Executable (PE) is a file format for executables, object code, and DLLs in Windows operating systems. PE provides a standardized way for the operating system to manage and execute applications, including handling the various aspects of code and data involved in complex software programs. PE injection involves the injection of a PE file, such as an EXE or DLL, into the memory space of another process running on a Windows operating system to execute arbitrary code within the context of the target process. Adversaries typically inject a small piece of malicious shellcode or call the CreateRemoteThread function to create a new thread.
The Portable Executable (PE) file format is designed to encapsulate the necessary information for the Windows loader to manage and execute the code contained within it. This structure includes various headers and sections, each serving a distinct purpose in the organization and execution of the file.
The PE file format is an important part of the Windows OS architecture and is designed to support the execution and management of applications.
Adversary Use of Portable Executable Injection
PE injection attacks follow a path similar to DLL injection. The difference lies in the use of the WriteProcessMemory function. Instead of writing the path to the malicious DLL within the allocated memory of the target process, adversaries write their malicious code in that memory.
Although it seems stealthy, PE injection has an inherent challenge. When adversaries inject their PE into the target process's memory, the injected code acquires an unpredictable new base address. To overcome this problem, adversaries design their malware to locate the host process's relocation table address and resolve the cloned image's absolute addresses via a loop over its relocation descriptors.
Below is the general attack lifecycle of PE Injection:
1- Process Handle Acquisition: Attackers obtain a handle to the target process using the OpenProcess Windows API with appropriate access rights, allowing them to perform operations such as memory manipulation within the target process.
2- Selecting and Preparing the PE File: The appropriate PE file to be injected is selected. Attackers determine the PE's preferred image base address, which is the address where the code expects to be loaded in memory. The size of the PE, necessary for its operation in memory, is acquired.
3- Local Memory Allocation and PE Copy: A block of memory is allocated within the attacker's local process, copying the selected PE image here. This action allows attackers to modify the PE image if needed before injection, including accommodating new base addresses or resolving addresses of imported functions.
4- Allocating Memory in Target Process: Using VirtualAllocEx, attackers allocate memory in the target process's address space, creating space for the injected PE file. This space must be sufficient to hold the entire PE file and have execute-read-write permissions. The base address of this memory block is referred to as target_address.
5- Calculating Delta and Patching PE: The delta between the local copy's address (local_address) and the target allocation (target_address) is calculated to aid any necessary relocations within the PE file to match the target address space. The PE file is then patched or adjusted based on the delta to ensure it will execute correctly when loaded at the target_address instead of its preferred base address.
6- Injecting the PE into the Target Process: The patched PE file is transferred from the attacker's local process to the allocated memory block in the target process using WriteProcessMemory. This ensures the entire image is correctly positioned in memory where it can be executed.
7- Executing Injected PE: A remote thread is created within the target process using CreateRemoteThread, with its entry point set to the InjectionEntryPoint function of the now-injected PE file. This triggers the execution of the injected PE, effectively starting the malicious code in the context of the target process.
Throughout this lifecycle, attackers must carefully handle the PE file and the target process to ensure successful injection and execution. This includes dealing with potential hurdles like Address Space Layout Randomization (ASLR), which can change base addresses, and ensuring that any dependencies (like specific DLLs or system resources) are correctly resolved.
Portable Executable (PE) injection attack is commonly leveraged in the wild. In fact, in June 2023, the DarkGate Malware-as-a-Service threat group released version 4 of the DarkGate malware [6]. Its rootkit capabilities allow adversaries to inject code or binaries into different processes using the portable executable injection technique.
#1.3. T1055.003 Thread Execution Hijacking
Thread Execution Hijacking is a technique that allows an attacker to execute arbitrary code in the context of a separate process on a computer. It involves injecting code into a process that is already running on the system and then redirecting the execution of one of the threads in that process to the injected code.
Adversary Use of Thread Execution Hijacking
Thread execution hijacking is a technique that allows an attacker to execute arbitrary code in the context of a separate process on a computer. It involves injecting code into a process that is already running on the system and then redirecting the execution of one of the threads in that process to the injected code.
To perform this technique, an attacker would first need to find a suitable process to hijack. This could be a process that is running with high privileges or a process that is trusted by other programs on the system. Once found, malware suspends the target process, unmaps/hollows its memory, and then injects malicious shellcode or DLL into the process. Finally, they would need to redirect the execution of a thread in the process to the injected code.
This technique is similar to the process hollowing technique, but instead of creating a new process in a suspended state, it aims to find an already existing process on the target system. Below is the general attack lifecycle typically followed by adversaries performing Thread Execution Hijacking attacks:
1- Process Handle Acquisition: The attacker acquires a handle to the target process that they want to inject code into. This involves using the OpenProcess API with appropriate access rights, such as PROCESS_VM_OPERATION, PROCESS_VM_WRITE, and PROCESS_VM_READ.
2- Thread Suspension: Once the handle to the process is obtained, the attacker identifies a thread within that process to hijack. The OpenThread API is then used to get a handle on this thread, which is suspended using SuspendThread to prevent it from executing any more instructions while the attack is carried out.
3- Memory Allocation: After successfully suspending the thread, the attacker allocates memory in the virtual address space of the target process. This is typically done with VirtualAllocEx, specifying MEM_COMMIT and PAGE_EXECUTE_READWRITE as the desired memory state and protection. This ensures that the allocated memory is both executable and writable.
4- Writing Shellcode: With the memory allocated, the attacker writes their malicious payload (shellcode) to the allocated space using the WriteProcessMemory function, which copies data from the attacker's buffer to the allocated memory in the target's process space.
5- Hijacking Thread Context: The attacker then hijacks the thread's execution context by retrieving it with GetThreadContext, which includes register values. The EIP register (on x86 architectures) or RIP register (on x86-64 architectures) within the context is set to point to the address of the shellcode in the allocated memory.
6- Context Manipulation: After altering the context to point to the malicious code, SetThreadContext is used to apply the modified context to the suspended thread. This changes the execution flow of the thread to the injected shellcode.
7- Thread Resumption: Finally, the attacker resumes the thread with the ResumeThread function. The thread will continue execution at the new entry point specified by the altered EIP/RIP register, thereby executing the attacker's malicious code within the context of the target process.
One example is from February 2023, where Pipedance Backdoor malware was observed to use a thread execution hijacking technique when running under a 32-bit architecture [7]. The following code snippet is used by Pipedance to allocate memory in a target process, set a thread's instruction pointer to that memory, allow the execution of code there, and write some data into that memory, effectively hijacking the thread to execute arbitrary code. If any step in this process fails, it tries to clean up by freeing the allocated memory and returns the error code encountered during the process.
LastError = 0; |
Download the Red Report - Top Ten MITRE ATT&CK Techniques
#1.4. T1055.004 Asynchronous Procedure Call
Asynchronous procedure calls (APCs) are functions executed asynchronously within a specific thread's context. When an APC is queued to a thread, it is added to the thread's APC queue. When the thread is scheduled to run again, it checks its APC queue for any pending APCs and executes them before continuing with its normal execution. Malware developers often exploit this mechanism by attaching malicious code to the APC queue of a target thread.
An Asynchronous Procedure Call (APC) is a mechanism that allows a thread to execute a function asynchronously in the context of another thread. APCs are used in the Windows operating system to perform various tasks, such as allowing a thread to wait for the completion of an I/O operation or to perform a task in the context of a different thread.
APCs are queued to a thread's APC queue, and the thread is notified when an APC is ready to be executed. The thread can then execute the APC by calling the function KeWaitForSingleObject with the APC object as a parameter.
There are two types of APCs: kernel APCs and user APCs. Kernel APCs are executed in the context of the system kernel, while user APCs are executed in the context of a user-mode process.
APCs are often used in the implementation of Windows device drivers to perform tasks such as reading and writing data to a device. They are also used by system libraries and applications to perform tasks asynchronously, such as waiting for the completion of an I/O operation.
Adversary Use of Asynchronous Procedure Call (APC)
One way that adversaries may use APCs is by queuing a kernel APC to the APC queue of a system thread, such as a thread that is running with elevated privileges. When the APC is executed, the code will be executed in the context of the system thread, allowing the adversary to perform actions with the privileges of the thread.
Another way that adversaries may use APCs is by injecting a PE into a process and using an APC to execute code from the injected PE within the context of the process. This can be used to evade security measures that are designed to prevent the injection of code into a process, as the APC is executed in a way that is transparent to the process itself.
Unlike the previous methods, which involve direct manipulation of thread contexts or PE images that may be detected by security defenses, APC injection queues a function to be executed when the thread is in an alertable state.
Here's an overview of the APC injection attack lifecycle:
1- Process and Thread Handle Acquisition: The attacker obtains a handle to a target process using OpenProcess with necessary privileges, such as PROCESS_VM_OPERATION and PROCESS_VM_WRITE. Then, a thread within the target process is targeted. A handle to this thread is obtained via OpenThread, with access rights that allow APC queuing (e.g., THREAD_SET_CONTEXT).
2- Memory Allocation in Target Process: Using VirtualAllocEx, the attacker allocates memory within the target process's address space, where the malicious payload (shellcode) will be placed. The memory permissions are set to allow read, write, and execute actions, often PAGE_EXECUTE_READWRITE.
3- Writing Shellcode: The attacker writes the malicious code into the allocated memory section within the target process via WriteProcessMemory.
4- Queueing the APC: An APC is queued to the target thread using QueueUserAPC. The APC points to the shellcode in the allocated memory area. APCs will only run when the thread enters an alertable state, which can be achieved by calling certain functions such as SleepEx, SignalObjectAndWait, or WaitForSingleObjectEx with the appropriate flags to put the thread in an alertable state.
5- Triggering Execution: The attacker waits for the thread to enter an alertable state or triggers such a state themselves. When the thread becomes alertable, the queued APC is executed, and consequently, the malicious shellcode runs within the context of the target thread.
Asynchronous Procedure Shell (APC) also had its share among adversaries in 2023. The DarkGate Malware-as-a-Service group uses APC injection via NtTestAlert to execute arbitrary code within the address space of another process and evade detection [8]. After allocating memory within the target process, DarkGate writes its malicious code into the memory space. Adversaries use the NtQueueApcThread call to queue the address of this memory as an APC in the target thread. After creating a new process in the suspended state, the malware appends the handler of the process to the newly created APC queue. By executing the syscall NtTestAlert, the malware resumes the thread and causes the target process to execute any pending APCs.
#1.5. T1055.005 Thread Local Storage
Thread Local Storage (TLS) callback injection is a technique that involves manipulating pointers within a PE file to redirect a process to malicious code before it reaches the legitimate entry point of the code. TLS is a mechanism that allows threads to have their private storage area. The OS uses TLS callbacks to initialize and clean up data used by threads. These callbacks are functions that the OS calls when a thread is created or terminated.
Thread Local Storage (TLS) allows each thread in a process to have its own instance of a global variable. This can be useful in cases where multiple threads need to access global data, but the data needs to be unique for each thread.
Adversary Use of Thread Local Storage
Attackers use TLS callbacks to inject and execute malicious code at the start of a program's execution or whenever a new thread is created. For example, in March 2023, a Chinese cyber espionage group was observed to utilize TLS Callback injection in attack campaigns against the telecommunication sector [9].
Here's how TLS callback injection typically works:
1- Select Target Application: The attacker chooses a target application, which should preferably have TLS callbacks or be modified to include them.
2- Analyze or Modify TLS Directory: If the target application already utilizes TLS, the attacker can hook or replace existing TLS callbacks with malicious ones. Otherwise, the attacker must modify the PE file of the application to include a TLS directory. This entails altering the PE header and possibly adding new sections to the file.
3- Write Malicious Callback: The attacker writes a malicious TLS callback function. This function should be designed to perform whatever malicious activities the attacker desires, such as setting up a backdoor or executing a payload.
4- Inject Malicious Callback: Using a tool or exploit, the attacker injects the address of the malicious callback into the TLS callback table of the target application. This can involve directly modifying the binary on disk or in memory to point to the attacker's code rather than legitimate initialization functions.
5- Execute Target Application: Upon execution of the target application, the Windows Loader processes the PE file and executes all TLS callbacks before reaching the main entry point of the application or whenever a new thread that uses TLS is created.
6- Callback Execution: When the malicious TLS callback is executed, it runs the attacker's code within the context of the application's process. This activation occurs in the early stages of the program's start-up, making the injected code one of the first things to run.
#1.6. T1055.008 Ptrace System Calls
The ptrace() function is a system call in Unix and Unix-like operating systems that enables one process, controller, to manipulate and observe the internal state of another process, tracee. Ptrace system call injection is a technique that involves utilizing the ptrace() system call to attach to an already running process and modify its memory and registers. This technique can be utilized for a range of purposes, including injecting code into a process to alter its behavior.
Ptrace is a system call that allows one process (the tracer) to control another process (the tracee) and observe its execution. It is used by debuggers and other tools to perform tasks such as inspecting the memory and registers of a process, modifying its execution, and single-stepping its instructions.
Ptrace is implemented as a set of system calls in Unix-like operating systems, such as Linux. It is used by specifying the ptrace function and a set of arguments that specify the operation to be performed and the process to be traced.
Some common operations that can be performed using ptrace include:
- Reading and writing the memory and registers of the tracee
- Setting breakpoints in the tracee's code
- Single-stepping the tracee's instructions
- Attaching to and detaching from a running process
Ptrace is a powerful tool that can be used for a variety of purposes, including debugging, reverse engineering, and malware analysis. It can also be used by adversaries to inspect and modify the execution of processes on a system, which can be used to evade detection and achieve persistence.
Adversary Use of Ptrace System Calls
Here's how an attacker might use the ptrace system call to perform code injection:
1- Attaching to the Target Process: The attacker's process uses ptrace with the PTRACE_ATTACH option to attach to the target process. This causes the target process to pause execution and become traceable by the attacker's process.
2- Waiting for the Target Process to Stop: The attacker's process waits for a signal from the target process that indicates it has stopped and is ready for tracing. This is typically done by listening for a SIGSTOP signal.
3- Injection Preparation: The attacker locates or allocates a section of memory within the target process's address space, where the malicious code (often referred to as shellcode) will be injected. This may involve searching for existing executable memory regions or allocating new memory using ptrace to invoke the mmap system call in the target process.
4- Copying the Shellcode: Using ptrace with the PTRACE_POKEDATA or PTRACE_POKETEXT operation, the attacker writes the shellcode byte by byte into the allocated memory space of the target process.
5- Setting Instruction Pointer: With the shellcode in place, the attacker uses ptrace to set the instruction pointer (IP) register (e.g., EIP on x86, RIP on x86_64) of the target process to the address of the injected code.
6- Resuming Target Process Execution: After the shellcode is in place and the instruction pointer is set, the attacker resumes the execution of the target process using ptrace with the PTRACE_CONT option, causing the target process to jump to and execute the injected shellcode.
7- Detaching from the Target Process (if applicable): Once the code has been executed, and if further interaction with the target process is not needed, the attacker process can use ptrace with the PTRACE_DETACH option to detach from the target process and allow it to continue execution normally.
Ptrace system call injection is a powerful method of executing arbitrary code in the context of another process and can be used by attackers to manipulate or spy on target applications, or to run malicious payloads without requiring a binary file on disk. However, modern Linux distributions have security mechanisms like Yama and SELinux that can restrict ptrace usage to prevent debugging by unauthorized users and, thus, mitigate this kind of attack.
#1.7. T1055.009 Proc Memory
In Unix-like operating systems, the /proc filesystem is a virtual filesystem that provides access to information about processes running on a system. Proc memory injection involves enumerating the process's memory through the /proc filesystem and constructing a return-oriented programming (ROP) payload. ROP is a technique that involves using small blocks of code, known as "gadgets," to execute arbitrary code within the context of another process.
As mentioned, the /proc filesystem is implemented as a virtual filesystem, meaning that it does not exist on a physical storage device. Instead, it is a representation of the system's processes and their status, and the information it contains is generated on demand by the kernel.
One of the things that the /proc filesystem provides access to is the memory of the processes that are running on the system. For example, the /proc/[pid]/mem file can be used to access the memory of a process with the specified pid (process ID). The /proc/[pid] directory contains several files that provide information about the process, such as its memory mappings, open file descriptors, and so on. This can be useful for tasks such as debugging or reverse engineering, as well as for detecting and mitigating vulnerabilities in a process's memory.
Adversary Use of Proc Memory
To perform proc memory injection, an attacker first enumerates the process's memory by accessing the /proc/[pid] directory for the target process. Upon accessing the /proc/[pid], the attacker can examine the process's memory mappings to locate gadgets, which are small blocks of code that can be used to execute arbitrary code within the context of the process. Gadgets are typically found in the process's code segments, such as the text segment, which contains the instructions that make up the program.
Here is an example gadget that can be used to execute arbitrary code in the context of a process:
# pop the address of the code to execute into the rdi register |
This gadget consists of two instructions: a "pop" instruction that pops an address off the top of the stack and stores it in the rdi register, and a "ret" instruction that returns to the address stored in the rdi register.
To use this gadget, an attacker could redirect the execution flow of the process to the gadget and then push the address of their own code onto the stack. The pop instruction would then pop this address off the stack and store it in the rdi register, and the ret instruction would return to the address stored in the rdi register, causing the attacker's code to be executed.
Gadgets are useful for an attacker because they allow them to execute code without having to inject their own code into the process's memory. Instead, they can use gadgets that are already present in the process's code segments to execute their own code. To find gadgets, an attacker can use tools (such as ROPgadget, Ropper, and ROPChain) that search the process's memory mappings for specific instructions or instruction sequences.
For instance, adversaries can leverage the ROPgadget tool with the following attack lifecycle:
1- The first step for the attacker will be finding the target process where he wants to inject the code.
2- Then the attacker uses ROPgadget to find gadgets in the binary of the target process, looking for gadgets that can be used to change the flow of execution, such as gadgets that can be used to jump to a specific memory address or gadgets that can be used to call a specific function.
3- Once the attacker has identified a sufficient number of gadgets, they can construct an ROP payload by chaining together the gadgets in a specific order.
4- The payload can then be injected into the process's memory using techniques such as Ptrace System Call injection or by exploiting a vulnerability in the process.
5- Once the payload is executed, it allows the attacker to execute arbitrary code within the context of the process.
#1.8. T1055.011 Extra Window Memory Injection
Extra Window Memory Injection (EWMI) is a technique that involves injecting code into the Extra Window Memory (EWM) of the Explorer tray window, which is a system window that displays icons for various system functions and notifications. This technique can be used to execute malicious code within the context of the Explorer tray window, potentially allowing the attacker to evade detection and carry out malicious actions.
In the Windows operating system, a window class is a data structure that specifies the appearance and behavior of a window. When a process creates a window, it must first register a window class that defines the characteristics of the window. As part of this registration process, the process can request that up to 40 bytes of extra memory (EWM) be allocated for each instance of the class. This extra memory is intended to store data specific to the window and can be accessed using specific API functions, such as GetWindowLong and SetWindowLong. These functions take the window handle as the first argument and the index of the field to be retrieved or set as the second argument. The field values are stored in the form of "window longs."
Adversary Use of Extra Window Memory Injection
The EWM is large enough to store a 32-bit pointer, which can point to a Windows procedure (a.k.a Window proc). A window procedure is a function that handles input and output for a window, including messages sent to the window and actions performed by the window. Malware may attempt to use the EWM as part of an attack chain in which it writes code to shared sections of memory within a process, places a pointer to that code in the EWM, and then executes the code by returning control to the address stored in the EWM.
This technique, known as Extra Window Memory Injection (EWMI), allows the malware to execute code within the context of a target process, giving it access to both the process's memory and potentially elevated privileges. Malware developers may use this technique to avoid detection by writing payloads to shared sections of memory rather than using API calls like WriteProcessMemory and CreateRemoteThread, which are more closely monitored. More sophisticated malware may also bypass security measures like data execution prevention (DEP) by triggering a series of Windows procedures and other system functions that rewrite the malicious payload within an executable portion of the target process. This allows the malware to execute its code while bypassing DEP and other protection mechanisms.
Attackers can inject malicious code into this space and execute it, which can be particularly stealthy, given that EWM is a legitimate and less commonly monitored part of a window object. The essence of this technique is to place malicious code into the EWM and then have it executed, often through a callback function like a window procedure (the function that receives and processes all messages sent to a window).
Here's a high-level overview of how Extra Window Memory Injection typically works:
1- Identify Victim Application: The attacker selects a target Windows application that has a window with extra memory allocated.
2- Allocate or Find EWM: If the attacker has control over the application's source code or can alter it through other injection methods, they may directly allocate extra memory for a window using the RegisterClassEx or CreateWindowEx Windows API functions. Alternatively, the attacker finds a window class with previously allocated EWM.
3- Inject Malicious Code into EWM: The attacker uses an appropriate API, such as SetWindowLongPtr with GWL_USERDATA or a similar flag, to copy the malicious code into the EWM of the target window.
4- Trigger Execution: To execute the injected shellcode, the attacker will typically set up a scenario where a message sent to the target window causes the window procedure to jump to the EWM and execute the shellcode. This could be via a crafted message that manipulates the execution flow or by modifying the window procedure pointer directly to point to the injected code.
#1.9. T1055.012 Process Hollowing
Process Hollowing is a sub-technique that adversaries generally use to bypass process-based defenses by injecting malicious code into a suspended or hollowed process. Process hollowing involves creating a process in a suspended state, then unmapping or hollowing out its memory and replacing it with malicious code. This allows the attacker to execute their code within the context of the target process.
Adversary Use of Process Hollowing
Process hollowing is a technique used by malware to hide its code execution within the memory of a legitimate process. The malware begins by creating a new, suspended process of a legitimate, trusted system process. It then hollows out the contents of the legitimate process's memory, replacing it with the malicious code, and resumes the execution of the process. This can make it more difficult for security software to detect the presence of the malware, as it is running within the context of a trusted process. The legitimate process's original code is usually unmapped from memory, so it is no longer visible to the operating system.
An example Process Hollowing attack is given below.
1- Create a suspended process: This initial step is about creating a suspended process, which adversaries will later use to hollow. To create a new process, the malware uses the CreateProcess function. As discussed before, this attack includes hollowing the memory of a suspended process. Thus, malware suspends this newly created process' primary thread via the CREATE_SUSPEND option used in the fdwCreate flag.
2- Hollow out the legitimate code: Malware hollows out the legitimate code from the memory of the suspended process. This is done by using particular API calls such as ZwUnmapViewOfSection or NtUnmapViewOfSection. The malware calls the ZwUnmapViewOfSection function to remove a previously mapped view of a section from the virtual address space of the target process. One important thing to add is that the ZwUnmapViewOfSection function is called from kernel mode, meaning that it is not intended to be called directly from user mode. To unmap a view of a section from the virtual address space of the target process from user mode, adversaries should use the NtUnmapViewOfSection function instead.
3- Allocate memory in the target process: Malware allocates memory in the target process via the VirtualAllocEx function. One critical thing to note is that malware uses the flProtect parameter to ensure that the code is marked as writeable and executable.
4- Write shellcode to the allocated memory: The adversary uses the WriteProcessMemory function to write the malicious code (also known as shellcode) to the allocated memory within the hollowed process.
5- Change the memory protection: The malware calls the VirtualProtectEx function to change the memory protection of the code and data sections in the target process to make it appear normal, meaning that the memory in these sections will be marked as readable and in the case of "Read/Execute", executable.
6- Retrieve the target thread's context: The target thread's context is retrieved using the GetThreadContext.
7- Update the target thread's instruction pointer: Malware updates the target thread's instruction pointer to point to the written shellcode that the malware has written in the fourth step. Following this, malware commits the hijacked thread's new context with SetThreadContext.
8- Resume the suspended process: The malware uses the ResumeThread to make the suspended process resume so that it can run the shellcode within.
Process Hollowing is commonly leveraged in the wild. Blister loader malware leverages several defense evasion techniques to deploy other malware stealthily, including process hollowing in a remote process [10]. Blister usually uses the rundll32.exe, werfault.exe, or Internet Explorer executable for remote process hollowing.
if(Engine::GetModuleHandle(&engine, 0x12453653u)) |
Using the CreateProcessInternalW API, the malware creates a new process of rundll32.exe in the suspended state. After allocating a new memory via ZwAllocateVirtualMemory, the payload is copied to the buffer, and the ZwWriteVirtualMemory API is used to write malicious code into the target process. To make the thread of the new process point to newly written code, the malware alters the entry point of the current thread via ZwGetContextThread and ZwSetContextThread. Using the NtResumeThreat API, the suspended state is resumed, and the target process starts executing the malicious payload hollowed into the target process.
Another process hollowing technique is used by the LokiBot infostealer malware [11]. The malware uses an obfuscated .NET file that executes the process hollowing to inject malicious code into aspnet_compiler.exe.
CALL to CreateProcessW from mscorwks.61781D16 |
Note that malware can be easy to notice as the CreateProcessW() function call has a flag value of CREATE_NO_WINDOW | CREATE_SUSPENDED.
Download the Red Report - Top Ten MITRE ATT&CK Techniques
#1.10. T1055.013 Process Doppelgänging
Transactional NTFS (TxF) is a feature in Windows that allows file operations on an NTFS file system volume to be performed as part of a transaction [12]. Transactions help improve applications' reliability by ensuring that data consistency and integrity are maintained even in a failure. Adversaries may abuse TxF to perform a technique called "process doppelgänging" which involves replacing the memory of a legitimate process with malicious code using TxF transactions.
Adversary Use of Process Doppelgänging
Process Doppelgänging is a fileless attack technique that allows an attacker to execute arbitrary code in the context of a legitimate process without writing any malicious code to disk. This technique can be used by malware to evade detection by security software that is designed to detect and block the execution of malicious code on a victim's machine.
One way in which process doppelgänging can be implemented is through the use of the Transactional NTFS (TxF) feature of the Windows operating system. TxF is a feature that allows applications to perform transactional operations on files, meaning that changes to the files are not committed until the transaction is completed. This can be used to ensure the integrity of the file system by rolling back any changes that are not completed correctly.
An attacker can use TxF to implement process doppelgänging by creating a new, suspended process and injecting malicious code into the process's memory. The attacker then creates a transaction and modifies the legitimate process's executable file within the context of the transaction. The attacker then commits the transaction, replacing the legitimate process's code with the malicious code. The legitimate process is then resumed, executing the malicious code within the context of the trusted process.
Process Doppelgänging is similar to the Process Hollowing technique, which also involves replacing the memory of a legitimate process with a malicious shellcode. What differentiates Process Doppelgänging from Process Hollowing is its use of Transactional NTFS (TxF) transactions to perform the injection, allowing the malware to evade detection more efficiently.
Below, you can find the four steps of the Process Doppelgänging sub-technique attack flow.
- Transact: A TxF transaction is created using a legitimate executable, and the file is then overwritten with malicious code. These changes are isolated and only visible within the context of the transaction.
- CreateTransaction() - called to create a transaction.
- CreateFileTransacted() - called to open a "clean" file transacted.
- WriteFile() - called to overwrite the file with a malicious shellcode.
- Load: A shared section of memory is created, and the malicious executable is loaded into it.
- NtCreateSection() - called to create a section from the transacted file.
- Rollback: The changes to the original executable are undone, effectively removing the malicious code from the file system.
- RollbackTransaction() - called to rollback the transaction to remove the changes from the file system.
- Animate: A process is created from the tainted section of memory, and execution is initiated.
- NtCreateProcessEx() and NtCreateThreadEx() - called to create process and thread objects.
- RtlCreateProcessParametersEx() - called to create process parameters.
- VirtualAllocEx() and WriteProcessMemory() - called to copy parameters to the newly created process's address space.
- NtResumeThread() - called to start execution of the doppelgänged process.
GhostPulse is a loader malware observed to use the process doppelgänging technique [13]. The malware follows the typical attack flow by leveraging the NTFS transactions to inject the final payload into a new child process. GhostPulse malware uses this technique to deploy other malware, such as NetSupport, Rhadamanthys, SectopRAT, and Vidar.
if(!sub_420ED((int *)a1)) core::roll_back_transcation((core::stage4::IAT ***)а1); |
In another example, the Malware-as-a-Server (MaaS) group LummaStealer was observed to use IDAT Loader to deploy LummaC2 via process doppelgänging [14]. When first executed, IDAT Loader uses DLL load order hijacking to load malicious DLLs and creates a cmd.exe process. This process then injects the LummaC2 payload into explorer.exe using the NtWriteVirtualMemory API call.
#1.11. T1055.014 VDSO Hijacking
VDSO Hijacking involves redirecting calls to dynamically linked shared libraries to a malicious shared object that has been injected into the process's memory. This allows adversaries to execute their code in the target process's address space, potentially giving attackers unauthorized access to the system.
Virtual Dynamic Shared Object (VDSO) is a special shared object that is dynamically linked into the address space of all user-space applications by the Linux kernel when executed.
A VDSO is implemented as a shared object that is mapped into the address space of each process that uses it. The VDSO contains a small number of functions that are frequently used by applications, such as time-related functions and functions for accessing the process ID and user ID.
When a process makes a VDSO system call, it executes the code stub for the desired system call from the VDSO page in its own memory rather than making a system call instruction to the kernel. This avoids the overhead of a system call instruction, such as the cost of switching between user mode and kernel mode, and allows the process to execute the system call more efficiently.
Adversary Use of VDSO Hijacking
The VDSO is intended to be used only by the operating system and trusted applications, as it provides direct access to kernel functions. However, it has been exploited by malware in the past to gain access to kernel functions and perform malicious actions on a victim's machine. For example, malware may use the VDSO to bypass security measures or to gain elevated privileges.
VDSO hijacking is a technique that adversaries can use to inject malicious code into a running process by exploiting the VDSO feature in the Linux operating system. This feature allows processes to make certain system calls without the overhead of a system call instruction by providing a fast interface in the form of code stubs that are mapped into the process's memory.
There are two main methods by which adversaries can perform VDSO hijacking:
1. Patching the Memory Address References
In the first method of VDSO hijacking, an adversary patches the memory address references stored in the process's global offset table (GOT) to redirect the execution flow of the process to a malicious function.
The global offset table (GOT) is a data structure that is used by dynamic linkers to resolve symbols (e.g., functions and variables) in dynamically linked libraries. When a process is loaded, the dynamic linker creates a GOT for the process and initializes it with the addresses of the symbols in the dynamically linked libraries that the process uses.
During runtime, when the process calls a symbol in a dynamically linked library, it accesses the symbol's address from the GOT. If the symbol's address is not yet resolved (i.e., the symbol is not yet bound to its final address), the dynamic linker resolves the symbol and updates the GOT with the symbol's final address.
Adversaries can exploit this process by replacing the memory address references in the GOT with the address of a malicious function, thereby redirecting the execution flow of the process to the malicious function when the process calls a symbol. This allows the adversary to execute arbitrary code within the context of the compromised process.
2. Overwriting the VDSO Page
In this method, an adversary can exploit the VDSO feature in the Linux operating system to inject malicious code into a running process.
The VDSO page is a memory region that is mapped into the virtual address space of a process and contains the code stubs for the VDSO functions. These functions provide a fast interface for calling certain system calls, allowing processes to make system calls without the overhead of a system call instruction.
To inject malicious code into a process using this method, the adversary can use a technique called "memory corruption" to overwrite the VDSO page with malicious code. Memory corruption refers to the exploitation of vulnerabilities in a program that allows an attacker to write arbitrary data to a memory location.
There are several ways in which an adversary can corrupt memory and overwrite the VDSO page. For example, the adversary may use a buffer overflow vulnerability to write past the end of a buffer and corrupt adjacent memory. Alternatively, the adversary may use a use-after-free vulnerability to write to memory that has been freed and is no longer in use. Once the VDSO page has been overwritten with malicious code, the adversary can cause the process to execute the malicious code by making a VDSO system call. This allows the adversary to execute arbitrary code within the context of the compromised process.
#1.12. T1055.015 ListPlanting
A list-view control is a type of user interface element that allows a user to view a list of items in various ways. These controls are often used to display large amounts of data in a way that is easy to browse and navigate. Attackers can exploit list-view controls to inject malicious shellcode into the hijacked processes to bypass process-based defenses and potentially gain privileges within the system.
Adversary Use of ListPlanting
ListPlanting is a form of code injection that exploits the behaviors of list-view controls within the graphical user interface elements of Windows applications. An example flow of the ListPlanting process injection technique is:
1- Initial Reconnaissance: An attacker identifies a target application with a list-view control (SysListView32) that stores and displays data in a list-like structure.
2- Memory Allocation in Target Process: Using process injection methods or API calls to obtain a handle to the SysListView32 window, the attacker allocates memory in the target process's address space. The attacker aims to use legitimate-looking system calls to avoid detection and may avoid functions like WriteProcessMemory that are closely monitored.
3- Payload Placement via Windows Messages: Instead of writing to the process's memory space directly, the attacker may use window messages (PostMessage or SendMessage) to indirectly inject the payload. These messages can be LVM_SETITEMPOSITION and LVM_GETITEMPOSITION list-view messages to copy the payload into the target process's allocated memory two bytes at a time.
4- Setting Up Execution Trigger: The malicious payload serves as a custom sorting callback to be executed when the list items are sorted. To arrange for this execution, the attacker prepares the conditions by manipulating the list-view control settings such that the malicious code will act as the callback function.
5- Triggering Payload Execution: Execution is triggered by sending an LVM_SORTITEMS message, instructing the SysListView32 to sort the items, which in turn causes the malicious callback (the payload previously injected) to be executed.
6- Execution: When the target process receives the sorting command, it unknowingly executes the payload in the callback, thereby running the attacker's code within the process. The list-view's built-in behavior to use callbacks for item sorting facilitates this stealthy execution.