Skip to content

Add ttc sampling and expected value to AttackGraphNode #128

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

mrkickling
Copy link
Contributor

@mrkickling mrkickling commented Mar 21, 2025

I thinl it makes sense having ttc distribution sampling/expected value methods in the AttackGraphNode class since ttcs are part of the AttackGraphNode.

This way the sampling can be used in the simulator (or any other tool that wants to sample ttcs for attack graph nodes).

Problem is I don't understand fully how these distributions should work. I could only look in the traverser and the MAL documentation and try to understand it.

Need some expert knowledge and someone who knows how the distributions should work.

Note:

  • when sampling ttc with bernoulli and bernoulli returns 0, ttc value for that sample will be infinite. Does this make sense?

@nkakouros
Copy link
Contributor

when sampling ttc with bernoulli and bernoulli returns 0, ttc value for that sample will be infinite. Does this make sense?

Reading the wiki:

An attack step with a TTC function Bernoulli(p) means that the attack step can be performed immediately with a probability of p, and not at all with a probability of 1 - p.

To me, this means "disable the step". What you suggest (setting a TTC of infinite) is similar but different. The agents can still attempt the step but never progress past it. However, according to the wiki, the step should not be "performable" at all.

@mrkickling
Copy link
Contributor Author

when sampling ttc with bernoulli and bernoulli returns 0, ttc value for that sample will be infinite. Does this make sense?

Reading the wiki:

An attack step with a TTC function Bernoulli(p) means that the attack step can be performed immediately with a probability of p, and not at all with a probability of 1 - p.

To me, this means "disable the step". What you suggest (setting a TTC of infinite) is similar but different. The agents can still attempt the step but never progress past it. However, according to the wiki, the step should not be "performable" at all.

Removing the attack step completely might be the solution then. But maybe that is a choice to be taken by the simulator when it samples the ttcs and removing those that has a sampled ttc that disables the node.

Comment on lines 193 to 207
def sample(exponential: float, bernoulli=1.0):
"""
Generate a random sample for the given distributions.
If bernoulli distribution is not given, the sample will
simply be from an exponential.

If the Bernoulli trial fails (0), return infinity (impossible).
If the Bernoulli trial succeeds (1), return sample from
exponential distribution.
"""

# If bernoulli is set to 1, the sample is just exponential
if np.random.choice([0, 1], p=[1 - bernoulli, bernoulli]):
return np.random.exponential(scale=1 / exponential)
return math.inf
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is too convoluted. Why not use np.random.exponential directly in all cases in the lines below, except the bernoulli one where you do just random.choice? The way it is now, it seems like you are doing Bernoulli * Exponential, when the TTC in the mal spec does may not use the Bernoulli distribution at all and you have to default to the Bernoulli part returning 1 for such cases. While the result is the same, it is unnecessarily complex.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After I wrote this, I realized that most of the distributions here are the codenamed ones, eg hard and uncertain which do use the Bernoulli distribution in their formula. But still others, like the pure exponential, do not and I still think that this could be simplified a bit.

Maybe it merits an online call to discuss some aspects of this.


# If bernoulli is set to 1, the sample is just exponential
if np.random.choice([0, 1], p=[1 - bernoulli, bernoulli]):
return np.random.exponential(scale=1 / exponential)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need numpy (a dependency). We can use random.expovariate that is builtin.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this will return a float. The TTC is an integer. There should be some rounding done here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did we decide that the TTC is an integer?

@andrewbwm
Copy link
Collaborator

So, I think I'd like two things to happen.

  1. Have the toolbox just have a collection of predefined TTCs, such as HardAndUncertain, and when we process the language graph we simply replace it with what it actually represents(Bernouli(0.5) * Exponential(0.1) or whatever it is). We may even want to save this in a separate file for easy editing.
  2. Have the entire sampling logic happen in the Simulator. TTCs are yet another ambiguous feature that kind of lives in the liminal space between toolbox and simulator. However, it really doesn't have any intrinsic meaning within the toolbox, so I'd be quite happy to just move it wholesale to the simulator. We also expect most people to use the simulator if they intend to do any sort of Attack Graph traversal at this point anyway.

| expo [Exponential(0.1)]
| berni [Bernoulli(0.5)]
| composite [Exponential(0.1) * Bernoulli(0.5)]
| compositeComplex [Bernoulli(0.5) * (Binomial(10, 0.1) + Exponential(0.2) - Gamma(0.1, 0.2) * LogNormal(5, 0.5) / Pareto(7, 0.25) ^ TruncatedNormal(6, 0.3) + Uniform(0.1, 0.9))]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nkakouros just as a heads up, we don't need it right now since it will not be used any time soon, but the compiler does not handle the / correctly. The expression simply terminates when it encounters it.

@@ -654,6 +654,7 @@ class LanguageGraph():
"""Graph representation of a MAL language"""
def __init__(self, lang: Optional[dict] = None):
self.assets: dict = {}
self.load_predefined_ttcs()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this needs to be a method since all it does is to set an instance variable (predef_ttcs). I would either remove the method or have the method return the predefined ttc values so the instance variable is defined in __init__.

p = float(probs_dict['arguments'][1])
# TODO: Someone with basic probabilities competences should
# actually check if this is correct.
return random.binomialvariate(n, p)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My linter tells me this method does not exist in module random

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am also not the correct person to say if these sampling methods are correct :P

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://docs.python.org/3/library/random.html#random.binomialvariate

I think the issue is that it was introduced in python 3.12

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh alright, that would make mal-toolbox require 3.12 I guess so maybe it is worth to see if there is a different option.

Copy link
Collaborator

@andrewbwm andrewbwm Apr 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There isn't and something somewhere(maybe in the mal simulator) already requires 3.12. Some people trying to use it ran into it. So, I think we should just specify 3.12 as a requirement.

@mrkickling
Copy link
Contributor Author

Looks good to me, but I can not say if the sampling methods are correct

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants