In this tutorial you will learn how to load a language, create a model and run simulations on the generated attack graph.
Create a directory for the tutorial:
mkdir mal-tutorial2 && cd mal-tutorial2
Download tyrLang from github and put tyrLang in the directory:
git clone https://github.com/mal-lang/tyrLang.git
Create a Python virtual environment and activate it.
- On Linux-based operating systems:
python -m venv .venv
source .venv/bin/activate
- On Windows:
python -m venv .venv
.\.venv\Scripts\activate
Install the requirements:
pip install mal-toolbox
pip install mal-simulator
Create a python file in the directory called tutorial2.py with your text editor of choice.
Copy this piece of code into tutorial2.py:
import os
from maltoolbox.language import LanguageGraph
from maltoolbox.model import Model, ModelAsset
from maltoolbox.visualization.graphviz_utils import render_model
def connect_net_to_net(model: Model, net1: ModelAsset, net2: ModelAsset):
"""
Create a connection rule between net1 and net 2 and return it.
"""
cr_asset_name = f"ConnectionRule {net1.name} {net2.name}"
cr_asset = model.add_asset("InternetworkConnectionRule", cr_asset_name)
net1.add_associated_assets("interNetConnections", {cr_asset})
net2.add_associated_assets("interNetConnections", {cr_asset})
return cr_asset
def connect_app_to_net(model: Model, app: ModelAsset, net: ModelAsset) -> ModelAsset:
"""
Create a connection rule between app and net and return it.
"""
cr_asset_name = f"ConnectionRule {app.name} {net.name}"
cr_asset = model.add_asset("ConnectionRule", cr_asset_name)
app.add_associated_assets("appConnections", {cr_asset})
net.add_associated_assets("netConnections", {cr_asset})
return cr_asset
def add_vulnerability_to_app(model: Model, app: ModelAsset) -> ModelAsset:
"""
Add vulnerability and association from `app` to the vuln.
Return the vuln.
"""
asset_name = f"Vulnerability {app.name}"
vuln_asset = model.add_asset("SoftwareVulnerability", asset_name)
vuln_asset.add_associated_assets("application", {app})
return vuln_asset
def add_data_to_app(model: Model, app: ModelAsset, data_asset_name: str) -> ModelAsset:
"""
Add a data asset and association from `app` to the data.
return the data asset.
"""
data_asset = model.add_asset("Data", data_asset_name)
data_asset.add_associated_assets("containingApp", {app})
return data_asset
def add_user_to_app(model: Model, app: ModelAsset, data_asset_name: str) -> ModelAsset:
"""
Add a user asset and association from `app` to the user.
return the user asset.
"""
user_asset = model.add_asset("Identity", data_asset_name)
user_asset.add_associated_assets("execPrivApps", {app})
return user_asset
def add_creds_to_user(model: Model, identity: ModelAsset, data_asset_name: str) -> ModelAsset:
"""
Add a credentials asset and association from `identity` to the credentials.
return the credentials asset.
"""
creds_asset = model.add_asset("Credentials", data_asset_name)
creds_asset.add_associated_assets("identities", {identity})
return creds_assetThese helper functions are made to work with the MAL language tyrLang, association fieldnames and asset types are specific per language. Therefore, they would need to be adapted depending on the MAL language in use.
Each function creates assets in a model and connects the assets to other assets using associations (associatoin fieldnames to be more exact).
Let's create a model and use the helper functions. First, add these imports to the others at the beginning of the python file:
from maltoolbox.attackgraph import AttackGraph
from maltoolbox.visualization.graphviz_utils import render_model, render_attack_graphThen, add this to the end of the file:
def create_model(lang_graph: LanguageGraph) -> Model:
"""Create a model with 4 apps"""
model = Model("my-model", lang_graph)
# Two networks
net_a = model.add_asset("Network", "NetworkA")
net_b = model.add_asset("Network", "NetworkB")
# Connection between networks
connect_net_to_net(model, net_a, net_b)
# Four apps with connections to networks
app1 = model.add_asset("Application", "App 1")
connect_app_to_net(model, app1, net_a)
app2 = model.add_asset("Application", "App 2")
connect_app_to_net(model, app2, net_a)
app3 = model.add_asset("Application", "App 3")
connect_app_to_net(model, app3, net_b)
app4 = model.add_asset("Application", "App 4")
connect_app_to_net(model, app4, net_b)
# Add a vulnerability to app4
add_vulnerability_to_app(model, app4)
# Add data to app4
add_data_to_app(model, app4, "DataOnApp4")
# Add user to app3
user_on_app_3 = add_user_to_app(model, app3, "UserOnApp3")
# Add user to app3
add_creds_to_user(model, user_on_app_3, "User3Creds")
return model
def main():
lang_file = "tyrLang/src/main/mal/main.mal"
current_dir = os.path.dirname(os.path.abspath(__file__))
lang_file_path = os.path.join(current_dir, lang_file)
tyr_lang = LanguageGraph.load_from_file(lang_file_path)
# Create our example model
model = create_model(tyr_lang)
# Generate an attack graph from the model
graph = AttackGraph(tyr_lang, model)
# render_model(model) # Uncomment to render graphviz pdf
# render_attack_graph(graph) # Uncomment to render graphviz pdf
if __name__ == '__main__':
main()For the next steps you need the tool Graphviz. If you do not have already installed it you might find more information about it in the following link: How to download & install Graphviz.
Once Graphviz is installed, uncomment the line with 'render_model' and run the file with python tutorial2.py to see a render of the model. This can be helpful to debug generated models. We also have a specific tool for visualizing and creating MAL models covered in this tutorial.
Then try it with the other line (render_graph) to see a render of the attack graph. The attack graph contains all the attack steps and their relations in the model according to the definition in the MAL language, tyrLang in our case. Conceptually this represents the full blueprint of all possible attacks steps and attack paths possible in the model. As seen from the render, we see that even small models in simple languages easily become difficult to overview. Therefore, we typically would like to apply some form of analysis mechanism on the attack graph.
In the next section we will use the mal-simulator to run simulations with different agents. In these simulations the agents steps through the full attack graph and produces a (typically partial) graph traversal path, conceptually mimicking the activity of red team penetration tests in the modeled system environment.
To run simulations, add these imports to the top of the file (below the other imports):
from malsim.mal_simulator import MalSimulator, run_simulation
from malsim.config import AttackerSettings, DefenderSettings, MalSimulatorSettings, TTCMode
from malsim.policies import RandomAgent, TTCSoftMinAttacker, PassiveAgentNow we can create a MalSimulator from the attack graph and run simulations.
Add this to the end of the main function:
simulator = MalSimulator(graph)
path = run_simulation(simulator, {})When we run python tutorial2.py now we will just see "Simulation over after 0 steps.". This is because we don't have any agents. Let us add an attacker agent.
Replace the above code with:
agent_settings = {
"MyAttacker": AttackerSettings(
"MyAttacker",
entry_points={"App 1:fullAccess"},
goals={'DataOnApp4:read'},
policy=TTCSoftMinAttacker,
),
"MyDefender": DefenderSettings(
"MyDefender",
policy=PassiveAgent,
)
}
simulator = MalSimulator(
graph,
agent_settings=agent_settings,
sim_settings=MalSimulatorSettings(
ttc_mode=TTCMode.PRE_SAMPLE
)
)
run_simulation(simulator, agent_settings)
import pprint
pprint.pprint(simulator.recording)This creates a dict of agents that are used for registering agents and running policies with run_simulation. The attacker agent uses a policy which tries to take the easiest node (low TTC) every step and the defender is passive (does nothing).
When we run python tutorial2.py now, we can see that the simulation runs until the attacker reaches DataOnApp4:read. This tells us that there was a path from App 1 to DataOnApp4.
As we repeat the command, we can see that it reaches it on different iterations, since it is a probabilistic agent.
Try this out with different policies in malsim.policies.
run_simulation will return the recording of the simulation which can be found also in simulator.recording.
See the finished script in tutorial2.py.