mal-toolbox 0.2.0__py3-none-any.whl → 0.3.0__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.
- {mal_toolbox-0.2.0.dist-info → mal_toolbox-0.3.0.dist-info}/METADATA +43 -25
- mal_toolbox-0.3.0.dist-info/RECORD +29 -0
- mal_toolbox-0.3.0.dist-info/entry_points.txt +2 -0
- maltoolbox/__init__.py +38 -57
- maltoolbox/__main__.py +43 -14
- maltoolbox/attackgraph/__init__.py +1 -1
- maltoolbox/attackgraph/analyzers/apriori.py +6 -5
- maltoolbox/attackgraph/attacker.py +26 -13
- maltoolbox/attackgraph/attackgraph.py +175 -148
- maltoolbox/attackgraph/node.py +56 -54
- maltoolbox/attackgraph/query.py +4 -2
- maltoolbox/file_utils.py +0 -8
- maltoolbox/ingestors/neo4j.py +146 -157
- maltoolbox/language/__init__.py +7 -3
- maltoolbox/language/compiler/__init__.py +485 -17
- maltoolbox/language/compiler/mal_lexer.py +172 -152
- maltoolbox/language/compiler/mal_parser.py +1370 -663
- maltoolbox/language/languagegraph.py +103 -99
- maltoolbox/model.py +306 -488
- maltoolbox/translators/securicad.py +164 -163
- maltoolbox/translators/updater.py +231 -108
- mal_toolbox-0.2.0.dist-info/RECORD +0 -32
- maltoolbox/default.conf +0 -17
- maltoolbox/language/classes_factory.py +0 -259
- maltoolbox/language/compiler/mal_visitor.py +0 -416
- maltoolbox/wrappers.py +0 -62
- {mal_toolbox-0.2.0.dist-info → mal_toolbox-0.3.0.dist-info}/AUTHORS +0 -0
- {mal_toolbox-0.2.0.dist-info → mal_toolbox-0.3.0.dist-info}/LICENSE +0 -0
- {mal_toolbox-0.2.0.dist-info → mal_toolbox-0.3.0.dist-info}/WHEEL +0 -0
- {mal_toolbox-0.2.0.dist-info → mal_toolbox-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: mal-toolbox
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: A collection of tools used to create MAL models and attack graphs.
|
|
5
|
-
Author-email: Andrei Buhaiu <buhaiu@kth.se>,
|
|
5
|
+
Author-email: Andrei Buhaiu <buhaiu@kth.se>, Joakim Loxdal <loxdal@kth.se>, Nikolaos Kakouros <nkak@kth.se>, Jakob Nyberg <jaknyb@kth.se>, Giuseppe Nebbione <nebbione@kth.se>
|
|
6
6
|
License: Apache Software License
|
|
7
7
|
Project-URL: Homepage, https://github.com/mal-lang/mal-toolbox
|
|
8
8
|
Project-URL: Bug Tracker, https://github.com/mal-lang/mal-toolbox/issues
|
|
@@ -19,7 +19,6 @@ Description-Content-Type: text/markdown
|
|
|
19
19
|
License-File: LICENSE
|
|
20
20
|
License-File: AUTHORS
|
|
21
21
|
Requires-Dist: py2neo>=2021.2.3
|
|
22
|
-
Requires-Dist: python-jsonschema-objects>=0.5.5
|
|
23
22
|
Requires-Dist: antlr4-tools
|
|
24
23
|
Requires-Dist: antlr4-python3-runtime
|
|
25
24
|
Requires-Dist: docopt
|
|
@@ -48,17 +47,6 @@ then be used to generate python classes representing the assets and
|
|
|
48
47
|
associations of the language and to determine the attack steps for each asset
|
|
49
48
|
when generating the attack graph.
|
|
50
49
|
|
|
51
|
-
### The Language Classes Factory Submodule
|
|
52
|
-
|
|
53
|
-
The language classes factory submodule is used to generate python classes
|
|
54
|
-
using the `python_jsonschema_objects` package from a language specification.
|
|
55
|
-
The classes generated by the `create_classes` function can then be accessed
|
|
56
|
-
from within that namespace(.e.g: `lang_classes_factory.ns.Application()`,
|
|
57
|
-
`lang_classes_factory.ns.AppExecution()`). Because these classes are built
|
|
58
|
-
using JSON Schema validators they will enforce their restrictions when using
|
|
59
|
-
the python objects created. These classes are typically used in conjunction
|
|
60
|
-
with model module to create instance models.
|
|
61
|
-
|
|
62
50
|
## The Model Module
|
|
63
51
|
|
|
64
52
|
With a MAL language a Model (a MAL instance model) can be created either
|
|
@@ -109,21 +97,51 @@ pip install mal-toolbox
|
|
|
109
97
|
```
|
|
110
98
|
|
|
111
99
|
## Configuration
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
100
|
+
You can use a `maltoolbox.yml` file in the current working directory to
|
|
101
|
+
configure the toolbox. Alternatively, you can use the `MALTOOLBOX_CONFIG`
|
|
102
|
+
environment variable to set a custom config file location:
|
|
103
|
+
|
|
104
|
+
"""bash
|
|
105
|
+
# in your shell, e.g. bash do:
|
|
106
|
+
export MALTOOLBOX_CONFIG=path/to/yml/config/file
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
The default configuration can be found here:
|
|
110
|
+
|
|
111
|
+
https://github.com/mal-lang/mal-toolbox/blob/main/maltoolbox/__init__.py#L39-L53
|
|
115
112
|
|
|
116
113
|
## Command Line Client
|
|
117
|
-
In addition to the modules that make up the MAL-Toolbox package it also
|
|
118
|
-
provides a simple command line client that can be used to easily generate
|
|
119
|
-
attack graphs from a .mar language specification file and a JSON instance
|
|
120
|
-
model file.
|
|
121
114
|
|
|
122
|
-
|
|
123
|
-
|
|
115
|
+
You can use the maltoolbox cli to:
|
|
116
|
+
|
|
117
|
+
- Generate attack graphs from model files
|
|
118
|
+
- Compile MAL languages
|
|
119
|
+
- Upgrade model files from older versions
|
|
124
120
|
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
```
|
|
122
|
+
Command-line interface for MAL toolbox operations
|
|
123
|
+
|
|
124
|
+
Usage:
|
|
125
|
+
maltoolbox attack-graph generate [options] <model_file> <lang_file>
|
|
126
|
+
maltoolbox compile <lang_file> <output_file>
|
|
127
|
+
maltoolbox upgrade-model <model_file> <lang_file> <output_file>
|
|
128
|
+
|
|
129
|
+
Arguments:
|
|
130
|
+
<model_file> Path to JSON instance model file.
|
|
131
|
+
<lang_file> Path to .mar or .mal file containing MAL spec.
|
|
132
|
+
<output_file> Path to write the result of the compilation (yml/json).
|
|
133
|
+
|
|
134
|
+
Options:
|
|
135
|
+
--neo4j Ingest attack graph and instance model into a Neo4j instance
|
|
136
|
+
|
|
137
|
+
Notes:
|
|
138
|
+
- <lang_file> can be either a .mar file (generated by the older MAL
|
|
139
|
+
compiler) or a .mal file containing the DSL written in MAL.
|
|
140
|
+
|
|
141
|
+
- If --neo4j is used, the Neo4j instance should be running. The connection
|
|
142
|
+
parameters required for this app to reach the Neo4j instance should be
|
|
143
|
+
defined in the default.conf file.
|
|
144
|
+
```
|
|
127
145
|
|
|
128
146
|
## Code examples / Tutorial
|
|
129
147
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
maltoolbox/__init__.py,sha256=q_M3mEsQW1IFQ5xVZ3fK0gVyt8r_c2kxUC3ZDAtPmII,2088
|
|
2
|
+
maltoolbox/__main__.py,sha256=PSg8vFS8X-klJBJdSzrg0aLh9ykZgbcoSSEy3DTQoQQ,3499
|
|
3
|
+
maltoolbox/exceptions.py,sha256=0YjPx2v1yYumZ2o7pVZ1s_jS-GAb3Ng979KEFhROSNY,1399
|
|
4
|
+
maltoolbox/file_utils.py,sha256=tBR8Kjl8IoFzAtYaLNHNALuQrdMT3pD1ZpczHm1pu2g,1875
|
|
5
|
+
maltoolbox/model.py,sha256=XCneM5Hdl2GQhtgXYq0SUAlHBksXn7okevIsSKMSwVY,24084
|
|
6
|
+
maltoolbox/attackgraph/__init__.py,sha256=AHDyX6dAkx3mDic2K56v1xche9N6ofDfbaHkKbdJ2qQ,230
|
|
7
|
+
maltoolbox/attackgraph/attacker.py,sha256=dKMAcOwlNM3LL8qh1tBJuzjFxlTNG-QI2st1LzP8Ofc,4030
|
|
8
|
+
maltoolbox/attackgraph/attackgraph.py,sha256=A5TjDE-B4c0hY28M4IVwHVFhM_ww2eAt7uITjSUCNKc,32495
|
|
9
|
+
maltoolbox/attackgraph/node.py,sha256=Ec67_u_8qf_MgCHaUg4wIbZFC013GWxbIsC8EjoguzE,6465
|
|
10
|
+
maltoolbox/attackgraph/query.py,sha256=s66EQcHlldn7XLCWxZ2FRjZTgk8o3V0mD4LnRaImdCw,6836
|
|
11
|
+
maltoolbox/attackgraph/analyzers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
+
maltoolbox/attackgraph/analyzers/apriori.py,sha256=iH1KvKbMi9J3_1qia8FXI80O3ZMQ1LP67HkXbqweq-c,8802
|
|
13
|
+
maltoolbox/ingestors/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
+
maltoolbox/ingestors/neo4j.py,sha256=gjQQqk0HVI8ojIqwS1ymt7-_ZvDOTNi8AcczDAlvsGs,8310
|
|
15
|
+
maltoolbox/language/__init__.py,sha256=9p5nvVqDCKEhXbDMIz1MtwZ9GN7x1jmUUXbpjEwuqnw,269
|
|
16
|
+
maltoolbox/language/languagegraph.py,sha256=npP1lX1EE1rqqKNr9qTb4Jylvdn5JYApNyJhjwSebJs,67785
|
|
17
|
+
maltoolbox/language/compiler/__init__.py,sha256=JQyAgDwJh1pU7AmuOhd1-d2b2PYXpgMVPtxnav8QHVc,15872
|
|
18
|
+
maltoolbox/language/compiler/mal_lexer.py,sha256=BeifykDAt4PloRASOaLzBgWF35ev_zgD8lXMIsSHykc,12063
|
|
19
|
+
maltoolbox/language/compiler/mal_parser.py,sha256=sUoaE43l2VKg-Dou30mk2wlVS1FvdOREwHNIyFe4IkY,114699
|
|
20
|
+
maltoolbox/translators/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
|
+
maltoolbox/translators/securicad.py,sha256=PJYjieioWN5tE_oKm83dtgV5UkC8EUH9Vsy3-FxBtUo,7017
|
|
22
|
+
maltoolbox/translators/updater.py,sha256=8bisZnzMWjGaG5tu8jdF-Oq6bPwIjXkVO-_yZDGc6cA,8652
|
|
23
|
+
mal_toolbox-0.3.0.dist-info/AUTHORS,sha256=zxLrLe8EY39WtRKlAY4Oorx4Z2_LHV2ApRvDGZgY7xY,127
|
|
24
|
+
mal_toolbox-0.3.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
25
|
+
mal_toolbox-0.3.0.dist-info/METADATA,sha256=Yfkp5QVZL6W4DIsYkQqRQU3HpTC6dkrVCHsfchgb4k0,6158
|
|
26
|
+
mal_toolbox-0.3.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
27
|
+
mal_toolbox-0.3.0.dist-info/entry_points.txt,sha256=oqby5O6cUP_OHCm70k_iYPA6UlbTBf7se1i3XwdK3uU,56
|
|
28
|
+
mal_toolbox-0.3.0.dist-info/top_level.txt,sha256=phqRVLRKGdSUgRY03mcpi2cmbbDo5YGjkV4gkqHFFcM,11
|
|
29
|
+
mal_toolbox-0.3.0.dist-info/RECORD,,
|
maltoolbox/__init__.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
|
2
|
-
# MAL Toolbox v0.
|
|
2
|
+
# MAL Toolbox v0.3.0
|
|
3
3
|
# Copyright 2024, Andrei Buhaiu.
|
|
4
4
|
#
|
|
5
5
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
@@ -20,75 +20,56 @@
|
|
|
20
20
|
MAL-Toolbox Framework
|
|
21
21
|
"""
|
|
22
22
|
|
|
23
|
-
__title__ =
|
|
24
|
-
__version__ =
|
|
25
|
-
__authors__ = [
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
__title__ = "maltoolbox"
|
|
24
|
+
__version__ = "0.3.0"
|
|
25
|
+
__authors__ = [
|
|
26
|
+
"Andrei Buhaiu",
|
|
27
|
+
"Giuseppe Nebbione",
|
|
28
|
+
"Nikolaos Kakouros",
|
|
29
|
+
"Jakob Nyberg",
|
|
30
|
+
"Joakim Loxdal",
|
|
31
|
+
]
|
|
32
|
+
__license__ = "Apache 2.0"
|
|
33
|
+
__docformat__ = "restructuredtext en"
|
|
32
34
|
|
|
33
35
|
__all__ = ()
|
|
34
36
|
|
|
35
37
|
import os
|
|
36
|
-
import
|
|
38
|
+
import yaml
|
|
37
39
|
import logging
|
|
40
|
+
from typing import Any
|
|
41
|
+
|
|
42
|
+
config: dict[str, Any] = {
|
|
43
|
+
"logging": {
|
|
44
|
+
"log_level": logging.INFO,
|
|
45
|
+
"log_file": "logs/log.txt",
|
|
46
|
+
"attackgraph_file": "logs/attackgraph.yml",
|
|
47
|
+
"model_file": "logs/model.yml",
|
|
48
|
+
"langspec_file": "logs/langspec_file.yml",
|
|
49
|
+
},
|
|
50
|
+
"neo4j": {"uri": None, "username": None, "password": None, "dbname": None},
|
|
51
|
+
}
|
|
38
52
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
CONFIGFILE = os.path.join(
|
|
42
|
-
os.path.dirname(os.path.abspath(__file__)),
|
|
43
|
-
"default.conf"
|
|
44
|
-
)
|
|
45
|
-
|
|
46
|
-
config = configparser.ConfigParser()
|
|
47
|
-
config.read(CONFIGFILE)
|
|
53
|
+
config_file = os.getenv("MALTOOLBOX_CONFIG", "maltoolbox.yml")
|
|
48
54
|
|
|
49
|
-
if
|
|
50
|
-
|
|
55
|
+
if os.path.exists(config_file):
|
|
56
|
+
with open(config_file) as f:
|
|
57
|
+
config |= yaml.safe_load(f)
|
|
51
58
|
|
|
52
|
-
|
|
53
|
-
raise ValueError('Config file is missing a log_file location, cannot proceed.')
|
|
59
|
+
log_configs, neo4j_configs = config.values()
|
|
54
60
|
|
|
55
|
-
log_configs =
|
|
56
|
-
'log_file': config['logging']['log_file'],
|
|
57
|
-
'log_level': config['logging']['log_level'],
|
|
58
|
-
'attackgraph_file': config['logging']['attackgraph_file'],
|
|
59
|
-
'model_file': config['logging']['model_file'],
|
|
60
|
-
'langspec_file': config['logging']['langspec_file'],
|
|
61
|
-
}
|
|
61
|
+
os.makedirs(os.path.dirname(log_configs["log_file"]), exist_ok=True)
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
file_handler = logging.FileHandler(log_configs[
|
|
63
|
+
formatter = logging.Formatter(
|
|
64
|
+
"%(asctime)s %(name)-12s %(levelname)-8s %(message)s", datefmt="%m-%d %H:%M"
|
|
65
|
+
)
|
|
66
|
+
file_handler = logging.FileHandler(log_configs["log_file"], mode="w")
|
|
67
67
|
file_handler.setFormatter(formatter)
|
|
68
68
|
|
|
69
69
|
logger = logging.getLogger(__name__)
|
|
70
70
|
logger.addHandler(file_handler)
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
level = logging.getLevelName(log_level)
|
|
75
|
-
logger.setLevel(level)
|
|
76
|
-
logger.info('Set loggin level of %s to %s.', __name__, log_level)
|
|
77
|
-
|
|
78
|
-
if 'neo4j' in config:
|
|
79
|
-
for term in ['uri', 'username', 'password', 'dbname']:
|
|
80
|
-
if term not in config['neo4j']:
|
|
81
|
-
|
|
82
|
-
msg = (
|
|
83
|
-
'Config file is missing essential Neo4J '
|
|
84
|
-
f'information: {term}, cannot proceed.'
|
|
85
|
-
)
|
|
86
|
-
logger.critical(msg)
|
|
87
|
-
raise ValueError(msg)
|
|
72
|
+
logger.setLevel(log_configs.get("log_level"))
|
|
73
|
+
logger.info("Set loggin level of %s to %s.", __name__, log_configs.get("log_level"))
|
|
88
74
|
|
|
89
|
-
|
|
90
|
-
'uri': config['neo4j']['uri'],
|
|
91
|
-
'username': config['neo4j']['username'],
|
|
92
|
-
'password': config['neo4j']['password'],
|
|
93
|
-
'dbname': config['neo4j']['dbname'],
|
|
94
|
-
}
|
|
75
|
+
logger.debug("Config file location: %s", config_file)
|
maltoolbox/__main__.py
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
Command-line interface for MAL toolbox operations
|
|
3
3
|
|
|
4
4
|
Usage:
|
|
5
|
-
maltoolbox attack-graph generate [options] <
|
|
5
|
+
maltoolbox attack-graph generate [options] <model_file> <lang_file>
|
|
6
6
|
maltoolbox compile <lang_file> <output_file>
|
|
7
|
+
maltoolbox upgrade-model <model_file> <lang_file> <output_file>
|
|
7
8
|
|
|
8
9
|
Arguments:
|
|
9
|
-
<
|
|
10
|
+
<model_file> Path to JSON instance model file.
|
|
10
11
|
<lang_file> Path to .mar or .mal file containing MAL spec.
|
|
11
|
-
<output_file> Path to write the
|
|
12
|
+
<output_file> Path to write the result of the compilation (yml/json).
|
|
12
13
|
|
|
13
14
|
Options:
|
|
14
15
|
--neo4j Ingest attack graph and instance model into a Neo4j instance
|
|
@@ -26,10 +27,12 @@ import logging
|
|
|
26
27
|
import json
|
|
27
28
|
import docopt
|
|
28
29
|
|
|
29
|
-
from maltoolbox.wrappers import create_attack_graph
|
|
30
30
|
from . import log_configs, neo4j_configs
|
|
31
|
+
from .attackgraph import create_attack_graph
|
|
31
32
|
from .language.compiler import MalCompiler
|
|
32
33
|
from .ingestors import neo4j
|
|
34
|
+
from .language.languagegraph import LanguageGraph
|
|
35
|
+
from .translators.updater import load_model_from_older_version
|
|
33
36
|
|
|
34
37
|
logger = logging.getLogger(__name__)
|
|
35
38
|
|
|
@@ -39,7 +42,7 @@ def generate_attack_graph(
|
|
|
39
42
|
send_to_neo4j: bool
|
|
40
43
|
) -> None:
|
|
41
44
|
"""Create an attack graph and optionally send to neo4j
|
|
42
|
-
|
|
45
|
+
|
|
43
46
|
Args:
|
|
44
47
|
model_file - path to the model file
|
|
45
48
|
lang_file - path to the language file
|
|
@@ -53,19 +56,23 @@ def generate_attack_graph(
|
|
|
53
56
|
|
|
54
57
|
if send_to_neo4j:
|
|
55
58
|
logger.debug('Ingest model graph into Neo4J database.')
|
|
56
|
-
neo4j.ingest_model(
|
|
59
|
+
neo4j.ingest_model(
|
|
60
|
+
attack_graph.model,
|
|
57
61
|
neo4j_configs['uri'],
|
|
58
62
|
neo4j_configs['username'],
|
|
59
63
|
neo4j_configs['password'],
|
|
60
64
|
neo4j_configs['dbname'],
|
|
61
|
-
delete=True
|
|
65
|
+
delete=True
|
|
66
|
+
)
|
|
62
67
|
logger.debug('Ingest attack graph into Neo4J database.')
|
|
63
|
-
neo4j.ingest_attack_graph(
|
|
68
|
+
neo4j.ingest_attack_graph(
|
|
69
|
+
attack_graph,
|
|
64
70
|
neo4j_configs['uri'],
|
|
65
71
|
neo4j_configs['username'],
|
|
66
72
|
neo4j_configs['password'],
|
|
67
73
|
neo4j_configs['dbname'],
|
|
68
|
-
delete=False
|
|
74
|
+
delete=False
|
|
75
|
+
)
|
|
69
76
|
|
|
70
77
|
|
|
71
78
|
def compile(lang_file: str, output_file: str) -> None:
|
|
@@ -75,9 +82,31 @@ def compile(lang_file: str, output_file: str) -> None:
|
|
|
75
82
|
json.dump(compiler.compile(lang_file), f, indent=2)
|
|
76
83
|
|
|
77
84
|
|
|
78
|
-
|
|
85
|
+
def upgrade_model(model_file: str, lang_file: str, output_file: str):
|
|
86
|
+
lang_graph = LanguageGraph.load_from_file(lang_file)
|
|
87
|
+
|
|
88
|
+
if log_configs['langspec_file']:
|
|
89
|
+
lang_graph.save_to_file(log_configs['langspec_file'])
|
|
90
|
+
|
|
91
|
+
model = load_model_from_older_version(model_file, lang_graph)
|
|
92
|
+
model.save_to_file(output_file)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def main():
|
|
96
|
+
args = docopt.docopt(__doc__)
|
|
97
|
+
|
|
98
|
+
if args['attack-graph'] and args['generate']:
|
|
99
|
+
generate_attack_graph(
|
|
100
|
+
args['<model_file>'], args['<lang_file>'], args['--neo4j']
|
|
101
|
+
)
|
|
102
|
+
elif args['compile']:
|
|
103
|
+
compile(
|
|
104
|
+
args['<lang_file>'], args['<output_file>']
|
|
105
|
+
)
|
|
106
|
+
elif args['upgrade-model']:
|
|
107
|
+
upgrade_model(
|
|
108
|
+
args['<model_file>'], args['<lang_file>'], args['<output_file>']
|
|
109
|
+
)
|
|
79
110
|
|
|
80
|
-
if
|
|
81
|
-
|
|
82
|
-
elif args['compile']:
|
|
83
|
-
compile(args['<lang_file>'], args['<output_file>'])
|
|
111
|
+
if __name__ == "__main__":
|
|
112
|
+
main()
|
|
@@ -12,11 +12,12 @@ Currently these are:
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
from __future__ import annotations
|
|
15
|
-
from typing import Optional
|
|
15
|
+
from typing import Optional, TYPE_CHECKING
|
|
16
16
|
import logging
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
from ..
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from ..attackgraph import AttackGraph
|
|
20
|
+
from ..node import AttackGraphNode
|
|
20
21
|
|
|
21
22
|
logger = logging.getLogger(__name__)
|
|
22
23
|
|
|
@@ -162,7 +163,7 @@ def calculate_viability_and_necessity(graph: AttackGraph) -> None:
|
|
|
162
163
|
graph - the attack graph for which we wish to determine the
|
|
163
164
|
viability and necessity statuses for the nodes.
|
|
164
165
|
"""
|
|
165
|
-
for node in graph.nodes:
|
|
166
|
+
for node in graph.nodes.values():
|
|
166
167
|
if node.type in ['exist', 'notExist', 'defense']:
|
|
167
168
|
evaluate_viability_and_necessity(node)
|
|
168
169
|
if not node.is_viable:
|
|
@@ -178,7 +179,7 @@ def prune_unviable_and_unnecessary_nodes(graph: AttackGraph) -> None:
|
|
|
178
179
|
the nodes which are not viable or necessary.
|
|
179
180
|
"""
|
|
180
181
|
logger.debug('Prune unviable and unnecessary nodes from the attack graph.')
|
|
181
|
-
for node in graph.nodes:
|
|
182
|
+
for node in graph.nodes.values():
|
|
182
183
|
if (node.type == 'or' or node.type == 'and') and \
|
|
183
184
|
(not node.is_viable or not node.is_necessary):
|
|
184
185
|
graph.remove_node(node)
|
|
@@ -3,7 +3,6 @@ MAL-Toolbox Attack Graph Attacker Class
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from __future__ import annotations
|
|
6
|
-
from dataclasses import dataclass, field
|
|
7
6
|
import copy
|
|
8
7
|
import logging
|
|
9
8
|
|
|
@@ -14,13 +13,19 @@ if TYPE_CHECKING:
|
|
|
14
13
|
|
|
15
14
|
logger = logging.getLogger(__name__)
|
|
16
15
|
|
|
17
|
-
@dataclass
|
|
18
16
|
class Attacker:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
name: str,
|
|
21
|
+
entry_points: set[AttackGraphNode],
|
|
22
|
+
reached_attack_steps: set[AttackGraphNode],
|
|
23
|
+
attacker_id: Optional[int] = None
|
|
24
|
+
):
|
|
25
|
+
self.name = name
|
|
26
|
+
self.entry_points = entry_points
|
|
27
|
+
self.reached_attack_steps = reached_attack_steps
|
|
28
|
+
self.id = attacker_id
|
|
24
29
|
|
|
25
30
|
def to_dict(self) -> dict:
|
|
26
31
|
attacker_dict: dict = {
|
|
@@ -40,18 +45,26 @@ class Attacker:
|
|
|
40
45
|
return attacker_dict
|
|
41
46
|
|
|
42
47
|
def __repr__(self) -> str:
|
|
43
|
-
return
|
|
48
|
+
return f'Attacker(name: "{self.name}", id: {self.id})'
|
|
44
49
|
|
|
45
50
|
def __deepcopy__(self, memo) -> Attacker:
|
|
46
|
-
"""Deep copy an Attacker
|
|
51
|
+
"""Deep copy an Attacker
|
|
52
|
+
The deepcopy will copy over attacker specific information, name and
|
|
53
|
+
id, but it will not copy relations to attack graph nodes, reached
|
|
54
|
+
attack steps or entry points. These references should be recreated
|
|
55
|
+
when deepcopying the attack graph itself.
|
|
56
|
+
|
|
57
|
+
"""
|
|
47
58
|
|
|
48
59
|
# Check if the object is already in the memo dictionary
|
|
49
60
|
if id(self) in memo:
|
|
50
61
|
return memo[id(self)]
|
|
51
62
|
|
|
52
63
|
copied_attacker = Attacker(
|
|
53
|
-
id = self.id,
|
|
54
64
|
name = self.name,
|
|
65
|
+
attacker_id = self.id,
|
|
66
|
+
entry_points = set(),
|
|
67
|
+
reached_attack_steps = set()
|
|
55
68
|
)
|
|
56
69
|
|
|
57
70
|
# Remember that self was already copied
|
|
@@ -66,7 +79,7 @@ class Attacker:
|
|
|
66
79
|
|
|
67
80
|
def compromise(self, node: AttackGraphNode) -> None:
|
|
68
81
|
"""
|
|
69
|
-
Have the
|
|
82
|
+
Have the attacker compromise the node given as a parameter.
|
|
70
83
|
|
|
71
84
|
Arguments:
|
|
72
85
|
node - the node that the attacker will compromise
|
|
@@ -90,8 +103,8 @@ class Attacker:
|
|
|
90
103
|
)
|
|
91
104
|
return
|
|
92
105
|
|
|
93
|
-
node.compromised_by.
|
|
94
|
-
self.reached_attack_steps.
|
|
106
|
+
node.compromised_by.add(self)
|
|
107
|
+
self.reached_attack_steps.add(node)
|
|
95
108
|
|
|
96
109
|
def undo_compromise(self, node: AttackGraphNode) -> None:
|
|
97
110
|
"""
|