Skip to content

Commit 6daab54

Browse files
authored
Merge pull request #267 from uber/sliver-dev
Added basic support for Sliver
2 parents 0bac709 + cf8c942 commit 6daab54

File tree

7 files changed

+301
-1
lines changed

7 files changed

+301
-1
lines changed

c2servers/cron.d/redelk_sliver

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#
2+
# Part of RedELK
3+
# cron.d script for periodic actions related to RedELK and Cobalt Strike
4+
#
5+
# Author: hypnoticpattern
6+
#
7+
8+
SHELL=/bin/sh
9+
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
10+
11+
# Command to sync the logs from sliver to our scponly user's home directory
12+
# m h dom mon dow user command
13+
* * * * * root /usr/bin/rsync -rvx --append-verify --delete /root/.sliver/logs/audit.json /home/scponly/sliver/; /bin/chown -R scponly:scponly /home/scponly/sliver/*
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
- type: log
2+
scan_frequency: 5s
3+
enabled: true
4+
fields_under_root: true
5+
json.keys_under_root: true
6+
json.add_error_key: true
7+
paths:
8+
- /root/.sliver/logs/audit.json
9+
fields:
10+
infra:
11+
attack_scenario: @@ATTACKSCENARIO@@
12+
log:
13+
type: rtops
14+
c2:
15+
program: sliver
16+
log:
17+
type: events

elkserver/docker/redelk-base/redelkinstalldata/42_redelk-base-docker-init.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ fi
206206
echo "" >>$LOGFILE
207207

208208
echo "[*] Fixing cron file permissions" | tee -a $LOGFILE
209-
chown root:root /etc/cron.d/redelk >>$LOGFILE 2>&1
209+
chown root:root /etc/cron.d/redelk && chmod 644 /etc/cron.d/redelk >> $LOGFILE 2>&1
210210
ERROR=$?
211211
if [ $ERROR -ne 0 ]; then
212212
echo "[X] Could not fix cron file permissions (Error Code: $ERROR)."

elkserver/docker/redelk-base/redelkinstalldata/scripts/config.py

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
enrich = {
8383
"enrich_csbeacon": {"enabled": True, "interval": 300},
8484
"enrich_stage1": {"enabled": True, "interval": 300},
85+
'enrich_sliver': {"enabled": True, 'interval': 300},
8586
"enrich_greynoise": {
8687
"enabled": True,
8788
"interval": 310,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
#!/usr/bin/python3
2+
"""
3+
Part of RedELK
4+
This script enriches rtops lines with data from initial Sliver implant
5+
6+
Authors:
7+
- Outflank B.V. / Mark Bergman (@xychix)
8+
- Lorenzo Bernardi (@fastlorenzo)
9+
- hypnoticpattern
10+
"""
11+
12+
import logging
13+
import traceback
14+
15+
from modules.helpers import es, get_initial_alarm_result, get_query, get_value
16+
17+
info = {
18+
'version': 0.1,
19+
'name': 'Enrich Sliver implant data',
20+
'alarmmsg': '',
21+
'description': 'This script enriches rtops lines with data from initial Sliver session',
22+
'type': 'redelk_enrich',
23+
'submodule': 'enrich_sliver'
24+
}
25+
26+
27+
class Module():
28+
""" enrich sliver module """
29+
def __init__(self):
30+
self.logger = logging.getLogger(info['submodule'])
31+
32+
def run(self):
33+
""" run the enrich module """
34+
ret = get_initial_alarm_result()
35+
ret['info'] = info
36+
hits = self.enrich_sliver_data()
37+
ret['hits']['hits'] = hits
38+
ret['hits']['total'] = len(hits)
39+
self.logger.info('finished running module. result: %s hits', ret['hits']['total'])
40+
return ret
41+
42+
def enrich_sliver_data(self):
43+
""" Get all lines in rtops that have not been enriched yet (for Sliver) """
44+
es_query = f'implant.id:* AND c2.program: sliver AND NOT c2.log.type:implant_newsession AND NOT tags:{info["submodule"]}'
45+
not_enriched_results = get_query(es_query, size=10000, index='rtops-*')
46+
47+
# Created a dict grouped by implant ID
48+
implant_ids = {}
49+
for not_enriched in not_enriched_results:
50+
implant_id = get_value('_source.implant.id', not_enriched)
51+
if implant_id in implant_ids:
52+
implant_ids[implant_id].append(not_enriched)
53+
else:
54+
implant_ids[implant_id] = [not_enriched]
55+
56+
hits = []
57+
# For each implant ID, get the initial session line
58+
for implant_id, implant_val in implant_ids.items():
59+
initial_sliver_session_doc = self.get_initial_sliver_session_doc(implant_id)
60+
61+
# If not initial session line found, skip the session ID
62+
if not initial_sliver_session_doc:
63+
continue
64+
65+
for doc in implant_val:
66+
# Fields to copy: host.*, implant.*, process.*, user.*
67+
res = self.copy_data_fields(initial_sliver_session_doc, doc, ['host', 'implant', 'user', 'process'])
68+
if res:
69+
hits.append(res)
70+
71+
return hits
72+
73+
def get_initial_sliver_session_doc(self, implant_id):
74+
""" Get the initial implant document from Sliver or return False if none found """
75+
query = f'implant.id:{implant_id} AND c2.program: sliver AND c2.log.type:implant_newsession'
76+
initial_sliversession_doc = get_query(query, size=1, index='rtops-*')
77+
initial_sliversession_doc = initial_sliversession_doc[0] if len(initial_sliversession_doc) > 0 else False
78+
self.logger.debug('Initial sliver session line [%s]: %s', implant_id, initial_sliversession_doc)
79+
return initial_sliversession_doc
80+
81+
def copy_data_fields(self, src, dst, fields):
82+
""" Copy all data of [fields] from src to dst document and save it to ES """
83+
for field in fields:
84+
if field in dst['_source']:
85+
self.logger.info('Field [%s] already exists in destination document, it will be overwritten', field)
86+
dst['_source'][field] = src['_source'][field]
87+
88+
try:
89+
es.update(index=dst['_index'], id=dst['_id'], body={'doc': dst['_source']})
90+
return dst
91+
# pylint: disable=broad-except
92+
except Exception as error:
93+
# stackTrace = traceback.format_exc()
94+
self.logger.error('Error enriching sliver session document %s: %s', dst['_id'], traceback)
95+
self.logger.exception(error)
96+
return False
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
# Part of RedELK
2+
#
3+
# In this file we configure the logstash filters for Sliver logs
4+
#
5+
# Author: hypnoticpattern
6+
#
7+
8+
filter {
9+
if [infra][log][type] == "rtops" and [c2][program] == "sliver" {
10+
if [c2][log][type] == "events" {
11+
12+
# Removing periodic commands run by the C2 server "GetSessions", "GetBeacons" and "GetVersion"
13+
if "info" in [level] and "GetSessions" in [msg] {
14+
drop {}
15+
}
16+
17+
if "info" in [level] and "GetBeacons" in [msg] {
18+
drop {}
19+
}
20+
21+
if "info" in [level] and "GetVersion" in [msg] {
22+
drop {}
23+
}
24+
25+
# Remove base64 encoded payload from the Upload command
26+
if "Upload" in [msg] and "Data" in [msg] {
27+
mutate {
28+
gsub => [ "[msg]", '\\"Data\\":\\"[a-zA-Z0-9+\/]+={0,2}\\",', "" ]
29+
}
30+
}
31+
32+
json {
33+
source => "msg"
34+
}
35+
36+
# Remove base64 encoded payload from the Upload command
37+
if [msg][Upload] {
38+
mutate {
39+
40+
remove_field => ["time"]
41+
}
42+
}
43+
44+
date {
45+
match => [ "[time]", "ISO8601" ]
46+
target => "@timestamp"
47+
timezone => "Etc/UTC"
48+
}
49+
50+
mutate {
51+
copy => { "@timestamp" => "[c2][timestamp]" }
52+
remove_field => ["time"]
53+
}
54+
55+
mutate {
56+
rename => { "[msg]" => "[c2][message]" }
57+
}
58+
59+
if [method] {
60+
mutate {
61+
replace => { "[c2][log][type]" => "c2_command" }
62+
gsub => [ "[method]", "/rpcpb\.SliverRPC/", "" ]
63+
}
64+
}
65+
66+
mutate {
67+
rename => { "[method]" => "[c2][command][name]" }
68+
rename => { "[request]" => "[c2][command][arguments]" }
69+
rename => { "[session]" => "[c2][implant]" }
70+
}
71+
72+
json {
73+
source => "[c2][command][arguments]"
74+
target => "[c2][command][arguments]"
75+
}
76+
77+
json {
78+
source => "[c2][implant]"
79+
target => "[c2][implant]"
80+
}
81+
82+
if [c2][command][arguments][Request][SessionID] {
83+
mutate {
84+
rename => { "[c2][command][arguments][Request][SessionID]" => "[implant][id]" }
85+
}
86+
}
87+
88+
if [c2][implant][Hostname] {
89+
mutate {
90+
copy => { "[c2][implant][Hostname]" => "[host][name]" }
91+
}
92+
}
93+
94+
if [c2][implant][Username] {
95+
mutate {
96+
copy => { "[c2][implant][Username]" => "[user][name]" }
97+
}
98+
}
99+
100+
# Logstash errors out converting TunnelID to long. Forcing casting to string.
101+
if [c2][command][arguments][TunnelID]
102+
{
103+
mutate {
104+
convert => { "[c2][command][arguments][TunnelID]" => "string" }
105+
}
106+
}
107+
108+
if [c2][command][name] == "LootAdd" {
109+
if [c2][command][arguments][Credential] {
110+
mutate {
111+
replace => { "[c2][log][type]" => "credentials"}
112+
}
113+
114+
# User/Password
115+
if [c2][command][arguments][CredentialType] == 1 {
116+
mutate {
117+
copy => { "[c2][command][arguments][Credential][User]" => "[creds][username]" }
118+
copy => { "[c2][command][arguments][Credential][Password]" => "[creds][credential]"}
119+
}
120+
}
121+
122+
# API Key
123+
if [c2][command][arguments][CredentialType] == 1 {
124+
mutate {
125+
copy => { "[c2][command][arguments][Credential][Name]" => "[creds][username]"}
126+
copy => { "[c2][command][arguments][Credential][APIKey]" => "[creds][credential]"}
127+
}
128+
}
129+
}
130+
}
131+
132+
# Handle new Sliver session
133+
if [level] == "warning" and [Session] {
134+
135+
grok {
136+
match => { "[Session][RemoteAddress]" => "%{IP:[host][ip_ext]}:%{POSINT}"}
137+
}
138+
139+
if [Session][ActiveC2]
140+
{
141+
mutate {
142+
convert => { "[Session][ActiveC2]" => "string" }
143+
}
144+
}
145+
146+
mutate {
147+
replace => { "[c2][log][type]" => "implant_newsession" }
148+
rename => { "[Session][OS]" => "[host][os][family]" }
149+
rename => { "[Session][Hostname]" => "[host][name]" }
150+
rename => { "[Session][ID]" => "[implant][id]" }
151+
rename => { "[Session][LastCheckin]" => "[implant][checkin]" }
152+
rename => { "[Session][Name]" => "[implant][name]" }
153+
rename => { "[Session][Username]" => "[user][name]" }
154+
rename => { "[Session][PID]" => "[process][pid]" }
155+
rename => { "[Session][Filename]" => "[process][name]" }
156+
rename => { "[Session][Transport]" => "[c2][listener][type]" }
157+
rename => { "[Session][ActiveC2]" => "[implant][url]" }
158+
}
159+
160+
date {
161+
match => [ "[implant][checkin]", "UNIX" ]
162+
target => "[implant][checkin]"
163+
timezone => "Etc/UTC"
164+
}
165+
166+
}
167+
}
168+
}
169+
}

elkserver/mounts/redelk-config/etc/redelk/config.json.example

+4
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@
7272
"enabled": true,
7373
"interval": 300
7474
},
75+
"enrich_sliver": {
76+
"enabled": true,
77+
"interval": 300
78+
},
7579
"enrich_greynoise": {
7680
"enabled": true,
7781
"interval": 310,

0 commit comments

Comments
 (0)