---
title: 'Living Off the Land'
source: 'https://academia.sh/en/courses/endpoint-security/living-off-the-land'
course: 'Endpoint and System Hardening'
language: en
updated: '2026-09-26T03:02:51+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Counts the number of built-in tools carrying the same capability class and shows the option left behind by blocking one tool'
    - 'Measures that the signature-based rule set flags zero items in built-in, signed tool runs'
    - 'Evaluates three context dimensions separately and together: counts the separated suspicious run and the false positive together'
---

# Living Off the Land

The overlap of fourteen built-in, signed tools across seven capability classes is counted: an average of 3.4 tools carrying the same capability, three signature rules flagging zero of twenty-four runs, and three context dimensions — parent process, command pattern, timing — evaluated singly, combined, and intersected; five of six suspicious runs are separated out, one is identical in four dimensions to a legitimate run.

The previous lesson's allowlist closed the paths it could close and named the paths it could not:
the remote support flow could not be removed. The tool that carries out that flow sits on the
endpoint, signed and recorded in the inventory's baseline. This lesson counts what happens on the
detection side when that tool — and thirteen others like it — is run with malicious intent.

Three things are measured. The first is overlap: how many separate built-in tools can do the same
job. The second is how many items the signature-based rule flags among these runs. The third is how
many suspicious runs the behavioral context — parent process, command pattern, and timing — can
separate, and how many legitimate runs it flags in return. No command, parameter, or step sequence
is written in this lesson; the runs are modeled at the level of type and count. Trying these
techniques on a system without written authorization and a defined scope is both illegal and
unprofessional.

- **EP25.** Fourteen tools, seven capability classes, and twenty-four runs belong to one endpoint of
  the fictional regional measurement network. What is measured is the model built with `node`.
- **EP26.** All of the tools are built-in, validly signed, and recorded in the baseline. This is the
  lesson's definition, not its assumption: a tool not in the inventory was already measured in
  another lesson.
- **EP27.** The true class of each run is known in the model. In reality it is known only after
  investigation; it is assumed known here so that the separated and the false positive can be
  counted separately.
- **EP28.** Context was modeled with three discrete dimensions. In reality each dimension has a
  continuous value and needs a threshold; the discrete value shrinks the rule count, it does not
  change the direction of the combinations.
- **EP29.** The command pattern classes are a definition of **form**, not content; no example
  command was written.
- **EP30.** A run either matches a rule or it does not; frequency, recurrence, and correlation with
  other runs on the same endpoint were not modeled. All three increase separating power, and all
  three need more data.

## How Many Tools Can Do the Same Job

```js
// tools.mjs — built-in and signed tool inventory on the endpoint, capabilities, and run record; model
// tool type -> capability classes it carries (all assumed built-in and signed)
export const TOOL = {
  'script interpreter': ['code interpretation', 'process launch', 'network data fetch'],
  'shell interpreter': ['code interpretation', 'process launch'],
  'file transfer tool': ['network data fetch'],
  'package manager': ['network data fetch', 'process launch'],
  'installer engine': ['network data fetch', 'process launch'],
  'certificate helper': ['network data fetch'],
  'remote management client': ['remote connection', 'process launch'],
  'management script interface': ['remote connection', 'process launch', 'code interpretation'],
  'archiver': ['archiving'],
  'backup helper': ['archiving', 'scheduling'],
  'scheduler interface': ['scheduling', 'process launch'],
  'system information tool': ['inventory read'],
  'configuration query tool': ['inventory read'],
  'log query tool': ['inventory read'],
};
export const NAME = Object.keys(TOOL);
export const CAPABILITY = [...new Set(Object.values(TOOL).flat())];
export const carries = (y) => NAME.filter((a) => TOOL[a].includes(y));
// signature-based rule set: each rule looks at one field of a run
export const SIGNATURE_RULE = [
  ['unknown file identity', (k) => k.identity === 'unknown'],
  ['unsigned binary', (k) => k.signature === 'none'],
  ['item not in baseline', (k) => k.baseline === false],
];
// run: [tool, parent process class, command pattern, time slot, true class]
export const RUN = [
  ['script interpreter', 'scheduler', 'ordinary', 'maintenance window', 'legitimate'],
  ['script interpreter', 'management agent', 'ordinary', 'business hours', 'legitimate'],
  ['shell interpreter', 'scheduler', 'ordinary', 'off hours', 'legitimate'],
  ['archiver', 'scheduler', 'ordinary', 'maintenance window', 'legitimate'],
  ['file transfer tool', 'management agent', 'ordinary', 'business hours', 'legitimate'],
  ['file transfer tool', 'scheduler', 'externally sourced parameter', 'maintenance window', 'legitimate'],
  ['remote management client', 'user session', 'ordinary', 'business hours', 'legitimate'],
  ['remote management client', 'remote session', 'ordinary', 'business hours', 'legitimate'],
  ['scheduler interface', 'management agent', 'ordinary', 'business hours', 'legitimate'],
  ['certificate helper', 'service', 'ordinary', 'business hours', 'legitimate'],
  ['system information tool', 'management agent', 'ordinary', 'business hours', 'legitimate'],
  ['configuration query tool', 'user session', 'ordinary', 'business hours', 'legitimate'],
  ['log query tool', 'management agent', 'long single line', 'off hours', 'legitimate'],
  ['backup helper', 'scheduler', 'ordinary', 'off hours', 'legitimate'],
  ['installer engine', 'user session', 'ordinary', 'business hours', 'legitimate'],
  ['package manager', 'management agent', 'ordinary', 'maintenance window', 'legitimate'],
  ['management script interface', 'service', 'ordinary', 'business hours', 'legitimate'],
  ['shell interpreter', 'document application', 'ordinary', 'business hours', 'legitimate'],
  ['script interpreter', 'document application', 'encoded input', 'business hours', 'suspicious'],
  ['shell interpreter', 'remote session', 'long single line', 'off hours', 'suspicious'],
  ['file transfer tool', 'user session', 'externally sourced parameter', 'off hours', 'suspicious'],
  ['archiver', 'service', 'ordinary', 'off hours', 'suspicious'],
  ['remote management client', 'user session', 'ordinary', 'business hours', 'suspicious'],
  ['scheduler interface', 'remote session', 'ordinary', 'business hours', 'suspicious'],
];
export const record = (k) => ({ tool: k[0], identity: 'known', signature: 'valid', baseline: true });
export const CONTEXT = [
  ['parent process', (k) => ['document application', 'remote session'].includes(k[1])],
  ['command pattern', (k) => ['encoded input', 'externally sourced parameter'].includes(k[2])],
  ['timing', (k) => k[3] === 'off hours' && k[1] !== 'scheduler'],
];
```

```js
// run1.mjs — tool count carrying the same capability, and items the signature-based rule set flags
import { TOOL, NAME, CAPABILITY, RUN, SIGNATURE_RULE, carries, record } from './tools.mjs';

console.log(`${NAME.length} built-in tools, ${CAPABILITY.length} capability classes, ${RUN.length} run records\n`);
console.log(`${'capability class'.padEnd(24)} ${'tools'.padStart(12)} ${'left if one blocked'.padStart(23)}`);
for (const y of CAPABILITY)
  console.log(`${y.padEnd(24)} ${String(carries(y).length).padStart(12)} ${String(carries(y).length - 1).padStart(23)}`);
const many = CAPABILITY.filter((y) => carries(y).length > 1);
console.log(`\ncapabilities tied to one tool: ${CAPABILITY.length - many.length}/${CAPABILITY.length}`);
console.log(`capabilities with multiple tools: ${many.length}/${CAPABILITY.length}, average ` +
  `${(many.reduce((a, y) => a + carries(y).length, 0) / many.length).toFixed(1)} tools`);

const suspicious = RUN.filter((k) => k[4] === 'suspicious');
console.log(`\n${'signature rule'.padEnd(26)} ${'flagged runs'.padStart(18)} ${'of suspicious'.padStart(16)}`);
for (const [name, f] of SIGNATURE_RULE) {
  const flagged = RUN.filter((k) => f(record(k)));
  console.log(`${name.padEnd(26)} ${String(flagged.length).padStart(18)} ` +
    `${`${flagged.filter((k) => k[4] === 'suspicious').length}/${suspicious.length}`.padStart(16)}`);
}
console.log(`\ntool count used by suspicious runs: ${new Set(suspicious.map((k) => k[0])).size}`);
console.log(`how many of these tools have a valid signature and a baseline record: ` +
  `${[...new Set(suspicious.map((k) => k[0]))].filter((a) => NAME.includes(a)).length}/${new Set(suspicious.map((k) => k[0])).size}`);
```

```
14 built-in tools, 7 capability classes, 24 run records

capability class                tools     left if one blocked
code interpretation                 3                       2
process launch                      7                       6
network data fetch                  5                       4
remote connection                   2                       1
archiving                           2                       1
scheduling                          2                       1
inventory read                      3                       2

capabilities tied to one tool: 0/7
capabilities with multiple tools: 7/7, average 3.4 tools

signature rule                   flagged runs    of suspicious
unknown file identity                       0              0/6
unsigned binary                             0              0/6
item not in baseline                        0              0/6

tool count used by suspicious runs: 6
how many of these tools have a valid signature and a baseline record: 6/6
```

The first table gives, in numbers, why one defensive move does not work. None of the seven
capability classes is tied to a single tool; an average of 3.4 tools carry the same capability. Seven
tools can launch a process; five can fetch data from the network. Removing a tool from the
application allowlist does not remove the capability, it only reduces the option count by one — and
if the removed tool is used for legitimate work, it stops that work too. This is the measure of the
overlap: blocking has a gain of one and a cost of one.

The second table gives the result the lesson is named for. Three signature-based rules flag zero of
the twenty-four runs and catch zero of the six suspicious runs. This is not because the rules are
badly written, but because the field they look at carries the same value in every one of these runs:
file identity known, signature valid, item recorded in the baseline. All six of the tools used by the
suspicious runs pass all three criteria. Signature-based detection does not give a wrong answer; it
**gives no answer at all**, because the question it asks is not discriminating here.

## Three Context Dimensions

A question that could be discriminating looks not at the tool but at the run: what launched this
tool, how was it invoked, when did it run. All three dimensions can already be recorded on the
endpoint; the separating power of all three is measured separately and together.

```js
// run2.mjs — three context dimensions singly, combined, and intersected; the run that cannot be separated
import { RUN, CONTEXT } from './tools.mjs';

const S = RUN.filter((k) => k[4] === 'suspicious'), M = RUN.filter((k) => k[4] === 'legitimate');
const matches = (k) => CONTEXT.filter(([, f]) => f(k)).map(([a]) => a);
const measure = (rule) => [S.filter(rule).length, M.filter(rule).length];
console.log(`${'context set'.padEnd(44)} ${'separated suspicious'.padStart(21)} ${'false positive'.padStart(14)}`);
for (let m = 1; m < 8; m++) {
  const chosen = CONTEXT.filter((_, i) => m & (1 << i));
  const [a, y] = measure((k) => chosen.some(([, f]) => f(k)));
  console.log(`${chosen.map(([name]) => name).join(' or ').padEnd(44)} ${`${a}/${S.length}`.padStart(21)} ${`${y}/${M.length}`.padStart(14)}`);
}
for (const n of [2, 3]) {
  const [a, y] = measure((k) => matches(k).length >= n);
  console.log(`${`at least ${n} dimensions at once`.padEnd(44)} ${`${a}/${S.length}`.padStart(21)} ${`${y}/${M.length}`.padStart(14)}`);
}

console.log(`\n${'run'.padEnd(30)} ${'parent process'.padStart(22)} ${'pattern'.padStart(29)} ${'time'.padStart(15)}  matching dimensions`);
for (const k of S)
  console.log(`${k[0].padEnd(30)} ${k[1].padStart(22)} ${k[2].padStart(29)} ${k[3].padStart(15)}  ${matches(k).join(', ') || '-'}`);

const key = (k) => k.slice(0, 4).join('|');
const twin = S.filter((s) => M.some((m) => key(m) === key(s)));
console.log(`\nsuspicious runs identical to a legitimate run in four dimensions: ${twin.length}/${S.length}`);
for (const s of twin) console.log(`  ${s[0]} / ${s[1]} / ${s[2]} / ${s[3]}`);
console.log(`suspicious runs matching no context dimension: ${S.filter((k) => matches(k).length === 0).length}/${S.length}`);
```

```
context set                                   separated suspicious false positive
parent process                                                 3/6           2/18
command pattern                                                2/6           1/18
parent process or command pattern                              4/6           3/18
timing                                                         3/6           1/18
parent process or timing                                       5/6           3/18
command pattern or timing                                      4/6           2/18
parent process or command pattern or timing                    5/6           4/18
at least 2 dimensions at once                                  3/6           0/18
at least 3 dimensions at once                                  0/6           0/18

run                                    parent process                       pattern            time  matching dimensions
script interpreter               document application                 encoded input  business hours  parent process, command pattern
shell interpreter                      remote session              long single line       off hours  parent process, timing
file transfer tool                       user session  externally sourced parameter       off hours  command pattern, timing
archiver                                      service                      ordinary       off hours  timing
remote management client                 user session                      ordinary  business hours  -
scheduler interface                    remote session                      ordinary  business hours  parent process

suspicious runs identical to a legitimate run in four dimensions: 1/6
  remote management client / user session / ordinary / business hours
suspicious runs matching no context dimension: 1/6
```

Single dimensions are weak: the best one separates three of the six suspicious runs. Combinations do
better, but they do not add up cleanly. Parent process together with timing separates five of them
and flags three legitimate runs. Adding the third dimension — command pattern — **does not change
the separated count at all** and raises the false positives from three to four. Command pattern
alone separated two runs; both were runs the other two dimensions already caught. If adding a layer
closes zero additional paths, that layer counts as unmeasured, and this rule holds on the detection
side here too.

The intersection rows show the gain in the opposite direction. When at least two dimensions are
required to match at once, the separated count drops to three, but the false positives go to zero:
none of the eighteen legitimate runs match two dimensions at once. This cuts the number of alerts to
review to three, and all three of them are real. The choice here has the same shape as in earlier
lessons — five separated and four wrong, or three separated and zero wrong.

The list below names the run that cannot be separated, and the reason is measurable: a remote
management client running from a user session with an ordinary pattern during business hours is
identical, in four dimensions, to a legitimate run in the list. There is no field left to separate it
by. The only way to separate this run in the model is to add a fourth dimension — the endpoint it
connects to, the volume of data carried, the other work the same user did that day — and each fourth
dimension means a new telemetry source, which means a new cut into the window measured in the third
lesson.

## Blocking's Two Columns

Blocking instead of separating is also an option: removing the tools that appear in suspicious runs
from the application allowlist. The gain and the cost of this move can be counted on the same model.

```js
// run3.mjs — the gain and cost of blocking a tool: stopped runs and the remaining alternative tool
import { TOOL, RUN, carries } from './tools.mjs';

const used = [...new Set(RUN.map((k) => k[0]))];
console.log(`${'blocked tool'.padEnd(30)} ${'stopped legit'.padStart(13)} ${'stopped suspicious'.padStart(19)} ` +
  `${'other tool, same capability'.padStart(27)}`);
let totalLegit = 0, totalSuspicious = 0;
for (const a of used) {
  const k = RUN.filter((x) => x[0] === a);
  const [m, s] = [k.filter((x) => x[4] === 'legitimate').length, k.filter((x) => x[4] === 'suspicious').length];
  const alternative = new Set(TOOL[a].flatMap((y) => carries(y)).filter((x) => x !== a));
  if (s > 0) { totalLegit += m; totalSuspicious += s; }
  console.log(`${a.padEnd(30)} ${String(m).padStart(13)} ${String(s).padStart(19)} ${String(alternative.size).padStart(27)}`);
}
const suspiciousTools = used.filter((a) => RUN.some((x) => x[0] === a && x[4] === 'suspicious'));
const transferable = suspiciousTools.filter((a) => TOOL[a].some((y) => carries(y).length > 1));
console.log(`\ntools used by suspicious runs: ${suspiciousTools.length}`);
console.log(`cost of blocking all of them: ${totalLegit} legitimate runs stop, ${totalSuspicious} suspicious runs stop`);
console.log(`of these tools, how many can hand their capability to another tool: ${transferable.length}/${suspiciousTools.length}`);
```

```
blocked tool                   stopped legit  stopped suspicious other tool, same capability
script interpreter                         2                   1                           8
shell interpreter                          2                   1                           6
archiver                                   1                   1                           1
file transfer tool                         2                   1                           4
remote management client                   2                   1                           6
scheduler interface                        1                   1                           7
certificate helper                         1                   0                           4
system information tool                    1                   0                           2
configuration query tool                   1                   0                           2
log query tool                             1                   0                           2
backup helper                              1                   0                           2
installer engine                           1                   0                           8
package manager                            1                   0                           8
management script interface                1                   0                           6

tools used by suspicious runs: 6
cost of blocking all of them: 10 legitimate runs stop, 6 suspicious runs stop
of these tools, how many can hand their capability to another tool: 6/6
```

Blocking all six tools stops six suspicious runs and stops ten legitimate runs along with them: the
ratio is greater than one, meaning more than one legitimate job is cut for every run gained. The
column on the right, however, shows that the gain is not lasting. Other tools carrying the
capabilities of every blocked tool still sit on the endpoint — eight for the script interpreter,
seven for the scheduler interface, one in the narrowest case, the archiver. All six of the six tools
can hand their capability to another tool.

Reading this table separates the course's two columns once more. Blocking really does close
something here, but what it closes is the **tool**, not the capability; as long as the capability
stays in the inventory, no path is closed. Separating, on the other hand, closes nothing — it only
makes visible — and for the five suspicious runs it makes visible, it also flags four legitimate
runs.

## Summary

- None of the 7 capability classes is tied to a single tool; an average of 3.4 built-in tools carry
  the same capability. 7 tools can launch a process. Blocking a tool does not remove the capability,
  it reduces the option by one and stops a legitimate job.
- 3 signature-based rules flag 0 of the 24 runs. All 6 of the tools used by suspicious runs have a
  known identity, a valid signature, and a baseline record; the rule does not give a wrong answer, it
  gives no answer.
- The best single context dimension separates 3 of 6 suspicious runs. Parent process together with
  timing separates 5 of them and produces 3 false positives.
- Adding a third dimension raises false positives from 3 to 4 without changing the separated count: a
  layer that closes zero paths counts as unmeasured here too.
- When at least 2 dimensions are required to match at once, the separated count drops to 3 and the
  false positives go to 0. None of the 18 legitimate runs match two dimensions at once.
- 1 suspicious run is identical to a legitimate run in four dimensions and matches no context
  dimension. Separating it requires a fourth dimension — a new telemetry source.

## Next Step

This lesson gave, in numbers, the limit of separating the misuse of a tool, but what it measured
were individual runs. A run is transient: it ends and leaves only a record behind. What persists is
the item that run writes on the endpoint — the foothold points counted in the first lesson. The topic
therefore returns to where it began. The next lesson's question is this: how many items are the
persistence point types on the endpoint as an inventory, how many of them do five separate audits
close, how many do they only log, and how many points remain that no audit either closes or sees.
