Skip to content
academia.sh

Lesson 04 / 22

Adversary Behavior Classifications

Mapping techniques onto steps and counting the step with no control at all: 8 tactics and 27 technique classes producing 10,800 chains; five control items covering 40.7% of the techniques; three steps remaining steps no control sees at all; 80% of chains breaking within the first two steps while 72 chains (0.67%) cannot be broken at any link; the latest-breaking 1.33% slice carrying 42.6% of the average items; and two interventions that yield the same coverage percentage (51.9%) producing gains that differ threefold, 1.39 versus 0.47 items.

Published Updated

In this lesson

  • Models adversary behavior as a matrix made of tactic columns and technique rows
  • Maps every technique to a control and derives the count of steps with no control at all by running the model
  • Separates coverage percentage from early-break gain and compares interventions that yield the same percentage
Contents

The previous lesson measured eight steps and the single control set against each. In reality, a step is not matched against a single control; it needs as many controls as the number of distinct forms in which that step can be carried out. Lateral movement was one row there; underneath it there are many independent technique classes, and a single control sees only a portion of them.

Works that classify adversary behavior are built exactly to fill this gap. The structure is always the same: steps become columns, technique classes become rows, and a control is mapped to each cell. This lesson builds that structure and asks a single question: as the coverage percentage rises, does the number of steps with no control at all fall, or does coverage only grow on the steps that were already seen.

The Matrix Is a Step–Technique Mapping

The structure below is a model: techniques are written only by class name and by which control sees them; how any of them is carried out never appears. The work is done on a fictional measurement network.

TM10: every step (tactic) has more than one technique class under it, and a chain picks one technique from each step. TM11: a control item sees particular techniques; a step carried out through a technique it does not see cannot be broken on that chain. TM12: the earliest link at which a chain can be broken is the step where the first seen technique among the chosen ones sits; if none is seen, the chain cannot be broken.

// threat/matrix.mjs — the model of the tactic and technique matrix: rows are the technique
// class, columns are the chain step. There is no running technique here; techniques are
// written only by class name and by which control sees them. There is no randomness.

// TM10: every step (tactic) has more than one technique class under it; a chain picks one technique
// from each step. `items` is the cumulative fictional item count reached once the step completes.
export const TACTIC = [
  { name: "recon", items: 0, technique: ["open-source-compilation", "external-surface-enumeration",
    "employee-list-harvesting", "service-fingerprinting"] },
  { name: "initial-access", items: 1, technique: ["valid-credential-login", "attachment-based",
    "external-service-exploitation", "supply-chain-connection", "removable-media"] },
  { name: "execution", items: 2, technique: ["interpreter-invocation", "scheduled-trigger",
    "user-interaction"] },
  { name: "persistence", items: 3, technique: ["startup-entry", "service-definition", "scheduled-task",
    "account-creation", "configuration-hook"] },
  { name: "privilege-escalation", items: 9, technique: ["permission-misuse", "token-takeover",
    "path-hijacking"] },
  { name: "lateral-movement", items: 26, technique: ["remote-service-session", "share-write",
    "credential-reuse"] },
  { name: "collection", items: 41, technique: ["local-data-collection", "share-scanning"] },
  { name: "exfiltration", items: 48, technique: ["encrypted-channel", "split-transfer"] },
];

// TM11: a control item sees particular techniques; a step carried out through a technique it
// does not see cannot be broken on that chain.
export const CONTROL = {
  "identity-log": ["valid-credential-login", "external-service-exploitation", "account-creation"],
  "process-log": ["interpreter-invocation", "scheduled-trigger"],
  "startup-inventory": ["startup-entry", "service-definition", "scheduled-task"],
  "privilege-change": ["permission-misuse", "token-takeover"],
  "egress-volume": ["encrypted-channel"],
};

export const seenSet = (extra = {}) =>
  new Set(Object.values({ ...CONTROL, ...extra }).flat());
export const TECHNIQUE_COUNT = TACTIC.reduce((t, k) => t + k.technique.length, 0);
export const CHAIN_COUNT = TACTIC.reduce((t, k) => t * k.technique.length, 1);

// TM12: a chain picks one technique from each tactic; the earliest link at which the chain can be
// broken is the step where the first seen technique among the chosen ones sits. If none is seen, it cannot be broken.
export function distribution(extra = {}) {
  const g = seenSet(extra), count = new Array(TACTIC.length + 1).fill(0);
  const walk = (i, first) => {
    if (i === TACTIC.length) return count[first === -1 ? TACTIC.length : first]++;
    for (const t of TACTIC[i].technique) walk(i + 1, first === -1 && g.has(t) ? i : first);
  };
  walk(0, -1);
  return count;
}
export const itemsBefore = (i) => (i === 0 ? 0 : TACTIC[i - 1].items);
export function averageItems(extra = {}) {
  const s = distribution(extra);
  return s.reduce((t, n, i) =>
    t + n * (i === TACTIC.length ? TACTIC.at(-1).items : itemsBefore(i)), 0) / CHAIN_COUNT;
}
// threat/coverage.mjs — as the coverage percentage rises, does the count of steps with no control at all fall?
import { TACTIC, CONTROL, seenSet, TECHNIQUE_COUNT, CHAIN_COUNT, distribution, itemsBefore,
  averageItems } from "./matrix.mjs";

const print = (w, ...s) => console.log(s.map((v, i) =>
  (w[i] < 0 ? String(v).padEnd(-w[i]) : String(v).padStart(w[i]))).join(""));
const G = seenSet();
const covered = TECHNIQUE_COUNT - TACTIC.reduce((t, k) =>
  t + k.technique.filter((x) => !G.has(x)).length, 0);
const emptyTactic = TACTIC.filter((k) => k.technique.every((x) => !G.has(x)));

console.log(`${TACTIC.length} tactics x ${TECHNIQUE_COUNT} techniques, ` +
  `${Object.keys(CONTROL).length} control items; covered techniques ${covered} ` +
  `(${((covered / TECHNIQUE_COUNT) * 100).toFixed(1)}%), steps with no control at all ` +
  `${emptyTactic.length} (${emptyTactic.map((k) => k.name).join(", ")}); ` +
  `chain count ${CHAIN_COUNT}, average items ${averageItems().toFixed(2)}`);

const A = [-23, 11, 6, 11, 12, 8];
console.log("\n1. matrix: how many techniques per step, how many are seen");
print(A, "tactic", "technique", "seen", "unseen", "coverage %", "items");
for (const k of TACTIC) {
  const seen = k.technique.filter((x) => G.has(x)).length;
  print(A, k.name, k.technique.length, seen, k.technique.length - seen,
    ((seen / k.technique.length) * 100).toFixed(1), k.items);
}

const B = [-20, 11, 30, 16];
console.log("\n2. techniques seen per control item");
print(B, "control", "technique", "which step", "matrix share %");
for (const [d, t] of Object.entries(CONTROL)) {
  const step = [...new Set(t.map((x) => TACTIC.find((k) => k.technique.includes(x)).name))];
  print(B, d, t.length, step.join("/"), ((t.length / TECHNIQUE_COUNT) * 100).toFixed(1));
}

const C = [-23, 8, 10, 15, 19];
console.log("\n3. the earliest link a chain can be broken at");
print(C, "first seen step", "chains", "chains %", "items by then", "item contribution");
const S = distribution();
for (let i = 0; i <= TACTIC.length; i++) {
  if (S[i] === 0) continue;
  const items = i === TACTIC.length ? TACTIC.at(-1).items : itemsBefore(i);
  print(C, i === TACTIC.length ? "cannot be broken" : TACTIC[i].name, S[i],
    ((S[i] / CHAIN_COUNT) * 100).toFixed(2), items,
    ((S[i] * items) / CHAIN_COUNT).toFixed(2));
}

const D = [-43, 12, 12, 16, 9, 13];
console.log("\n4. if one more control item is added");
print(D, "added control", "coverage %", "empty step", "average items", "gain", "unbreakable");
const EXTRA = [
  ["recon: two techniques", { newControl: ["open-source-compilation", "external-surface-enumeration"] }],
  ["initial-access: all three techniques", { newControl: ["attachment-based", "supply-chain-connection",
    "removable-media"] }],
  ["persistence: the one remaining technique", { newControl: ["configuration-hook"] }],
  ["lateral-movement: all three techniques", { newControl: ["remote-service-session", "share-write",
    "credential-reuse"] }],
  ["collection: both techniques", { newControl: ["local-data-collection", "share-scanning"] }],
];
const baseline = averageItems();
for (const [name, extra] of EXTRA) {
  const g = seenSet(extra);
  const cov = TECHNIQUE_COUNT - TACTIC.reduce((t, k) =>
    t + k.technique.filter((x) => !g.has(x)).length, 0);
  const empty = TACTIC.filter((k) => k.technique.every((x) => !g.has(x))).length;
  const o = averageItems(extra);
  print(D, name, ((cov / TECHNIQUE_COUNT) * 100).toFixed(1), empty, o.toFixed(2),
    (baseline - o).toFixed(2), distribution(extra)[TACTIC.length]);
}
const late = S[TACTIC.length - 1] + S[TACTIC.length];
console.log(`baseline: unbreakable ${S[TACTIC.length]} chains; the ${late} chains in the two latest rows ` +
  `(${((late / CHAIN_COUNT) * 100).toFixed(2)}%) carry ` +
  `${(((S[TACTIC.length - 1] * TACTIC.at(-2).items + S[TACTIC.length] * TACTIC.at(-1).items) /
    CHAIN_COUNT / baseline) * 100).toFixed(1)}% of the average items`);
8 tactics x 27 techniques, 5 control items; covered techniques 11 (40.7%), steps with no control at all 3 (recon, lateral-movement, collection); chain count 10800, average items 1.39

1. matrix: how many techniques per step, how many are seen
tactic                   technique  seen     unseen  coverage %   items
recon                            4     0          4         0.0       0
initial-access                   5     2          3        40.0       1
execution                        3     2          1        66.7       2
persistence                      5     4          1        80.0       3
privilege-escalation             3     2          1        66.7       9
lateral-movement                 3     0          3         0.0      26
collection                       2     0          2         0.0      41
exfiltration                     2     1          1        50.0      48

2. techniques seen per control item
control               technique                    which step  matrix share %
identity-log                  3    initial-access/persistence            11.1
process-log                   2                     execution             7.4
startup-inventory             3                   persistence            11.1
privilege-change              2          privilege-escalation             7.4
egress-volume                 1                  exfiltration             3.7

3. the earliest link a chain can be broken at
first seen step          chains  chains %  items by then  item contribution
initial-access             4320     40.00              0               0.00
execution                  4320     40.00              1               0.40
persistence                1728     16.00              2               0.32
privilege-escalation        288      2.67              3               0.08
exfiltration                 72      0.67             41               0.27
cannot be broken             72      0.67             48               0.32

4. if one more control item is added
added control                                coverage %  empty step   average items     gain  unbreakable
recon: two techniques                              48.1           2            0.70     0.70           36
initial-access: all three techniques               51.9           3            0.00     1.39            0
persistence: the one remaining technique           44.4           3            0.80     0.59            0
lateral-movement: all three techniques             51.9           2            0.92     0.47            0
collection: both techniques                        48.1           2            1.15     0.25            0
baseline: unbreakable 72 chains; the 144 chains in the two latest rows (1.33%) carry 42.6% of the average items

The numbers are in the measurement class; their inputs are TM10–TM12.

Inside Forty Percent Coverage

Under the eight steps there are 27 technique classes, and five control items see 11 of them — 40.7% coverage. This number alone carries no decision; the decision emerges once the first table breaks it down step by step.

Coverage is not evenly distributed: four of the persistence step’s five techniques are seen (80%), two-thirds of execution and privilege escalation are seen, two-fifths of initial access. Coverage is zero on three steps — recon, lateral movement, and collection. These three steps are the same three uncontrolled steps from the previous lesson; the matrix confirms that observation at the technique level and writes why: none of these steps’ techniques falls in the set the five control items see.

The matrix structure’s function in this lesson is not the classification itself. The classification only provides a countable grid: every cell answers yes or no to “can this step be carried out through this technique,” and whether a control is mapped to every yes cell is written separately. Without the grid, coverage stays an opinion; with the grid, coverage becomes the quotient of two numbers.

The second table reads the same number from the control side. The widest of the five controls sees three techniques and covers 11.1% of the matrix; the narrowest sees one technique, 3.7%. Two controls overlap on the same step: the identity log sees one technique that falls under the persistence step, while the startup inventory sees all three. Adding more controls is not the same as covering more steps.

Ten Thousand Eight Hundred Chains

Multiplying the technique counts per step gives 10,800 distinct chains. The third table finds the earliest link at which each chain can be broken and groups chains by that link.

This multiplication writes the difference between counting techniques and counting chains. The technique count is additive: adding two techniques to a step takes 27 to 29. The chain count is multiplicative: if those same two techniques take collection’s count from two to four, the chain count goes from 10,800 to 21,600. Coverage percentage is calculated over the technique count and so grows additively, while the probability set the defense faces grows multiplicatively. There is no reason for the two measures to move in the same direction.

The distribution piles up at the start: 40% of chains can be broken at initial access, 40% at execution, 16% at persistence. A total of 96% collects in the first three breakable links, and on those chains the items reached are 0, 1, or 2. Average items are 1.39. Even though the coverage percentage looks low, coverage sitting at the start of the chain keeps the average down.

The tail runs the opposite way. 72 chains can only be broken at the exfiltration step — by that point 41 items have been reached. A further 72 chains cannot be broken at any link: an unseen technique was picked at all eight of the eight steps. Together these two groups are only 1.33% of the chains, yet they carry 42.6% of the average items. A matrix evaluated by coverage averages never sees this 1.33% at all.

This weight in the tail comes directly from the item sequence. Item counts run 0, 1, 2, 3, 9, 26, 41, 48 across the steps; the first four steps’ combined contribution is 3, the last four steps’ is 45. While the large majority of chains break in the cheap region, the unbreakable minority carries the whole of the expensive region.

The 72 unbreakable chains are a situation appearing for the first time in this course: the chain has no break point at any link. In this case the measure shifts from breaking to detection — the question is no longer “at which link does it break” but “once the event has happened, from which record is it seen.”

Same Coverage, a Threefold Difference

The fourth table compares five separate interventions on the same scale and gives this lesson’s central finding.

Two rows produce exactly the same coverage percentage: covering initial access’s three unseen techniques, and covering lateral movement’s three techniques — both take coverage from 40.7% to 51.9%. Their gains, though, are 1.39 and 0.47 items; the first is three times the second. The same size of effort, the same percentage increase, a threefold difference in outcome. The difference comes only from the position in the chain of the step it lands on.

The second row is even sharper: once initial access is fully covered, every chain becomes breakable right after the first step and average items drop to 0.00 — yet the empty-step count stays at 3. An assessment that looks at the coverage table finds this intervention lacking, saying “there are still three blind steps”; an assessment that looks at the item measure records it as the best move.

The opposite example is in the same table. Covering lateral movement and collection brings the empty-step count from 3 to 2 — the only improvement visible in the coverage table — and their gains are 0.47 and 0.25 items, the two lowest on the list. Lowering the empty-step count and raising the early-break gain are separate goals, and they do not pick the same intervention.

The last column brings a third criterion: the count of unbreakable chains. The baseline is 72. Covering persistence’s one remaining technique raises coverage only to 44.4% and does not change the empty-step count at all, but it takes the unbreakable chain count from 72 to 0 — because that technique is the single common gap every unbreakable chain passes through. Covering two techniques at recon brings the same number down by only half. Three criteria pick three different rows; a coverage decision made without stating which criterion was used counts as not measured at all.

Summary

  • Adversary behavior classifications build a matrix that makes steps columns and technique classes rows: 8 steps, 27 techniques, and the product of the per-step technique counts is 10,800 chains.
  • Five control items see 11 of the 27 techniques (40.7%); coverage is zero on three steps, and these are the same three steps that came out uncontrolled in the previous lesson.
  • 96% of chains break at the first three breakable links, and average items are 1.39; coverage collecting at the start of the chain keeps the average low.
  • 1.33% of chains (144 chains) carry 42.6% of the average items; 72 of these cannot be broken at any link, and on those chains the measure shifts from breaking to detection.
  • Two interventions that yield the same coverage percentage (51.9%) save 1.39 and 0.47 items; the difference comes only from the covered step’s position in the chain.
  • Three criteria pick three different interventions: coverage percentage and item gain point to initial access, empty-step count to lateral movement, unbreakable-chain count to persistence’s one remaining technique.

Next Step

The matrix leaves one question unanswered. Cells say “this step can be carried out through this technique” and record whether a control sees that technique. But once an observation is in hand — a record, a connection, a file trace — which chain that observation belongs to cannot be read from the matrix. The same technique class occurs in many independent events.

There is a second structure that fills this gap and asks a different question from the matrix: an event’s four corners — who did it, which infrastructure was used, which capability it carried, and who it targeted — are linked to one another. Knowing one corner narrows the others. The next lesson measures this narrowing as a link count: how many candidates knowing one corner eliminates from the other three, and which corner becomes visible at the chain’s earliest link.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close