---
title: 'Persistence Mechanisms'
source: 'https://academia.sh/en/courses/endpoint-security/persistence-mechanisms'
course: 'Endpoint and System Hardening'
language: en
updated: '2026-09-26T03:02:51+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Counts persistence point types as an inventory and separates the types each control closes from the types it makes visible'
    - "Measures controls' applicability by endpoint class and derives the fleet average"
    - 'Derives three columns for each endpoint class: closed, only visible, and points no control touches'
---

# Persistence Mechanisms

Ten persistence point types are counted as an inventory and six controls run across four endpoint classes: how many types each control closes, how many it only makes visible, which control cannot be applied in which endpoint class; the number of types closed in all four classes is zero, the number of types no control touches is one, and the cost of closing is 36 blocked legitimate items plus 52 manual-review items.

The previous lesson measured runs and showed that a run is transient: it ends and leaves only a
record behind. What persists is the item that run writes on the endpoint. The topic therefore
returns to where it began — the first lesson counted the points the types used; this lesson
counts those points as an inventory on the endpoint and runs six controls across them.

The three columns measured are the course's three columns. How many point types there are; how
many each control **closes**, how many it only makes **visible**; what closing costs in
legitimate work. A fourth distinction is added to this: a control cannot be applied in every
endpoint class. No persistence-establishment method was written in this lesson; what is counted
is the point itself and the control over it.

- **EP31.** Ten point types, legitimate item counts, six controls, and their applicability belong
  to a fictional regional measurement network. What is measured is the model built with `node`.
- **EP32.** A control's effect on a point type is one of three values: closes, makes visible,
  does not touch. Partial closing was not modeled; this makes the closed column look better than
  it is.
- **EP33.** Applicability is at the endpoint **class** level. In reality, some endpoints in the
  same class do not carry the control; drift measurement was the previous topic's subject and is
  taken here as input.
- **EP34.** Cost was counted with two items: blocked legitimate items and work that falls to
  manual review. Ongoing maintenance load was not counted; had it been, the cost of
  visibility-granting controls would have grown.
- **EP35.** A control that closes a point can also log the same point; in the model, closing is
  counted first. The two columns are therefore disjoint: a type is either closed or only visible.

## Inventory and Six Controls

```js
// inventory.mjs — persistence point types, endpoint classes, and control effects; model
// point type -> legitimate item count on one endpoint
export const LEGIT = {
  'startup-entry': 14, 'service-definition': 63, 'scheduled-task': 41, 'login-script': 6,
  'load-path-hook': 3, 'event-subscription': 2, 'legitimate-binary-attach': 0,
  'media-autorun': 1, 'application-extension': 27, 'boot-entry': 4,
};
export const POINT = Object.keys(LEGIT);
export const ENDPOINTS = { 'field laptop': 120, 'branch endpoint': 240, 'aggregator': 60, 'central server': 12 };
export const CLASS = Object.keys(ENDPOINTS);
// control -> [effect map, applicable endpoint classes, blocked legitimate items, work that falls to manual review]
export const CONTROL = {
  'write-permission restriction': [
    { 'login-script': 'closes', 'application-extension': 'closes', 'startup-entry': 'closes' },
    ['branch endpoint', 'aggregator', 'central server'], 8, 4],
  'application allowlist': [
    { 'startup-entry': 'closes', 'service-definition': 'closes', 'scheduled-task': 'closes',
      'media-autorun': 'closes', 'login-script': 'visible' },
    ['branch endpoint', 'aggregator'], 22, 31],
  'signed code requirement': [
    { 'legitimate-binary-attach': 'closes', 'load-path-hook': 'closes', 'application-extension': 'visible' },
    ['field laptop', 'branch endpoint', 'aggregator'], 6, 3],
  'secure boot chain': [
    { 'boot-entry': 'closes' },
    ['field laptop', 'branch endpoint', 'central server'], 0, 2],
  'baseline comparison': [
    { 'startup-entry': 'visible', 'service-definition': 'visible', 'scheduled-task': 'visible',
      'login-script': 'visible', 'load-path-hook': 'visible', 'application-extension': 'visible',
      'media-autorun': 'visible' },
    ['field laptop', 'branch endpoint', 'aggregator', 'central server'], 0, 12],
  'process creation log': [
    { 'startup-entry': 'visible', 'service-definition': 'visible', 'scheduled-task': 'visible',
      'login-script': 'visible' },
    ['field laptop', 'branch endpoint', 'aggregator', 'central server'], 0, 0],
};
export const CONTROL_NAME = Object.keys(CONTROL);
export const applies = (d, s) => CONTROL[d][1].includes(s);
export function status(type, cls) {
  const effects = CONTROL_NAME.filter((d) => applies(d, cls)).map((d) => CONTROL[d][0][type]).filter(Boolean);
  return effects.includes('closes') ? 'closed' : effects.includes('visible') ? 'visible' : 'dark';
}
export const size = (s) => ENDPOINTS[s];
export const TOTAL_ENDPOINTS = Object.values(ENDPOINTS).reduce((a, b) => a + b, 0);
```

```js
// run1.mjs — point type inventory and the types each control closes / makes visible
import { LEGIT, POINT, CONTROL, CONTROL_NAME, CLASS, TOTAL_ENDPOINTS } from './inventory.mjs';

const items = Object.values(LEGIT).reduce((a, b) => a + b, 0);
console.log(`${POINT.length} persistence point types, ${items} legitimate items per endpoint, ${TOTAL_ENDPOINTS} endpoints\n`);
console.log(`${'point type'.padEnd(26)} ${'legitimate items'.padStart(17)} ${'closing controls'.padStart(16)} ${'visible controls'.padStart(16)}`);
for (const t of POINT) {
  const k = CONTROL_NAME.filter((d) => CONTROL[d][0][t] === 'closes'), g = CONTROL_NAME.filter((d) => CONTROL[d][0][t] === 'visible');
  console.log(`${t.padEnd(26)} ${String(LEGIT[t]).padStart(17)} ${String(k.length).padStart(16)} ${String(g.length).padStart(16)}`);
}
console.log(`\n${'control'.padEnd(30)} ${'closes'.padStart(7)} ${'makes visible'.padStart(13)} ${'endpoint classes'.padStart(16)} ` +
  `${'blocked items'.padStart(16)} ${'manual review'.padStart(13)}`);
for (const d of CONTROL_NAME) {
  const e = CONTROL[d][0];
  console.log(`${d.padEnd(30)} ${String(Object.values(e).filter((x) => x === 'closes').length).padStart(7)} ` +
    `${String(Object.values(e).filter((x) => x === 'visible').length).padStart(13)} ` +
    `${`${CONTROL[d][1].length}/${CLASS.length}`.padStart(16)} ${String(CONTROL[d][2]).padStart(16)} ${String(CONTROL[d][3]).padStart(13)}`);
}
const none = POINT.filter((t) => !CONTROL_NAME.some((d) => CONTROL[d][0][t]));
console.log(`\npoint types no control touches: ${none.length}/${POINT.length} (${none.join(', ') || '-'})`);
console.log(`total blocked legitimate items: ${CONTROL_NAME.reduce((a, d) => a + CONTROL[d][2], 0)}; ` +
  `work that falls to manual review: ${CONTROL_NAME.reduce((a, d) => a + CONTROL[d][3], 0)}`);
```

```
10 persistence point types, 161 legitimate items per endpoint, 432 endpoints

point type                  legitimate items closing controls visible controls
startup-entry                             14                2                2
service-definition                        63                1                2
scheduled-task                            41                1                2
login-script                               6                1                3
load-path-hook                             3                1                1
event-subscription                         2                0                0
legitimate-binary-attach                   0                1                0
media-autorun                              1                1                1
application-extension                     27                1                2
boot-entry                                 4                1                0

control                         closes makes visible endpoint classes    blocked items manual review
write-permission restriction         3             0              3/4                8             4
application allowlist                4             1              2/4               22            31
signed code requirement              2             1              3/4                6             3
secure boot chain                    1             0              3/4                0             2
baseline comparison                  0             7              4/4                0            12
process creation log                 0             4              4/4                0             0

point types no control touches: 1/10 (event-subscription)
total blocked legitimate items: 36; work that falls to manual review: 52
```

The lower table separates the two control families with a clean line. The four closing controls
close ten point types in total and block thirty-six legitimate items in return. The two
visibility controls close no type, make eleven types visible, and block not a single legitimate
item. Their costs sit in different places: the closing controls' cost is in blocked items, the
visibility controls' is in work that falls to manual review — and twelve of the total
fifty-two-item manual-review load comes from a single visibility control.

The applicability column supplies the third distinction, and the closing controls are all weak
here. Two controls are applicable in all four endpoint classes, and both of them give
only visibility. None of the closing controls is applicable in all four classes; the
narrowest, application allowlist, is applicable in only two. There is a tension between closing
and spreading: the more a control closes, the fewer endpoints it can be applied in.

Two rows in the upper table also stand out. No control touches the persistent event subscription
— neither a closing one nor a visibility one. One control closes the boot entry, but none makes
it visible: on an endpoint where closing cannot be applied, that type stays entirely dark. The
two most crowded point types, service definition and scheduled task, are each closed by one
control and made visible by two.

## Three Columns by Endpoint Class

```js
// run2.mjs — closed / only-visible / dark point type count per endpoint class
import { POINT, LEGIT, CLASS, CONTROL_NAME, CONTROL, status, size, TOTAL_ENDPOINTS } from './inventory.mjs';

console.log(`${'point type'.padEnd(26)} ${CLASS.map((s) => s.padStart(16)).join(' ')}`);
for (const t of POINT)
  console.log(`${t.padEnd(26)} ${CLASS.map((s) => status(t, s).padStart(16)).join(' ')}`);
console.log(`\n${'endpoint class'.padEnd(15)} ${'endpoints'.padStart(9)} ${'applied controls'.padStart(17)} ${'closed'.padStart(6)} ` +
  `${'only visible'.padStart(14)} ${'dark'.padStart(8)} ${'dark items'.padStart(14)}`);
const count = (s, d) => POINT.filter((t) => status(t, s) === d);
for (const s of CLASS)
  console.log(`${s.padEnd(15)} ${String(size(s)).padStart(9)} ${String(CONTROL_NAME.filter((d) => CONTROL[d][1].includes(s)).length).padStart(17)} ` +
    `${String(count(s, 'closed').length).padStart(6)} ${String(count(s, 'visible').length).padStart(14)} ` +
    `${String(count(s, 'dark').length).padStart(8)} ${String(count(s, 'dark').reduce((a, t) => a + LEGIT[t], 0)).padStart(14)}`);
const weight = (d) => CLASS.reduce((a, s) => a + size(s) * count(s, d).length, 0) / TOTAL_ENDPOINTS;
console.log(`${'fleet average'.padEnd(15)} ${String(TOTAL_ENDPOINTS).padStart(9)} ${''.padStart(17)} ` +
  `${weight('closed').toFixed(1).padStart(6)} ${weight('visible').toFixed(1).padStart(14)} ${weight('dark').toFixed(1).padStart(8)}`);

const everywhere = POINT.filter((t) => CLASS.every((s) => status(t, s) === 'closed'));
const neverClosed = POINT.filter((t) => CLASS.every((s) => status(t, s) !== 'closed'));
console.log(`\npoint types closed in all four endpoint classes: ${everywhere.length}/${POINT.length} (${everywhere.join(', ') || '-'})`);
console.log(`point types closed in no endpoint class        : ${neverClosed.length}/${POINT.length} (${neverClosed.join(', ')})`);
const darkest = CLASS.map((s) => [s, count(s, 'dark').length]).sort((a, b) => b[1] - a[1])[0];
console.log(`endpoint class carrying the most dark point types : ${darkest[0]} (${darkest[1]} types, ${size(darkest[0])} endpoints)`);
```

```
point type                     field laptop  branch endpoint       aggregator   central server
startup-entry                       visible           closed           closed           closed
service-definition                  visible           closed           closed          visible
scheduled-task                      visible           closed           closed          visible
login-script                        visible           closed           closed           closed
load-path-hook                       closed           closed           closed          visible
event-subscription                     dark             dark             dark             dark
legitimate-binary-attach             closed           closed           closed             dark
media-autorun                       visible           closed           closed          visible
application-extension               visible           closed           closed           closed
boot-entry                           closed           closed             dark           closed

endpoint class  endpoints  applied controls closed   only visible     dark     dark items
field laptop          120                 4      3              6        1              2
branch endpoint       240                 6      9              0        1              2
aggregator             60                 5      8              0        2              6
central server         12                 4      4              4        2              2
fleet average         432                      7.1            1.8      1.2

point types closed in all four endpoint classes: 0/10 (-)
point types closed in no endpoint class        : 1/10 (event-subscription)
endpoint class carrying the most dark point types : aggregator (2 types, 60 endpoints)
```

Zero is this table's most important number. None of the ten point types is closed in all four
endpoint classes. The fleet average is seven point one closed, one point eight
only-visible, and one point two dark types; but the average summarizes this table wrongly,
because the distribution splits sharply by class. Nine of the ten types are closed on branch
endpoints, three on field laptops. The same inventory, the same control list, different
applicability.

The field laptop column shows where closing gets stuck. Four controls are applicable, and six
point types stay only visible. On these endpoints, the diversity of legitimate work makes
application allowlist and write-permission restriction inapplicable; in return, these endpoints
log the six types that cannot be closed. The measurement here gives a consequence, not a choice:
where closing cannot be applied, the only thing left is visibility, and on these endpoints
visibility carries all six types.

The dark column, in turn, counts the types that are neither closed nor made visible. In type
count, two classes run neck and neck — aggregator and central server, both with two types — but
the legitimate items left in the dark are highest at the aggregator. Two types are dark at
aggregators: the persistent event subscription and the boot entry. The reason for the second is
hardware — the secure boot chain is not applicable in this class, and no control makes that type
visible. Six legitimate items sit in these two types, and no control looks at them. Central
servers also have two dark types; that looks like a small number for twelve endpoints, but these
twelve endpoints carry the most valuable data the course measures.

## Cost at Fleet Scale

Cost counted per endpoint does not carry a decision; the number that carries the decision is that
cost multiplied by the number of endpoints the control is applied on. The same model can also be
run at fleet scale.

```js
// run3.mjs — fleet-scale cost: covered endpoints, blocked items, and review load per control
import { POINT, LEGIT, CLASS, CONTROL_NAME, CONTROL, status, size, TOTAL_ENDPOINTS } from './inventory.mjs';

console.log(`${'control'.padEnd(30)} ${'covered endpoints'.padStart(17)} ${'blocked items'.padStart(16)} ` +
  `${'review items'.padStart(12)} ${'closed pairs'.padStart(18)}`);
let [blocked, review] = [0, 0];
for (const d of CONTROL_NAME) {
  const n = CONTROL[d][1].reduce((a, s) => a + size(s), 0);
  const closed = Object.entries(CONTROL[d][0]).filter(([, v]) => v === 'closes').length;
  blocked += n * CONTROL[d][2]; review += n * CONTROL[d][3];
  console.log(`${d.padEnd(30)} ${String(n).padStart(17)} ${String(n * CONTROL[d][2]).padStart(16)} ` +
    `${String(n * CONTROL[d][3]).padStart(12)} ${String(n * closed).padStart(18)}`);
}
console.log(`${'total'.padEnd(30)} ${String(TOTAL_ENDPOINTS).padStart(17)} ${String(blocked).padStart(16)} ${String(review).padStart(12)}`);

const pair = (d) => CLASS.reduce((a, s) => a + size(s) * POINT.filter((t) => status(t, s) === d).length, 0);
const itemPair = (d) => CLASS.reduce((a, s) =>
  a + size(s) * POINT.filter((t) => status(t, s) === d).reduce((x, t) => x + LEGIT[t], 0), 0);
console.log(`\n${'column'.padEnd(16)} ${'point-endpoint pairs'.padStart(21)} ${'share'.padStart(6)} ${'legitimate items'.padStart(17)}`);
const totalPairs = TOTAL_ENDPOINTS * POINT.length;
for (const d of ['closed', 'visible', 'dark'])
  console.log(`${d.padEnd(16)} ${String(pair(d)).padStart(21)} ${`${(pair(d) * 100 / totalPairs).toFixed(1)}%`.padStart(6)} ` +
    `${String(itemPair(d)).padStart(17)}`);
console.log(`${'total'.padEnd(16)} ${String(totalPairs).padStart(21)}`);
```

```
control                        covered endpoints    blocked items review items       closed pairs
write-permission restriction                 312             2496         1248                936
application allowlist                        300             6600         9300               1200
signed code requirement                      420             2520         1260                840
secure boot chain                            372                0          744                372
baseline comparison                          432                0         5184                  0
process creation log                         432                0            0                  0
total                                        432            11616        17736

column            point-endpoint pairs  share  legitimate items
closed                            3048  70.6%             48912
visible                            768  17.8%             19536
dark                               504  11.7%              1104
total                             4320
```

The ranking changes at fleet scale. Application allowlist was the control that closed the most
types per endpoint; at fleet scale it is also the most expensive: six thousand six hundred
blocked items and nine thousand three hundred review items. Secure boot chain, by contrast,
closes three hundred seventy-two point-endpoint pairs with zero blocked items. The difference
between the two controls is the legitimate-item density of the point type they close: one
touches the most crowded points on the endpoint, the other touches a point that is nearly empty.

The lower table gives the topic's closing number. Four hundred thirty-two endpoints and ten point
types make four thousand three hundred twenty point-endpoint pairs; of these, seventy point six
percent are closed, seventeen point eight percent are only visible, and eleven point seven
percent are dark. The legitimate items carried by the dark pairs are only eleven hundred four —
meaning the points left in the dark are the sparsest points on the endpoint. This can read as
good news, but the reverse is also true: a sparse point is the easiest point to notice when an
item is added to it — if a record is watching. The definition of the dark column is exactly the
absence of such a record.

## Summary

- 10 persistence point types hold 161 legitimate items per endpoint. 4 closing controls close 10
  types in total and block 36 legitimate items; 2 visibility controls close no type, make 11
  types visible, and block 0 items.
- None of the closing controls is applicable in all 4 of the 4 endpoint classes; the two controls
  applicable in all four classes give only visibility. The more a control closes, the fewer
  endpoints it applies to.
- The number of point types closed in all 4 of the 4 endpoint classes is **0/10**. 9 types are
  closed on branch endpoints, 3 on field laptops; the same inventory, different applicability.
- No control touches the persistent event subscription: neither a closing one nor a visibility
  one. One control closes the boot entry, none makes it visible — on an endpoint where closing
  cannot be applied, it stays entirely dark.
- The dark type count is equally highest in two classes (aggregator and central server, both
  with 2 types); the legitimate items left in the dark, however, are highest at the aggregator —
  6 items, versus 2 at the central server. The fleet average is 7.1 closed, 1.8 only-visible,
  1.2 dark.
- The total cost of closing is 36 blocked legitimate items and 52 manual-review items; of the 52,
  31 come from a single control (application allowlist) and 12 from a single visibility control.

## Next Step

This topic counted the layers of protection one by one across six lessons and wrote down what
each layer misses: that closing persistence points could not stop seven of ten types, that there is
no threshold that zeroes out both signature and behavior errors at once, that telemetry never saw
two of eight points, that the host-based filter could not close legitimate peer-to-peer traffic,
that living-off-the-land tools never showed up for a signature rule, and that no point is closed
in all four endpoint classes at once. Each lesson left a missed set behind, and the
same sentence was written every time: a point that cannot be closed must at least be made
visible.

But visibility rested on an assumption, and that assumption was never tested. When a point was
said to be "logged," no one asked where that record sits, who can read it, how long it stays,
and — most important — what proves it has not been tampered with. Only a record can show that
what was missed was missed; if that record itself can be missed, nothing is left to measure. The
next topic, **System Logs**, starts with this question: which sources hold the records on the
endpoint, which of those sources sit somewhere someone standing at the endpoint can reach, and
with what evidence a set of logs is said to be untampered with.
