---
title: Eradication
source: 'https://academia.sh/en/courses/security-operations/eradication'
course: 'Security Operations and Incident Response'
language: en
updated: '2026-09-26T03:02:57+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Models the foothold inventory and the five discovery methods as data structures'
    - "Counts each method's marginal contribution: how many new points, how many minutes, which type it never finds"
    - 'Converts missed points into reinfection events and compares the cost of eradication regimes'
---

# Eradication

Measuring the removal of persistence in terms of footholds: finding 17 of 22 points across nine affected assets in 506 minutes with five discovery methods; three of four configuration hooks going unfound by any method because they were lost on assets taken off the network during containment; across six eradication regimes, the missed-point count dropping from 10 to 0 as total time climbs from 353 minutes to 1,361 minutes; and five missed points producing 8 reinfections in 60 days, with the first on day five.

The previous lesson stopped access. Short-term containment was applied at minute 393, long-term
actions kicked in within hours, and the cut was lifted. What stopped was access; the items
automatically run on the endpoints did not stop. Once the short-term cut lifts and assets return to
the network, they return with whatever is on them.

Eradication's unit of measure is the **foothold**: an automatically run item. The inventory of
point types was measured in the Endpoint Security course; here it is taken as input. Three separate
things are counted in this lesson — how many points are **known**, how many are **eradicated**, and
how many produce reinfection when **missed**. The three are not the same number; the gap between
them is this lesson's subject. No persistence-establishment method is written out; what is counted
is the point itself and the discovery method applied to it.

## Point Inventory and Discovery Methods

**IR22:** The nine assets affected in the previous lesson have a total of 22 footholds; they are
spread across seven types, and each point's daily reactivation probability is a given. **IR23:**
There are five discovery methods, and each finds only specific types: baseline comparison,
process-creation log scanning, identity store audit, network flow data monitoring, and memory
examination. **IR24:** Not every method applies to every asset — baseline comparison works on an
asset that has a baseline, process log scanning on an asset that forwards process logs, and
**memory examination only on an asset not taken off the network during containment**. **IR25:** A
found point can be removed, and removal takes 8 minutes per point; rebuilding an asset takes 95
minutes and removes every point on that asset — including the ones not found. **IR26:** A missed
point reactivates each day with its own probability; the scan runs for 60 days with seed 20260214.
All dice are rolled from scratch, and the six regimes share the same daily sequence.

```js
// eradication/model.mjs — persistence eradication model: no real endpoint, persistence
// method or script involved. Footholds, discovery methods and asset properties are data structures.

export const SEED = 20260214, DAYS = 60;       // reinfection scan
export const REBUILD_MIN = 95, REMOVE_MIN = 8;   // per asset, per point

// affected asset: name -> { has baseline?, forwards process logs?,
// taken off network during containment (memory lost) }
export const ASSETS = {
  "collector-03": { baseline: true, process: true, offNetwork: true },
  "collector-07": { baseline: true, process: false, offNetwork: true },
  "collector-11": { baseline: true, process: false, offNetwork: true },
  "validator-01": { baseline: true, process: true, offNetwork: true },
  "validator-02": { baseline: true, process: true, offNetwork: true },
  "billing-01": { baseline: true, process: false, offNetwork: true },
  "portal-01": { baseline: true, process: false, offNetwork: false },
  "workorder-01": { baseline: true, process: false, offNetwork: false },
  "endpoint-01": { baseline: false, process: true, offNetwork: true },
};

// foothold: [asset, type, daily reactivation probability]
// M24/K05 measured the type inventory; here it is taken as input, no method is written out
export const POINTS = [
  ["collector-03", "service definition", 0.06], ["collector-03", "scheduled task", 0.05],
  ["collector-03", "configuration hook", 0.04], ["collector-07", "service definition", 0.06],
  ["collector-07", "session script", 0.03], ["collector-11", "scheduled task", 0.05],
  ["collector-11", "in-memory run", 0.02], ["validator-01", "service definition", 0.07],
  ["validator-01", "identity item", 0.09], ["validator-01", "outbound connection", 0.05],
  ["validator-02", "identity item", 0.09], ["validator-02", "configuration hook", 0.04],
  ["billing-01", "identity item", 0.08], ["billing-01", "session script", 0.03],
  ["billing-01", "outbound connection", 0.05], ["portal-01", "configuration hook", 0.04],
  ["portal-01", "identity item", 0.08], ["workorder-01", "scheduled task", 0.05],
  ["workorder-01", "in-memory run", 0.02], ["endpoint-01", "session script", 0.03],
  ["endpoint-01", "service definition", 0.06], ["endpoint-01", "configuration hook", 0.04],
];

// discovery method: name, minutes per asset, types it finds, which assets it applies to
export const METHODS = [
  ["baseline comparison", 22, ["service definition", "scheduled task", "session script"],
    (v) => v.baseline],
  ["process-creation log scan", 14,
    ["service definition", "scheduled task", "in-memory run"], (v) => v.process],
  ["identity store audit", 9, ["identity item"], () => true],
  ["network flow data monitoring", 11, ["outbound connection"], () => true],
  ["memory examination", 47, ["in-memory run", "configuration hook"], (v) => !v.offNetwork],
];

// index of points found and minutes spent, for a given set of chosen methods
export function discover(chosen) {
  const found = new Set();
  let min = 0;
  for (const [name, minutes, types, applies] of METHODS) {
    if (!chosen.includes(name)) continue;
    for (const [a, v] of Object.entries(ASSETS)) {
      if (!applies(v)) continue;
      min += minutes;
      POINTS.forEach(([pa, pt], i) => { if (pa === a && types.includes(pt)) found.add(i); });
    }
  }
  return { found, min };
}

export function rng(seed) {                    // linear congruential generator
  let x = seed >>> 0;
  return () => ((x = (x * 1664525 + 1013904223) >>> 0) / 2 ** 32);
}
// all dice are rolled from scratch: runs share the same daily sequence
const ROLLS = (() => { const z = rng(SEED);
  return Array.from({ length: DAYS * POINTS.length }, () => z()); })();

// a missed point reactivates daily with its own probability; first activation and total are counted
export function reinfection(missed) {
  let first = null, events = 0;
  for (let d = 0; d < DAYS; d++)
    for (const i of missed)
      if (ROLLS[d * POINTS.length + i] < POINTS[i][2]) { events++; if (first === null) first = d + 1; }
  return { first, events };
}

export function measure(chosen, rebuilt) {
  const { found, min } = discover(chosen);
  const rebuiltSet = new Set(rebuilt);
  const clean = new Set(found);
  POINTS.forEach(([a], i) => { if (rebuiltSet.has(a)) clean.add(i); });
  const missed = POINTS.map((_, i) => i).filter((i) => !clean.has(i));
  const removed = [...found].filter((i) => !rebuiltSet.has(POINTS[i][0])).length;
  return { found: found.size, clean: clean.size, missed,
    discoveryMin: min, cleanupMin: removed * REMOVE_MIN + rebuiltSet.size * REBUILD_MIN,
    rebuilt: rebuiltSet.size, ...reinfection(missed) };
}
```

The measurement file first adds the methods one by one and counts how many new points each brings,
then runs six eradication regimes against the same inventory.

```js
// eradication/measure.mjs — how many footholds there are, how many the discovery method
// finds, how many eradication removes, and how many days later a missed point produces reinfection.
import { SEED, DAYS, ASSETS, POINTS, METHODS, discover, measure } from "./model.mjs";

const write = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join(""));
const t0 = process.hrtime.bigint();
const ALL = METHODS.map((y) => y[0]);
const assetIds = Object.keys(ASSETS);
const TYPES = [...new Set(POINTS.map(([, t]) => t))];

console.log(`${assetIds.length} affected assets, ${POINTS.length} footholds, ` +
  `${TYPES.length} types; ${METHODS.length} discovery methods; seed ${SEED}`);

console.log("\n1. points found as discovery methods are added (cumulative)");
const A = [-32, 10, 8, 12, 15, 16];
write(A, "method", "min/asset", "assets", "new points", "cumul. points", "cumul. minutes");
let chosen = [], prevN = 0;
for (const [name, minutes, , applies] of METHODS) {
  chosen = [...chosen, name];
  const k = discover(chosen), fit = assetIds.filter((a) => applies(ASSETS[a])).length;
  write(A, name, minutes, fit, k.found.size - prevN, `${k.found.size}/${POINTS.length}`, k.min);
  prevN = k.found.size;
}

console.log("\n2. point type and number of methods that can find it");
const B = [-24, 8, 9, -40];
write(B, "type", "points", "methods", "   missing points / reason");
const all = discover(ALL).found;
for (const type of TYPES) {
  const index = POINTS.map((n, i) => [n, i]).filter(([n]) => n[1] === type);
  const missing = index.filter(([, i]) => !all.has(i));
  write(B, type, index.length, METHODS.filter((y) => y[2].includes(type)).length,
    missing.length ? `   ${missing.length}: ${missing.map(([n]) => n[0]).join(", ")}` : "   -");
}

const SCOPE = ["collector-03", "collector-07", "collector-11",
  "validator-01", "validator-02", "billing-01"];
const REGIMES = {
  "cheap discovery": [[ALL[0], ALL[2]], []],
  "excluding memory": [ALL.slice(0, 4), []],
  "full discovery": [ALL, []],
  "full discovery + scope rebuild": [ALL, SCOPE],
  "cheap discovery + scope rebuild": [[ALL[0], ALL[2]], SCOPE],
  "rebuild all assets": [ALL, assetIds],
};

console.log(`\n3. six eradication regimes (${DAYS}-day reinfection scan)`);
const C = [-33, 9, 8, 11, 14, 12, 8, 18, 7];
write(C, "regime", "found", "clean", "missed", "discovery min", "cleanup min", "rebuild",
  "first reinfection", "events");
const R = {};
for (const [name, [s, k]] of Object.entries(REGIMES)) {
  const r = R[name] = measure(s, k);
  write(C, name, `${r.found}/${POINTS.length}`, r.clean, r.missed.length, r.discoveryMin, r.cleanupMin,
    r.rebuilt, r.first ? `day ${r.first}` : "-", r.events);
}

console.log("\n4. points missed after full discovery");
for (const i of R["full discovery"].missed)
  write([-4, -16, -24, -18], "", POINTS[i][0], POINTS[i][1],
    `daily probability ${POINTS[i][2].toFixed(2)}`);

console.log("\n5. cost per missed point");
const D = [-33, 11, 12, 21, 16];
write(D, "regime", "total min", "missed", "min/closed point", "events/missed");
const base = R["cheap discovery"];
for (const [name, r] of Object.entries(R)) {
  const closed = r.clean - base.clean, min = r.discoveryMin + r.cleanupMin - base.discoveryMin - base.cleanupMin;
  write(D, name, r.discoveryMin + r.cleanupMin, r.missed.length, closed > 0 ? (min / closed).toFixed(0) : "-",
    r.missed.length ? (r.events / r.missed.length).toFixed(1) : "-");
}

const ms = Number(process.hrtime.bigint() - t0) / 1e6;   // the raw measurement is not printed, only the band is checked
console.log(`\nsix regimes and the ${DAYS}-day scan within the 500 ms band: ${ms < 500 ? "yes" : "no"}`);
```

```
9 affected assets, 22 footholds, 7 types; 5 discovery methods; seed 20260214

1. points found as discovery methods are added (cumulative)
method                           min/asset  assets  new points  cumul. points  cumul. minutes
baseline comparison                     22       8           8           8/22             176
process-creation log scan               14       4           1           9/22             232
identity store audit                     9       9           4          13/22             313
network flow data monitoring            11       9           2          15/22             412
memory examination                      47       2           2          17/22             506

2. point type and number of methods that can find it
type                      points  methods   missing points / reason
service definition             4        2   -
scheduled task                 3        2   -
configuration hook             4        1   3: collector-03, validator-02, endpoint-01
session script                 3        1   1: endpoint-01
in-memory run                  2        2   1: collector-11
identity item                  4        1   -
outbound connection            2        1   -

3. six eradication regimes (60-day reinfection scan)
regime                               found   clean     missed discovery min cleanup min rebuild first reinfection events
cheap discovery                      12/22      12         10           257          96       0             day 3     19
excluding memory                     15/22      15          7           412         120       0             day 5     11
full discovery                       17/22      17          5           506         136       0             day 5      8
full discovery + scope rebuild       17/22      20          2           506         610       6             day 5      4
cheap discovery + scope rebuild      12/22      17          5           257         586       6             day 5      9
rebuild all assets                   17/22      22          0           506         855       9                 -      0

4. points missed after full discovery
    collector-03    configuration hook      daily probability 0.04
    collector-11    in-memory run           daily probability 0.02
    validator-02    configuration hook      daily probability 0.04
    endpoint-01     session script          daily probability 0.03
    endpoint-01     configuration hook      daily probability 0.04

5. cost per missed point
regime                             total min      missed     min/closed point   events/missed
cheap discovery                          353          10                    -             1.9
excluding memory                         532           7                   60             1.6
full discovery                           642           5                   58             1.6
full discovery + scope rebuild          1116           2                   95             2.0
cheap discovery + scope rebuild          843           5                   98             1.8
rebuild all assets                      1361           0                  101               -

six regimes and the 60-day scan within the 500 ms band: yes
```
The numbers belong to the **measurement** class; their inputs are the assumptions above.

## Points Found as Methods Are Added

The first table counts discovery cumulatively. Baseline comparison applies to eight assets, takes
176 minutes, and on its own finds **eight** of the 22 points. Process-creation log scanning applies
to only four assets — five assets do not forward process logs — and for 56 minutes it brings **one**
new point; baseline comparison had already found the rest of what it turns up. Identity store audit
adds four new points across nine assets in 81 minutes, and network flow data monitoring adds two
new points in 99 minutes.

The last row is the most expensive. Memory examination takes 47 minutes per asset and applies to
only **two** assets: on the seven assets taken off the network during containment, memory contents
were lost. For 94 minutes it brings two new points and raises the total to 17. Five points cannot be
found by any method.

## The Five Points That Cannot Be Found

The second table breaks the loss down by type. **Three** of the four configuration hooks cannot be
found, because only memory examination finds this type, and that method cannot be applied on the
`collector-03`, `validator-02` and `endpoint-01` assets. The in-memory run on `collector-11` is lost
for the same reason. The session script on `endpoint-01` slips through for a different reason: that
asset has no baseline, and only baseline comparison finds this type.

The fourth table writes out these five points by name. What they have in common is not a discovery
gap but **a side effect of a containment decision**. In the previous lesson, short-term containment
took assets off the network and stopped access at minute 393; the same decision made memory
examination impossible on seven assets. Here is a third cost of early containment, and it does not
show up in the previous lesson's tables.

## Six Regimes and Reinfection

The third table compares eradication regimes. Cheap discovery — baseline comparison and identity
audit — finds 12 points in 257 minutes, misses 10, and produces **19 reinfections** in 60 days; the
first comes on day three. Full discovery finds 17 points in 506 minutes, misses five, and produces
eight events; the first comes on day five.

The only way to close a missed point is not to wait for it to be found, but to rebuild the asset.
When rebuilding the six scope assets is added to full discovery, clean points rise from 17 to
**20** — the unfound points on the rebuilt assets go too — missed drops to two and events fall to
four. Rebuilding all nine assets brings missed down to zero, and there is no reinfection at all in
60 days.

The fifth table gives the minutes for this. Cheap discovery totals 353, full discovery 642, full
discovery plus scope rebuild 1,116, rebuilding everything 1,361 minutes. Relative to cheap
discovery, the cost per closed point is 58 minutes for full discovery, 95 for scope rebuild, and
101 for full rebuild. The row that stands out is **cheap discovery plus scope rebuild**: it cleans
17 points in 843 minutes — the same count as full discovery, at 201 minutes more — and five points
still stay open. Rebuilding does not substitute for discovery; it only holds on the asset that gets
rebuilt.

## Summary

- Nine affected assets have 22 footholds; five discovery methods find 17 of them in 506 minutes,
  and five cannot be found by any method.
- The methods' marginal contribution is not equal: baseline comparison brings eight points in 176
  minutes, process log scanning one point in 56 minutes, and memory examination two points in 94
  minutes.
- Three of the four configuration hooks and one in-memory run slip through because memory
  examination cannot be applied on seven assets; those assets were taken off the network during
  containment.
- Five missed points produce eight reinfections in 60 days, the first on day five; when ten points
  are missed, events rise to 19 and the first moves up to day three.
- The only way to bring missed points to zero is to rebuild the asset: nine assets take 1,361
  minutes, 101 minutes per closed point.

## Next Step

The points are eradicated and the missed count is on record. What comes after this is bringing the
service back, and a new question arises there: which point in time does the rebuilt asset return
to. Some of the backups were taken after the first foothold moment, and points sit in those backups.
The next lesson measures recovery along the axis of reinfection risk: which backup is clean to
return to, how many items reverting to a clean backup costs, how many items are manually rebuilt,
and at which stage a supervised restoration catches reinfection.
