吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 14740|回复: 24
收起左侧

[C&C++ 转载] 傀儡进程技术实现

  [复制链接]
Gslab 发表于 2016-5-27 10:42
本帖最后由 Gslab 于 2016-5-27 10:56 编辑

【游戏安全实验室致力于游戏安全技术分享及交流,以后将在52pojie发布更多游戏安全相关技术分享,欢迎大家一起来讨论交流】

文章最后带傀儡进程源码下载
一、傀儡进程简介

    傀儡进程是指将目标进程的映射文件替换为指定的映射文件,替换后的进程称之为傀儡进程。

二、傀儡进程技术要点

       实现傀儡进程必须要选择合适的时机,若目标进程已经开始运行则难以进行替换,因此要在目标进程刚加载进内存后还未开始运行之前替换。技术要点如下:

1. 创建挂起进程

       系统函数CreateProcess中参数dwCreationFlgs传递CREATE_SUSPEND便可创建一个挂起状态的进程。进程被创建后系统会为它分配足够的资源和初始化必要的操作,如为进程分配空间,加载映像文件,创建主线程,将EIP指向代码入口点,并将主线程挂起等。
[C++] 纯文本查看 复制代码
BOOL WINAPI CreateProcess([/align]  __in          LPCTSTR lpApplicationName,
  __in_out      LPTSTR lpCommandLine,
  __in          LPSECURITY_ATTRIBUTES lpProcessAttributes,
  __in          LPSECURITY_ATTRIBUTES lpThreadAttributes,
  __in          BOOL bInheritHandles,
  __in          DWORD dwCreationFlags,
  __in          LPVOID lpEnvironment,
  __in          LPCTSTR lpCurrentDirectory,
  __in          LPSTARTUPINFO lpStartupInfo,
  __out         LPPROCESS_INFORMATION lpProcessInformation
);


创建挂起进程实例代码如下:
[C++] 纯文本查看 复制代码
STARTUPINFOA stSi = {0};
PROCESS_INFORMATION stPi = {0};
stSi.cb = sizeof(stSi);
if (CreateProcessA(strTargetProcess.c_str(),NULL,NULL,
                                NULL, FALSE,CREATE_SUSPENDED,        
                                NULL, NULL,&stSi, &stPi) == 0)
{
           return FALSE;
}


2.  保存现场,收集信息
       傀儡进程在替换目标进程之前,必须要保存当前线程的上下文环境,在替换完成后要及时恢复。这样系统才能将傀儡进程视为“正常”进程,而不会被发现。另外为了后边清空内存空间的操作,也必须要通过上下文获得进程的加载基地址。利用系统函数GetThreadContext便可得到当前的线程上下文。相关的API和结构信息如下:
[C++] 纯文本查看 复制代码
BOOL WINAPI GetThreadContext(
  __in          HANDLE hThread,
  __in_out      LPCONTEXT lpContext
);
[C++] 纯文本查看 复制代码
typedef struct _CONTEXT {
    DWORD ContextFlags;
    DWORD   Dr0;
    DWORD   Dr1;
    DWORD   Dr2;
    DWORD   Dr3;
    DWORD   Dr6;
    DWORD   Dr7;
    DWORD   SegGs;
    DWORD   SegFs;
    DWORD   SegEs;
    DWORD   SegDs;
    DWORD   Edi;
    DWORD   Esi;
    DWORD   Ebx;
    DWORD   Edx;
    DWORD   Ecx;
    DWORD   Eax;
    DWORD   Ebp;
    DWORD   Eip;
    DWORD   SegCs;              // MUST BE SANITIZED
    DWORD   EFlags;             // MUST BE SANITIZED
    DWORD   Esp;
    DWORD   SegSs;
    BYTE    ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;
        获取线程相关信息示例代码如下:
[C++] 纯文本查看 复制代码
CONTEXT stThreadContext;
 stThreadContext.ContextFlags = CONTEXT_FULL;
 if (GetThreadContext(stPi.hThread, &stThreadContext) == 0)
 {
    return FALSE;
 }

3.  清空目标进程

       目标进程被初始化后,进程的映像文件也随之被加载进对应的内存空间。傀儡进程在替换之前必须将目标进程的内容清除掉。此时要用到另外一个系统未文档化的函数NtUnmapViewOfSection,需要自行从ntdll.dll中获取。该函数需要指定的进程加载的基地址,基地址即是从第2步中的上下文取得。相关的函数说明及基地址计算方法如下:
[C++] 纯文本查看 复制代码
ULONG WINAPI NtUnmapViewOfSection(
__in HANDLE ProcessHandle, 
__in PVOID BaseAddress
);

        context.Ebx+ 8 = 基地址的地址,因此从context.Ebx + 8的地址读取4字节的内容并转化为DWORD类型,既是进程加载的基地址。示例代码如下:

[C++] 纯文本查看 复制代码
BOOL  UnMapTargetProcess(HANDLE hProcess, CONTEXT& stThreadContext)
{
    DWORD dwProcessBaseAddr = 0;
    if (ReadProcessMemory(hProcess, (LPCVOID)(stThreadContext.Ebx + 8),                                                                                  &dwProcessBaseAddr, sizeof(PVOID), NULL) == 0)
    {
        return FALSE;
    }
    HMODULE hNtModule = GetModuleHandle(_T("ntdll.dll"));
    if (hNtModule == NULL)
    {
        return FALSE;
    }
    NtUnmapViewOfSection  pfnNtUnmapViewOfSection  =                     (NtUnmapViewOfSection)GetProcAddress(hNtModule, "NtUnmapViewOfSection");
    if (pfnNtUnmapViewOfSection == NULL)
    {
        return FALSE;
    }
    return (pfnNtUnmapViewOfSection(hProcess, (PVOID)dwProcessBaseAddr) == 0);
}

4.  重新分配空间

       在第3步中,NtUnmapViewOfSection将原始空间清除并释放了,因此在写入傀儡进程之前需要重新在目标进程中分配大小足够的空间。需要用到跨进程内存分配函数VirtualAllocEx。
[C++] 纯文本查看 复制代码
LPVOID WINAPI VirtualAllocEx(
  __in          HANDLE hProcess,
  __in          LPVOID lpAddress,
  __in          SIZE_T dwSize,
  __in          DWORD flAllocationType,
  __in          DWORD flProtect
);

  一般情况下,在写入傀儡进程之前,需要将傀儡进程对应的文件按照申请空间的首地址作为基地址进行“重定位”,这样才能保证傀儡进程的正常运行。为了避免这一步操作,可以以傀儡进程PE文件头部的建议加载基地址作为VirtualAllocEx 的lpAddress参数,申请与之对应的内存空间,然后以此地址作为基地址将傀儡进程写入目标进程,就不会存在重定位问题。关于“重定位”的原理可以自行网络查找相关资料。示例代码如下

[C++] 纯文本查看 复制代码
 LPVOID lpPuppetProcessBaseAddr =
 VirtualAllocEx(stPi.hProcess, (LPVOID)pNtHeaders->OptionalHeader.ImageBase,
             pNtHeaders->OptionalHeader.SizeOfImage,
             MEM_COMMIT | MEM_RESERVE,
             PAGE_EXECUTE_READWRITE);
 if (lpPuppetProcessBaseAddr == NULL)
 {
     return FALSE;
 }

5.   写入傀儡进程


       准备工作完成后,现在开始将傀儡进程的代码写入到对应的空间中,注意写入的时候要按照傀儡进程PE文件头标明的信息进行。一般是先写入PE头,再写入PE节,如果存在附加数据还需要写入附加数据。示例代码如下:
[C++] 纯文本查看 复制代码
 // 替换PE头
 BOOL bRet = WriteProcessMemory( stPi.hProcess, 
                               lpPuppetProcessBaseAddr, 
                               lpPuppetProcessData,
                               pNtHeaders->OptionalHeader.SizeOfHeaders, 
                               NULL);
 if (!bRet)
 {
     return FALSE;
 }
 // 替换节
 LPVOID lpSectionBaseAddr = (LPVOID)((DWORD)lpPuppetProcessData 
                        + 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)lpPuppetProcessBaseAddr+
                                              pSectionHeader->VirtualAddress),
          (LPCVOID)((DWORD)lpPuppetProcessData+                                                                                                 pSectionHeader->PointerToRawData),
          pSectionHeader->SizeOfRawData,
          NULL);
     if (!bRet)
     {
          return FALSE;
     }
     lpSectionBaseAddr=(LPVOID)((DWORD)lpSectionBaseAddr+                                                                        sizeof(IMAGE_SECTION_HEADER));
  }

6. 恢复现场并运行傀儡进程


       在第2步中,保存的线程上下文信息需要在此时就需要及时恢复了。由于目标进程和傀儡进程的入口点一般不相同,因此在恢复之前,需要更改一下其中的线程入口点,需要用到系统函数SetThreadContext。将挂起的进程开始运行需要用到函数ResumeThread。
[C++] 纯文本查看 复制代码
BOOL WINAPI SetThreadContext(
  __in          HANDLE hThread,
  __in          const CONTEXT* lpContext
);

[C++] 纯文本查看 复制代码
DWORD WINAPI ResumeThread(
  __in          HANDLE hThread
);

       示例代码如下:
[C++] 纯文本查看 复制代码
 // 替换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);


傀儡进程源码.7z (212.73 KB, 下载次数: 611)







免费评分

参与人数 7热心值 +7 收起 理由
zzzzzga + 1 为什么在win7 64位总是报 oxC0000005
返人类 + 1 楼主不地道,看雪2012年的帖子搬过来也不放个转载说明。
tflyr + 1 我很赞同!
a919305124 + 1 顶 大神
Zero丶冻结 + 1 用心讨论,共获提升!
gmh5225 + 1 已答复!
华夏小荧虫 + 1 用心讨论,共获提升!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

huangai93 发表于 2016-10-14 15:39

学习                一下
var 发表于 2017-5-17 16:04
本帖最后由 var 于 2017-5-17 16:06 编辑

喜欢这种教程,

我还找到了一个 http://www.programlife.net/dynamic-forking-of-win32-exe.html

以下代码也很有帮助

[C++] 纯文本查看 复制代码
#include <windows.h>

extern "C" NTSYSAPI LONG NTAPI ZwUnmapViewOfSection(HANDLE, PVOID);

ULONG protect(ULONG characteristics)
{
    static const ULONG mapping[]
        = {PAGE_NOACCESS, PAGE_EXECUTE, PAGE_READONLY, PAGE_EXECUTE_READ,
           PAGE_READWRITE, PAGE_EXECUTE_READWRITE, PAGE_READWRITE, PAGE_EXECUTE_READWRITE};

    return mapping[characteristics >> 29];
}

int main(int argc, char *argv[])
{
    PROCESS_INFORMATION pi;
    STARTUPINFO si = {sizeof si};

    CreateProcess(0, "cmd", 0, 0, FALSE, CREATE_SUSPENDED, 0, 0, &si, &pi);

    CONTEXT context = {CONTEXT_INTEGER};

    GetThreadContext(pi.hThread, &context);

    PVOID x; ReadProcessMemory(pi.hProcess, PCHAR(context.Ebx) + 8, &x, sizeof x, 0);

    ZwUnmapViewOfSection(pi.hProcess, x);

    PVOID p = LockResource(LoadResource(0, FindResource(0, "Image", "EXE")));

    PIMAGE_NT_HEADERS nt = PIMAGE_NT_HEADERS(PCHAR(p) + PIMAGE_DOS_HEADER(p)->e_lfanew);

    PVOID q = VirtualAllocEx(pi.hProcess,
                             PVOID(nt->OptionalHeader.ImageBase),
                             nt->OptionalHeader.SizeOfImage,
                             MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

    WriteProcessMemory(pi.hProcess, q, p, nt->OptionalHeader.SizeOfHeaders, 0);

    PIMAGE_SECTION_HEADER sect = IMAGE_FIRST_SECTION(nt);

    for (ULONG i = 0; i < nt->FileHeader.NumberOfSections; i++) {

        WriteProcessMemory(pi.hProcess,
                           PCHAR(q) + sect[i].VirtualAddress,
                           PCHAR(p) + sect[i].PointerToRawData,
                           sect[i].SizeOfRawData, 0);

        ULONG x;

        VirtualProtectEx(pi.hProcess, PCHAR(q) + sect[i].VirtualAddress, sect[i].Misc.VirtualSize,
                         protect(sect[i].Characteristics), &x);
    }

    WriteProcessMemory(pi.hProcess, PCHAR(context.Ebx) + 8, &q, sizeof q, 0);

    context.Eax = ULONG(q) + nt->OptionalHeader.AddressOfEntryPoint;

    SetThreadContext(pi.hThread, &context);

    ResumeThread(pi.hThread);

    return 0;
}

games 发表于 2016-5-27 10:57
勇者为王 发表于 2016-5-27 11:00
学习了,感谢楼主教程。
萌萌哒、merry 发表于 2016-5-27 11:32
感谢楼主教程。
117578111 发表于 2016-5-27 12:18

感谢楼主教程
天地无空 发表于 2016-5-27 13:06
学习一下
1070885984 发表于 2016-5-29 08:48
看不懂、、、。
叶pp123 发表于 2016-7-23 09:33
学习                一下
xd785 发表于 2016-7-24 21:10
学习一下,没有看太懂 。。。
dhs347 发表于 2016-7-24 21:24
不懂,楼主啊。。。。。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-12-27 02:58

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表