CS二开之睡眠混淆(五)BeaconGate,UDRL,Sleepmask组合拳

CS二开之睡眠混淆(五)BeaconGate,UDRL,Sleepmask组合拳 CS4.10版本大概是在2024年7月发布的。引入了一个新的技术概念叫做BeaconGate。并且还有Postex Kit 和 Sleepmask-VS 等更新。Beacon Sleepmask的缺陷在4.10版本以前要bypass异常调用栈检测以及API calls from unbacked memory的检测很困难。基于一些原因例如这无法基于Beacon的syscall来实现只能在UDRL里面通过 IAT Hook 来实现对Beacon里面的API call进行精细控制。这个实现起来很复杂。而BeaconGate的诞生就是为了解决这些问题。认识BeaconGate从宏观层面来看Sleepmask 的概念类似于远程过程调用(RPC)尽管它跟Beacon运行在同一个进程的内存空间中。例如当 Beacon 进入睡眠状态时它会调用 Sleepmask 的 BOF 执行 mask and sleep。Beacon 在这里充当“Client”而 Sleepmask 则充当“Server”Sleepmask代表Beacon 执行 Sleep call。在 Cobalt Strike 4.10 中我们将这一理念发挥到了极致Sleepmask 现在支持执行任意函数。因此现在可以配置 Beacon使其 Windows API 调用通过 Sleepmask也称为 BeaconGate执行。实际上就是通过Sleepmask 把API调用代理了Beacon只需要告诉Sleepmask BOF需要调用什么API以及参数Sleepmask BOF就可以去执行。因此BeaconGate 使用户能够实现最前沿的调用栈欺骗 TTP并将其普遍应用于 Beacon 的 WinAPI 调用。此外由于 Beacon 现在与其 WinAPI 调用解耦还带来的一个好处是Beacon在调用可疑的API的时候并不会暴露自身因为Beacon仍处于加密状态而是通过Sleepmask BOF来进行的调用。而你可以通过配置不同的gates以及修改Sleepmask BOF来应用不同的TTPs。默认情况下如果您启用一个 API to be proxied via BeaconGate则在 API 执行期间 Beacon 将被mask。这意味着 Beacon 现在开箱即用并且支持在调用api的同时保持mask自身。这可以有效缓解防病毒厂商可能基于 Kernel callbacks/ETW TI events. 触发内存扫描的风险。通过设置新的stage.beacon_gateMalleable C2 选项来启用stage { beacon_gate { All; } }有几个可选的值Comms– 目前仅支持InternetOpenA和InternetConnectA即仅限 HTTP(S) WinInet BeaconCore– 这是 Beacon 现有系统调用 API 的 Windows API 等效项例如VirtualAlloc。有关支持函数的完整列表请参阅 BeaconGate 文档 。Cleanup– 目前支持通过 Sleepmask 代理ExitThread。启用此功能后Sleepmask 默认会在 Beacon 退出前清除内存中的 Beacon 进程。此外这也为操作人员提供了在 Beacon 退出前执行自定义清理操作的机会。All– Comms Core Cleanup.也支持手动指定需要代理的APIstage { beacon_gate { VirtualAlloc; VirtualAllocEx; InternetConnectA; } }需要注意的是如果启用了Core某些更 intensive 的 Beacon 命令例如ps可能会导致 CPU 使用率飙升。这是预期行为因为ps命令在底层实现 Beacon 在睡眠中masking的时候会产生多次OpenProcess/CloseHandleAPI的调用。如果需要您可以在运行时通过beacon_gate disable禁用 BeaconGate或者在您自己的 Sleepmask BOF 中禁用特定函数的masking。还需要指出的是BeaconGate 和 Cobalt Strike 现有的syscall_method选项是互斥的如果为某个 API 启用 BeaconGate它将优先于系统调用。但是您可以为特定的 API 启用 BeaconGate而对其他 API 使用 Beacon 现有的系统调用方法。例如stage { set syscall_method Indirect; beacon_gate { VirtualAlloc; // Only VirtualAlloc is proxied via BeaconGate } }UDRL的不足使用 UDRL 时需要注意的一点是加载的 C2 配置文件的stage块中定义的任何选项都将被忽略。这是有意为之因为 UDRL 的设计理念是让开发者掌控全局。然而这在使用默认的sleepmask时可能会造成混淆因为它在mask和unmask Beacon 内存时会使用stage.userwx作为提示。例如如果stage.userwx设置为true但 UDRL 将内存分配为 R/RW/RX根据每个section的实际情况分配则sleepmask要么无法正确mask Beacon 的所有section导致它们处于unmask状态要么会尝试mask但最终崩溃因为它不知道需要先将内存属性设置为可写。另外值得一提的是这个 UDRL 与 Beacon 的 fork run Post-ex 命令例如 execute-assembly、powerpick 等所使用的UDRL并不相同。操作员可以编写自定义的 Post-ex UDRL 来替换默认的它们几乎完全相同。但是正如自定义 UDRL 会忽略 Malleable C2 的stage块中的选项一样自定义的Post-ex UDRL 也会忽略post-ex块中的选项。解决方案 - 三板斧Custom Sleep MasksThe issue of memory allocations 可以通过使用自定义UDRL和Sleepmask来解决因为 UDRL 可将已分配的内存信息传递给Sleepmask。这不仅包括为 Beacon 的各个Sections.data、.text 等分配的内存还包括开发者希望进行的任何自定义内存分配。这些数据通过ALLOCATED_MEMORY_REGION结构提供。4.10版本的BeaconUserData.htypedef struct _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;然后在调用DLL_BEACON_START之前通过调用 DllMain 并传入DLL_BEACON_USER_DATAreason 将此信息传递给 Beacon。// pass Beacon User Data (BUD) to Beacon ((DLLMAIN)entryPoint)(0, DLL_BEACON_USER_DATA, userData); // call Beacons entrypoints ((DLLMAIN)entryPoint)((HINSTANCE)loadedDllBaseAddress, DLL_PROCESS_ATTACH, NULL); ((DLLMAIN)entryPoint)((HINSTANCE)loaderStart, DLL_BEACON_START, NULL);当 Beacon 准备进入睡眠状态时执行权会通过PSLEEPMASK_INFO结构传递给 Sleep Mask。void sleep_mask(PSLEEPMASK_INFO info, PFUNCTION_CALL funcCall)已分配的内存数据位于BEACON_INFO结构中可以对其进行循环并根据需要进行处理。info-beacon_info.allocatedMemory.AllocatedMemoryRegionsSystem Calls开发者还可以将 Beacon 的默认系统调用解析器我认为它是基于 SysWhispers3 的替换为其他完全不同的解析器例如 Hells Gate、Halos Gate、Tartarus Gate、RecycledGate 等。解析 UDRL 中的系统调用号和函数指针并填充SYSCALL_API和RTL_API结构。4.10版本的BeaconUserData.htypedef struct { 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; typedef struct { PVOID rtlDosPathNameToNtPathNameUWithStatusAddr; PVOID rtlFreeHeapAddr; PVOID rtlGetProcessHeapAddr; } RTL_API, *PRTL_API;然后这些信息会按照上述方式通过同一个DLL_BEACON_USER_DATA调用传递给 Beacon。SYSCALL_API_ENTRYSYSCALL_API_ENTRY如下所示typedef struct { PVOID fnAddr; // address of Nt* function PVOID jmpAddr; // syscall/FastSysCall/KiFastSystemCall instruction DWORD sysnum; // System Call number } SYSCALL_API_ENTRY, *PSYSCALL_API_ENTRY;如果在 C2 配置文件中将stage.syscall_method设置为direct则使用fnAddr值。如果设置为indirect则使用jmpAddr和sysnum值。BeaconGateBeaconGate 是一项功能它指示 Beacon 通过自定义的 Sleep Mask 代理支持的 API 调用。其原理是Sleep Mask 可以mask Beacon 的内存设置任何额外的规避功能例如调用堆栈欺骗发起 API 调用unmask Beacon然后将结果返回给调用者Beacon。如果配置文件中同时定义了syscall_method和beacon_gate则 BeaconGate 将优先。在下面的示例中只有 VirtualAlloc 会通过 BeaconGate 代理而其他所有调用都将使用间接系统调用。stage { set syscall_method indirect; beacon_gate { VirtualAlloc; } }但这并不意味着你不能从 BeaconGate 发出系统调用我们稍后会谈到这一点。当 API 调用被代理到Sleepmask时相关数据保存在FUNCTION_CALL结构中。sleepmask.cpp:void sleep_mask(PSLEEPMASK_INFO info, PFUNCTION_CALL funcCall)beacon_gate.htypedef struct { 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;处理这些调用的最佳位置可能是在BeaconGateWrapper函数中。使用 WinAPI 枚举来检查正在调用哪个 API并使用您选择的任何技术例如 VulcanRaven、SilentMoonwalk 等执行相应的调用堆栈欺骗。如果您不需要或不想使用系统调用例如因为您在 UDRL 中执行了一些解除钩子操作只需在堆栈欺骗之后调用原始函数指针即可。void BeaconGateWrapper(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; }如果您确实需要使用系统调用可以忽略原始函数指针直接执行自定义的系统调用代码。BeaconGate 仍然可以受益于您在 UDRL 中所做的任何自定义系统调用解析只需使用BeaconGetSyscallInformationBOF API 即可。该 API 返回一个PBEACON_SYSCALLS结构其中包含您通过 BUD 提供的PSYSCALL_API和PRTL_API。typedef struct { PSYSCALL_API syscalls; PRTL_API rtls; } BEACON_SYSCALLS, *PBEACON_SYSCALLS;需要注意的是这些数据是从 Beacon 复制的因此必须在mask Beacon 之前执行此操作。gate.cpp:void BeaconGateWrapper(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); } ... }然后您可以根据需要通过调用 API 访问 ssn、直接跳转和间接跳转值。void BeaconGateWrapper(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 系统调用 APIBeacon 还公开了一组特定的系统调用 API例如BeaconVirtualAlloc、BeaconVirtualProtect和BeaconOpenProcess用于Post-ex BOF。调用这些函数时执行将根据 Beacon 的配置进行。如果 Beacon 未配置为使用系统调用则它们将作为常规 WinAPI 调用执行如果配置为使用默认的直接/间接系统调用实现则它们将根据 SysWhispers3(?) 执行如果您从 UDRL 传递了自定义系统调用数据则它们将根据您的自定义解析方法执行如果配置为使用 BeaconGate则调用将被代理到您的 Sleep Mask。对于使用这些通用 API 的 BOF它可以避免在所有后扩展 BOF 之间重复编写相同的系统调用和/或调用堆栈欺骗代码。好像到4.10Beacon只支持了20个API是可以通过BOF调用的。参考https://rastamouse.me/udrl-sleepmask-and-beacongate/https://www.cobaltstrike.com/blog/cobalt-strike-410-through-the-beacongate