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
0 commit comments