---
title: 'Adversary Behavior Classifications'
source: 'https://academia.sh/en/courses/attack-techniques/adversary-behavior-classifications'
course: 'Attack Techniques and the Threat Landscape'
language: en
updated: '2026-09-26T03:02:49+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - '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'
---

# 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.

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.

```js
// 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;
}
```

```js
// 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.
