Skip to content
academia.sh

Lesson 06 / 18

Packet Capture and Analysis

Measuring the evidentiary value of captured traffic: 10 flows from 24 fictional packets, 18, 44, and 85 claim candidates at three capture points, 50 of the 85 candidates at the core mirror turning out to be evidence and 7 spoofable, low-value claims being proven at a rate of 20/20 while high-value claims are proven at only 10/25, and passive capture leaving zero records on the target while requiring one configuration change.

Published Updated

In this lesson

  • Derives flows from a fictional packet set and counts the visible fields per flow
  • Classifies fields by their producer as direct evidence, inference, or a spoofable claim
  • Measures the capture point's placement in terms of claim count, evidence ratio, and the trace it leaves
Contents

The previous topic produced the externally visible inventory and counted candidates; the question left open at its close was this — no reconnaissance step can say whether a candidate is truly open, and answering that requires looking at the traffic and the request itself. This topic covers the tools that look there, and it tests all of them against a single measure: the gap between the candidate they produce and the evidenced finding that remains.

The first tool class is the packet capturer: it records the frames passing through the network at one point. How protocols work was already established in the Network Models course in the Computer Networks curriculum, and it is not repeated here. The only question asked here is this: which field in this recorded pile counts as direct evidence, which is only an inference, and which is a claim the other side can write freely. Even in an authorized assessment, recording traffic belonging to another organization without written authorization and a defined scope is both illegal and unprofessional.

A Field’s Class Depends on Its Producer

Reading every field in a packet with the same eye is analysis’s most common mistake. The capture point itself produces one portion of the fields: the time of the record, which leg the frame entered on, direction, size, count. These are not the sender’s claim; they are the measuring instrument’s observation, and the sender cannot change them. Headers carry another portion: addresses, the port number, the server name field. These have to stay consistent along the network path, so they are not entirely free either, but they are not an observation — they are inference. The remaining portion is carried by the payload: username, meter ID, client clock. The party writing these is the sender, and unless they are cryptographically bound, the sender can write whatever it wants.

There is a fourth class: the signature. If a field inside the payload is cryptographically bound, that field stops being the sender’s claim and turns into evidence that can be tested — but if the flow is encrypted, the capture point cannot see the signature either.

  • AT1. The packet set, service table, capture points, and claim list are a model; no real capture was performed. No real address, hostname, or organization is written.
  • AT2. A flow is the set of packets carrying the same source, destination, and service triple.
  • AT3. A field’s class depends on its producer: a point observation and a signature are evidence, a header field is inference, a payload field is spoofable.
  • AT4. In an encrypted flow, payload fields and the signature are not visible; the server name field is counted as visible.
  • AT5. A claim is a proposition derived from captured traffic. If at least one of its bases is visible, the claim is a candidate; its class is the class of the strongest visible basis.
  • AT6. Every claim is given a value: how much weight it carries in the report. The value was assigned by hand.
  • AT7. Capture is passive; it produces no record on the target systems.
// pkt/flow.mjs — fictional packet set, flow extraction, and field classification are a MODEL.
// There is no real capture; no real address, hostname, or organization is written.
// A field's class depends on who produced it.
export const FIELD = {
  'capture-time': 'point', 'ingress-leg': 'point', 'direction': 'point',
  'frame-size': 'point', 'packet-count': 'point', 'plaintext-presence': 'point',
  'source-address': 'header', 'destination-address': 'header', 'port-number': 'header',
  'server-name-field': 'header', 'signature-verification': 'signature',
  'username': 'payload', 'password-field': 'payload', 'meter-id': 'payload',
  'reading-value': 'payload', 'subscriber-no': 'payload', 'session-token': 'payload',
  'client-clock': 'payload', 'device-id': 'payload', 'transaction-id': 'payload',
};
export const CLASS = { point: 'evidence', signature: 'evidence', header: 'inference', payload: 'spoofable' };
export const STRENGTH = { evidence: 3, inference: 2, spoofable: 1 };

// Service: whether it is encrypted, whether the payload is signed, which fields the payload carries.
export const SERVICE = {
  'meter-collection':  { encrypted: false, signed: true,  payload: ['meter-id', 'reading-value', 'client-clock'] },
  'validation-call':   { encrypted: true,  signed: true,  payload: ['transaction-id', 'subscriber-no'] },
  'portal-session':    { encrypted: true,  signed: false, payload: ['session-token', 'username'] },
  'field-sync':        { encrypted: false, signed: false, payload: ['username', 'device-id'] },
  'admin-access':      { encrypted: false, signed: false, payload: ['username', 'password-field'] },
  'external-notify':   { encrypted: true,  signed: false, payload: ['subscriber-no'] },
};

// Capture point: which legs' packets it sees.
export const POINT = {
  'branch-leg':         ['branch-1'],
  'core-mirror':        ['branch-1', 'branch-2', 'core', 'external'],
  'collector-endpoint': ['core'],
};

// row: time(s) | leg | source | destination | service | size(bytes)
export const PACKET = `
  0 branch-1 s1-end collector meter-collection 420
  1 branch-1 s1-end collector meter-collection 418
  2 branch-1 s1-end collector meter-collection 421
  3 branch-2 s2-end collector meter-collection 419
  4 branch-2 s2-end collector meter-collection 420
  5 core collector validator validation-call 1180
  6 core collector validator validation-call 1174
  7 core collector validator validation-call 1190
  8 external sub-end portal portal-session 2240
  9 external sub-end portal portal-session 2180
 10 external sub-end portal portal-session 2260
 11 branch-1 field-end field-svc field-sync 960
 12 branch-1 field-end field-svc field-sync 955
 13 branch-2 field-end field-svc field-sync 962
 14 core admin collector admin-access 310
 15 core admin collector admin-access 305
 16 core admin validator admin-access 308
 17 external billing ext-end external-notify 1420
 18 external billing ext-end external-notify 1415
 19 branch-1 s1-end collector meter-collection 417
 20 core collector billing validation-call 1160
 21 core collector billing validation-call 1168
 22 branch-2 s2-end collector meter-collection 422
 23 core admin field-svc admin-access 312
`.trim().split('\n').map((r) => {
  const [t, leg, source, destination, service, size] = r.trim().split(/\s+/);
  return { t: +t, leg, source, destination, service, size: +size };
});

// Flow: packets carrying the same (source, destination, service) triple.
export function flows(packets) {
  const m = new Map();
  for (const p of packets) {
    const k = `${p.source}>${p.destination}/${p.service}`;
    if (!m.has(k)) m.set(k, { name: k, service: p.service, leg: new Set(), packets: 0, bytes: 0 });
    const a = m.get(k);
    a.leg.add(p.leg); a.packets += 1; a.bytes += p.size;
  }
  return [...m.values()];
}

// Fields visible in a flow: point and header fields always; payload fields and the
// signature only in an unencrypted flow.
export function visibleFields(a) {
  const h = SERVICE[a.service];
  const g = ['capture-time', 'ingress-leg', 'direction', 'frame-size', 'packet-count',
    'source-address', 'destination-address', 'port-number'];
  if (h.encrypted) g.push('server-name-field');
  else {
    g.push('plaintext-presence', ...h.payload.filter((f) => FIELD[f]));
    if (h.signed) g.push('signature-verification');
  }
  return g;
}

// Claim: a proposition derived from captured traffic. If at least one of its basis fields is
// visible, the claim becomes a CANDIDATE; the claim's class is the class of the strongest visible basis.
export const CLAIM = [
  { name: 'flow-exists',               value: 'low',    basis: ['direction', 'frame-size'] },
  { name: 'volume-and-timing',         value: 'low',    basis: ['packet-count', 'capture-time'] },
  { name: 'transaction-time',          value: 'medium', basis: ['client-clock', 'capture-time'] },
  { name: 'traffic-from-this-leg',     value: 'medium', basis: ['ingress-leg'] },
  { name: 'service-spoken',            value: 'medium', basis: ['port-number'] },
  { name: 'counterparty-identity',     value: 'medium', basis: ['destination-address', 'server-name-field'] },
  { name: 'fields-in-plaintext',       value: 'high',   basis: ['plaintext-presence'] },
  { name: 'sender-identity',           value: 'high',   basis: ['source-address', 'signature-verification'] },
  { name: 'meter-reading-value',       value: 'high',   basis: ['reading-value', 'meter-id', 'signature-verification'] },
  { name: 'credential-field-contents', value: 'high',   basis: ['password-field', 'session-token'] },
  { name: 'person-performing-action',  value: 'high',   basis: ['username', 'device-id'] },
];

export function claimClass(claim, visible) {
  const v = claim.basis.filter((f) => visible.includes(f));
  if (v.length === 0) return null;
  return v.map((f) => CLASS[FIELD[f]]).sort((x, y) => STRENGTH[y] - STRENGTH[x])[0];
}

export function pointMeasure(point) {
  const legs = POINT[point];
  const p = PACKET.filter((x) => legs.includes(x.leg));
  const s = { packets: p.length, bytes: p.reduce((t, x) => t + x.size, 0), flows: 0,
    candidates: 0, evidence: 0, inference: 0, spoofable: 0 };
  for (const a of flows(p)) {
    s.flows += 1;
    const g = visibleFields(a);
    for (const i of CLAIM) {
      const c = claimClass(i, g);
      if (c) { s.candidates += 1; s[c] += 1; }
    }
  }
  return s;
}

Three Capture Points

// pkt/measure.mjs — the same packet set is measured from three capture points.
import { PACKET, POINT, CLAIM, SERVICE, flows, visibleFields, claimClass, pointMeasure } from './flow.mjs';

const y = (g, ...s) => console.log(s.map((v, i) =>
  (g[i] < 0 ? String(v).padEnd(-g[i]) : String(v).padStart(g[i]))).join('').trimEnd());

console.log(`${PACKET.length} packets -> ${flows(PACKET).length} flows, ` +
  `${Object.keys(SERVICE).length} services, ${CLAIM.length} claim types`);

const A = [-22, -18, 9, 7, 11, 8, 8];
console.log('\n1. visible fields per flow');
y(A, 'flow', 'service', 'packets', 'bytes', 'encrypted', 'signed', 'fields');
for (const a of flows(PACKET)) {
  const h = SERVICE[a.service];
  y(A, a.name.split('/')[0], a.service, a.packets, a.bytes, h.encrypted ? 'yes' : 'no',
    h.signed ? 'yes' : 'no', visibleFields(a).length);
}

const B = [-20, 9, 7, 12, 10, 11, 11];
console.log('\n2. claims per capture point');
y(B, 'point', 'packets', 'flows', 'candidates', 'evidence', 'inference', 'spoofable');
for (const n of Object.keys(POINT)) {
  const s = pointMeasure(n);
  y(B, n, s.packets, s.flows, s.candidates, s.evidence, s.inference, s.spoofable);
}

const C = [-27, 8, 10, 11, 11];
console.log('\n3. class distribution per claim type at the core mirror');
y(C, 'claim', 'value', 'evidence', 'inference', 'spoofable');
const ag = flows(PACKET).map(visibleFields);
const box = { low: [0, 0], medium: [0, 0], high: [0, 0] };
for (const i of CLAIM) {
  const c = { evidence: 0, inference: 0, spoofable: 0 };
  for (const g of ag) { const s = claimClass(i, g); if (s) c[s] += 1; }
  y(C, i.name, i.value, c.evidence, c.inference, c.spoofable);
  box[i.value][0] += c.evidence;
  box[i.value][1] += c.evidence + c.inference + c.spoofable;
}

console.log('\n4. evidence ratio by claim value');
for (const d of ['low', 'medium', 'high'])
  console.log(`  ${d.padEnd(8)} evidence ${String(box[d][0]).padStart(3)} / ${box[d][1]}`);

const t = pointMeasure('core-mirror');
console.log(`\nrecords produced on target systems            0`);
console.log(`configuration change needed to enable capture 1 (mirror leg, written approval)`);
console.log(`volume examined                               ${t.bytes} bytes`);
console.log(`flows not visible from the branch leg         ${t.flows - pointMeasure('branch-leg').flows} / ${t.flows}`);
24 packets -> 10 flows, 6 services, 11 claim types

1. visible fields per flow
flow                  service             packets  bytes  encrypted  signed  fields
s1-end>collector      meter-collection          4   1676         no     yes      13
s2-end>collector      meter-collection          3   1261         no     yes      13
collector>validator   validation-call           3   3544        yes     yes       9
sub-end>portal        portal-session            3   6680        yes      no       9
field-end>field-svc   field-sync                3   2877         no      no      11
admin>collector       admin-access              2    615         no      no      11
admin>validator       admin-access              1    308         no      no      11
billing>ext-end       external-notify           2   2835        yes      no       9
collector>billing     validation-call           2   2328        yes     yes       9
admin>field-svc       admin-access              1    312         no      no      11

2. claims per capture point
point                 packets  flows  candidates  evidence  inference  spoofable
branch-leg                  6      2          18        12          5          1
core-mirror                24     10          85        50         28          7
collector-endpoint          9      5          44        23         15          6

3. class distribution per claim type at the core mirror
claim                         value  evidence  inference  spoofable
flow-exists                     low        10          0          0
volume-and-timing               low        10          0          0
transaction-time             medium        10          0          0
traffic-from-this-leg        medium        10          0          0
service-spoken               medium         0         10          0
counterparty-identity        medium         0         10          0
fields-in-plaintext            high         6          0          0
sender-identity                high         2          8          0
meter-reading-value            high         2          0          0
credential-field-contents      high         0          0          3
person-performing-action       high         0          0          4

4. evidence ratio by claim value
  low      evidence  20 / 20
  medium   evidence  20 / 40
  high     evidence  10 / 25

records produced on target systems            0
configuration change needed to enable capture 1 (mirror leg, written approval)
volume examined                               22436 bytes
flows not visible from the branch leg         8 / 10

Evidence Is Cheap, a Valuable Claim Is Expensive

The fourth table is this lesson’s result. 20 of the 20 low-value claims fall in the evidence class. “There is a flow between these two endpoints,” “this many packets passed at this time” — all of it is true, none of it is disputable, and none of it is worth a line in the report. For medium-value claims the ratio drops by half: the leg and the time are evidence, while the spoken service and the counterparty’s identity are read from the header and are inference. For high-value claims, only 10 of 25 fall into the evidence class.

This inverse relationship is not a flaw; it is the structure of the method. The more a capture point observes, the less that observation says; the fields that actually say something are produced by the sender, and the sender is always suspect. On the measurement network this has three concrete counterparts.

Credential field contents and person performing the action are never evidence in any flow; they are produced as candidates seven times, and all seven rest solely on a payload field. Seeing a username in a capture does not show that person performed the action — it only shows that name was written into that field. A report line that reads “user X performed this action” is an unproven candidate, and it spends the remediation budget in the wrong place.

Sender identity is evidence in two of ten candidates, inference in eight. The two pieces of evidence come from the meter-collection flows, whose payload is signed and unencrypted: the signature is visible and can be verified. In the remaining eight, all that is on hand is the source address. In encrypted flows the signature also stays inside the payload and so is not visible — encryption is the right call, but it also hides from the analyst a field that could have been evidence.

Fields in plaintext are in the evidence class in six flows, and this is the most solid finding on the fictional network. The three admin-access flows, the field-sync flow, and two meter-collection flows carry their fields unencrypted. The distinction matters here: that the fields are carried in plaintext is the capture point’s observation and is evidence; what those fields say is the sender’s claim and is not.

The Point’s Placement and the Trace It Leaves

The second table is the measure of where the capturer is placed. The branch leg has 6 packets, 2 flows, and 18 candidates; the core mirror has 24 packets, 10 flows, and 85 candidates. An analyst looking from the branch leg never sees eight of the ten flows and can say nothing about a flow it does not see. The collector endpoint sits in the middle: it sees the core’s traffic, not the branch legs, but because three of the five flows it sees are unencrypted, it is the point with the highest spoofable-candidate ratio — 6 of 44 candidates.

The cost column consists of two items, and both are small. The volume examined is 22,436 bytes; in a real assessment this number is far larger, and it is the actual item that determines analysis time. The trace left on the defense side, though, is zero records: capture is passive, no request goes to the target systems, no log line is produced. This is the lowest-trace step we will see in this course.

The one item that is not zero is what makes the capture possible. Opening a mirror leg is a configuration change; it produces a change record and requires the network team’s approval and an explicit line in the scope document. Because subscriber data passes through the traffic, where the record will be stored and when it will be deleted are also written into the same document. It is the capture itself that is passive; the permission to access it is not.

Summary

  • Twenty-four fictional packets produce 10 flows; the number of visible fields per flow is 9 in encrypted flows and between 11 and 13 in unencrypted flows.
  • A field’s evidentiary value depends on its producer: the capture point’s observation and a cryptographic signature are evidence, a header field is inference, a payload field is a spoofable claim.
  • At the core mirror, 50 of 85 claim candidates are evidence, 28 are inference, and 7 are spoofable; a claim’s class is the class of its strongest visible basis.
  • The evidence ratio runs inverse to the claim’s value: 20/20 of low-value claims are proven, but only 10/25 of high-value ones. None of the seven candidates that tie a user to an action are evidence.
  • The capture point’s placement determines which flow is seen: the branch leg never sees 8 of the 10 flows.
  • Passive capture leaves zero records on the target; the only trace is the configuration change the mirror leg requires and the written approval tied to it.

Next Step

In this lesson, fields were classified one by one, but encrypted flows were passed over in a single line: the payload is not visible, the server name field is. Four of the ten flows on the fictional network are encrypted, and those four are exactly the flows carrying the most weight — the subscriber portal session, the validation call, the external notification. It is not true that analysis comes up empty-handed on these flows; its hands are full in a different way.

The next lesson measures application-layer inspection in terms of visibility. The same set of flows is run through a protocol analyzer, and two columns are counted separately: how many fields can be read in an unencrypted flow, and how many fields remain only metadata in an encrypted flow. It measures how much of a claim metadata carries, which scope and privacy item decrypting requires, and when the analyzer’s own interpretation amounts to an inference.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close