---
title: 'Anti-Malware Software'
source: 'https://academia.sh/en/courses/endpoint-security/anti-malware-software'
course: 'Endpoint and System Hardening'
language: en
updated: '2026-09-26T03:02:51+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Runs signature and behavior-based detection over the same event set and extracts the three numbers separately'
    - 'Scans the decision threshold and shows that the missed count and the false positive count move in opposite directions'
    - "Separates the same detection's counterpart in two modes: the foothold point that closes versus the point that is only made visible"
---

# Anti-Malware Software

Twenty execution items are evaluated in the same set with two detection types: the items signature-based detection catches, misses, and falsely flags; the threshold scan of the behavior score derived from six indicators; how the missed count falls and the false positive count rises as the threshold drops; and the foothold point closed in block mode together with the legitimate work it cuts, versus the same point only made visible in alert mode.

The previous lesson could not stop seven of the ten types by closing points, and two of them had no
point to close in the first place. The layer that stops those seven asks a different question: once
something runs on the endpoint, **how is it known** that this item is a problem? There are two
separate answers, and both can be measured over the same execution set.

The first answer relies on recognition: I have seen this item before and flagged it as a problem.
The second relies on behavior: what this item does resembles what problem items do. The first has
no single setting; the second has a threshold, and this lesson scans that threshold end to end.
Three numbers are written together for every run: how many bad items were caught, how many slipped
through, how many legitimate items were flagged by mistake. Detection on the network side was
measured in another course, and the threshold was not modeled there; what is measured here is
exactly the threshold itself.

- **EP7.** Twenty execution items, six indicators, and their weights belong to one field laptop
  endpoint of a fictional regional measurement network. No real item is run; what is measured is the
  model built with `node`.
- **EP8.** The true class of each item is known in the model. In reality this class is only known
  after investigation; it is assumed known here so that the missed count and the false positive
  count can be counted separately.
- **EP9.** The behavior score is the sum of indicator weights, and the weights are fixed. In real
  systems weights are learned and drift over time; a fixed weight simplifies the curve's shape, it
  does not change the opposite direction of the two errors.
- **EP10.** The signature was modeled with three values: exact match, broad match, and no match. A
  broad match means a rule that looks at a packaging format, and it can also touch a legitimate
  item.
- **EP11.** An item either runs or is blocked; partial blocking, quarantine, and rollback were not
  modeled.
- **EP12.** The foothold point column comes from the previous lesson's inventory: it is the number
  of points the item writes to. It has no relation to the score; it is carried as a separate column.

## Score Derived from Indicators

The behavior score is not a number assigned by hand; it is the sum of the weights of six
indicators. The weights say how distinguishing an indicator is: writing to another process's memory
and adding an item to an automatically run point carry the highest weight, running at an unusual
hour the lowest. None of the indicators is a problem on its own; legitimate administration tools do
the same things, and that is where the model's tension comes from.

```js
// events.mjs — execution events on the endpoint, indicator weights, and two detection types; model
export const INDICATOR = {
  'writes to a foothold point': 3, 'writes to another process': 3, 'unsigned binary': 2,
  'bulk file modification': 2, 'sustained external connection': 2, 'runs outside user hours': 1,
};
const B = Object.keys(INDICATOR);
const [N, S, I, D, G, Z] = B;
// [item, true class, signature status, indicators, foothold points written]
export const EVENT = [
  ['network-spreading component', 'bad', 'exact', [N, I, G, Z], 2],
  ['remote-command listener', 'bad', 'none', [N, S, G], 2],
  ['data-encrypting component', 'bad', 'exact', [I, D, G, Z], 0],
  ['keystroke and screen logger', 'bad', 'none', [N, S, Z], 3],
  ['memory-resident loader', 'bad', 'none', [I, G], 0],
  ['living-off-the-land step', 'bad', 'none', [G, Z], 0],
  ['endpoint management agent', 'legit', 'none', [N, S, G], 2],
  ['patch deployment agent', 'legit', 'none', [N, D, Z], 1],
  ['backup tool', 'legit', 'none', [D, G, Z], 0],
  ['inventory scan tool', 'legit', 'none', [N, G], 1],
  ['enterprise installer package', 'legit', 'broad', [N, I], 1],
  ['remote support client', 'legit', 'none', [S, G], 0],
  ['field measurement uploader', 'legit', 'none', [I, G, Z], 0],
  ['compiler output directory', 'legit', 'none', [I, D], 0],
  ['scheduled report script', 'legit', 'none', [N, Z], 1],
  ['log collector', 'legit', 'none', [G, Z], 0],
  ['disk encryption helper', 'legit', 'none', [D, Z], 0],
  ['archiving tool', 'legit', 'none', [D], 0],
  ['browser', 'legit', 'none', [G], 0],
  ['document editor', 'legit', 'none', [], 0],
];
export const score = (o) => o[3].reduce((a, b) => a + INDICATOR[b], 0);
export const MAX_SCORE = Object.values(INDICATOR).reduce((a, b) => a + b, 0);
export const signatureAlerts = (o) => o[2] !== 'none';
export const behaviorAlerts = (o, threshold) => score(o) >= threshold;
export function count(alerts) {
  const bad = EVENT.filter((o) => o[1] === 'bad');
  const caught = bad.filter(alerts), falsePositive = EVENT.filter((o) => o[1] === 'legit' && alerts(o));
  return { caught, missed: bad.filter((o) => !alerts(o)), falsePositive,
    point: caught.reduce((a, o) => a + o[4], 0), legitPoint: falsePositive.reduce((a, o) => a + o[4], 0) };
}
```

## The Signature's Three Numbers

```js
// run1.mjs — event set, indicator-derived behavior score, and signature-based detection's three numbers
import { EVENT, INDICATOR, MAX_SCORE, score, signatureAlerts, count } from './events.mjs';

console.log(`${EVENT.length} execution items, ${EVENT.filter((o) => o[1] === 'bad').length} bad; ` +
  `${Object.keys(INDICATOR).length} indicators, max score ${MAX_SCORE}\n`);
console.log(`${'indicator'.padEnd(30)} ${'weight'.padStart(7)} ${'bad'.padStart(12)} ${'legit'.padStart(13)}`);
for (const [b, a] of Object.entries(INDICATOR)) {
  const g = EVENT.filter((o) => o[3].includes(b));
  console.log(`${b.padEnd(30)} ${String(a).padStart(7)} ${String(g.filter((o) => o[1] === 'bad').length).padStart(12)} ` +
    `${String(g.filter((o) => o[1] === 'legit').length).padStart(13)}`);
}
console.log(`\n${'item'.padEnd(30)} ${'class'.padStart(5)} ${'sig'.padStart(5)} ${'score'.padStart(5)} ${'point'.padStart(5)}`);
for (const o of [...EVENT].sort((a, b) => score(b) - score(a)))
  console.log(`${o[0].padEnd(30)} ${o[1].padStart(5)} ${o[2].padStart(5)} ${String(score(o)).padStart(5)} ${String(o[4]).padStart(5)}`);
const r = count(signatureAlerts);
console.log(`\nsignature-based detection: caught ${r.caught.length}/${r.caught.length + r.missed.length}, ` +
  `missed ${r.missed.length}, false positive ${r.falsePositive.length}`);
console.log(`  missed: ${r.missed.map((o) => o[0]).join('; ')}`);
console.log(`  false positive: ${r.falsePositive.map((o) => `${o[0]} (${o[2]} signature)`).join('; ')}`);
```

```
20 execution items, 6 bad; 6 indicators, max score 13

indicator                       weight          bad         legit
writes to a foothold point           3            3             5
writes to another process            3            2             2
unsigned binary                      2            3             3
bulk file modification               2            1             5
sustained external connection        2            5             7
runs outside user hours              1            4             6

item                           class   sig score point
network-spreading component      bad exact     8     2
remote-command listener          bad  none     8     2
endpoint management agent      legit  none     8     2
data-encrypting component        bad exact     7     0
keystroke and screen logger      bad  none     7     3
patch deployment agent         legit  none     6     1
backup tool                    legit  none     5     0
inventory scan tool            legit  none     5     1
enterprise installer package   legit broad     5     1
remote support client          legit  none     5     0
field measurement uploader     legit  none     5     0
memory-resident loader           bad  none     4     0
compiler output directory      legit  none     4     0
scheduled report script        legit  none     4     1
living-off-the-land step         bad  none     3     0
log collector                  legit  none     3     0
disk encryption helper         legit  none     3     0
archiving tool                 legit  none     2     0
browser                        legit  none     2     0
document editor                legit  none     0     0

signature-based detection: caught 2/6, missed 4, false positive 1
  missed: remote-command listener; keystroke and screen logger; memory-resident loader; living-off-the-land step
  false positive: enterprise installer package (broad signature)
```

The indicator table gives the first result, and it is not encouraging at all: four of the six
indicators appear **more** often in legitimate items than in bad items, and two appear in exactly
the same number; none stands out on the bad side. Sustained external connection appears in five bad
and seven legitimate items; writes to a foothold point in three bad and five legitimate items. This
is not a modeling flaw but a fact of endpoints: the number of legitimate programs running on an
endpoint is far larger than the number of bad items, so no indicator stands out on the bad side by
raw count.

The sorted list shows the result of this in a single line. One of the three highest-scoring items is
legitimate and sits at the **same** place as two bad items, with a score of eight: the endpoint
management agent writes to automatically run points, injects code into other processes, and holds a
sustained outbound connection. No behavior-based threshold can separate this item from the other
two, because there is no number to separate them with.

Signature-based detection's three numbers are at the bottom of the list. It catches two of the six
bad items, misses four, and flags one legitimate item. What the missed items have in common is that
they were never seen before; the legitimate item it flags happens to touch a broad signature. Zero
false positives is the frequently cited advantage of signature-based detection, and it is not true
even in the model: a single broadly written rule ends that advantage.

## Moving the Threshold

```js
// run2.mjs — behavior threshold scan; the opposite direction of the two errors and the cost of combined detection
import { EVENT, score, signatureAlerts, behaviorAlerts, count } from './events.mjs';

const BAD = EVENT.filter((o) => o[1] === 'bad').length;
console.log(`${'threshold'.padStart(9)} ${'caught'.padStart(9)} ${'missed'.padStart(6)} ${'false'.padStart(6)} ` +
  `${'comb. caught'.padStart(13)} ${'combined false'.padStart(15)} ${'total false'.padStart(13)}`);
const result = [];
for (let e = 1; e <= 10; e++) {
  const d = count((o) => behaviorAlerts(o, e));
  const b = count((o) => signatureAlerts(o) || behaviorAlerts(o, e));
  result.push({ e, d, b });
  console.log(`${String(e).padStart(9)} ${`${d.caught.length}/${BAD}`.padStart(9)} ${String(d.missed.length).padStart(6)} ` +
    `${String(d.falsePositive.length).padStart(6)} ${`${b.caught.length}/${BAD}`.padStart(13)} ${String(b.falsePositive.length).padStart(15)} ` +
    `${String(b.missed.length + b.falsePositive.length).padStart(13)}`);
}
const min = result.reduce((a, s) => (s.b.missed.length + s.b.falsePositive.length < a.b.missed.length + a.b.falsePositive.length ? s : a));
const full = result.filter((s) => s.b.missed.length === 0);
console.log(`\nthreshold minimizing total false: ${min.e} (missed ${min.b.missed.length} + false ${min.b.falsePositive.length})`);
console.log(`thresholds that never miss: ${full.map((s) => `${s.e} (false ${s.b.falsePositive.length})`).join(', ')}`);
console.log(`zero-false-positive threshold: ${result.filter((s) => s.b.falsePositive.length === 0).map((s) => `${s.e} (missed ${s.b.missed.length})`).join(', ') || 'none'}`);

console.log(`\n${'threshold'.padStart(9)} ${'closed points'.padStart(13)} ${'legit work lost'.padStart(16)} ` +
  `${'seen points'.padStart(13)} ${'work lost'.padStart(10)} ${'manual review alerts'.padStart(20)}`);
for (const { e, b } of result.filter((s) => [3, 6, 8, 9].includes(s.e)))
  console.log(`${String(e).padStart(9)} ${String(b.point).padStart(13)} ${String(b.falsePositive.length).padStart(16)} ` +
    `${String(b.point).padStart(13)} ${'0'.padStart(10)} ${String(b.caught.length + b.falsePositive.length).padStart(20)}`);
const six = count((o) => signatureAlerts(o) || behaviorAlerts(o, 6));
console.log(`\nthreshold 6 missed bad items: ${six.missed.map((o) => `${o[0]} (score ${score(o)})`).join('; ')}`);
console.log(`threshold 6 legit work lost : ${six.falsePositive.map((o) => `${o[0]} (score ${score(o)})`).join('; ')}`);
```

```
threshold    caught missed  false  comb. caught  combined false   total false
        1       6/6      0     13           6/6              13            13
        2       6/6      0     13           6/6              13            13
        3       6/6      0     11           6/6              11            11
        4       5/6      1      9           5/6               9            10
        5       4/6      2      7           4/6               7             9
        6       4/6      2      2           4/6               3             5
        7       4/6      2      1           4/6               2             4
        8       2/6      4      1           3/6               2             5
        9       0/6      6      0           2/6               1             5
       10       0/6      6      0           2/6               1             5

threshold minimizing total false: 7 (missed 2 + false 2)
thresholds that never miss: 1 (false 13), 2 (false 13), 3 (false 11)
zero-false-positive threshold: none

threshold closed points  legit work lost   seen points  work lost manual review alerts
        3             7               11             7          0                   17
        6             7                3             7          0                    7
        8             4                2             4          0                    5
        9             2                1             2          0                    3

threshold 6 missed bad items: memory-resident loader (score 4); living-off-the-land step (score 3)
threshold 6 legit work lost : endpoint management agent (score 8); patch deployment agent (score 6); enterprise installer package (score 5)
```

The scan gives the opposite direction of two columns row by row. At threshold three, all six of the
six bad items are caught, and eleven legitimate items are flagged along with them. At threshold
nine, the false positive count drops to zero and behavior-based detection catches no bad items at
all. Every row in between is a trade-off, and the trade-off ratio is not constant: going from five
to six drops the false positive count from seven to two while never changing the caught count — this
is the steepest point of the curve. Going from seven to eight, by contrast, drops the caught count
from four to two without lowering the false positive count at all.

Combining signature with behavior is read in the difference between the third and fourth columns.
There is a gain at high thresholds: at threshold nine, behavior catches nothing while combined
detection catches two items. At low thresholds the signature contributes nothing, it only raises the
false positive count by one. The two detection types complement each other for this reason: one
works on the known, the other on the unknown, and as the known set shrinks the load the threshold
carries grows.

The bottom table separates the course's two columns. At threshold six, four bad items are caught
and, in block mode, seven foothold points close; in exchange three legitimate jobs are cut, and
among those cut are the patch deployment agent and the endpoint management agent — the very
mechanisms that protect the endpoints. In alert mode the same seven points are only made
**visible**, no legitimate job is cut, and in exchange seven items wait for manual review. The
closed-point count and the visible-point count are numerically the same; what makes the difference
is that in one the point disappears, in the other it is only written to a record.

Lowering the threshold to three shows a measurable trap in this table. The caught bad item count
rises from four to six, but the closed foothold point count stays at seven, because the two newly
added items have no persistence point. Cut legitimate work rises from three to eleven. A setting
that decides by caught item count counts this as an improvement; a setting that decides by foothold
point sees no gain at all.

The scores of the two missed items open a separate question when looked at from the bottom of the
list. The memory-resident loader sits below three legitimate items with a score of four; the
living-off-the-land step sits at the same place as two legitimate items with a score of three. A
threshold that would catch both also catches every legitimate item with a higher score; this is not
a problem that a threshold setting can solve. The item with the lower score is not doing less work —
it is doing work that **leaves fewer indicators**, because every tool it uses is already standing on
the endpoint and is signed. This distinction will be measured by number not in the next lesson but
in the fifth.

## Summary

- 4 of the 6 indicators appear more often in legitimate items than in bad items, and 2 appear in
  exactly the same number. Since the count of legitimate software on an endpoint is far larger, no
  indicator stands out on the bad side by raw count.
- One of the 3 highest-scoring items is legitimate and shares the same score (8) as 2 bad items: the
  endpoint management agent. No behavior threshold can separate these three from each other.
- Signature-based detection catches 2 of the 6 bad items, misses 4, and flags 1 legitimate item
  because of a single broadly written rule. "Signatures produce no false positives" is wrong even in
  the model.
- At threshold 3, 6/6 are caught and 11 legitimate items are cut; at threshold 9, false positives
  are zero and behavior catches nothing. The threshold that minimizes total error is 7 (2 missed +
  2 false); no threshold zeroes both errors at once.
- At threshold 6, 7 foothold points close and 3 legitimate jobs are cut, including the patch
  deployment agent and the endpoint management agent. In alert mode the same 7 points are only made
  visible, cut work is 0, manual review alerts are 7.
- Lowering the threshold from 6 to 3 raises the caught count from 4 to 6 but leaves the closed point
  count at 7: the two newly caught items have no persistence point. Cut legitimate work rises from
  3 to 11.

## Next Step

Alert mode was this lesson's cheapest option: no legitimate job was cut. Its cost looked at the time
like a queue of seven items, but its real cost lies elsewhere. For an item to be merely visible,
someone must have **recorded** it, and not everything on the endpoint is recorded. That was also
what the two items missed at threshold six had in common: neither of them wrote anything to disk.
The next lesson's question is this: how many of the foothold points does endpoint telemetry see, how
many never appear in any source at all, and how much does the volume of collected data shorten the
retention window.
