BeaconEye
的核心原理是通过扫描 CobaltStrike
中的内存特征,并进行 BeaconConfig
扫描解析出对应的 Beacon
信息,项目地址是https://github.com/CCob/BeaconEye
CobaltStrike
的 shellcode
,实际都是通过反射加载的方式加载 Beacon.dll
,而 Beacon.dll
中存在 BeaconConfig
配置信息(主要定义通信目标/通信方式等),在 CobaltStrike
中对应的 Resource
是 sleeve/beacon.dll
BeaconConfig
的生成在 BeaconPayload
类的 exportBeaconStage
函数中这上面指向的 Settings
结构体就是 BeaconConfig
,比如 var1
,它代表实际通信的端口最终 CobaltStrike
会将 Settings
转化为 bytes
数组,然后使用固定的密钥进行 Xor
,并对剩余空白字段填入随机字符最后将生成的 beacon.dll
嵌入到最终的 PE
文件中
Settings
的 Add
系列函数,如 AddShort
,并不是简单的将 Short
类型直接追加到 bytes
数组中,而是追加了一个结构体第一个字段是 index
,第二个是 type
(short/int/...),第三个是 length
,第四个则是关键的 value
值,因此根据这个结构即可解析在内存或在文件中的 BeaconConfig
接下来让我们看一下 BeaconEye
的 yara
规则32位的 BeaconConfig
规则长这个样子,如果你认真阅读了前文一定会觉得很疑惑,因为按照 Java
当中的结构,它应该分为四个部分
[ ID ] [ DATA TYPE ID ] [ LENGTH OF VALUE ] [ VALUE ]
但是实际的 yara
规则却没有办法对上 java
中的 BeaconConfig
结构,说明 Beacon.dll
在装载的过程中,并没有直接将上述数据 memcpy
分配到堆中,接下来让我们通过对 beacon.dll
进行逆向通过 dllmain
跟进,发现有一个关键函数,里面首先解密了先前 BeaconConfig
的加密数据,然后遍历 BeaconConfig
。首先是在拿到了 Type
之后,直接往堆中分配的内存写入 WORD
长度的 Type
,然后根据 Type
进行判断, case1
对应 Short
, case2
对应 Int
, case3
对应 Data
,所以实际上最终的 BeaconConfig
的结构是
DWORD DWORD
[ DATA TYPE] [ VALUE ]
因此最终的 yara
规则可以解读如下 ??
代表通配符,实际匹配的就是 beacon.dll
当中真正的 config
结构体,到这一步,后面的结构体还原就是顺水推舟了
目前网上公开的 Bypass
方式,是没有办法 Bypass
的,原因在于网上的方式更多是修改 Xor
的密钥,但是因为 BeaconEye
是扫描内存,所以 Xor
的密钥并不重要(始终会还原)。因此如果需要在使用 CobaltStrike
但是仍然规避检测的话,只能选择重写 Beacon
,并将结构体特征修改。抑或是通过其他的一些方式使 HIDS/EDR
无法检测,比如使得它们无法 Scan/Dump
内存等