APT X – Process Hollowing

APT X – Process Hollowing
January 27, 2021 26 mins

APT X – Process Hollowing

APT X – Process Hollowing

A detailed walkthrough of the process hollowing injection technique.

Advanced Persistent Threats (APTs) utilize a set of methodologies to achieve their tasks. The APT X series will discuss common Techniques, Tactics, and Procedures (TTPs) utilized during Red Team operations conducted by Aon’s Cyber Solutions. These techniques are either inspired by real-world APTs during threat emulations, or directly replicate specific APTs during threat simulations.

Context

Prior to the course of network infiltration, threat actors must obtain a foothold within an organization’s network perimeter. This is often done via phishing, vulnerable external network infrastructure, physical access, and in extremely rare cases, by utilizing zero-day exploits.

After an attacker is able to execute code on a machine, due to a vulnerability for example, the attacker may then be interested in some form of process injection.

Why Inject into a Process?

Before we dive into a specific technique of process injection (process hollowing), let us first understand the general need for process injection. If the attacker can execute code on a machine, why does the attacker need to inject into another process, particularly since the attacker is likely executing from the context of some process already? There are multiple reasons for this; the following motifs are relevant to modern threats.

Stability. Often, due to sophisticated memory corruption exploits, the compromised process that the attacker is running under becomes unstable after exploitation. If the current process where the attacker is executing code from crashes, then the attacker’s control over the host is lost. Therefore, in order to maintain a foothold on the target system, the attacker may choose to inject malicious code into a new or existing process to increase the attacker’s foothold’s stability. This is also known as “process migration” and is a built-in technique in various remote access tools (RATs) and popular penetration testing frameworks such as Metasploit (meterpreter).

Stealth. As attackers have become more sophisticated, so have defenders. Blue teams will often look for indicators of compromise (IOCs) on a system that follow certain trends. For example, if Microsoft Excel executes code to spawn a command prompt, a blue team analyst may observe this as suspicious behavior and will investigate further. This may ultimately cost the attacker the obtained foothold and potentially a much-increased alertness within the organization’s blue team. This makes further attacks and exploitation more difficult.

Access. In some strains of malware such as Emotet, which is a banking trojan, injecting into the browser process allows for more effective hooking into certain functionality which aids the malware in performing its tasks. This may be the act of extracting and exfiltrating banking credentials from the browser process, for example.

As mentioned earlier, process hollowing is one sub-technique of process injection. Each sub-technique of process injection comes with its own set of pros and cons. As you will observe shortly, process hollowing is not an effective technique to obtain better access to a process (because the victim process is hollowed out), but it excels as a stealth technique, because you can run one program under the guise of another program. Therefore, it is often the chosen method for APTs as they perform lateral movement and further infiltrate an organization.

What is a Process?

Before we answer that question, we must first describe the environment where a process exists – the operating system. An operating system is a program that allows, among other things, for the scheduled execution of other programs within it, hardware resource management, and control of various peripherals connected to the machine. General purpose operating systems such as Windows can host and execute multiple processes simultaneously. Each process itself is a data structure which can contain threads that execute instructions.

A natural feature of the Windows operating system is the ability to observe and manipulate other processes running with the same security context. For example, a user may run a process that can start or terminate other processes under the same username. Moreover, the user may obtain a handle on other processes. A handle is simply a programmatic object that allows for control over a target process and/or thread. This functionality can be used by normal programs to control other programs, but it can also facilitate malicious activities.

What is Process Injection?

Since one process can programmatically manipulate the internals of other processes under the same context, it therefore follows that a process can inject code into another process. This technique is generically known as process injection. The MITRE ATT&CK framework identifies additional sub-techniques of process injection. In this article, we will examine a sub-technique of process injection utilized by various ransomware threat actors, APTs, and red teams called process hollowing.

APT X – Process Hollowing Image 1

Process Injection and Sub-Techniques (source: MITRE)
How Do You Perform Process Hollowing?

From a big-picture perspective, process hollowing requires the following steps in order to execute code.

  • The attacker-controlled malicious process spawns a new process, which we will refer to as the victim process. This victim process is created in a suspended state.
  • The malicious process unmaps (or hollows out) the executable portion of the newly suspended process. This effectively removes the mapping of the executable program associated with the process in memory. Any previous functionality associated with the victim process is bygone. Only an exterior shell remains of the victim process, such as the program name and metadata.
  • The malicious process allocates new memory space inside the victim process.
  • The malicious process injects code into the newly allocated space of the victim process.
  • The malicious process performs some rewiring within the victim process to ensure that upon victim process resumption, the process is stable and executional.

There are numerous ways of performing process hollowing, in terms of technical implementation.

We will examine a basic form of process injection on the Windows operating system in the following section. We will be injecting a 32-bit program (a basic message box program) into another 32-bit program (Internet Explorer). The techniques demonstrated can be also applied to 64-bit programs without adding significant complexity.

Considerations for more advanced versions will primarily revolve around additional stealth and injection stability. This may involve the use of different WinAPI calls (and in a different order), performing certain actions before calling WinAPI functions such as AV / EDR unhooking, marking certain sections of memory differently in the victim process, or baking in some anti-reverse-engineering techniques to buy the attacker some additional time during the attack phase.

I. Prepare Replacement Executable

Before we begin, let us create a sample executable to inject into our victim process. This will be a simple Windows-based program that spawns a message box. The code for the replacement executable follows, and it uses a Windows application project template in Visual Studio 2019.

#include <windows.h>
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
  MessageBoxA(0, "@primal0xF7", "simple_hollow", 0);
  return 0;
}

After compiling this project, we now have a “replacement.exe” executable. Running this program generates a straightforward message box.

APT X – Process Hollowing Image 2

Replacement Executable Generated

Now, we want to inject this program into another program.

II. Spawn Victim Process

The following sections will run through the code in a linear manner. We will examine critical code sections more closely and utilize a debugger to dive deeper where needed.

One of the most interesting parts of the program will be when the victim process is “hollowed”. This will require the usage of the Native API. Some methods in the Native API are undocumented; however, the function call we will be using, “NtUnmapViewOfSection”, is documented here. The curious reader may ask a couple of questions:

1. What is the WinAPI?

This is a programmatic interface that can be used to talk to the operating system. For example, if you’d like to create a file, you can use the “CreateFileA” WinAPI call. If you’d like to allocate memory in a remote process, you can use the “VirtualAllocEx” WinAPI call. These calls are well-documented by Microsoft and can be found online, for example https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex.

2. Why are we using a Native API call? What does this call do?

Microsoft strongly encourages the usage of documented calls as it provides for a smoother programming experience; however, some WinAPI calls are constituted of more complex, sometimes undocumented calls. These lower-level calls usually provide for greater control over the operating system than their higher-level counterparts. One group of these lower-level functions is known as the Native API. In addition to providing building blocks for some WinAPI calls, this API is typically used during system startup (before WinAPI is available) and when programming drivers. Within the Native API are functions that start with “Nt” for calls made from user-mode, or “Zw” for calls made from kernel-mode. If you don’t know the difference between user-mode and kernel-mode, just worry about the “Nt” prefix for now because that’s what we can use for this exercise, as we are operating in user-mode. The documentation contains more details.

Sometimes, there simply is no way to accomplish what we are trying to do without using an undocumented or low-level call. In our case, we need to unmap a section from a process. There is no “non-Nt” method to call from the WinAPI to accomplish this, so we must use the Native API. It should be noted that unmapping a “section” here does not mean deallocating a Windows PE section, but rather getting rid of a “file mapping” object which is also known as a section, although it effectively does deallocate sections of memory. More information can be found here: https://docs.microsoft.com/en-us/windows/win32/memory/file-mapping.

These Native API calls are sometimes also used by malware authors to find alternate ways to accomplish the same goal, perhaps to throw off heuristic / behavioral analysis by AV / EDR. For example, we can use the “NtTerminateProcess” call instead of “TerminateProcess” to terminate a process. The latter is the cleaner function to use, because the non-Nt equivalent might take some actions before and after calling “NtTerminateProcess” to ensure clean execution, although some methods translate identically between abstraction levels (there’s no extra clean-up work). However, the former “NtTerminateProcess” call may bypass some AV / EDR detection heuristics if utilized in a certain manner or sequence of calls.

For our exercise, we present a simple implementation that is not particularly concerned with AV / EDR evasion. There are numerous other AV-evading implementations such as those that rely solely on “Nt*” methods for all the other functions in the injector, but this is not necessary to demonstrate a simple implementation.

Note that the process IDs and memory addresses shown in this post / screenshots vary across different runs of the program, but the procedures remain the same.

With the above in mind, we can now begin writing the process hollowing program.

#include <stdio.h>
#include <Windows.h>

#pragma comment(lib, "ntdll.lib")

EXTERN_C NTSTATUS NTAPI NtUnmapViewOfSection(HANDLE, PVOID); 

The above few lines enable us to utilize “NtUnmapViewOfSection” later in our code.

Afterwards, we must create the victim process. We will use the “CreateProcessA” method to spawn a 32-bit Internet Explorer (“iexplore.exe”) victim process. This process will be created in a suspended state.

int main() {

LPSTARTUPINFOA pVictimStartupInfo = new STARTUPINFOA();
LPPROCESS_INFORMATION pVictimProcessInfo = new PROCESS_INFORMATION();
LPCSTR victimImage = "C:\\Program Files (x86)\\Internet Explorer\\iexplore.exe";
LPCSTR replacementImage = "C:\\Users\\ft\\source\\repos\\replacement\\replacement.exe";

// Create victim process
if (!CreateProcessA(
    0,
    (LPSTR)victimImage,
    0,
    0,
    0,
    CREATE_SUSPENDED,
    0,
    0,
    pVictimStartupInfo,
    pVictimProcessInfo)) {
  printf("[-] Failed to create victim process %i\r\n", GetLastError());
  return 1;
};
printf("[+] Created victim process\r\n");
printf("\t[*] PID %i\r\n", pVictimProcessInfo->dwProcessId); 

We have also created and utilized two data structures, pVictimStartupInfo and pVictimProcessInfo. The latter will contain useful information with regards to the victim process after process creation.

We can verify that the victim process was created in a suspended state by placing a breakpoint after the call and inspecting Task Manager.

APT X – Process Hollowing Image 3

Suspended Process Created
III. Load Replacement Executable

Now we must import the executable that we’d like to inject into the victim process. We will load this executable into the current process’ memory. This requires us to obtain a handle to the executable file in the filesystem, allocate memory in the current process, and then read the contents from the handle into the allocated memory. This is depicted in the following code snippet.

// Open replacement executable to place inside victim process
HANDLE hReplacement = CreateFileA(
  replacementImage,
  GENERIC_READ,
  FILE_SHARE_READ,
  0,
  OPEN_EXISTING,
  0,
  0
);

if (hReplacement == INVALID_HANDLE_VALUE) {
  printf("[-] Unable to open replacement executable %i\r\n", GetLastError());
  TerminateProcess(pVictimProcessInfo->hProcess, 1);
  return 1;
}

DWORD replacementSize = GetFileSize(
  hReplacement,
  0);
printf("[+] Replacement executable opened\r\n");
printf("\t[*] Size %i bytes\r\n", replacementSize);

// Allocate memory for replacement executable and then load it
PVOID pReplacementImage = VirtualAlloc(
  0, 
  replacementSize, 
  MEM_COMMIT | MEM_RESERVE, 
  PAGE_READWRITE);

DWORD totalNumberofBytesRead;

if (!ReadFile(
    hReplacement, 
    pReplacementImage, 
    replacementSize, 
    &totalNumberofBytesRead, 
    0)) {
  printf("[-] Unable to read the replacement executable into an image in memory %i\r\n", GetLastError());
  TerminateProcess(pVictimProcessInfo->hProcess, 1);
  return 1;
}
CloseHandle(hReplacement);
printf("[+] Read replacement executable into memory\r\n");
printf("\t[*] In current process at 0x%08x\r\n", (UINT)pReplacementImage); 

There are a few things that happened here. In order to verify that this successfully completed, we can debug the current process (simplehollow.exe) at the location our program claims to have place the replacement image.

APT X – Process Hollowing Image 4

Replacement Image Successfully Loaded into Memory
IV. Hollow Out Victim Image

Before we can perform the hollowing, we must extract some information from the victim process in its suspended state. We will need two key pieces of data, which can be extracted from the victim’s primary “thread context” that contains the thread’s current register values. We are interested in two items:

  • Victim process primary thread EBX register. This register contains the address of the victim process’s Process Execution Block (PEB). An offset of 8 bytes from this address gives us the victim process’s executable image base address. We will need this to hollow out the correct section in the victim process, and it will provide us with the address where we can inject the replacement image. So, we will be reading the value from EBX.
  • Victim process primary thread EAX register. This register contains the address of the victim process’s entry point for program execution. We will need to modify this to match the replacement image’s program entry point. So, we will be writing a new value to EAX.

The non-Windows hacker may wonder, “how on Earth did you figure this out?” We know that we need to get the victim process’s image base address and the entry point, because without them, for now, we can’t perform important memory manipulation procedures and we can’t begin execution of the replacement image. So how do we figure these two values out? Well, we already have a suspended process on hand. Let’s attach a debugger to examine register contents.

APT X – Process Hollowing Image 5

Yikes, dude.

The above screenshot contains a wealth of information. This is the debugger attached to the suspended victim process, which is 32-bit Internet Explorer. Let’s walk through the important items.

  1. At the indicator (1) we show that we must switch our thread context. When we attach the debugger to the program, we are not observing the primary thread. The debugger injects a thread to break execution and control execution flow. We list the available threads using “~”. We note that thread 0, the primary thread, is indeed suspended. We switch to it using “~0s”.
  2. After switching to the primary thread, our register values immediately update. The “!peb” instruction lists the Process Execution Block. We will use this output to verify some register content outputs. You will see later in the article that we can perform this examination by listing out the executable’s NT Image Headers, which are actually used as the source of values for some built-in debugger commands.
  3. Note how the PEB is at 0x00370000. If we look at the suspended primary thread’s EBX register, that is indeed the address of our PEB. Great, well where is the image base address? WinDBG indicates it is at 0x012C0000. How do we get this value programmatically? If we look at the “memory” pane on the right, we notice that the image base address is at PEB+0x8 (keeping endianness in mind). So now we can extract the image base address from the primary thread context, which contains the value of the EBX register.
  4. Let us examine the EAX register. Notice the value is 0x012C36D0. Notice the left “disassembly” pane. Indeed, this appears to be the entry point for the Internet Explorer process. So now, given the same primary thread context, we can extract the image entry point from the EAX register.

To perform this extraction of register values programmatically, we will need to get the primary thread context of the victim process, and then read the victim process memory using some of the values from this thread context. We will perform this as follows.

// Obtain context / register contents of victim process's primary thread
CONTEXT victimContext;
victimContext.ContextFlags = CONTEXT_FULL;
GetThreadContext(pVictimProcessInfo->hThread, &victimContext);
printf("[+] Obtained context from victim process's primary thread\r\n");
printf("\t[*] Victim PEB address / EBX = 0x%08x\r\n", (UINT)victimContext.Ebx);
printf("\t[*] Victim entry point / EAX = 0x%08x\r\n", (UINT)victimContext.Eax);

// Get base address of the victim executable
PVOID pVictimImageBaseAddress;
ReadProcessMemory(
  pVictimProcessInfo->hProcess, 
  (PVOID)(victimContext.Ebx + 8), 
  &pVictimImageBaseAddress, 
  sizeof(PVOID), 
  0);
printf("[+] Extracted image base address of victim process\r\n");
printf("\t[*] Address: 0x%08x\r\n", (UINT)pVictimImageBaseAddress); 

We are now armed with the victim image base address and have access to the context so we can manipulate it later. We no longer require the victim executable image to be loaded in the victim process; we may perform the hollowing. This is where we utilize the Native API function discussed earlier, “NtUnmapViewOfSection”.

// Unmap executable image from victim process    
DWORD dwResult = NtUnmapViewOfSection(
  pVictimProcessInfo->hProcess,
  pVictimImageBaseAddress);
if (dwResult) {
  printf("[-] Error unmapping section in victim process\r\n");
  TerminateProcess(pVictimProcessInfo->hProcess, 1);
  return 1;
}

printf("[+] Hollowed out victim executable via NtUnmapViewOfSection\r\n");
printf("\t[*] Utilized base address of 0x%08x\r\n", (UINT)pVictimImageBaseAddress); 

APT X – Process Hollowing Image 6

Victim Process Hollowed
V. Allocate Memory in Victim Process

Now, we must allocate memory in the process we just hollowed out. But before we do that, we will need to know the amount of memory required to load the replacement executable. Note that this is different from the replacement executable’s size on the filesystem. This is because a file loaded into memory upon execution has already undergone PE parsing by the operating system, which we will simulate in step VI. Let us begin by obtaining the replacement image base address and size.

// Allocate memory for the replacement image in the remote process
PIMAGE_DOS_HEADER pDOSHeader = (PIMAGE_DOS_HEADER)pReplacementImage;
PIMAGE_NT_HEADERS pNTHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)pReplacementImage + pDOSHeader->e_lfanew);
DWORD replacementImageBaseAddress = pNTHeaders->OptionalHeader.ImageBase;
DWORD sizeOfReplacementImage = pNTHeaders->OptionalHeader.SizeOfImage;

printf("[+] Replacement image metadata extracted\r\n");
printf("\t[*] replacementImageBaseAddress = 0x%08x\r\n", (UINT)replacementImageBaseAddress);
printf("\t[*] Replacement process entry point = 0x%08x\r\n", (UINT)pNTHeaders->OptionalHeader.AddressOfEntryPoint);
  
PVOID pVictimHollowedAllocation = VirtualAllocEx(
  pVictimProcessInfo->hProcess,
  (PVOID)pVictimImageBaseAddress,
  sizeOfReplacementImage,
  MEM_COMMIT | MEM_RESERVE,
  PAGE_EXECUTE_READWRITE);
if (!pVictimHollowedAllocation) {
  printf("[-] Unable to allocate memory in victim process %i\r\n", GetLastError());
  TerminateProcess(pVictimProcessInfo->hProcess, 1);
  return 1;
}
printf("[+] Allocated memory in victim process\r\n");
printf("\t[*] pVictimHollowedAllocation = 0x%08x\r\n", (UINT)pVictimHollowedAllocation); 

There are quite a few things going on here, so let’s go through them line by line.

  • We have a pointer to the replacement image in the current process memory. We cast this pointer to a pointer of structure IMAGE_DOS_HEADER. The reason we do this is so we can traverse this structure when we dereference it based on the properties of the IMAGE_DOS_HEADER structure.
  • From this structure, we obtain the value of “e_lfanew”. This contains the number of bytes between the DOS header and the NT headers. The NT headers contain some useful information.
  • The first piece of useful information is the base address of the replacement image we have loaded in the current process; however, we are not going to use this because our entry point will be relative to the victim process’ base address. It is printed out only for clarity and debugging.
  • The second piece of useful information that we will actually use is the size of the replacement image. This will help us allocate the right amount of memory in the victim process.
  • We print a couple of things out.
  • We perform the allocation in the target process. Note how we use the size of the replacement image, which we just extracted, in this call. This is also the line where half of the red teaming community and every major EDR vendor will tell me to quit hacking and start a lemonade stand business instead. The reason is because I designated the memory permission in the remote process to “PAGE_EXECUTE_READWRITE”. This is a strong heuristic for any competent EDR solution that some nasty business has either happened or is about to happen inside this process, particularly since the entire memory region is marked as such. As a reminder, this exercise is one of the simplest implementations of process hollowing, and so we will not dive into the particulars of how to evade AV / EDR.

With that, let us attach a debugger to the currently running process that is performing the hollowing to see where all these data structures are. First, we note where the replacement image is loaded into memory in this current run from our program output. Also note that this screenshot is pointing out the same sequence of events to figure out where the replacement executable is as previously shown. It is demonstrated here again due to a new image base address (0x014b0000 from Step III vs. 0x00200000 here in Step V).

APT X – Process Hollowing Image 7

Replacement Executable Address in Current Process

In the debugger, we examine memory inside the process at that location. We notice the DOS header.

APT X – Process Hollowing Image 8

We Are Barking Up the Right Tree

Let us extract the IMAGE_DOS_HEADER and examine its contents. We tell WinDBG to print out the structure’s contents starting from our base address.

APT X – Process Hollowing Image 9

Value of “e_lfanew”

We can now perform basic arithmetic to print the NT headers. To get to the NT headers, we will simply add 0x200000 + 0n240. Note that the “0x” prefix indicates a hexadecimal value whereas “0n” indicates a decimal value. Fortunately, our debugger can do that for us. We will then repeat the same above command, except we will print the IMAGE_NT_HEADERS structure instead, using a base address of the sum of 0x200000 + 0n240.

APT X – Process Hollowing Image 10

NT Headers Structure

There are a few key pieces of data here that were helpful to us. This shows where the values we extracted from the DOS and NT headers were coming from.

VI. Inject Code into Victim Process

There are two groups of items we need to write into the victim process’ memory space for successful code execution. The first is the headers we examined. The second is the different sections that constitute the executable. Let us begin with the headers.

// Write replacement process headers into victim process
WriteProcessMemory(
  pVictimProcessInfo->hProcess, 
  (PVOID)pVictimImageBaseAddress,
  pReplacementImage,
  pNTHeaders->OptionalHeader.SizeOfHeaders,
  0);
printf("\t[*] Headers written into victim process\r\n"); 

This should be relatively straightforward. We are using the “OptionalHeader.SizeOfHeaders” value which can be observed from the previous screenshot to write the first 0x400 bytes into the target. We then need to write the various sections. This involves some arithmetic.

// Write replacement process sections into victim process
for (int i = 0; i < pNTHeaders->FileHeader.NumberOfSections; i++) {
PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((LPBYTE)pReplacementImage + pDOSHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (i * sizeof(IMAGE_SECTION_HEADER)));
    
WriteProcessMemory(pVictimProcessInfo->hProcess, 
  (PVOID)((LPBYTE)pVictimHollowedAllocation + pSectionHeader->VirtualAddress),
  (PVOID)((LPBYTE)pReplacementImage + pSectionHeader->PointerToRawData),
  pSectionHeader->SizeOfRawData, 
  0);
  printf("\t[*] Section %s written into victim process at 0x%08x\r\n", pSectionHeader->Name, (UINT)pVictimHollowedAllocation + pSectionHeader->VirtualAddress);
  printf("\t\t[*] Replacement section header virtual address: 0x%08x\r\n", (UINT)pSectionHeader->VirtualAddress);
  printf("\t\t[*] Replacement section header pointer to raw data: 0x%08x\r\n", (UINT)pSectionHeader->PointerToRawData);
  } 

The following sequence of events simulates part of what the operating system’s PE loader does when it runs an executable, so that the memory sections are laid out in memory in the same manner that they would have been had they been parsed by the actual PE loader.

We extract the number of sections from the NT header, which can also be seen in the previous screenshot. There are nine sections. We first obtain an address to the section we are about to copy from the replacement image. To do this we start from the replacement image address and add the “e_lfanew” value. This places us at the NT headers. Then we add the size of the NT headers structure. This brings us to our first section. As the loop progresses, we simply multiply the current section number (0 through 8 for a total of 9) by the size of the section header. Each section has a header which contains important information about its own section.

We now have an address to the current section. Note that the contents of the “pSectionHeader” structure are all relative, so we will need to add the appropriate image base addresses where necessary. We then begin writing into the victim’s process memory.

For each section, we take the base address of the victim image, which is the same address as where we performed our “VirtualAllocEx” from earlier, and then add on the virtual address of the current section we are copying (which is a relative address). We can extract the size of each section from the section header and use that in the above “WriteProcessMemory” call.

The following screenshot depicts the written sections with their respective addresses. Note how the section header values are relative.

APT X – Process Hollowing Image 11

Replacement Image Sections Written into Victim Process
VII. Rewire Victim Process

We are almost finished. Now we just need to make sure that the suspended process knows where to begin execution from. The current value in the EAX register refers to the entry point from the hollowed-out image. If we don’t change this value before resuming the process, the process will crash. We obtain the replacement image’s entry point from its NT headers. Remember that this is a relative address, so we will need to add it to the image base address of the victim process. We do this with the following piece of code.

// Set victim process entry point to replacement image's entry point - change EAX
victimContext.Eax = (SIZE_T)((LPBYTE)pVictimHollowedAllocation + pNTHeaders->OptionalHeader.AddressOfEntryPoint);
SetThreadContext(
  pVictimProcessInfo->hThread, 
  &victimContext);
printf("[+] Victim process entry point set to replacement image entry point in EAX register\n");
printf("\t[*] Value is 0x%08x\r\n", (UINT)pVictimHollowedAllocation + pNTHeaders->OptionalHeader.AddressOfEntryPoint);
printf("[+] Resuming victim process primary thread...\n");
ResumeThread(pVictimProcessInfo->hThread);
printf("[+] Cleaning up\n");
CloseHandle(pVictimProcessInfo->hThread);
CloseHandle(pVictimProcessInfo->hProcess);
VirtualFree(pReplacementImage, 0, MEM_RELEASE);

return 0; 

We already have the “victimContext” structure from before we hollowed the victim process out. We will set the EAX register value in that structure, then set the actual thread context of the victim process by passing in the structure by reference. At that point, the victim process’s EAX register should point to the new entry point. The statement that follows resumes the primary thread, so the process is no longer in a suspended state. Finally, we do some cleanup in the current process. Let’s verify all this with the debugger.

APT X – Process Hollowing Image 12

EAX Register Set to Entry Point

The above screenshot depicts how EAX is set to the replacement image’s entry point, as we debug the victim process. The “replacement.exe” executable was generated from a Visual Studio project called “leetwindow” and actually named “leetwindow.exe” in that project. We also built the debug version rather than the release version, so we have debugging information included. It is interesting to see how WinDBG tries to display these within the context of the victim process. This likely explains the weirdness in the names, as WinDBG marks the lines as part of some “msISO”.

Let us set a breakpoint at that command, and let the program continue. We should hit the breakpoint.

APT X – Process Hollowing Image 13

Breakpoint Hit

We’ve hit our breakpoint. This was the value in the EAX register when the process was suspended (from previous screenshot). And now for the finale – let’s let the program continue one more time.

APT X – Process Hollowing Image 14

Process Hollowing Completed

In the words of one hacker legend, “Excellent.”

The above screenshot depicts an Internet Explorer process in the task bar but with our replacement executable code running the message box program.

Author
  • Faisal Tameesh

About Cyber Solutions:

Aon’s Cyber Solutions offers holistic cyber risk management, unsurpassed investigative skills, and proprietary technologies to help clients uncover and quantify cyber risks, protect critical assets, and recover from cyber incidents.

General Disclaimer

This material has been prepared for informational purposes only and should not be relied on for any other purpose. You should consult with your own professional advisors or IT specialists before implementing any recommendation, following any of the steps or guidance provided herein. Although we endeavor to provide accurate and timely information and use sources that we consider reliable, there can be no guarantee that such information is accurate as of the date it is received or that it will continue to be accurate in the future.

Terms of Use

The contents herein may not be reproduced, reused, reprinted or redistributed without the expressed written consent of Aon, unless otherwise authorized by Aon. To use information contained herein, please write to our team.

More Like This

View All
Subscribe CTA Banner