Patrick Kelley 04da5c1250 Inital
2025-05-28 14:31:31 -04:00

173 lines
5.4 KiB
Plaintext

@load base/protocols/smb
module Cobaltstrike;
export {
# Number of times to match pattern before logging
const beacon_iterations: count = 4 &redef;
# Max size of read/write to track
const hb_len_limit = 25;
}
export {
redef enum Notice::Type +=
{
C1::Beacon_Activity
};
}
# start with buffer overflow response
event smb2_message(c: connection, hdr: SMB2::Header, is_orig: bool) {
if (c$smb_state$current_cmd$command == "IOCTL" && c$smb_state$current_cmd?$status) {
if (c$smb_state$current_cmd$status == "BUFFER_OVERFLOW") {
SumStats::observe("cs_smb",
SumStats::Key($host=c$id$orig_h,
$str=cat_sep($sep="|",
$def="NULL",
c$id$orig_p,
c$id$resp_h,
c$id$resp_p)),
SumStats::Observation($num=0,
$str=cat_sep($sep="|", $def="NULL", "buffer_overflow")));
}
}
}
event smb2_read_request(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID, offset: count, length: count) {
# if tagged, mark size
SumStats::observe("cs_smb",
SumStats::Key($host=c$id$orig_h,
$str=cat_sep($sep="|",
$def="NULL",
c$id$orig_p,
c$id$resp_h,
c$id$resp_p)),
SumStats::Observation($num=length,
$str=cat_sep($sep="|", $def="NULL", "read", length)));
}
event smb2_write_request(c: connection, hdr: SMB2::Header, file_id: SMB2::GUID, offset: count, length: count) {
if (length < hb_len_limit) {
SumStats::observe("cs_smb",
SumStats::Key($host=c$id$orig_h,
$str=cat_sep($sep="|",
$def="NULL",
c$id$orig_p,
c$id$resp_h,
c$id$resp_p)),
SumStats::Observation($num=length,
$str=cat_sep($sep="|", $def="NULL", "write", length)));
}
}
function thresh_crossed(key:SumStats::Key, result:SumStats::Result)
{
local r = result["cs_smb"];
#unpack our key 0: orig_p, 1: resp_h, 2: resp_p
local keys = split_string($str = key$str, $re=/\|/);
# note data_len is a str now because of pack/unpack
#local s = fmt("Possible SMB Beacon traffic: observered %.0f writes of data length %s", r$sum, keys[2]);
local s = fmt("Potential Cobalt Strike SMB Beacon Activity.");
print fmt(s);
local cid: conn_id = [$orig_h = key$host,
$orig_p = to_port(keys[0]),
$resp_h = to_addr(keys[1]),
$resp_p = to_port(keys[2])];
# TODO: error check this
local conn = lookup_connection(cid);
# Raise Notice
NOTICE([$note=C1::Beacon_Activity,
$msg=s,
#$src=key$host,
#$dst=to_addr(keys[0]),
#$identifier=key$str,
$conn = conn,
$suppress_for=30min]);
SumStats::next_epoch("CS_SMB_Beacon");
}
function check_beacon(key: SumStats::Key, result: SumStats::Result): double {
local cmds = vector("read" ,"write", "buffer_overflow");
local mark: int = -1;
local len: count = 0;
local r_vec = SumStats::get_last(result["cs_smb"]);
for (i in r_vec)
{
local keys = split_string($str = r_vec[i]$str, $re=/\|/);
if (mark == -1)
{
for (j in cmds)
{
if (keys[0] == cmds[j])
{
# keep track of where we are in the sequence
mark = j;
# get out of this loop
break;
}
}
}
# mark is set if first cmd write or read
if (len == 0 && r_vec[i]$num != 0)
{
len = r_vec[i]$num;
}
# len is set
if (keys[0] == cmds[mark])
{
if(cmds[mark] == "read" || cmds[mark] == "write")
{
if(len != r_vec[i]$num)
{
return 1.0;
}
}
else
{
if (cmds[mark] != "buffer_overflow")
{
#print fmt("cmd: %s\t\tvec: %s",cmds[mark], keys[0]);
return 1.0;
}
}
}
else
{
return 1.0;
}
# move mark up
mark = (mark + 1) % 3;
}
# winner, fit the pattern all the same length
return 3.0;
}
event zeek_init()
{
local r1 = SumStats::Reducer($stream="cs_smb",
$apply=set(SumStats::LAST),
$num_last_elements = beacon_iterations * 3
);
SumStats::create([$name = "CS_SMB_Beacon",
$epoch = 0secs, #manual epochs
$reducers = set(r1),
$threshold = 2.0,
$threshold_val = check_beacon,
$threshold_crossed = thresh_crossed
]);
}