---
title: 'Advanced Persistent Threat'
source: 'https://academia.sh/en/courses/attack-techniques/advanced-persistent-threat'
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 a long-running campaign through step durations and daily trace production'
    - 'Counts, separately, the number of days a control sees and the number of days no control sees'
    - 'Calculates dwell time from the detection day and derives the item cost of the delay'
---

# Advanced Persistent Threat

Measuring a long-running campaign on the time axis: in a 330-day, eight-step sequence, 261 days (79.1%) are days no control sees; only two of five controls reach their record threshold; the two longest steps carry 65.5% of the duration and two-thirds of the item growth; first detection arrives on day 92 with 7 items; raising a single record threshold from 180 to 300 pushes detection to day 325 and raises dwell time from 47 days to 280 days and the item count from 7 to 45; and a control placed on the recon step saves all 48 items without foothold ever being established.

In every measurement in the previous lesson, duration was never binding: the chain's total cost
was written as 52 days, the most patient level's budget was 400 days, and no actor ever stopped
because of its day budget. Patience never came into play as a constraint.

In a long-running campaign, the measure itself is duration. A step can take not days but
**months**, and over that month a record is born on the defender's side every day. Every record is
a **window of opportunity**: a moment at which a control could alert. This lesson sets the chain on
the time axis and gives three numbers — how many days it takes, how many days no control ever
looks at, and how much the detection delay grows the item count.

## A Campaign Is a Sequence in Time

The structure below is a **model**: there is no real campaign, actor, or incident name; steps are
identified only by duration, daily trace, and item count. The work is done on a fictional
measurement network.

**TM4:** each step has a duration, a count of fictional records it produces per day (`trace`), a
control that sees that record, and the record threshold the control waits for before it alerts.
**TM5:** a control only alerts once records accumulate up to the threshold on its own step; if the
step ends before that many records are produced, the control never alerts even if it exists.
**TM6:** the item count accumulates linearly within a step.

```js
// threat/campaign.mjs — the duration model of a long-running campaign: there is no running
// attack step here, only a data structure made of day, trace, and item counts. No real
// campaign, actor, or incident name appears. There is no randomness.

// TM4: each step has a duration (days), a count of fictional records produced per day (trace),
// a control that sees that record (or none), and the record threshold the control waits for
// before it alerts. `items` is the cumulative fictional item count reached once the step ends.
export const STEP = [
  { name: "recon", days: 45, trace: 1, control: null, threshold: 0, items: 0 },
  { name: "initial-access", days: 12, trace: 4, control: "identity-log", threshold: 60, items: 1 },
  { name: "execution", days: 2, trace: 9, control: "process-log", threshold: 30, items: 2 },
  { name: "persistence", days: 3, trace: 2, control: "startup-inventory", threshold: 10, items: 3 },
  { name: "privilege-escalation", days: 38, trace: 6, control: "privilege-change", threshold: 180, items: 9 },
  { name: "lateral-movement", days: 96, trace: 3, control: null, threshold: 0, items: 26 },
  { name: "collection", days: 120, trace: 2, control: null, threshold: 0, items: 41 },
  { name: "exfiltration", days: 14, trace: 12, control: "egress-volume", threshold: 100, items: 48 },
];

export const START = STEP.map((_, i) =>
  STEP.slice(0, i).reduce((t, s) => t + s.days, 0));
export const DURATION = STEP.reduce((t, s) => t + s.days, 0);
export const FOOTHOLD = START[1]; // day the initial-access step starts

// TM5: a control only alerts once records accumulate to the threshold on its own step; if the
// step ends before that many records are produced, the control never alerts even if it exists.
export function alertDay(i, override = {}) {
  const s = { ...STEP[i], ...(override[STEP[i].name] || {}) };
  if (!s.control || s.days * s.trace < s.threshold) return null;
  return START[i] + Math.ceil(s.threshold / s.trace);
}
export const firstDetection = (override = {}) => STEP
  .map((_, i) => alertDay(i, override)).filter((g) => g !== null).sort((a, b) => a - b)[0] ?? null;

// TM6: the item count accumulates linearly within a step; the items reached on day d are as follows.
export function itemsOnDay(d) {
  let previous = 0;
  for (let i = 0; i < STEP.length; i++) {
    const start = START[i], end = start + STEP[i].days;
    if (d < end) return Math.floor(previous + ((d - start) / STEP[i].days) * (STEP[i].items - previous));
    previous = STEP[i].items;
  }
  return STEP.at(-1).items;
}
```

```js
// threat/duration.mjs — the time axis: which step runs for months, how many windows of
// opportunity are born, how many items every day of dwell time grows.
import { STEP, START, DURATION, FOOTHOLD, alertDay, firstDetection, itemsOnDay }
  from "./campaign.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 monitored = STEP.filter((s) => s.control);
const windowDays = monitored.reduce((t, s) => t + s.days, 0);
const windowRecords = monitored.reduce((t, s) => t + s.days * s.trace, 0);

console.log(`${STEP.length} steps, ${DURATION} days; ${monitored.length} steps have a control, ` +
  `${STEP.length - monitored.length} steps no control sees at all; ` +
  `monitored days ${windowDays} (${((windowDays / DURATION) * 100).toFixed(1)}%), ` +
  `monitored records ${windowRecords}`);

const A = [-23, 6, 7, 22, 11, 10, 14, 8];
console.log("\n1. step-by-step duration and window of opportunity");
print(A, "step", "days", "trace", "control", "threshold", "produced", "alert day", "items");
for (let i = 0; i < STEP.length; i++) {
  const s = STEP[i], u = alertDay(i);
  print(A, s.name, s.days, s.trace, s.control || "-", s.threshold || "-", s.days * s.trace,
    u === null ? (s.control ? "no threshold" : "-") : u, s.items);
}
console.log(`controls reaching threshold: ${STEP.filter((_, i) => alertDay(i) !== null).length}/` +
  `${monitored.length}; first detection day ${firstDetection()}`);

const B = [-24, 8, 18, 9, 13];
console.log("\n2. where the duration collects");
print(B, "group", "days", "duration share %", "records", "item growth");
for (const [label, k] of [["steps with a control", monitored],
  ["steps no control sees", STEP.filter((s) => !s.control)],
  ["the two longest steps", [...STEP].sort((x, y) => y.days - x.days).slice(0, 2)]]) {
  const g = k.reduce((t, s) => t + s.days, 0);
  const growth = k.reduce((t, s) => {
    const i = STEP.indexOf(s);
    return t + s.items - (i === 0 ? 0 : STEP[i - 1].items);
  }, 0);
  print(B, label, g, ((g / DURATION) * 100).toFixed(1), k.reduce((t, s) => t + s.days * s.trace, 0), growth);
}

const C = [-56, 15, 13, 7, 7];
console.log("\n3. when the detection day falls, and how many items by then");
print(C, "override", "detection day", "dwell days", "items", "saved");
const SCENARIO = [
  ["thresholds as-is", {}],
  ["privilege threshold 180 -> 300", { "privilege-escalation": { threshold: 300 } }],
  ["privilege threshold 300 + control on lateral movement", { "privilege-escalation": { threshold: 300 },
    "lateral-movement": { control: "session-hopping", threshold: 60 } }],
  ["control on recon (threshold 30)", { recon: { control: "external-query-volume", threshold: 30 } }],
  ["no control alerts", { "privilege-escalation": { threshold: 300 }, exfiltration: { threshold: 300 } }],
];
for (const [label, override] of SCENARIO) {
  const g = firstDetection(override) ?? DURATION;
  print(C, label, firstDetection(override) ?? "-", g < FOOTHOLD ? "no foothold" : g - FOOTHOLD,
    itemsOnDay(g), STEP.at(-1).items - itemsOnDay(g));
}

const D = [-21, 6, 13, 11, 14];
console.log("\n4. the item rate of the delay");
print(D, "step", "days", "item growth", "items/day", "30-day delay");
for (let i = 0; i < STEP.length; i++) {
  const previous = i === 0 ? 0 : STEP[i - 1].items, growth = STEP[i].items - previous;
  print(D, STEP[i].name, STEP[i].days, growth, (growth / STEP[i].days).toFixed(3),
    Math.min(growth, Math.floor((growth / STEP[i].days) * 30)));
}
const t0 = firstDetection(), t1 = firstDetection({ "privilege-escalation": { threshold: 300 } });
console.log(`raising a single threshold from 180 to 300 pushes detection from day ${t0} to day ${t1}: ` +
  `dwell days ${t0 - FOOTHOLD} -> ${t1 - FOOTHOLD}, items ${itemsOnDay(t0)} -> ${itemsOnDay(t1)}`);
```

```
8 steps, 330 days; 5 steps have a control, 3 steps no control sees at all; monitored days 69 (20.9%), monitored records 468

1. step-by-step duration and window of opportunity
step                     days  trace               control  threshold  produced     alert day   items
recon                      45      1                     -          -        45             -       0
initial-access             12      4          identity-log         60        48  no threshold       1
execution                   2      9           process-log         30        18  no threshold       2
persistence                 3      2     startup-inventory         10         6  no threshold       3
privilege-escalation       38      6      privilege-change        180       228            92       9
lateral-movement           96      3                     -          -       288             -      26
collection                120      2                     -          -       240             -      41
exfiltration               14     12         egress-volume        100       168           325      48
controls reaching threshold: 2/5; first detection day 92

2. where the duration collects
group                       days  duration share %  records  item growth
steps with a control          69              20.9      468           16
steps no control sees        261              79.1      573           32
the two longest steps        216              65.5      528           32

3. when the detection day falls, and how many items by then
override                                                  detection day   dwell days  items  saved
thresholds as-is                                                     92           47      7     41
privilege threshold 180 -> 300                                      325          280     45      3
privilege threshold 300 + control on lateral movement               120           75     12     36
control on recon (threshold 30)                                      30  no foothold      0     48
no control alerts                                                     -          285     48      0

4. the item rate of the delay
step                   days  item growth  items/day  30-day delay
recon                    45            0      0.000             0
initial-access           12            1      0.083             1
execution                 2            1      0.500             1
persistence               3            1      0.333             1
privilege-escalation     38            6      0.158             4
lateral-movement         96           17      0.177             5
collection              120           15      0.125             3
exfiltration             14            7      0.500             7
raising a single threshold from 180 to 300 pushes detection from day 92 to day 325: dwell days 47 -> 280, items 7 -> 45
```

The numbers are in the **measurement** class; their inputs are TM4–TM6.

## Seventy-Nine Percent of Three Hundred Thirty Days in the Dark

The chain's total duration is **330 days**, and this duration is not spread evenly across the
steps. Execution takes two days, persistence three; lateral movement is 96, collection 120. Two
steps alone carry **65.5%** of the duration.

The distribution of control coverage is the inverse of duration. Five of the eight steps have a
control, three do not — and the three uncontrolled steps include the two longest. The steps that
have a control run for a combined **69 days**, that is 20.9% of the duration; the steps no control
ever sees run for **261 days**, that is 79.1%. The length of a campaign does not by itself buy the
defense time: the only time that is gained is monitored time.

The second table's last column sharpens this. Across the monitored 69 days, the item growth reached
is **16**; across the unmonitored 261 days, it is **32**. Two-thirds of the items accumulate in the
stretch of time that is never looked at. The record count runs the same way: the unmonitored steps
produce 573 records, and none of those records ever reach a control.

## A Control That Exists Can Still Be a Control That Never Alerts

The first table's **alert day** column writes the difference between a control existing and a
control working. Three of the five controls carry a "no threshold" row: the control is defined, it
sees the step's records, but the step ends before enough records are produced to reach the
threshold. Initial access produces 48 records in 12 days; its threshold is 60. Execution produces
18 records in two days; its threshold is 30. Persistence produces 6 records in three days; its
threshold is 10.

These three rows are three instances of the same flaw: **the threshold was set against record
volume, not against the step's duration**. Short steps can never reach a high threshold. Five items
appear in the control inventory; two of them actually work. If coverage is counted by step count,
it reads 5/8; a coverage measure that also counts reaching the threshold reads **2/8**.

The first alert falls on day 92, and it comes from the control on the privilege-escalation step:
the step starts on day 62, produces 6 records a day, and reaches its 180-record threshold in 30
days. The item count reached by that day is **7**, and the campaign has been in place for 47 days.
**Dwell time** is the difference between the day the initial-access step starts and the day of
detection.

## The Two-Hundred-Thirty-Three-Day Cost of One Threshold

The third table changes a single setting. If the privilege-change control's threshold is raised
from 180 to 300 — an ordinary adjustment made to cut down on alert volume — that step, which
produces 228 records, never reaches the threshold at all. The job of alerting then falls to the
chain's **last step**: the egress-volume control alerts on day 325.

The result shows up in three numbers at once. The detection day rises from 92 to 325, dwell time
from 47 days to **280 days**, and items reached from 7 to **45**. The items saved drop from 41 to 3.
A single threshold value, on a single control, makes a **38-item**, 233-day difference. A threshold
adjustment looks like a decision about alert volume; once measured, it is a decision about items.

The third row tries a remedy: the threshold stays at 300, but a control is added to the
lateral-movement step. That step produces 3 records a day, reaches its 60-record threshold in 20
days, and pulls detection back to day 120 — 12 items, 36 saved. Of the 38 items that were lost, 33
are recovered, not all of them. **Placing a control on a step that was never looked at does not
fully make up for a broken threshold.**

The fourth row goes to the start of the chain. If a control with a 30-record threshold is placed on
the recon step, its alert comes on day 30 — since the initial-access step does not start until day
45, **foothold is never even established**, and all 48 items are saved. The recon step takes 45 days and
produces only 1 record a day; the cost of monitoring this step is building a measure that separates
out incoming external query volume. The fifth row is the opposite extreme: if no control ever
alerts, the campaign stays in place for 285 days and reaches all 48 items.

## Every Day of Dwell

The fourth table turns the delay into a rate. The highest item rate is on the execution and
exfiltration steps: 0.500 items a day. The lowest rate, 0.083, is on initial access; on recon there
is no item growth at all. But the rate alone does not give the cost, because the duration it is
multiplied by varies by a factor of sixty from step to step.

The last column combines the two: the item cost of a thirty-day delay on each step. On execution,
1; on persistence, 1; on privilege escalation, 4; on lateral movement, **5**; on collection, 3
items. Waiting thirty days is not even possible on short steps — the step already only lasts two
days and its growth is capped at 1 item. On long steps, thirty days is a small slice of the step,
and even that slice carries several items.

The order that comes out of this is the time-axis reading of where to place a control. Placing a
control on a short step caps its alerting window at that step's duration: a control that does not
reach its threshold within two days never alerts at all. Placing it on a long step widens the
window but also grows the cost of any delay. **The most valuable control on the time axis is one
that can reach its threshold near the start of a long step** — the 20-day alert time on the
lateral-movement row is an example of this.

## Summary

- Across the 330-day, eight-step sequence, the steps with a control run for 69 days (20.9%) and the
  steps no control sees run for 261 days (79.1%); the two longest steps carry 65.5% of the duration
  and both are uncontrolled.
- Two-thirds of the item growth accumulates on uncontrolled days: 16 items across the 69 monitored
  days, 32 items across the 261 unmonitored days.
- A control existing does not mean it alerts: three of five controls never alert at all, because
  the step ends before enough records are produced to reach the threshold. Coverage by step count
  is 5/8; coverage by reaching the threshold is 2/8.
- First detection falls on day 92; by that day 7 items have been reached and dwell time has become
  47 days.
- If a single control's threshold rises from 180 to 300, detection shifts to day 325: dwell days
  rise from 47 to 280 and items from 7 to 45. Adding a control to lateral movement, without undoing
  that setting, saves 33 items.
- The cost of a thirty-day delay ranges from 0 to 7 items depending on the step; a control placed
  on the recon step saves all 48 items without foothold ever being established.

## Next Step

Two lessons' worth of chain have now been measured on two separate axes: the actor's capability
determined how many steps it passed, and the step's duration determined how many windows of
opportunity were born. In both, the chain itself was taken as given — eight steps, a fixed order,
each step following the one before it.

Yet the chain's real structure is not in its ordering but in its **preconditions**. What a step
requires, why the next step cannot happen without that requirement being met, which step can be
skipped and which cannot. Once the preconditions are written down, a control point can be set
against every step, and then the real question can be asked: at which step does the chain break,
what happens when it does not break, and **how many items does breaking one step early gain**. The
next lesson builds this course's unit of measure and calculates the early-break gain.
