Lesson 13 / 22
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.
Published Updated
In this lesson
- 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
Contents
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.
// 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.
// 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.
To keep your progress and take notes, Log in
My notes
Log in to take notes.