csle-attack-profiler 0.6.0__py3-none-any.whl → 0.6.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of csle-attack-profiler might be problematic. Click here for more details.
- csle_attack_profiler/__version__.py +1 -1
- csle_attack_profiler/hmm_profiling.py +25 -481
- {csle_attack_profiler-0.6.0.dist-info → csle_attack_profiler-0.6.1.dist-info}/METADATA +3 -3
- csle_attack_profiler-0.6.1.dist-info/RECORD +8 -0
- csle_attack_profiler-0.6.0.dist-info/RECORD +0 -8
- {csle_attack_profiler-0.6.0.dist-info → csle_attack_profiler-0.6.1.dist-info}/WHEEL +0 -0
- {csle_attack_profiler-0.6.0.dist-info → csle_attack_profiler-0.6.1.dist-info}/top_level.txt +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '0.6.
|
|
1
|
+
__version__ = '0.6.1'
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
from typing import List, Union, Tuple, Any
|
|
2
|
+
import numpy as np
|
|
3
|
+
import sys
|
|
1
4
|
from csle_common.dao.system_identification.emulation_statistics import EmulationStatistics
|
|
2
5
|
from csle_common.dao.emulation_action.attacker.emulation_attacker_action_id import EmulationAttackerActionId
|
|
3
6
|
from csle_common.dao.emulation_action.attacker.emulation_attacker_action_type import EmulationAttackerActionType
|
|
4
7
|
from csle_common.dao.emulation_action.attacker.emulation_attacker_action_outcome import EmulationAttackerActionOutcome
|
|
5
8
|
from csle_common.dao.emulation_action.attacker.emulation_attacker_action import EmulationAttackerAction
|
|
6
9
|
from csle_attack_profiler.attack_profiler import AttackProfiler
|
|
7
|
-
from typing import List, Union, Tuple, Any
|
|
8
|
-
import numpy as np
|
|
9
|
-
import sys
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class HMMProfiler:
|
|
@@ -20,6 +20,7 @@ class HMMProfiler:
|
|
|
20
20
|
|
|
21
21
|
:param statistics: The list of EmulationStatistics objects
|
|
22
22
|
:param model_name: The name of the model
|
|
23
|
+
:return: None
|
|
23
24
|
"""
|
|
24
25
|
self.statistics = statistics
|
|
25
26
|
self.transition_matrix: List[List[float]] = []
|
|
@@ -38,11 +39,12 @@ class HMMProfiler:
|
|
|
38
39
|
|
|
39
40
|
:param transition_matrix: The transition matrix
|
|
40
41
|
:param states: The list of states of the HMM (format: 'A:attack_name' or
|
|
41
|
-
|
|
42
|
+
'no_intrusion' based on emulation statistics file)
|
|
42
43
|
:param metrics: The list of metrics to profile
|
|
43
44
|
:param save: Whether to save the matrices to a file
|
|
44
45
|
:param location: The location to save the matrices, if save = True, e.g "./resources",
|
|
45
|
-
|
|
46
|
+
default is current directory
|
|
47
|
+
:return: None
|
|
46
48
|
"""
|
|
47
49
|
emission_matrix, emission_matrix_observations = self.get_matrices_of_observation(self.statistics,
|
|
48
50
|
metric, hidden_states)
|
|
@@ -63,6 +65,7 @@ class HMMProfiler:
|
|
|
63
65
|
Loads the HMM model from the given location.
|
|
64
66
|
|
|
65
67
|
:param location: The location of the model files, default is current directory
|
|
68
|
+
:return: None
|
|
66
69
|
"""
|
|
67
70
|
self.transition_matrix = np.load(f'{location}/transition_matrix.npy')
|
|
68
71
|
self.hidden_states = np.load(f'{location}/hidden_states.npy')
|
|
@@ -75,18 +78,16 @@ class HMMProfiler:
|
|
|
75
78
|
Profiles a sequence of observations based on the HMM model.
|
|
76
79
|
|
|
77
80
|
:param sequence: The sequence of observations
|
|
78
|
-
|
|
79
|
-
|
|
80
81
|
:return: The most likely sequence of states
|
|
81
82
|
"""
|
|
82
|
-
|
|
83
|
+
|
|
83
84
|
path = HMMProfiler.viterbi(self.hidden_states, self.start_state_probs,
|
|
84
85
|
self.transition_matrix, self.emission_matrix,
|
|
85
86
|
sequence, self.emission_matrix_observations)
|
|
86
87
|
profiled_sequence = []
|
|
87
88
|
for i in range(len(path)):
|
|
88
89
|
profiled_sequence.append(self.hidden_states[int(path[i])])
|
|
89
|
-
|
|
90
|
+
|
|
90
91
|
return profiled_sequence
|
|
91
92
|
|
|
92
93
|
def get_matrices_of_observation(self, statistics: List[EmulationStatistics],
|
|
@@ -97,14 +98,13 @@ class HMMProfiler:
|
|
|
97
98
|
:param statistics: The list of EmulationStatistics objects
|
|
98
99
|
:param metric: The metric to get the emission matrix for
|
|
99
100
|
:param states: The list of states
|
|
100
|
-
|
|
101
101
|
:return: The emission matrix, the list of observations, the list of states
|
|
102
102
|
"""
|
|
103
103
|
emission_matrix = []
|
|
104
104
|
attack_observations = {}
|
|
105
105
|
attack_observations_total_counts = {}
|
|
106
106
|
all_keys = set()
|
|
107
|
-
|
|
107
|
+
|
|
108
108
|
for stats in statistics:
|
|
109
109
|
for condition, metric_distribution in stats.conditionals_counts.items():
|
|
110
110
|
action = condition.split('_')
|
|
@@ -118,7 +118,7 @@ class HMMProfiler:
|
|
|
118
118
|
# Add the observations of the attack to the dictionary
|
|
119
119
|
if metric in metric_distribution:
|
|
120
120
|
attack_observations[action[0]] = metric_distribution[metric]
|
|
121
|
-
|
|
121
|
+
# Sum the total counts of the observations
|
|
122
122
|
attack_observations_total_counts[action[0]] = sum(attack_observations[action[0]].values())
|
|
123
123
|
# Aggregate the counts from the metric distribution
|
|
124
124
|
else:
|
|
@@ -135,7 +135,7 @@ class HMMProfiler:
|
|
|
135
135
|
# Store all possible values for the observation
|
|
136
136
|
if action[0] in attack_observations:
|
|
137
137
|
all_keys.update(attack_observations[action[0]])
|
|
138
|
-
|
|
138
|
+
|
|
139
139
|
# Normalize the counts
|
|
140
140
|
for attack, _ in attack_observations.items():
|
|
141
141
|
attack_observations_total_counts[attack] = sum(attack_observations[attack].values())
|
|
@@ -182,7 +182,6 @@ class HMMProfiler:
|
|
|
182
182
|
Converts a list of states to a list of AttackProfiles.
|
|
183
183
|
|
|
184
184
|
:param states: The list of states to convert
|
|
185
|
-
|
|
186
185
|
:return: The list of EmulationAttackerActionId
|
|
187
186
|
"""
|
|
188
187
|
|
|
@@ -198,8 +197,8 @@ class HMMProfiler:
|
|
|
198
197
|
id=EmulationAttackerActionId.CVE_2015_1427_EXPLOIT, name="CVE-2015-1427 exploit", cmds=None,
|
|
199
198
|
type=EmulationAttackerActionType.EXPLOIT,
|
|
200
199
|
descr="Uses the CVE-2015-1427 vulnerability to "
|
|
201
|
-
|
|
202
|
-
|
|
200
|
+
"get remote code execution and then sets up a SSH backdoor"
|
|
201
|
+
"to upgrade the channel", index=None, ips=[],
|
|
203
202
|
action_outcome=EmulationAttackerActionOutcome.SHELL_ACCESS)
|
|
204
203
|
p = AttackProfiler.get_attack_profile(action)
|
|
205
204
|
new_states.append(p)
|
|
@@ -232,7 +231,7 @@ class HMMProfiler:
|
|
|
232
231
|
id=EmulationAttackerActionId.PING_SCAN_HOST, name="Ping Scan",
|
|
233
232
|
cmds=None, type=EmulationAttackerActionType.RECON,
|
|
234
233
|
descr="A host discovery scan, it is quick because it only checks of hosts "
|
|
235
|
-
|
|
234
|
+
"are up with Ping, without scanning the ports.", ips=None, index=None,
|
|
236
235
|
action_outcome=EmulationAttackerActionOutcome.INFORMATION_GATHERING, backdoor=False)
|
|
237
236
|
p = AttackProfiler.get_attack_profile(action)
|
|
238
237
|
new_states.append(p)
|
|
@@ -241,7 +240,7 @@ class HMMProfiler:
|
|
|
241
240
|
id=EmulationAttackerActionId.SAMBACRY_EXPLOIT, name="Sambacry Explolit", cmds=None,
|
|
242
241
|
type=EmulationAttackerActionType.EXPLOIT,
|
|
243
242
|
descr="Uses the sambacry shell to get remote code execution and then"
|
|
244
|
-
|
|
243
|
+
"sets up a SSH backdoor to upgrade the channel",
|
|
245
244
|
index=None, ips=[], action_outcome=EmulationAttackerActionOutcome.SHELL_ACCESS)
|
|
246
245
|
p = AttackProfiler.get_attack_profile(action)
|
|
247
246
|
new_states.append(p)
|
|
@@ -259,7 +258,8 @@ class HMMProfiler:
|
|
|
259
258
|
name="SSH dictionary attack for username=pw", cmds=None,
|
|
260
259
|
type=EmulationAttackerActionType.EXPLOIT, index=None,
|
|
261
260
|
descr="A dictionary attack that tries common passwords and usernames for SSH"
|
|
262
|
-
|
|
261
|
+
"where username=password", ips=None,
|
|
262
|
+
action_outcome=EmulationAttackerActionOutcome.SHELL_ACCESS)
|
|
263
263
|
p = AttackProfiler.get_attack_profile(action)
|
|
264
264
|
new_states.append(p)
|
|
265
265
|
elif state == 'A:FTP dictionary attack for username=pw':
|
|
@@ -267,7 +267,7 @@ class HMMProfiler:
|
|
|
267
267
|
id=EmulationAttackerActionId.FTP_SAME_USER_PASS_DICTIONARY_HOST,
|
|
268
268
|
name="FTP dictionary attack for username=pw", cmds=None, type=EmulationAttackerActionType.EXPLOIT,
|
|
269
269
|
index=None, descr="A dictionary attack that tries common passwords and"
|
|
270
|
-
|
|
270
|
+
"usernames for FTP where username=password", ips=None,
|
|
271
271
|
action_outcome=EmulationAttackerActionOutcome.SHELL_ACCESS)
|
|
272
272
|
p = AttackProfiler.get_attack_profile(action)
|
|
273
273
|
new_states.append(p)
|
|
@@ -277,7 +277,7 @@ class HMMProfiler:
|
|
|
277
277
|
name="Telnet dictionary attack for username=pw", cmds=None,
|
|
278
278
|
type=EmulationAttackerActionType.EXPLOIT, index=None,
|
|
279
279
|
descr="A dictionary attack that tries common passwords and usernames for"
|
|
280
|
-
|
|
280
|
+
"Telnet where username=password", ips=None,
|
|
281
281
|
action_outcome=EmulationAttackerActionOutcome.SHELL_ACCESS)
|
|
282
282
|
p = AttackProfiler.get_attack_profile(action)
|
|
283
283
|
new_states.append(p)
|
|
@@ -302,7 +302,7 @@ class HMMProfiler:
|
|
|
302
302
|
id=EmulationAttackerActionId.SSH_BACKDOOR, name="Install SSH backdoor",
|
|
303
303
|
cmds=None, type=EmulationAttackerActionType.POST_EXPLOIT,
|
|
304
304
|
descr="If taken root on remote machine, installs a ssh backdoor useful for"
|
|
305
|
-
|
|
305
|
+
"upgrading telnetor weaker channels", index=None, ips=[],
|
|
306
306
|
action_outcome=EmulationAttackerActionOutcome.PIVOTING, alt_cmds=None, backdoor=True)
|
|
307
307
|
p = AttackProfiler.get_attack_profile(action)
|
|
308
308
|
new_states.append(p)
|
|
@@ -310,7 +310,7 @@ class HMMProfiler:
|
|
|
310
310
|
new_states.append(state)
|
|
311
311
|
|
|
312
312
|
return new_states
|
|
313
|
-
|
|
313
|
+
|
|
314
314
|
def calculate_initial_states(self, transition_matrix: List[List[float]]) -> List[float]:
|
|
315
315
|
"""
|
|
316
316
|
Calculates the initial states probabilities based on the transition matrix.
|
|
@@ -318,7 +318,6 @@ class HMMProfiler:
|
|
|
318
318
|
1 / (# of states)
|
|
319
319
|
|
|
320
320
|
:param transition_matrix: The transition matrix
|
|
321
|
-
|
|
322
321
|
:return: The start states probabilities
|
|
323
322
|
"""
|
|
324
323
|
start_states = []
|
|
@@ -341,7 +340,6 @@ class HMMProfiler:
|
|
|
341
340
|
:param emission_matrix: The emission matrix
|
|
342
341
|
:param obs: The observation sequence
|
|
343
342
|
:param emissions_list: The list of possible observations
|
|
344
|
-
|
|
345
343
|
:return: The most likely sequence of hidden states
|
|
346
344
|
"""
|
|
347
345
|
# Convert the emissions list to a numpy array, to use the where function
|
|
@@ -407,470 +405,18 @@ class HMMProfiler:
|
|
|
407
405
|
:param intrusion_length: The length of the intrusion
|
|
408
406
|
:param initial_state_index: The index of the initial state
|
|
409
407
|
:param seed: The seed for the random number generator
|
|
410
|
-
|
|
411
408
|
return: The sequence of states and observations
|
|
412
409
|
"""
|
|
413
|
-
|
|
414
|
-
P_obs = self.emission_matrix
|
|
415
|
-
P_states = self.transition_matrix
|
|
416
|
-
states = self.hidden_states
|
|
417
|
-
observations = self.emission_matrix_observations
|
|
418
|
-
|
|
419
|
-
if seed:
|
|
420
|
-
np.random.seed(seed)
|
|
421
|
-
obs_len = len(observations)
|
|
422
|
-
states_len = len(states)
|
|
423
|
-
# Return the geometric distribution of the initial state
|
|
424
|
-
dist = np.random.geometric(p=P_states[initial_state_index][0], size=1000)
|
|
425
|
-
T_i = round(sum(dist) / len(dist))
|
|
426
|
-
|
|
427
|
-
state_seq = [states[initial_state_index]] * T_i
|
|
428
|
-
obs_seq = []
|
|
429
|
-
for i in range(T_i):
|
|
430
|
-
|
|
431
|
-
o_i = np.random.choice(obs_len, p=P_obs[initial_state_index])
|
|
432
|
-
obs_seq.append(observations[o_i])
|
|
433
|
-
|
|
434
|
-
recon_states_sum = np.sum(P_states[initial_state_index][1:])
|
|
435
|
-
recon_states = P_states[initial_state_index][1:] / recon_states_sum
|
|
436
|
-
|
|
437
|
-
intrusion_start_state = np.random.choice(states_len - 1, p=recon_states) + 1
|
|
438
|
-
intrusion_start_observation = np.random.choice(obs_len, p=P_obs[intrusion_start_state])
|
|
439
|
-
state_seq.append(states[intrusion_start_state])
|
|
440
|
-
obs_seq.append(observations[intrusion_start_observation])
|
|
441
|
-
|
|
442
|
-
s_i = intrusion_start_state
|
|
443
|
-
if intrusion_length == 1:
|
|
444
|
-
return state_seq, obs_seq
|
|
445
|
-
for i in range(intrusion_length - 1):
|
|
446
|
-
# si ~ Ps(si | si-1)
|
|
447
|
-
s_i = np.random.choice(states_len, p=P_states[s_i])
|
|
448
|
-
# oi ~ Po(oi | si)
|
|
449
|
-
o_i = np.random.choice(obs_len, p=P_obs[s_i])
|
|
450
|
-
state_seq.append(states[s_i])
|
|
451
|
-
obs_seq.append(observations[o_i])
|
|
452
|
-
return state_seq, obs_seq
|
|
453
|
-
|
|
454
|
-
from csle_common.dao.system_identification.emulation_statistics import EmulationStatistics
|
|
455
|
-
from csle_common.dao.emulation_action.attacker.emulation_attacker_action_id import EmulationAttackerActionId
|
|
456
|
-
from csle_common.dao.emulation_action.attacker.emulation_attacker_action_type import EmulationAttackerActionType
|
|
457
|
-
from csle_common.dao.emulation_action.attacker.emulation_attacker_action_outcome import EmulationAttackerActionOutcome
|
|
458
|
-
from csle_common.dao.emulation_action.attacker.emulation_attacker_action import EmulationAttackerAction
|
|
459
|
-
from csle_attack_profiler.attack_profiler import AttackProfiler
|
|
460
|
-
from typing import List, Union, Tuple, Any
|
|
461
|
-
import numpy as np
|
|
462
|
-
import sys
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
class HMMProfiler:
|
|
466
|
-
"""
|
|
467
|
-
The HMMProfiler class is used to profile a sequence of observations based on a Hidden Markov Model (HMM).
|
|
468
|
-
"""
|
|
469
|
-
|
|
470
|
-
def __init__(self, statistics: List[EmulationStatistics], model_name: Union[str, None] = None) -> None:
|
|
471
|
-
"""
|
|
472
|
-
Class constructor
|
|
473
|
-
|
|
474
|
-
:param statistics: The list of EmulationStatistics objects
|
|
475
|
-
:param model_name: The name of the model
|
|
476
|
-
"""
|
|
477
|
-
self.statistics = statistics
|
|
478
|
-
self.transition_matrix: List[List[float]] = []
|
|
479
|
-
self.emission_matrix: List[List[float]] = []
|
|
480
|
-
self.hidden_states: List[str] = []
|
|
481
|
-
self.emission_matrix_observations: List[int] = []
|
|
482
|
-
self.start_state_probs: List[float] = []
|
|
483
|
-
self.model_name = None
|
|
484
|
-
|
|
485
|
-
def create_model(self, transition_matrix: List[List[float]],
|
|
486
|
-
hidden_states: List[str], metric: str,
|
|
487
|
-
save_model: bool = False, location: str = ".") -> None:
|
|
488
|
-
"""
|
|
489
|
-
Creates the HMM model based on the given transition matrix, states and metrics.
|
|
490
|
-
If save = True, matrices are saved to given location
|
|
491
|
-
|
|
492
|
-
:param transition_matrix: The transition matrix
|
|
493
|
-
:param states: The list of states of the HMM (format: 'A:attack_name' or
|
|
494
|
-
'no_intrusion' based on emulation statistics file)
|
|
495
|
-
:param metrics: The list of metrics to profile
|
|
496
|
-
:param save: Whether to save the matrices to a file
|
|
497
|
-
:param location: The location to save the matrices, if save = True, e.g "./resources",
|
|
498
|
-
default is current directory
|
|
499
|
-
"""
|
|
500
|
-
emission_matrix, emission_matrix_observations = self.get_matrices_of_observation(self.statistics,
|
|
501
|
-
metric, hidden_states)
|
|
502
|
-
self.emission_matrix = emission_matrix
|
|
503
|
-
self.emission_matrix_observations = emission_matrix_observations
|
|
504
|
-
self.transition_matrix = transition_matrix
|
|
505
|
-
self.start_state_probs = self.calculate_initial_states(self.transition_matrix)
|
|
506
|
-
self.hidden_states = hidden_states
|
|
507
|
-
if save_model and location:
|
|
508
|
-
np.save(f'{location}/transition_matrix.npy', transition_matrix)
|
|
509
|
-
np.save(f'{location}/hidden_states.npy', hidden_states)
|
|
510
|
-
np.save(f'{location}/start_state_probs.npy', self.start_state_probs)
|
|
511
|
-
np.save(f'{location}/emission_matrix_{metric}.npy', emission_matrix)
|
|
512
|
-
np.save(f'{location}/emission_matrix_observations_{metric}.npy', emission_matrix_observations)
|
|
513
|
-
|
|
514
|
-
def load_model(self, location: str, metric: str) -> None:
|
|
515
|
-
"""
|
|
516
|
-
Loads the HMM model from the given location.
|
|
517
|
-
|
|
518
|
-
:param location: The location of the model files, default is current directory
|
|
519
|
-
:param metric: The data metric to use
|
|
520
|
-
"""
|
|
521
|
-
self.transition_matrix = np.load(f'{location}/transition_matrix.npy')
|
|
522
|
-
self.hidden_states = np.load(f'{location}/hidden_states.npy')
|
|
523
|
-
self.start_state_probs = np.load(f'{location}/start_state_probs.npy')
|
|
524
|
-
self.emission_matrix = np.load(f'{location}/emission_matrix_{metric}.npy')
|
|
525
|
-
self.emission_matrix_observations = np.load(f'{location}/emission_matrix_observations_{metric}.npy')
|
|
526
|
-
|
|
527
|
-
def profile_sequence(self, sequence: List[int]) -> List[str]:
|
|
528
|
-
"""
|
|
529
|
-
Profiles a sequence of observations based on the HMM model.
|
|
530
|
-
|
|
531
|
-
:param sequence: The sequence of observations
|
|
532
|
-
:return: The most likely sequence of states
|
|
533
|
-
"""
|
|
534
|
-
|
|
535
|
-
path = HMMProfiler.viterbi(self.hidden_states, self.start_state_probs,
|
|
536
|
-
self.transition_matrix, self.emission_matrix,
|
|
537
|
-
sequence, self.emission_matrix_observations)
|
|
538
|
-
profiled_sequence = []
|
|
539
|
-
for i in range(len(path)):
|
|
540
|
-
profiled_sequence.append(self.hidden_states[int(path[i])])
|
|
541
|
-
|
|
542
|
-
return profiled_sequence
|
|
543
|
-
|
|
544
|
-
def get_matrices_of_observation(self, statistics: List[EmulationStatistics],
|
|
545
|
-
metric: str, states: List[str]) -> Tuple[List[List[float]], List[int]]:
|
|
546
|
-
"""
|
|
547
|
-
Creates the emission matrix for a given metric based on the statistics from the EmulationStatistics objects.
|
|
548
|
-
|
|
549
|
-
:param statistics: The list of EmulationStatistics objects
|
|
550
|
-
:param metric: The metric to get the emission matrix for
|
|
551
|
-
:return: The emission matrix, the list of observations, the list of states
|
|
552
|
-
"""
|
|
553
|
-
emission_matrix = []
|
|
554
|
-
attack_observations = {}
|
|
555
|
-
attack_observations_total_counts = {}
|
|
556
|
-
all_keys = set()
|
|
557
|
-
|
|
558
|
-
for stats in statistics:
|
|
559
|
-
for condition, metric_distribution in stats.conditionals_counts.items():
|
|
560
|
-
action = condition.split('_')
|
|
561
|
-
if action[0] == 'no':
|
|
562
|
-
action[0] = 'no_intrusion'
|
|
563
|
-
if action[0] not in attack_observations:
|
|
564
|
-
# We are not intrested in the observations from 'intrusion' or 'A:Continue'
|
|
565
|
-
if action[0] == 'intrusion' or action[0] == 'A:Continue':
|
|
566
|
-
continue
|
|
567
|
-
else:
|
|
568
|
-
# Add the observations of the attack to the dictionary
|
|
569
|
-
if metric in metric_distribution:
|
|
570
|
-
attack_observations[action[0]] = metric_distribution[metric]
|
|
571
|
-
# Sum the total counts of the observations
|
|
572
|
-
attack_observations_total_counts[action[0]] = sum(attack_observations[action[0]].values())
|
|
573
|
-
# Aggregate the counts from the metric distribution
|
|
574
|
-
else:
|
|
575
|
-
counts_observation = metric_distribution[metric]
|
|
576
|
-
for element in counts_observation:
|
|
577
|
-
if element in attack_observations[action[0]]:
|
|
578
|
-
# Aggregate the counts if the element is already in the dictionary
|
|
579
|
-
attack_observations[action[0]][element] += counts_observation[element]
|
|
580
|
-
else:
|
|
581
|
-
attack_observations[action[0]][element] = counts_observation[element]
|
|
582
|
-
# Sum the total counts of the observations
|
|
583
|
-
attack_observations_total_counts[action[0]] += sum(attack_observations[action[0]].values())
|
|
584
|
-
|
|
585
|
-
# Store all possible values for the observation
|
|
586
|
-
if action[0] in attack_observations:
|
|
587
|
-
all_keys.update(attack_observations[action[0]])
|
|
588
|
-
|
|
589
|
-
# Normalize the counts
|
|
590
|
-
for attack, _ in attack_observations.items():
|
|
591
|
-
attack_observations_total_counts[attack] = sum(attack_observations[attack].values())
|
|
592
|
-
for key in all_keys:
|
|
593
|
-
int_key = int(key)
|
|
594
|
-
if key in attack_observations[attack]:
|
|
595
|
-
count = attack_observations[attack].pop(key, 0)
|
|
596
|
-
attack_observations[attack][int_key] = count / attack_observations_total_counts[attack]
|
|
597
|
-
else:
|
|
598
|
-
attack_observations[attack][int_key] = 0
|
|
599
|
-
# Sort the dictionary by key
|
|
600
|
-
attack_observations[attack] = dict(sorted(attack_observations[attack].items()))
|
|
601
|
-
|
|
602
|
-
# Take any attack as the reference to get the keys
|
|
603
|
-
emission_matrix_observations = []
|
|
604
|
-
emission_matrix_states = []
|
|
605
|
-
# Create the emission matrix
|
|
606
|
-
for state in states:
|
|
607
|
-
if state in attack_observations:
|
|
608
|
-
# Normalize the and then append
|
|
609
|
-
emission_matrix.append(list(attack_observations[state].values()))
|
|
610
|
-
# Get the keys of all observations
|
|
611
|
-
emission_matrix_observations = list(attack_observations[state].keys())
|
|
612
|
-
emission_matrix_states.append(state)
|
|
613
|
-
else:
|
|
614
|
-
# LaPlace smoothing for missing observations
|
|
615
|
-
num_keys = len(all_keys)
|
|
616
|
-
laplace_probability = 1 / (num_keys + 2)
|
|
617
|
-
laplace_sum = laplace_probability * num_keys
|
|
618
|
-
laplace_probability_adj = laplace_probability / laplace_sum
|
|
619
|
-
emission_matrix.append([laplace_probability_adj] * num_keys)
|
|
620
|
-
emission_matrix_states.append(state)
|
|
621
|
-
|
|
622
|
-
# Check if the sum of the probabilities is 1
|
|
623
|
-
for i in range(len(emission_matrix)):
|
|
624
|
-
sum_prob = round(sum(emission_matrix[i]), 10)
|
|
625
|
-
if sum_prob != 1:
|
|
626
|
-
print(f'Sum of probabilities for state {emission_matrix_states[i]} is {sum_prob}')
|
|
627
|
-
|
|
628
|
-
return (emission_matrix, emission_matrix_observations)
|
|
629
|
-
|
|
630
|
-
def convert_states_to_profiles(self, states: List[str]) -> List[Union[AttackProfiler, str]]:
|
|
631
|
-
"""
|
|
632
|
-
Converts a list of states to a list of AttackProfiles.
|
|
633
|
-
|
|
634
|
-
:param states: The list of states to convert
|
|
635
|
-
:return: The list of EmulationAttackerActionId
|
|
636
|
-
"""
|
|
637
|
-
|
|
638
|
-
new_states: List[Union[AttackProfiler, str]] = []
|
|
639
|
-
for state in states:
|
|
640
|
-
if state == 'A:Continue':
|
|
641
|
-
action = EmulationAttackerAction(id=EmulationAttackerActionId.CONTINUE, name="Continue", cmds=[],
|
|
642
|
-
type=EmulationAttackerActionType.CONTINUE, descr="CONTINUE", ips=[],
|
|
643
|
-
index=0, action_outcome=EmulationAttackerActionOutcome.CONTINUE)
|
|
644
|
-
p = AttackProfiler.get_attack_profile(action)
|
|
645
|
-
new_states.append(p)
|
|
646
|
-
elif state == 'A:CVE-2015-1427 exploit':
|
|
647
|
-
action = EmulationAttackerAction(
|
|
648
|
-
id=EmulationAttackerActionId.CVE_2015_1427_EXPLOIT, name="CVE-2015-1427 exploit", cmds=[],
|
|
649
|
-
type=EmulationAttackerActionType.EXPLOIT,
|
|
650
|
-
descr="Uses the CVE-2015-1427 vulnerability to "
|
|
651
|
-
"get remote code execution and then sets up a SSH backdoor"
|
|
652
|
-
"to upgrade the channel", index=-1, ips=[],
|
|
653
|
-
action_outcome=EmulationAttackerActionOutcome.SHELL_ACCESS)
|
|
654
|
-
p = AttackProfiler.get_attack_profile(action)
|
|
655
|
-
new_states.append(p)
|
|
656
|
-
elif state == 'A:DVWA SQL Injection Exploit':
|
|
657
|
-
action = EmulationAttackerAction(
|
|
658
|
-
id=EmulationAttackerActionId.DVWA_SQL_INJECTION, name="DVWA SQL Injection Exploit",
|
|
659
|
-
cmds=[], type=EmulationAttackerActionType.EXPLOIT,
|
|
660
|
-
descr="Uses the DVWA SQL Injection exploit to extract secret passwords",
|
|
661
|
-
index=-1, ips=[], action_outcome=EmulationAttackerActionOutcome.SHELL_ACCESS)
|
|
662
|
-
p = AttackProfiler.get_attack_profile(action)
|
|
663
|
-
new_states.append(p)
|
|
664
|
-
elif state == 'A:Install tools':
|
|
665
|
-
action = EmulationAttackerAction(
|
|
666
|
-
id=EmulationAttackerActionId.INSTALL_TOOLS, name="Install tools", cmds=[],
|
|
667
|
-
type=EmulationAttackerActionType.POST_EXPLOIT,
|
|
668
|
-
descr="If taken root on remote machine, installs pentest tools, e.g. nmap",
|
|
669
|
-
index=-1, ips=[], action_outcome=EmulationAttackerActionOutcome.PIVOTING)
|
|
670
|
-
p = AttackProfiler.get_attack_profile(action)
|
|
671
|
-
new_states.append(p)
|
|
672
|
-
elif state == 'A:Network service login':
|
|
673
|
-
action = EmulationAttackerAction(
|
|
674
|
-
id=EmulationAttackerActionId.NETWORK_SERVICE_LOGIN, name="Network service login",
|
|
675
|
-
cmds=[], type=EmulationAttackerActionType.POST_EXPLOIT,
|
|
676
|
-
descr="Uses known credentials to login to network services on a server",
|
|
677
|
-
index=-1, ips=[], action_outcome=EmulationAttackerActionOutcome.LOGIN)
|
|
678
|
-
p = AttackProfiler.get_attack_profile(action)
|
|
679
|
-
new_states.append(p)
|
|
680
|
-
elif state == 'A:Ping Scan':
|
|
681
|
-
action = EmulationAttackerAction(
|
|
682
|
-
id=EmulationAttackerActionId.PING_SCAN_HOST, name="Ping Scan",
|
|
683
|
-
cmds=[], type=EmulationAttackerActionType.RECON,
|
|
684
|
-
descr="A host discovery scan, it is quick because it only checks of hosts "
|
|
685
|
-
"are up with Ping, without scanning the ports.", ips=[], index=-1,
|
|
686
|
-
action_outcome=EmulationAttackerActionOutcome.INFORMATION_GATHERING, backdoor=False)
|
|
687
|
-
p = AttackProfiler.get_attack_profile(action)
|
|
688
|
-
new_states.append(p)
|
|
689
|
-
elif state == 'A:Sambacry Explolit':
|
|
690
|
-
action = EmulationAttackerAction(
|
|
691
|
-
id=EmulationAttackerActionId.SAMBACRY_EXPLOIT, name="Sambacry Explolit", cmds=[],
|
|
692
|
-
type=EmulationAttackerActionType.EXPLOIT,
|
|
693
|
-
descr="Uses the sambacry shell to get remote code execution and then"
|
|
694
|
-
"sets up a SSH backdoor to upgrade the channel",
|
|
695
|
-
index=-1, ips=[], action_outcome=EmulationAttackerActionOutcome.SHELL_ACCESS)
|
|
696
|
-
p = AttackProfiler.get_attack_profile(action)
|
|
697
|
-
new_states.append(p)
|
|
698
|
-
elif state == 'A:ShellShock Explolit':
|
|
699
|
-
action = EmulationAttackerAction(
|
|
700
|
-
id=EmulationAttackerActionId.SHELLSHOCK_EXPLOIT, name="ShellShock Exploit",
|
|
701
|
-
cmds=[], type=EmulationAttackerActionType.EXPLOIT,
|
|
702
|
-
descr="Uses the Shellshock exploit and curl to do remote code execution and create a backdoor",
|
|
703
|
-
index=-1, ips=[], action_outcome=EmulationAttackerActionOutcome.SHELL_ACCESS)
|
|
704
|
-
p = AttackProfiler.get_attack_profile(action)
|
|
705
|
-
new_states.append(p)
|
|
706
|
-
elif state == 'A:SSH dictionary attack for username=pw':
|
|
707
|
-
action = EmulationAttackerAction(
|
|
708
|
-
id=EmulationAttackerActionId.SSH_SAME_USER_PASS_DICTIONARY_HOST,
|
|
709
|
-
name="SSH dictionary attack for username=pw", cmds=[],
|
|
710
|
-
type=EmulationAttackerActionType.EXPLOIT, index=-1,
|
|
711
|
-
descr="A dictionary attack that tries common passwords and usernames for SSH"
|
|
712
|
-
"where username=password", ips=[], action_outcome=EmulationAttackerActionOutcome.SHELL_ACCESS)
|
|
713
|
-
p = AttackProfiler.get_attack_profile(action)
|
|
714
|
-
new_states.append(p)
|
|
715
|
-
elif state == 'A:FTP dictionary attack for username=pw':
|
|
716
|
-
action = EmulationAttackerAction(
|
|
717
|
-
id=EmulationAttackerActionId.FTP_SAME_USER_PASS_DICTIONARY_HOST,
|
|
718
|
-
name="FTP dictionary attack for username=pw", cmds=[], type=EmulationAttackerActionType.EXPLOIT,
|
|
719
|
-
index=-1, descr="A dictionary attack that tries common passwords and"
|
|
720
|
-
"usernames for FTP where username=password", ips=[],
|
|
721
|
-
action_outcome=EmulationAttackerActionOutcome.SHELL_ACCESS)
|
|
722
|
-
p = AttackProfiler.get_attack_profile(action)
|
|
723
|
-
new_states.append(p)
|
|
724
|
-
elif state == 'A:Telnet dictionary attack for username=pw':
|
|
725
|
-
action = EmulationAttackerAction(
|
|
726
|
-
id=EmulationAttackerActionId.TELNET_SAME_USER_PASS_DICTIONARY_HOST,
|
|
727
|
-
name="Telnet dictionary attack for username=pw", cmds=[],
|
|
728
|
-
type=EmulationAttackerActionType.EXPLOIT, index=-1,
|
|
729
|
-
descr="A dictionary attack that tries common passwords and usernames for"
|
|
730
|
-
"Telnet where username=password", ips=[],
|
|
731
|
-
action_outcome=EmulationAttackerActionOutcome.SHELL_ACCESS)
|
|
732
|
-
p = AttackProfiler.get_attack_profile(action)
|
|
733
|
-
new_states.append(p)
|
|
734
|
-
elif state == 'A:CVE-2010-0426 exploit':
|
|
735
|
-
action = EmulationAttackerAction(
|
|
736
|
-
id=EmulationAttackerActionId.CVE_2010_0426_PRIV_ESC,
|
|
737
|
-
name="CVE-2010-0426 exploit", cmds=[], type=EmulationAttackerActionType.PRIVILEGE_ESCALATION,
|
|
738
|
-
descr="Uses the CVE-2010-0426 vulnerability to perform privilege escalation to get root access",
|
|
739
|
-
index=-1, ips=[], action_outcome=EmulationAttackerActionOutcome.PRIVILEGE_ESCALATION_ROOT)
|
|
740
|
-
p = AttackProfiler.get_attack_profile(action)
|
|
741
|
-
new_states.append(p)
|
|
742
|
-
elif state == 'A:TCP SYN (Stealth) Scan':
|
|
743
|
-
action = EmulationAttackerAction(
|
|
744
|
-
id=EmulationAttackerActionId.TCP_SYN_STEALTH_SCAN_HOST, name="TCP SYN (Stealth) Scan",
|
|
745
|
-
cmds=[], type=EmulationAttackerActionType.RECON,
|
|
746
|
-
descr="A stealthy and fast TCP SYN scan to detect open TCP ports on the subnet", ips=[],
|
|
747
|
-
index=-1, action_outcome=EmulationAttackerActionOutcome.INFORMATION_GATHERING, backdoor=False)
|
|
748
|
-
p = AttackProfiler.get_attack_profile(action)
|
|
749
|
-
new_states.append(p)
|
|
750
|
-
elif state == 'ssh backdoor':
|
|
751
|
-
action = EmulationAttackerAction(
|
|
752
|
-
id=EmulationAttackerActionId.SSH_BACKDOOR, name="Install SSH backdoor",
|
|
753
|
-
cmds=[], type=EmulationAttackerActionType.POST_EXPLOIT,
|
|
754
|
-
descr="If taken root on remote machine, installs a ssh backdoor useful for"
|
|
755
|
-
"upgrading telnetor weaker channels", index=-1, ips=[],
|
|
756
|
-
action_outcome=EmulationAttackerActionOutcome.PIVOTING, alt_cmds=None, backdoor=True)
|
|
757
|
-
p = AttackProfiler.get_attack_profile(action)
|
|
758
|
-
new_states.append(p)
|
|
759
|
-
else:
|
|
760
|
-
new_states.append(state)
|
|
761
|
-
|
|
762
|
-
return new_states
|
|
763
|
-
|
|
764
|
-
def calculate_initial_states(self, transition_matrix: List[List[float]]) -> List[float]:
|
|
765
|
-
"""
|
|
766
|
-
Calculates the initial states probabilities based on the transition matrix.
|
|
767
|
-
|
|
768
|
-
1 / (# of states)
|
|
769
|
-
|
|
770
|
-
:param transition_matrix: The transition matrix
|
|
771
|
-
|
|
772
|
-
:return: The start states probabilities
|
|
773
|
-
"""
|
|
774
|
-
start_states = []
|
|
775
|
-
total_states = len(transition_matrix)
|
|
776
|
-
for _ in range(total_states):
|
|
777
|
-
start_states.append(1 / total_states)
|
|
778
|
-
|
|
779
|
-
return start_states
|
|
780
|
-
|
|
781
|
-
@staticmethod
|
|
782
|
-
def viterbi(hidden_states: List[EmulationAttackerActionId], init_probs: List[float],
|
|
783
|
-
trans_matrix: List[List[float]], emission_matrix: List[List[float]],
|
|
784
|
-
obs: List[int], emissions_list: List[int]) -> List[float]:
|
|
785
|
-
"""
|
|
786
|
-
Viterbi algorithm for Hidden Markov Models (HMM).
|
|
787
|
-
|
|
788
|
-
:param hidden_states: The hidden states
|
|
789
|
-
:param init_probs: The initial probabilities of the hidden states
|
|
790
|
-
:param trans_matrix: The transition matrix
|
|
791
|
-
:param emission_matrix: The emission matrix
|
|
792
|
-
:param obs: The observation sequence
|
|
793
|
-
:param emissions_list: The list of possible observations
|
|
794
|
-
|
|
795
|
-
:return: The most likely sequence of hidden states
|
|
796
|
-
"""
|
|
797
|
-
# Convert the emissions list to a numpy array, to use the where function
|
|
798
|
-
emissions_list_typed: np.ndarray[int, Any] = np.array(emissions_list)
|
|
799
|
-
|
|
800
|
-
# Check that the sum equals 1
|
|
801
|
-
for i in range(len(emission_matrix)):
|
|
802
|
-
if round(sum(emission_matrix[i]), 10) != 1:
|
|
803
|
-
print(f'Sum of probabilities for state {hidden_states[i]} is not 1')
|
|
804
|
-
print(f'Sum of probabilities: {sum(emission_matrix[i])}')
|
|
805
|
-
|
|
806
|
-
# The number of hidden states
|
|
807
|
-
S = len(hidden_states)
|
|
808
|
-
# The number of observations
|
|
809
|
-
T = len(obs)
|
|
810
|
-
|
|
811
|
-
# The Viterbi matrix (prob) T x S matrix of zeroes
|
|
812
|
-
prob = np.zeros((T, S))
|
|
813
|
-
# The backpointer matrix (prev)
|
|
814
|
-
prev = np.empty((T, S))
|
|
815
|
-
# Initialization
|
|
816
|
-
for i in range(S):
|
|
817
|
-
# Fetch the index of the observation in the emission_matrix
|
|
818
|
-
index, = np.where(emissions_list_typed == obs[0])
|
|
819
|
-
if index[0].size > 0:
|
|
820
|
-
prob[0][i] = init_probs[i] * emission_matrix[i][index[0]]
|
|
821
|
-
else:
|
|
822
|
-
print(f'Observation {obs[0]} not found in the emission matrix')
|
|
823
|
-
sys.exit(1)
|
|
824
|
-
|
|
825
|
-
# Recursion
|
|
826
|
-
for t in range(1, T):
|
|
827
|
-
index, = np.where(emissions_list_typed == obs[t])
|
|
828
|
-
for i in range(S):
|
|
829
|
-
max_prob = -1
|
|
830
|
-
max_state = -1
|
|
831
|
-
for j in range(S):
|
|
832
|
-
new_prob = prob[t - 1][j] * trans_matrix[j][i] * emission_matrix[i][index[0]]
|
|
833
|
-
if new_prob > max_prob:
|
|
834
|
-
max_prob = new_prob
|
|
835
|
-
max_state = j
|
|
836
|
-
prob[t][i] = max_prob
|
|
837
|
-
prev[t][i] = max_state
|
|
838
|
-
|
|
839
|
-
path = np.zeros(T)
|
|
840
|
-
path[T - 1] = np.argmax(prob[T - 1])
|
|
841
|
-
for t in range(T - 2, -1, -1):
|
|
842
|
-
path[t] = prev[t + 1][int(path[t + 1])]
|
|
843
|
-
# Convert the path to a list
|
|
844
|
-
typed_path: List[float] = path.tolist()
|
|
845
|
-
|
|
846
|
-
return typed_path
|
|
847
|
-
|
|
848
|
-
def generate_sequence(self, intrusion_length: int, initial_state_index: int,
|
|
849
|
-
seed: Union[int, None] = None) -> Tuple[List[str], List[int]]:
|
|
850
|
-
"""
|
|
851
|
-
Generates a sequence of states and corresponding observations based on the given emission matrix,
|
|
852
|
-
and transition matrix. First, a sequence of observation from 'no intrusion' is generated
|
|
853
|
-
based on the geometric distribution of the initial state. Then, a sequence observations and states are
|
|
854
|
-
generated based on emission matrix and transition matrix. The length of this intrusion
|
|
855
|
-
sequence is given by the intrusion_length parameter.
|
|
856
410
|
|
|
857
|
-
:param P_obs: The emission matrix
|
|
858
|
-
:param P_states: The transition matrix
|
|
859
|
-
:param states: The list of states
|
|
860
|
-
:param observations: The list of observations
|
|
861
|
-
:param intrusion_length: The length of the intrusion
|
|
862
|
-
:param initial_state: The initial state
|
|
863
|
-
return: The sequence of states and observations
|
|
864
|
-
"""
|
|
865
411
|
P_obs = self.emission_matrix
|
|
866
412
|
P_states = self.transition_matrix
|
|
867
413
|
states = self.hidden_states
|
|
868
414
|
observations = self.emission_matrix_observations
|
|
415
|
+
|
|
869
416
|
if seed:
|
|
870
417
|
np.random.seed(seed)
|
|
871
418
|
obs_len = len(observations)
|
|
872
419
|
states_len = len(states)
|
|
873
|
-
|
|
874
420
|
# Return the geometric distribution of the initial state
|
|
875
421
|
dist = np.random.geometric(p=P_states[initial_state_index][0], size=1000)
|
|
876
422
|
T_i = round(sum(dist) / len(dist))
|
|
@@ -878,7 +424,6 @@ class HMMProfiler:
|
|
|
878
424
|
state_seq = [states[initial_state_index]] * T_i
|
|
879
425
|
obs_seq = []
|
|
880
426
|
for i in range(T_i):
|
|
881
|
-
|
|
882
427
|
o_i = np.random.choice(obs_len, p=P_obs[initial_state_index])
|
|
883
428
|
obs_seq.append(observations[o_i])
|
|
884
429
|
|
|
@@ -893,12 +438,11 @@ class HMMProfiler:
|
|
|
893
438
|
s_i = intrusion_start_state
|
|
894
439
|
if intrusion_length == 1:
|
|
895
440
|
return state_seq, obs_seq
|
|
896
|
-
for i in range(intrusion_length):
|
|
441
|
+
for i in range(intrusion_length - 1):
|
|
897
442
|
# si ~ Ps(si | si-1)
|
|
898
443
|
s_i = np.random.choice(states_len, p=P_states[s_i])
|
|
899
444
|
# oi ~ Po(oi | si)
|
|
900
445
|
o_i = np.random.choice(obs_len, p=P_obs[s_i])
|
|
901
446
|
state_seq.append(states[s_i])
|
|
902
447
|
obs_seq.append(observations[o_i])
|
|
903
|
-
|
|
904
448
|
return state_seq, obs_seq
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: csle-attack-profiler
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Library with MITRE attack profiler for CSLE
|
|
5
5
|
Author: Bength Pappila
|
|
6
6
|
Author-email: brpa@kth.se
|
|
@@ -15,8 +15,8 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
15
15
|
Classifier: Intended Audience :: Science/Research
|
|
16
16
|
Requires-Python: >=3.8
|
|
17
17
|
Requires-Dist: mitreattack-python ==2.0.14
|
|
18
|
-
Requires-Dist: csle-base ==0.6.
|
|
19
|
-
Requires-Dist: csle-common ==0.6.
|
|
18
|
+
Requires-Dist: csle-base ==0.6.1
|
|
19
|
+
Requires-Dist: csle-common ==0.6.1
|
|
20
20
|
Provides-Extra: testing
|
|
21
21
|
Requires-Dist: pytest >=6.0 ; extra == 'testing'
|
|
22
22
|
Requires-Dist: pytest-cov >=2.0 ; extra == 'testing'
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
csle_attack_profiler/__init__.py,sha256=C7_gE0lIQJ8Wh2jgU8C8P_xyvq76bKTf0gm8bGYhMBk,37
|
|
2
|
+
csle_attack_profiler/__version__.py,sha256=gd3s3RotD0_KL90Tua-YkOr60Jm2C2_wvlEhAT08068,22
|
|
3
|
+
csle_attack_profiler/attack_profiler.py,sha256=bYqSeItJkhswronvIRFPyvf1rtvUKEWPPF4K2t1JVqE,10535
|
|
4
|
+
csle_attack_profiler/hmm_profiling.py,sha256=cHE2wImgBJ3xwW33S0JcTwsiccstijR9uRMeBPXIT18,24167
|
|
5
|
+
csle_attack_profiler-0.6.1.dist-info/METADATA,sha256=qcoiNvO6GxPrQRxB9P9IiaIxwR46ZKAIRu2nDd0_svA,2006
|
|
6
|
+
csle_attack_profiler-0.6.1.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
7
|
+
csle_attack_profiler-0.6.1.dist-info/top_level.txt,sha256=OuI4zvPo3MQYLQ7Dqm32oM0V8rNdwHe9tjtngx2KVtA,21
|
|
8
|
+
csle_attack_profiler-0.6.1.dist-info/RECORD,,
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
csle_attack_profiler/__init__.py,sha256=C7_gE0lIQJ8Wh2jgU8C8P_xyvq76bKTf0gm8bGYhMBk,37
|
|
2
|
-
csle_attack_profiler/__version__.py,sha256=CBY3jsC-9HCm7eZ6CKD-sYLCejqOJ1pYWPQM4LGIXcI,22
|
|
3
|
-
csle_attack_profiler/attack_profiler.py,sha256=bYqSeItJkhswronvIRFPyvf1rtvUKEWPPF4K2t1JVqE,10535
|
|
4
|
-
csle_attack_profiler/hmm_profiling.py,sha256=891sNt1bWlqIIUKCnzZaEGnEsIhB6TY3ob46MSHymoI,48264
|
|
5
|
-
csle_attack_profiler-0.6.0.dist-info/METADATA,sha256=biQd6Up9qFTEihixwI4t_jGFAhkr9UWsTPG8bIdL2kM,2006
|
|
6
|
-
csle_attack_profiler-0.6.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
|
7
|
-
csle_attack_profiler-0.6.0.dist-info/top_level.txt,sha256=OuI4zvPo3MQYLQ7Dqm32oM0V8rNdwHe9tjtngx2KVtA,21
|
|
8
|
-
csle_attack_profiler-0.6.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|