---
title: 'Service and Version Detection'
source: 'https://academia.sh/en/courses/penetration-testing/service-and-version-detection'
course: 'Security Tooling and Penetration Testing'
language: en
updated: '2026-09-26T03:02:55+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Measures fingerprinting in terms of the candidate version set and precision'
    - 'Counts the false positives a misleading banner string carries into the finding list and the lines it misses'
    - 'Compares the trace and alert cost of increasing precision, per probe level'
---

# Service and Version Detection

Measuring fingerprinting in terms of precision: at the first probe level, 13 of 21 services narrowing to a single version but 4 of them being assigned the wrong version, the evidence rate rising from 42.9% to 76.2% while the trace climbs from 42 records to 168 records and 6 alerts, the confirmed count dropping from 14 to 11 at the third level while accuracy is completed, and incorrect version information carrying 9 wrong lines into the finding list while missing 10 lines.

The previous lesson definitively distinguished that a port is open: 21 open ports were marked at
the evidence level. A port being open, though, does not say what is running there. For now, each of
the twenty-one ports is only a location; what software sits behind it, which version it is
running, and whether that version is associated with a known vulnerability have not been asked
at all.

The step that asks this question is **fingerprinting** and it brings a new measure: **precision**.
The signatures collected from an endpoint point to a version set. The set sometimes has a single
member, sometimes contains dozens of versions — and sometimes has a single member but carries the
wrong one. The third case is this lesson's main subject, because a wrong answer that looks certain
is more expensive than a correct answer that stays ambiguous.

## Precision Is the Size of the Candidate Set

**RE13:** every service carries three things — the fictional true version, the banner string's
status (correct, misleading, none), and the size of the candidate version set per probe level.
**RE14:** a service is **confirmed** after N probes (candidate set has a single member); a service
with a misleading banner reaches the true version only at the third level, and is confirmed but
assigned the wrong one at earlier levels. **RE15:** the trace comes from the probe level — N
requests per service, each request a network and an application record; from the third level on,
unusual probes produce alerts.

```js
// recon/version.mjs — fictional fingerprinting results for 04's 21 evidence-level open ports
// (a model): no real product, no real version, and no real vulnerability identifier; the version
// labels are fictional too.

// RE13: every service carries three things — the fictional true version, the banner string's
// status (correct, misleading, none), and the candidate version set size for probe levels 1..4.
// For a service with a misleading banner, "wrongVersion" is the version the string points to.
export const SERVICE = `
  subscriber/n1      A4 correct    -   1 1 1 1
  subscriber/n2      B2 correct    -   2 1 1 1
  subscriber/n3      C7 none       -  12 5 2 1
  field/n1           A4 correct    -   1 1 1 1
  field/n5           D1 misleading D0   1 1 3 1
  field/n6           E3 correct    -   1 1 1 1
  field/b1           F2 none       -   9 4 2 2
  vpn/n6             E3 correct    -   1 1 1 1
  collector/n6       E3 correct    -   1 1 1 1
  collector/n7       G5 none       -  14 6 3 2
  collector/n8       H1 misleading H9   1 1 4 2
  collector/b1       F2 none       -   9 4 2 2
  report/n1          A4 correct    -   1 1 1 1
  report/n6          E3 correct    -   1 1 1 1
  shared/n1          A4 correct    -   2 1 1 1
  shared/n3          C7 none       -  12 5 2 1
  monitoring/n1      A4 correct    -   1 1 1 1
  monitoring/n7      I2 misleading I5   1 2 3 1
  monitoring/b1      F2 none       -   9 4 2 2
  log/n1             A4 correct    -   1 1 1 1
  log/n5             J8 misleading J1   1 1 2 1`
  .trim().split("\n").map((r) => {
    const [id, trueVersion, banner, wrongVersion, ...d] = r.trim().split(/\s+/);
    return { id, trueVersion, banner, wrongVersion, set: d.map(Number) };
  });

// Fictional version-to-finding mapping: how many lines a version label carries into the finding list.
export const FINDINGS = { A4: 1, B2: 0, C7: 2, D1: 3, E3: 2, F2: 1, G5: 2, H1: 4, I2: 1, J8: 2,
  D0: 0, H9: 1, I5: 3, J1: 5 };

// RE14: a service is confirmed after N probes (candidate set has a single member). A service with
// a misleading banner reaches the true version only at the third level; at earlier levels it is
// confirmed but assigned the wrong one.
export const confirmed = (s, n) => s.set[n - 1] === 1;
export const correct = (s, n) => (s.banner === "misleading" ? n >= 3 : true);
export const assigned = (s, n) => (correct(s, n) ? s.trueVersion : s.wrongVersion);

// RE15: the trace comes from the probe level — N requests per service, each request a network and
// an application record; from the third level on, unusual probes produce alerts.
export const trace = (n) => ({ requests: SERVICE.length * n, records: SERVICE.length * n * 2,
  alerts: n >= 3 ? (n - 2) * 3 : 0 });
```

```js
// recon/precision.mjs — fingerprinting in terms of precision: how many candidate versions narrow
// down to a single version.
import { SERVICE, FINDINGS, confirmed, correct, assigned, trace } from "./version.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 LEVEL = [1, 2, 3, 4];
const K = (n) => SERVICE.filter((s) => confirmed(s, n));

console.log(`${SERVICE.length} services: ${SERVICE.filter((s) => s.banner === "correct").length} ` +
  `with correct banner, ${SERVICE.filter((s) => s.banner === "misleading").length} misleading, ` +
  `${SERVICE.filter((s) => s.banner === "none").length} bannerless`);

const A = [-8, 8, 8, 10, 9, 8, 11];
console.log("\n1. precision per probe level");
write(A, "level", "requests", "conf", "ambiguous", "falseP", "evid", "prec %");
for (const n of LEVEL) {
  const k = K(n), fp = k.filter((s) => !correct(s, n)).length;
  write(A, n, trace(n).requests, k.length, SERVICE.length - k.length, fp, k.length - fp,
    (((k.length - fp) / SERVICE.length) * 100).toFixed(1));
}

const B = [-8, 8, 8, 12, 12, 15];
console.log("\n2. the trace cost of precision");
write(B, "level", "records", "alerts", "rec/evid", "alert/evid", "avg set size");
for (const n of LEVEL) {
  const k = K(n), evid = k.length - k.filter((s) => !correct(s, n)).length;
  const avg = SERVICE.reduce((t, s) => t + s.set[n - 1], 0) / SERVICE.length;
  write(B, n, trace(n).records, trace(n).alerts, (trace(n).records / evid).toFixed(2),
    (trace(n).alerts / evid).toFixed(3), avg.toFixed(2));
}

const C = [-8, 10, 8, 8, 8, 10];
console.log("\n3. lines carried into the finding list");
write(C, "level", "reported", "correct", "wrong", "missed", "wrong %");
for (const n of LEVEL) {
  const k = K(n);
  const rep = k.reduce((t, s) => t + FINDINGS[assigned(s, n)], 0);
  const cor = k.filter((s) => correct(s, n)).reduce((t, s) => t + FINDINGS[s.trueVersion], 0);
  const tru = k.reduce((t, s) => t + FINDINGS[s.trueVersion], 0);
  write(C, n, rep, cor, rep - cor, tru - cor, ((rep - cor) / rep * 100).toFixed(1));
}

const D = [-10, 7, 7, 7, 7, 7];
console.log("\n4. confirmed count by banner status");
write(D, "banner", "count", "level1", "level2", "level3", "level4");
for (const t of ["correct", "misleading", "none"]) {
  const l = SERVICE.filter((s) => s.banner === t);
  write(D, t, l.length, ...LEVEL.map((n) => l.filter((s) => confirmed(s, n)).length));
}
const b = SERVICE.filter((s) => s.banner === "misleading");
console.log(`misleading banner: at level 1, ${b.filter((s) => confirmed(s, 1)).length} services ` +
  `are confirmed but assigned the wrong version; their true finding count is ` +
  `${b.reduce((t, s) => t + FINDINGS[s.trueVersion], 0)}, reported is ` +
  `${b.reduce((t, s) => t + FINDINGS[s.wrongVersion], 0)}`);
```

```
21 services: 11 with correct banner, 4 misleading, 6 bannerless

1. precision per probe level
level   requests    conf ambiguous   falseP    evid     prec %
1             21      13         8        4       9       42.9
2             42      14         7        3      11       52.4
3             63      11        10        0      11       52.4
4             84      16         5        0      16       76.2

2. the trace cost of precision
level    records  alerts    rec/evid  alert/evid   avg set size
1             42       0        4.67       0.000           3.90
2             84       0        7.64       0.000           2.10
3            126       3       11.45       0.273           1.71
4            168       6       10.50       0.375           1.24

3. lines carried into the finding list
level     reported correct   wrong  missed   wrong %
1               22      13       9      10      40.9
2               20      14       6       9      30.0
3               14      14       0       0       0.0
4               24      24       0       0       0.0

4. confirmed count by banner status
banner      count level1 level2 level3 level4
correct        11      9     11     11     11
misleading      4      4      3      0      3
none            6      0      0      0      2
misleading banner: at level 1, 4 services are confirmed but assigned the wrong version; their true finding count is 10, reported is 9
```

The numbers belong to the **measurement** class; their inputs are RE13–RE15.

## Thirteen Confirmed, Nine Pieces of Evidence

The first row of the first table sets up this lesson's question all on its own. At the first
probe level, 13 of 21 services narrow to a single version — by the precision measure, 61.9% are
confirmed. Yet 4 of those thirteen are assigned the wrong version. The evidence count is not 13,
it is **9**, and the real precision is 42.9%.

The gap is not a measurement error; it has to do with the measure itself. A candidate set having a
single member only shows that ambiguity has been eliminated; it does not show that the member is
correct. A step that assigns a version by reading a banner string reads a piece of text the target
wrote and counts that text as evidence. Nothing checks whether the text is true.

The fourth table breaks this down by banner status. Of the 11 services with a correct banner, 9
are confirmed at the first level, two remain stuck between two versions and get resolved at the
second level. Of the 4 services with a misleading banner, **all four are confirmed at the first
level** — at a higher rate than the correctly-bannered services, in fact. Of the 6 bannerless
services, none is confirmed in the first three levels.

This is this lesson's most counterintuitive finding: **a misleading banner produces the highest
apparent precision.** A claim of "I am this version" from an endpoint narrows the candidate set to
one, regardless of whether the claim is true or false. The side that sees ambiguity is the
bannerless endpoint; the side that gets a wrong answer is the misleading one.

## The Level Where the Confirmed Count Drops

The third row of the first table looks, at first glance, like a regression: the confirmed count
drops from 14 at the second level to **11**. In the same row, false positives fall from 3 to 0,
and the evidence count stays at 11.

Here is what is happening. The behavioral probes at the third level produce signatures that
contradict the banner string; three misleading services now point to three or four candidates
instead of a single version. The candidate set **grows**, because the measurement accounts for the
first time for the banner possibly being a lie. Apparent precision falls, real precision holds
steady, and wrong assignments drop to zero.

In an assessment, this row reads as follows: **a drop in the confirmed count is not a loss, it is
the previous precision turning out to have been fake.** Adding a probe does not have to increase
precision; what it increases is accuracy, and when accuracy increases, some items rightly turn
back into ambiguous.

At the fourth level, the confirmed count rises to 16 and all of them are correct — precision is
**76.2%**. The remaining five services still sit at two-candidate sets even after four probes;
four of them are bannerless endpoints. Fingerprinting's ceiling in this run is 16 of 21.

## The Wrong Lines Carried Into the Finding List

The third table measures the cost of incorrect version information in the report itself. Every
version label carries a certain number of lines into the finding list; a wrong version carries
wrong lines.

At the first level, the finding list has 22 lines. 13 of them are correct, **9 are wrong** — 40.9%
of the list. At the same level, 10 real lines never show up at all. So on one side there are nine
items that waste the remediation budget, on the other side ten items that never get looked at. The
closing line gives this via the four misleading services: the true finding count totals 10 while
the reported count is 9; the two numbers are close to each other and **share not a single line**.

At the second level, the wrong ratio falls to 30%. At the third level, the finding list drops to
14 lines and all of them are correct; missed lines are zero, because all eleven services counted
as confirmed are correctly assigned. At the fourth level, the list rises to 24 lines and, again,
all of them are correct.

The comparison between the first and fourth levels is this lesson's practical takeaway: both
produce a list of roughly the same size (22 versus 24), but in one, 40.9% of the lines are wrong,
in the other, 0%. **The finding count does not tell the two apart; the only thing that does is
verification.**

This table has a counterpart on the defense side, and it shows the asymmetry clearly. In the
organization's own software inventory, the version of the same 21 services can be read in a
single step and with zero trace; installation records say which version is installed, which patch
is applied, and which component is not used at all, without looking at a single misleading banner
string. The measurement done from outside spends 84 requests and 168 records to reach 16 services,
while the same measurement done from inside covers all 21 services. The assessment's finding at
this step is an expensive and incomplete copy of information the organization **could read from
its own inventory**; its real value shows up in the places where that inventory **does not match**
what is visible from outside.

## The Trace Cost of Precision

The second table opens up, for the last time in this topic, the column that has been carried
throughout. The average candidate set size falls from 3.90 to 1.24; in return, the records
accumulating at the target rise from 42 to 168, and alerts from 0 to 6.

The cost is not distributed evenly. The first two levels leave 84 records and produce **no alerts
at all**: these probes are indistinguishable from ordinary requests. The unusual probes used from
the third level onward produce a protocol-level anomaly and turn into an alert. In other words,
the step that completes accuracy is, at the same time, **the first step the defense notices**.

The `rec/evid` column reduces this to a single number: 4.67, 7.64, 11.45, and 10.50. The most
expensive level is the third; the fourth level leaves more records but brings five new pieces of
evidence, so its cost per piece of evidence falls. Read from the blue team's side, it comes down
to this: **most of this topic's steps are seen but not noticed.** Across the total of five
lessons, the records accumulating at the target are measured in the hundreds; the items that turn
into an alert do not exceed the fingers of two hands, and every one of them comes from either
density or a protocol anomaly — none of them is a record in the shape of "reconnaissance is under
way."

## Summary

- Precision is the size of the candidate version set: at the first probe level, 13 of 21 services
  narrow to a single version but 4 are assigned the wrong version; evidence is 9, real precision
  is 42.9%.
- A misleading banner produces the highest apparent precision: all four services with a misleading
  banner are confirmed at the first level, none of the six bannerless services are confirmed in
  the first three levels.
- At the third level, the confirmed count drops from 14 to 11 while wrong assignments fall from 3
  to 0: a drop in precision is the previous precision turning out to have been fake.
- At the first level, wrong version information carries 9 wrong lines into the finding list
  (40.9%) and misses 10 real lines; the two sets share not a single line.
- At the fourth level, all 24 lines are correct; it is a list of similar size to the 22 lines at
  the first level, and the only difference between them is verification.
- The cost of raising precision from 42.9% to 76.2% is rising from 42 records to 168 and from 0
  alerts to 6; the first two levels produce no alerts at all, and the third level, which completes
  accuracy, produces the first alert.

## Next Step

At the end of five lessons, the externally visible inventory has been drawn up, and candidates
were counted at every step: 20 candidates from public items, 8 verified ownerships from
registration lookup, 21 records and 6 dangling items from the name space, 21 open ports from
scanning, 16 confirmed versions from fingerprinting. The trace each number leaves behind was
written down too.

Yet all of these measurements share a common limit. No step in this topic can say whether a
candidate is **actually exploitable**. A version label being associated with a known vulnerability
does not show that the installation at that endpoint is actually affected: a patch may have been
applied, the affected component may not be in use at all, or a control in front of it may never
let the relevant request through. The fourth level's 24 lines, even with full precision, are
still, each one, a **candidate**.

The only way to close this gap is to look at the traffic coming from and going to the endpoint:
which packets flow, how the response changes when a request is modified, what a file does when
it is run, what independent sources say about the same item. The next topic — **Analysis Tools** —
takes up each of these tools with the same two questions: what does it produce, and **what cannot
it prove**.
