Skip to content
academia.sh

Lesson 19 / 20

Deauthentication Attacks

Of twelve frame types, only one carries integrity proof at baseline; protected management frames bring this count to four and tie three of eight management frames to proof, leaving five unprotected because they are pre-connection. Three of the eight mechanisms that disconnect a connection close; the record the defense sees moves from the disconnect counter to the rejected-frame counter, and the catch rate rises from three sixths to six sixths.

Published Updated

In this lesson

  • Separates wireless frame types by class and integrity proof, and counts protected against unprotected
  • Lists the mechanisms that cause a connection to drop and measures how many protection closes
  • Measures the indicator the defense sees: the catch-rate and false-alarm balance of threshold selection in the disconnect counter
  • Shows the effect on catch rate of protection creating a counter whose baseline value is zero
Contents

The previous lesson counted the unproven signals a client looks at when choosing a network and measured how many profiles mutual authentication closes. The whole measurement rested on one assumption: that the client establishes its connection on its own. A client that is already connected never sees a rogue candidate.

A client staying connected, however, is a separate verification question. Data frames are encrypted and carry integrity proof; the frames that establish, sustain, and terminate the connection are a separate class. Deauthentication is the name for this distinction: a management frame that terminates the connection is accepted without any proof of the claim it carries. This lesson does not measure how that technique is carried out, but which frame is protected, how many paths protection closes, and what the defense sees in which counter.

  • WS18. Frame types fall into three classes: data, control, management. The classification and whether each type carries integrity proof is a model decision.
  • WS19. A pre-connection frame is one exchanged between the parties before they share a common key. Because there is no shared key, integrity proof cannot be produced.
  • WS20. A path is a mechanism that causes a client’s connection to drop. A path closing means the claim that mechanism carries becomes verifiable.
  • WS21. The log data is fictional and produced by a hand-written generator; the seed is visible and the run is reproducible. The expected normal disconnects per hour is a model value.
  • WS22. The threshold is a multiple of the average and standard deviation drawn from past days for the same access point and the same hour. The event day is excluded from the baseline calculation.
  • WS23. The attack steps, tooling, and frame generation are outside this lesson; what is measured is the scope of protection and what the defense’s record shows.

Which Frame Is Protected

A wireless connection does not carry only data. Three classes of frame travel over the same medium, and their encryption coverage is not the same.

// frame.mjs — wireless frame types and protected management frames (model).
// Attack steps are not written; what is measured is which frame carries integrity proof.
// [frame type, class, is it pre-connection, proof at baseline, proof with protection on]
export const FRAME = [
  ['data frame', 'data', false, true, true],
  ['acknowledgment', 'control', false, false, false],
  ['request to send / clear to send', 'control', false, false, false],
  ['power-save poll', 'control', false, false, false],
  ['beacon frame', 'management', true, false, false],
  ['probe request', 'management', true, false, false],
  ['probe response', 'management', true, false, false],
  ['authentication frame', 'management', true, false, false],
  ['association request / response', 'management', true, false, false],
  ['disassociation', 'management', false, false, true],
  ['deauthentication', 'management', false, false, true],
  ['action frame (channel, measurement)', 'management', false, false, true],
];
// Mechanisms that cause a connection to drop (model). The 'closing defense' field says
// which defense closes the path; 'none' = no defense.
export const PATH = [
  ['forged disassociation frame', 'management frame', 'protected management frames'],
  ['forged deauthentication frame', 'management frame', 'protected management frames'],
  ['forged action frame (channel change)', 'management frame', 'protected management frames'],
  ['impersonating a pre-connection probe response', 'management frame', 'none'],
  ['making the medium appear busy with a control frame', 'control frame', 'none'],
  ['jamming at the physical layer', 'physical', 'none'],
  ['the authentication server being unreachable', 'infrastructure', 'local authentication cache'],
  ['a client that does not support protection connecting', 'client', 'protection is made mandatory'],
];
// run1.mjs — which frame is protected, how many paths close with protection on
import { FRAME, PATH } from './frame.mjs';

const E = [38, 13, 18, 12, 14];
const row = (h) => h.map((d, i) => String(d)[i ? 'padStart' : 'padEnd'](E[i])).join('');
const mark = (b) => b ? 'present' : 'absent';
console.log(row(['frame type', 'class', 'pre-connection', 'baseline', 'protection on']));
for (const [name, s, o, a, b] of FRAME) console.log(row([name, s, o ? 'yes' : 'no', mark(a), mark(b)]));
const count = (i) => FRAME.filter((c) => c[i]).length;
console.log(`\nframe type ${FRAME.length}; carrying integrity proof: baseline ${count(3)}, ` +
  `protection on ${count(4)}`);
const management = FRAME.filter((c) => c[1] === 'management');
console.log(`management frame ${management.length}; protected ${management.filter((c) => c[4]).length}, ` +
  `unprotected ${management.filter((c) => !c[4]).length} (all pre-connection)`);

const A = [50, 22, 32];
const row2 = (h) => h.map((d, i) => String(d)[i ? 'padStart' : 'padEnd'](A[i])).join('');
console.log('\n' + row2(['mechanism causing the disconnection', 'class', 'closing defense']));
for (const [name, s, k] of PATH) console.log(row2([name, s, k]));
const protectionCloses = PATH.filter((y) => y[2] === 'protected management frames').length;
console.log(`\npath ${PATH.length}; protected management frames close ${protectionCloses}, ` +
  `${PATH.length - protectionCloses} stay open`);
console.log(`of those left open, ${PATH.filter((y) => y[2] === 'none').length} are closed by no ` +
  `verification at all (not a claim problem)`);
frame type                                    class    pre-connection    baseline protection on
data frame                                     data                no     present       present
acknowledgment                              control                no      absent        absent
request to send / clear to send             control                no      absent        absent
power-save poll                             control                no      absent        absent
beacon frame                             management               yes      absent        absent
probe request                            management               yes      absent        absent
probe response                           management               yes      absent        absent
authentication frame                     management               yes      absent        absent
association request / response           management               yes      absent        absent
disassociation                           management                no      absent       present
deauthentication                         management                no      absent       present
action frame (channel, measurement)      management                no      absent       present

frame type 12; carrying integrity proof: baseline 1, protection on 4
management frame 8; protected 3, unprotected 5 (all pre-connection)

mechanism causing the disconnection                                class                 closing defense
forged disassociation frame                             management frame     protected management frames
forged deauthentication frame                           management frame     protected management frames
forged action frame (channel change)                    management frame     protected management frames
impersonating a pre-connection probe response           management frame                            none
making the medium appear busy with a control frame         control frame                            none
jamming at the physical layer                                   physical                            none
the authentication server being unreachable               infrastructure      local authentication cache
a client that does not support protection connecting                client    protection is made mandatory

path 8; protected management frames close 3, 5 stay open
of those left open, 3 are closed by no verification at all (not a claim problem)

At baseline, one of the twelve frame types carries integrity proof. Encryption’s coverage is the data frame; the remaining eleven types travel in the open. This row is the entire basis for deauthentication — the frame that terminates the connection carries no proof at all of the “this connection is ending” claim it makes, and the client accepts it.

When protection is turned on, the count rises from one to four. The three types gained are management frames sent after the connection is established: disassociation, deauthentication, and the action frame. What the three have in common is that a shared key exists between the parties at that point; that is the condition for proof to be produced.

The five management frames that stay unprotected also share what is read in the same column: all of them are pre-connection. The beacon frame, the probe request and response, authentication, and the association request are exchanged while there is no shared key yet. The previous lesson’s twin-access-point path lives exactly in these five rows; protected management frames do not close that path, and cannot.

Control frames are a third set. Acknowledgment and request-to-send frames never carry proof under any circumstance, because the timing of medium sharing depends on these frames being small and fast. There is also no content in them worth protecting; the only claim they carry is “I will use the medium for this long” or “received.” Adding proof would enlarge these frames and slow down the medium sharing itself — their staying outside protection’s scope is not a gap, it is a measured trade-off.

The second table translates this into a path count. Of the eight mechanisms, three close, and five stay open. Three of the five that stay open are closed by no verification at all, and the reason matters: jamming at the physical layer is not a claim problem. There is no claim accepted without proof there; the medium is unusable, and verification does not bring the medium back. This course’s measure reaches its limit here.

The remaining two paths fall to other items of defense. The authentication server being unreachable is met with a local authentication cache; the problem of a client that does not support protection is closed by making protection mandatory, and its cost is the devices left out in the previous lessons’ inventory table. When protection is left optional, a client can choose to connect without it, and for that client the three paths reopen.

The Counter the Defense Sees

When protection is not on, the disassociation frame is treated as valid, and the only trace it leaves is clients reconnecting. The run below extracts, from thirty days of fictional log data, what threshold selection actually buys.

// run2.mjs — what the defense sees in the record: the deviation in the disconnect counter and threshold selection.
// The generator is hand-written, the seed is visible; the run is reproducible.
const SEED = 20240917;
let s = SEED;
const rand = () => (s = (s * 1103515245 + 12345) % 2147483648) / 2147483648;
const poisson = (l) => { let k = 0, p = 1, L = Math.exp(-l); do { k++; p *= rand(); } while (p > L); return k - 1; };

const DAY = 30, HOUR = 24, AP = 6, EVENT_DAY = 17, EVENT_AP = 3;
const EVENT_HOURS = [7, 10, 13, 16, 19, 22];
// Expected normal disconnects per hour (model): higher during work hours, low at night.
const BASELINE = Array.from({ length: HOUR }, (_, h) => (h >= 8 && h < 18) ? 6 : 1.2);
const disconnects = [], rejected = [];
for (let g = 0; g < DAY; g++) {
  disconnects.push([]); rejected.push([]);
  for (let a = 0; a < AP; a++) {
    disconnects[g].push([]); rejected[g].push([]);
    for (let h = 0; h < HOUR; h++) {
      const event = g === EVENT_DAY && a === EVENT_AP && EVENT_HOURS.includes(h);
      disconnects[g][a].push(poisson(BASELINE[h]) + (event ? poisson(7) : 0));
      // While protection is on, the same frames are rejected at the integrity check and counted separately.
      rejected[g][a].push(event ? poisson(7) : 0);
    }
  }
}
// The baseline is derived per hour and access point from the days outside the event day.
const historyDays = [...Array(DAY).keys()].filter((g) => g !== EVENT_DAY);
const avg = (v) => v.reduce((a, b) => a + b, 0) / v.length;
const stdev = (v) => Math.sqrt(avg(v.map((x) => (x - avg(v)) ** 2)));
const E = [10, 14, 16, 18, 16];
const row = (h) => h.map((d, i) => String(d)[i ? 'padStart' : 'padEnd'](E[i])).join('');
console.log(`day ${DAY}, access point ${AP}, hour ${DAY * AP * HOUR}, event hour ${EVENT_HOURS.length}`);
console.log(row(['threshold', 'caught', 'missed', 'false alarm', 'false/day']));
for (const k of [2, 3, 4, 5, 6]) {
  let caught = 0, falseAlarm = 0;
  for (let g = 0; g < DAY; g++) for (let a = 0; a < AP; a++) for (let h = 0; h < HOUR; h++) {
    const hist = historyDays.map((x) => disconnects[x][a][h]);
    const threshold = avg(hist) + k * stdev(hist);
    if (disconnects[g][a][h] <= threshold) continue;
    if (g === EVENT_DAY && a === EVENT_AP && EVENT_HOURS.includes(h)) caught++; else falseAlarm++;
  }
  console.log(row([k, `${caught}/${EVENT_HOURS.length}`, EVENT_HOURS.length - caught, falseAlarm,
    (falseAlarm / DAY).toFixed(2)]));
}
// While protection is on: the same frames do not disconnect the client, they accumulate in the rejected-frame counter.
const totalRejected = rejected.flat(2).reduce((a, b) => a + b, 0);
const rejectedHours = rejected.flat(2).filter((x) => x > 0).length;
console.log(`\nprotection on: rejected management frame ${totalRejected}, deviating hour ${rejectedHours}`);
console.log(`because this counter's baseline is 0, the threshold is 1: caught ${rejectedHours}/${EVENT_HOURS.length}, false alarm 0`);
const eventDisconnects = EVENT_HOURS.reduce((t, h) => t + disconnects[EVENT_DAY][EVENT_AP][h], 0);
const normalDisconnects = EVENT_HOURS.reduce((t, h) => t + avg(historyDays.map((x) => disconnects[x][EVENT_AP][h])), 0);
console.log(`disconnects during event hours ${eventDisconnects}, the same hours' normal ` +
  `${normalDisconnects.toFixed(1)}, factor ${(eventDisconnects / normalDisconnects).toFixed(1)}`);
day 30, access point 6, hour 4320, event hour 6
threshold         caught          missed       false alarm       false/day
2                    4/6               2               167            5.57
3                    4/6               2                20            0.67
4                    3/6               3                 2            0.07
5                    1/6               5                 0            0.00
6                    0/6               6                 0            0.00

protection on: rejected management frame 42, deviating hour 6
because this counter's baseline is 0, the threshold is 1: caught 6/6, false alarm 0
disconnects during event hours 58, the same hours' normal 23.8, factor 2.4

The disconnect counter is a weak indicator, and the table shows why. During event hours, the counter rises to two point four times the normal; that is a deviation hard to distinguish within a record of four thousand three hundred twenty hours. With threshold two, four of the six event hours are caught, but thirty days produce one hundred sixty-seven false alarms: five and a half alarms a day, none of them real. Raising the threshold to four brings false alarms down to two, while the catch rate drops to three. At threshold six, false alarms are zero, and so is the catch rate.

None of the five rows is a good option. The counter’s baseline is not zero, it is noise that varies with the hour of day, and the event itself is spread inside that noise. For a counter to produce a usable indicator, a large deviation is not enough; its baseline has to be small.

The last three lines give protection’s second gain, and it is a gain the path table does not show. While protection is on, the same frames do not disconnect the client; they fail the integrity check and are rejected. The rejected management frame is a separate counter, and this counter’s normal value is zero. Forty-two frames accumulate over six hours, the threshold is taken as one, and all six of the six event hours are caught with zero false alarms.

The three paths protection closes are therefore not the whole measurement. Protection does not only make the frame verifiable; it also gives the defense a counter whose baseline is zero. The byproduct of every claim that becomes verifiable is that the case where verification fails becomes countable.

Summary

  • Of the twelve frame types, only one carries integrity proof at baseline: the data frame. Protected management frames bring this count to four.
  • Three of the eight management frames are protected, five are not, and all five unprotected ones are pre-connection frames: proof cannot be produced without a shared key. The twin-access-point path lives in these five rows, and protection does not close it.
  • Of the eight mechanisms causing a connection to drop, three close, and five stay open. Three of the ones that stay open cannot be closed by any verification; jamming at the physical layer is not a claim problem.
  • In the disconnect counter, the event is 2.4 times the normal. At threshold 2, 4/6 is caught but at a cost of 5.57 false alarms a day; at threshold 4, the catch rate is 3/6; at threshold 6, it is 0/6. No threshold is a good option.
  • While protection is on, the same frames fall into the rejected-management-frame counter; because that counter’s baseline is zero, threshold 1 gives a 6/6 catch rate with 0 false alarms. Protection’s second gain is a counter with a zero baseline.

Next Step

This lesson separated out three paths that protection cannot close, and all three had one thing in common: none of them is about verifying a claim. Jamming at the physical layer, making the medium appear busy, and impersonating pre-connection frames can all be done anywhere the signal reaches. That is why wireless defense’s last question is not a protocol question but a geography question: how far the signal travels. The next lesson actually calculates a coverage map — how many square meters of area the signal spills outside the building’s boundary, and which items an unauthorized-device scan finds in that area, and which it does not. The same lesson also gathers the course’s twenty lessons into a single table.

To keep your progress and take notes, Log in

My notes

Log in to take notes.

Start typing to search.

↑↓ ↵ Esc navigate · open · close