干杯Security 10月27日 18:31
Cobalt Strike Sleep Mask的演进与规避策略
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入探讨了Cobalt Strike中Sleep Mask功能的演进历程,从最初的内存屏蔽机制到现代EDR规避技术的集成。文章详细介绍了Sleep Mask在Cobalt Strike 4.4至4.11版本中的更新,包括自定义代码能力、堆内存加密、BOF重构、Mutator Kit的应用以及BeaconGate的引入。通过分析代码实现和官方博客内容,文章阐述了Sleep Mask如何通过内存混淆、代码变异和API调用代理等方式来对抗检测。最后,文章还提及了针对Sleep Mask的检测方法和未来发展趋势,强调了对抗与检测的持续博弈。

🔵 **Sleep Mask功能演进与内存检测规避**:Cobalt Strike的Sleep Mask功能旨在通过在内存中屏蔽和解除自身代码,以避免内存检测。从4.4版本开始支持自定义Sleepmask代码,到4.5版本引入对Beacon堆内存的加密,再到后续版本将其重构为BOF形式,Sleep Mask不断演进以应对日益复杂的内存检测技术。其核心在于通过混淆和加密技术,使得Beacon在内存中的特征难以被静态或动态分析工具识别。

🟠 **Mutator Kit与LLVM代码变异**:为了应对基于静态特征的YARA签名检测,Mutator Kit应运而生。它利用LLVM的代码变异技术,通过替换等价运算符、插入虚假控制流块等多种混淆通道,动态生成每次都不同的Sleep Mask代码。这种方式极大地增强了代码的多样性和混淆度,使得静态签名难以准确识别,提高了Beacon的隐蔽性。

🟢 **BeaconGate与API调用自定义**:Cobalt Strike 4.10引入的BeaconGate允许用户自定义Beacon调用Windows API函数的方式。它将API调用代理给Sleep Mask执行,实现了类似于远程过程调用的机制。BeaconGate支持最多10个参数,并可通过Profile进行配置,为操作员提供了高度的灵活性,能够更精细地控制Beacon的行为,从而进一步规避异常API调用检测。

🟣 **Cobalt Strike 4.11新默认Sleep Mask与混淆**:Cobalt Strike 4.11版本提供了一个全新的默认Sleep Mask,集成了对Beacon、其堆分配以及自身进行的混淆,实现了开箱即用的静态签名防御。此外,该版本还引入了Stage的transform-obfuscate功能,可以直接对Beacon进行混淆,包括压缩、RC4加密和XOR运算等,显著提高了Shellcode的隐蔽性,使其能够绕过静态查杀。

🟤 **对抗与检测的持续博弈**:文章强调,安全领域的对抗是一个持续的过程。Cobalt Strike通过不断更新和引入新的规避技术,如Sleep Mask的演进、Mutator Kit和BeaconGate,来应对检测。同时,检测技术也在不断发展,如异常API调用检测、堆栈欺骗检测等。最终的有效性取决于操作员的技能水平和对最新技术的研究,而并非简单的“免杀”工具。

原创 鬼屋女鬼 2025-06-11 09:56 山东

好久没写文章了,开始今年的第一篇,顺便吐槽下,公众号这个排版真难用啊

好久没写文章了,开始今年的第一篇,顺便吐槽下,公众号这个排版真难用啊,话不多说,正文开始,今天主要来聊聊Cobalt Strike中的Sleep Mask


一、什么是Sleep Mask

Sleep Mask 是 Cobalt Strike 在内存中屏蔽和解除自身屏蔽的功能。主要目的是避免内存检测,但是Sleep Mask本身会被作为检测的一部分。

二、从 Sleep Mask 进化史看现代EDR规避


1、Cobalt Strike 4.4 首次加入Kit

2021年8月,Sleep Mask Kit 最初在 Cobalt Strike 4.4 中推出,提供自定义Sleepmask代码的能力,规避针对sleepmask本身的内存签名规则,默认大小为289字节

2、Cobalt Strike 4.5 Sleep Mask Update

2021年12月,Cobalt Strike 4.5 对Sleep mask进行了更新,Sleepmask能够加密Beacon使用的堆内存,从而规避BeaconEyes等针对堆内存特征的检测

源代码如下:

/******** 不要修改此文件开始 ********/

#include <windows.h>

#define MASK_SIZE 13

/*

 *  ptr  - 指向已分配内存的基地址指针。

 *  size - 为ptr分配的字节数。

 */

typedef struct {

        char * ptr;

        size_t size;

} HEAP_RECORD;

/*

 *  beacon_ptr   - 指向Beacon基地址的指针

 *  sections     - Beacon想要掩盖的内存区段列表。

 *                 每个区段由起始和结束索引位置的对表示。

 *                 列表以起始和结束位置均为0的对结束。

 *  heap_records - Beacon想要掩盖的堆内存地址列表。

 *                 列表以HEAP_RECORD.ptr为NULL结束。

 *  mask         - Beacon随机生成用于掩盖的掩码

 */

typedef struct {

        char  * beacon_ptr;

        DWORD * sections;

        HEAP_RECORD * heap_records;

        char    mask[MASK_SIZE];

} SLEEPMASKP;

void sleep_mask(SLEEPMASKP * parms, void(__stdcall *pSleep)(DWORD), DWORD time) {

/******** 不要修改此文件结束 ********/

/******** 修改文件开始 ********/

        DWORD * index;

        DWORD a, b;

        /* 遍历区段并进行掩盖 */

        index = parms->sections;

        while (TRUE) {

                a = *index; b = *(index + 1);

                index += 2;

                if (a == 0 && b == 0)

                        break;

                while (a < b) {

                        *(parms->beacon_ptr + a) ^= parms->mask[a % MASK_SIZE];

                        a++;

                }

        }

        /* 掩盖堆内存记录 */

        a = 0;

        while (parms->heap_records[a].ptr != NULL) {

                for (b = 0; b < parms->heap_records[a].size; b++) {

                        parms->heap_records[a].ptr[b] ^= parms->mask[b % MASK_SIZE];

                }

                a++;

        }

        pSleep(time);

        /* 取消掩盖堆内存记录 */

        a = 0;

        while (parms->heap_records[a].ptr != NULL) {

                for (b = 0; b < parms->heap_records[a].size; b++) {

                        parms->heap_records[a].ptr[b] ^= parms->mask[b % MASK_SIZE];

                }

                a++;

        }

        /* 遍历区段并取消掩盖 */

        index = parms->sections;

        while (TRUE) {

                a = *index; b = *(index + 1);

                index += 2;

                if (a == 0 && b == 0)

                        break;

                while (a < b) {

                        *(parms->beacon_ptr + a) ^= parms->mask[a % MASK_SIZE];

                        a++;

                }

        }

        /******** 修改文件结束 ********/

}

注:仅在睡眠时间超过 2 秒时进行屏蔽和取消屏蔽

来自官方的效果图,但是依旧会被卡巴斯基扫描到

3、Cobalt Strike 4.7-4.9 Sleep Mask Refactoring

2022年8月,Sleep Mask以BOF形式重构,不再存放在beacon的.text部分,而是存放在bof使用内存。同年6月,基于线程池的睡眠混淆技术Ekko发布,这使得在完成对Beacon相关内存加密后,再对Sleep Mask本身进行加密成为可能,这项技术后续被加入到 Sleep Mask Kit中

整个文件目录如下

更详细的以及一些注意事项,参考Sleep Mask Kit中的README.md,这里就详细分析关键代码

(1)加密内存区段、堆内存、Sleepmask bof 自身

首先是sleepmask.c,代码中存在一个sleep_mask函数,该函数根据用户的配置以及编译选项,来加密beacon的内存区段、堆内存以及是否加密sleep mask bof 自身。

加密函数mask_sections和mask_heap在common_mask.c文件中定义,mask_sections函数通过遍历SLEEPMASKP结构中的区段数组, 对每个区段调用mask_section函数来实现加密或解密操作,

mask_section函数用于xor加密或解密内存区段,接受三个参数:一个SLEEPMASKP指针和两个指定内存区段起始和结束位置的值

可以修改此处的异或加密方式,例如

    void mask_section(SLEEPMASKP * parms, DWORD a, DWORD b) {

       char key[] = "fjsihwisf349saf0";

       size_t key_lenght = sizeof(key) - 1;

       while (a < b) {

          *(parms->beacon_ptr + a) ^= key[a % key_lenght];

          a++;

       }

    }


    mask_heap函数对分配的堆内存进行加解密操作

    (2)修改.text属性

    首先调用setup_text_section函数确定Beacon中.text的位置,然后根据Profile文件配置中stage.userwx设置来决定是否掩码.text

    接下来使用

    mask_text_section

    unmask_text_section

    函数来修改

    .text

    的属性,根据用户选项,如果使用syscall则用

    NtProtectVirtualMemory

    函数修改内存属性,否则使用

    VirtualProtect

    函数修改内存属性

    (3)利用CreateTimerQueueTimer 混淆Sleep Mask自身

    sleepmask.c

    中,将

    EVASIVE_SLEEP

    设置为

    1

    ,将使用Ekko混淆Sleep Mask自身

    在下面的EVASIVE_SLEEP部分,我们可以选择是用默认的EVASIVE_SLEEP还是使用添加堆栈欺骗的EVASIVE_SLEEP

    需要注意此处的#define CFG_BYPASS 0的配置,默认情况下,使用EVASIVE_SLEEP功能会禁用CFG_BYPASS,如果需要进程注入,注入到受CFG保护的进程中,那么就需要把#define CFG_BYPASS设置为1,

    evasive_sleep

    函数,主要通过创建一系列的计时器,计时器在触发时使用

    NtContinue

    函数来将控制权转移到先前准备好的

    CONTEXT

     结构中指定的地址,

    NtContinue

    函数,其主要功能是恢复线程的上下文,其参数指向一个

    CONTEXT

    结构体的指针,该结构体包含线程的寄存器和其他信息

    其中加密使用

    SystemFunction032

    函数,该函数实现了RC4加密算法,可同时用于加密和解密,接收数据和密钥结构作为参数,配合

    VirtualProtect

    来实现对sleepmask代码进行加解密

    再次回到CFG的问题,为什么会触发CFG保护呢,先来看一下微软的介绍,再次回到我们的代码中CreateTimerQueueTimer 指向的回调例程是 NtContinue,它位于启用 CFG 时无法间接调用的特殊函数列表中。间接函数调用是从程序调用堆栈之外的另一个函数启动的调用。

    CFG中不允许的函数调用,此处是ntdll中

    那么此处的CFG_BYPASS,就是使用 markCFGValid_nt 函数调用 NtSetInformationVirtualMemory,将 NtContinue 添加到 CFG 允许列表从而绕过CFG保护

    不同的操作系统,tVmInformation 数据结构的大小不同,在某些环境下会导致崩溃,因此官方给出了一个默认注释的代码,利用 NtSetInformationVirtualMemory 函数实现了一个循环,直到找到合适的大小。

    (4)堆栈欺骗

    写累了,涉及的知识点比较多,等下一篇再详细介绍吧

    4、Mutator Kit(Sleep Mask mutated )

    之前的方法是运行时对Sleep Mask自身进行混淆(evasive sleep mask),也就是Ekko Sleep Obfuscation,这会大大增加我们被检测的概率,在某些环境下,用了Ekko反正直接被检测。除此之外还要考虑CFG(Control Flow Guard)保护问题,在某些环境下会直接崩溃。我本人非常不喜欢Ekko,有多远踢多远。

    所以后面官方推出了Mutator Kit,主要目的是通过LLVM的代码变异技术,动态生成每次都不同的sleep mask,从而避免被基于静态特征的YARA签名检测到。

    Mutator Kit利用LLVM的中间表示(IR)代码,应用多种变异passes(包括替换等价运算符、插入虚假控制流块、代码扁平化、基本块拆分等)对sleep mask的代码结构和机器码进行变异。这样生成的sleep mask在功能上等价,但机器码层面差异巨大,难以被静态签名准确识别

    具体作用包括:

    默认的sleep mask Kit每次编译后的的.text段完全一致,YARA签名可以直接匹配其特定的机器码指令序列,从而检测Sleepmask,

      // [1] Build the sleepmask.

      $ ./build.sh 49 WaitForSingleObject 

      true

       none /tmp/dist 

      [ ... ] 

      [Sleepmask kit] [*] Compile sleepmask.x64.o 

      [ ... ]

      // [2] Use objdump to find the text section size.

      $ objdump -h sleepmask.x64.o 

       sleepmask.x64.o:     file format pe-x86-64 

      Sections:

      Idx Name          Size      VMA               LMA               File off  Algn

        0 .text         00000200  0000000000000000  0000000000000000  00000104  2**4

                        CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE

      // [3] Extract the .text section.

      // NB skip is the offset('File off') of the .text section 

      // from objdmp and count is the size of the section 

      // e.g. python -c 'print(int("104", 16))' == 260.

      $ dd 

      if

      =sleepmask.x64.o of=sleepmask1.bin skip=260 count=512 bs=1 

      // [4] Calculate shasum.

      $ shasum sleepmask1.bin 

      4f7813a6aae018a4cf6a78040d9c20024b5a83da  sleepmask1.bin 

      // [5] Repeat the steps again to build another sleep 

      // mask and extract/hash the .text section - the hash is identical!

      $ shasum sleepmask2.bin 

      4f7813a6aae018a4cf6a78040d9c20024b5a83da  sleepmask2.bin


      比如说 Elastic  针对sleepm mask的 YARA扫描规则,Windows_Trojan_CobaltStrike_b54b94ac,它在默认Sleep mask中查找以下操作码模式:0x4C 0x8B 0x53 0x08

        mov r10, [rbx+0x08] 

        mov r9d, [r10] 

        mov r11d, [r10+0x04] 

        lea r10, [r10+0x08] 

        test r9d, r9d 

        jnz 0x0000000000000007 

        test r11d, r11d 

        jz 0x0000000000000035 

        cmp r9d, r11d 

        jnb 0xFFFFFFFFFFFFFFE8 

        mov rdi, r9 

        mov r8, [rbx]


        Windows_Trojan_CobaltStrike_b54b94ac规则:

          rule Windows_Trojan_CobaltStrike_b54b94ac {

              meta:

                  author = "Elastic Security"

                  id = "b54b94ac-6ef8-4ee9-a8a6-f7324c1974ca"

                  fingerprint = "2344dd7820656f18cfb774a89d89f5ab65d46cc7761c1f16b7e768df66aa41c8"

                  creation_date = "2021-10-21"

                  last_modified = "2022-01-13"

                  description = "Rule for beacon sleep obfuscation routine"

                  threat_name = "Windows.Trojan.CobaltStrike"

                  reference_sample = "36d32b1ed967f07a4bd19f5e671294d5359009c04835601f2cc40fb8b54f6a2a"

                  severity = 100

                  arch_context = "x86"

                  scan_context = "file, memory"

                  license = "Elastic License v2"

                  os = "windows"

              strings:

                  $a_x64 = { 4853 08 45 8045 8504 4852 08 45 85 C9 75 05 45 85 DB 74 33 45 3B CB 73 E6 49 8B F9 4803 }

                  $a_x64_smbtcp = { 4807 B8 4F EC C4 441 F7 E1 41 8B C1 C1 EA 02 41 FF C1 6B D2 02B C2 8438 10 42 30 006 48 }

                  $a_x86 = { 846 04 808 850 04 83 C0 08 89 55 08 89 45 085 C9 75 04 85 D2 74 23 3B CA 73 E6 806 8308 33 D2 }

                  $a_x86_2 = { 806 8308 33 D2 608B C1 5B F7 F3 844 32 08 30 07 41 3408 72 E6 845 FC EB C7 }

                  $a_x86_smbtcp = { 807 834 08 33 D2 608B C1 5B F7 F3 844 308 30 06 41 3408 72 E6 845 FC EB }

              condition:

                  any of them

          }


          Beacon 休眠时,sleep mask在内存中可见

          使用 Mutator Kit 生成Sleep mask,来自官方的默认 sleep mask 和mutated sleep mask 中相同函数的调用对比图

          详细的配置和说明参考官方文章:

          https://www.cobaltstrike.com/blog/introducing-the-mutator-kit-creating-object-file-monstrosities-with-sleep-mask-and-llvm

          5、Cobalt Strike 4.10 BeaconGate   with Custom Sleepmask BOFs

          2024年7月,Sleepmask-vs发布,支持添加堆栈欺骗等等,配合beacongate使的规避性更强

          在过去几年中,异常 API 调用的检测逻辑急剧增加。例如syscall-detect、MalMemDetect、Hunt-Sleeping-Beacons和pe-sieve等开源项目都展示了从未支持的内存中搜寻可疑 API 的调用。此外,Elastic 凭借 异常的调用堆栈检测逻辑 推动了防御行业的向前发展,这些都给红队操作带来了巨大挑战。之前Cobalt Strike难以应对这些检测,特别是无法基于Beacon的系统调用实现细粒度控制,只能通过复杂的IAT hooking实现。

          因此,BeaconGate正式诞生,先来看下官方的定义,从高层来看,睡眠掩码在概念上类似于远程过程调用(RPC),尽管在相同的进程地址空间内。例如,当Beacon睡眠时,它将调用Sleepmask BOF、mask和sleep。Beacon在这里充当“客户端”,Sleepmask是代表Beacon执行Sleep调用的“服务器”。在Cobalt Strike 4.10中,我们已经将这一想法推到了合乎逻辑的结论,Sleepmask现在支持执行任意函数。因此,现在可以配置Beacon转发其Windows API调用,以便通过Sleepmask(又名Beacon Gate)执行

          长话短说,官方说了那么多其实总结下来就是,BeaconGate 使用户能够自定义 Beacon 调用 WinAPI 函数的方式。 配置 并启用BeaconGate 后,Beacon 将代理其 Windows API 调用,以通过 Sleepmask 执行(即类似于远程过程调用)

          BeaconGate 工作原理的高级示意图

          目前beacongate支持的的调用API如下:

          可以通过Profile选项来配置 BeaconGate

            stage { 

                beacon_gate { 

                      All; 

                } 

            } 


            简单解读下代码,

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

            FUNCTION_CALL

            BeaconGate 目前最多支持 10 个参数

            那么怎么添加堆栈欺骗已经显而易见了

            如果想要使用自定义睡眠函数,只需要修改sleep.cpp即可

            例如,此处的MySleep() 的实现依赖于对 IO 对象实现等待 ,那么也可以自己塞个Ekko或者Foliage,个人感觉意义不大。

            如果拿BeaconGate和Brute Ratel C4的自定义堆栈帧做对比,我们会发现完全是2个极端:


            这几天官方又发布了新文章,对Sleepmask-vs进行了更新,使用clang来编译内敛汇编,彻底提供了傻瓜式 自带开箱即用规避的 Sleep Mask,官方文章

            https://www.cobaltstrike.com/blog/instrumenting-beacon-with-beacongate-for-call-stack-spoofing

            来自官方的配置图,根据用户选项应用不同的sleepmask,包括默认 Sleep Mask、间接syscall的Sleep Mask、带返回地址欺骗的Sleep Mask(执行X86)、带合成帧的Sleep Mask

            至于具体的效果如何,那不得而知

            6、Cobalt Strike 4.11 New default Sleep Mask

            2025年3月,在Cobalt Strike 4.11版本中,官方添加了一个全新的 Sleep Mask,在之前的版本中,Cobalt Strike 的"evasive sleep"功能已包含在 Arsenal Kit 中,它基于经过修改的开源技术(Ekko),因此需要进行大量定制修改才能与 Beacon 配合使用,全新的Sleep mask将对 Beacon、其堆分配 以及 自身 进行混淆,这意味着 Beacon 在运行时能够抵御静态签名,开箱即用无需任何额外配置。

            总结下来就是 Cobalt Strike 4.11提供了一个全新的默认Sleep mask,如果你不具备任何开发能力,那么可以直接使用这个全新的默认Slee pmask,如果不想使用这个默认Sleep mask,那么也可以继续使用Arsenal Kit 中提供的Sleep mask Kit 或者Sleep mask-vs进行自定义开发。

            阅读官方的文章,总结如下:

            至于其他的就是将Beacon 的默认反射加载器移植到一个新的 prepend/SRDI 样式加载器中,并添加了几个新的规避功能:

            其中 transform-obfuscate 是最大亮点,可以直接混淆Beacon,也就是说现在生成的shellcode是可以直接无视静态查杀的

            例如下面的选项将压缩 Beacon ,使用随机的 64 位密钥对其进行rc4 加密,并使用随机的 32 位密钥对其进行 xor 运算,最后再对其进行 base64 编码

              stage {

                  transform-obfuscate {

                      lznt1;

                      rc4 “64”;        # NB The max supported rc4 key size is 128

                      xor “32”;        # NB The max supported xor key size is 2048

                      base64;

                   }

              }


              至于很详细的还是直接看官方文章(https://www.cobaltstrike.com/blog/cobalt-strike-411-shh-beacon-is-sleeping)吧,至于什么时候能用上4.11,4.10都还没泄露呢,洗洗睡吧,梦里什么都有。

              三、最终回(闲聊几句)

              让我们再次回到Cobalt Strike 4.91,那么根据上面的这些知识,我们可以实现在不启用 evasive sleep mask 情况下实现堆栈欺骗,让我们的堆栈看起来合法

              如图:

              默认的sleepmask 堆栈,会出现内存地址

              修改后的sleepmask 堆栈

              Windows 10 效果

              Windows 11(evasive_sleep_stack_spoof中自带的堆栈欺骗,在windows 11上不生效)效果

              那么如何修改,相信大家看图就知道了,这里就不给代码了,拒绝伸手党,正所谓自己动手丰衣足食

              写在最后

              如果上个线就是免杀了,那么人均都是免杀大师,这个吊圈子就这样,上个线就是过了,演示嘎嘎过,实战落地没。

              对抗终是末路,检测才是王道。

              比如说某检测规则,可以做到通杀国内95%以上只会写Loader的脚本小子以及他们用的所谓的二开CS。如图,是不是感受到来自最新天擎的恐惧了

              至于怎么检测Sleep Mask,相信聪明的你已经想到了


              阅读原文

              跳转微信打开

              Fish AI Reader

              Fish AI Reader

              AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

              FishAI

              FishAI

              鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

              联系邮箱 441953276@qq.com

              相关标签

              Cobalt Strike Sleep Mask EDR规避 内存检测 Mutator Kit LLVM BeaconGate API调用 Shellcode 安全对抗 网络安全 信息安全 APT 红队 Cobalt Strike 4.11 BOF
              相关文章