1/*
2 * DESIGN
3 * Using string-as-bytes per https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data
4 */
5
6/**
7 * RESEARCH
8 * Additional Reading and References:
9 *
10 * Details on WPA encryption (byte-level) - http://sid.rstack.org/pres/0810_BACon_WPA2_en.pdf
11 * - Includes details on "caching" repeated calls to "BODY"
12 *
13 * HMAC-SHA1 in JS - https://github.com/Caligatio/jsSHA/tree/v2.0.1
14 * - Try online: https://caligatio.github.io/jsSHA/
15 * - Text: "value"
16 * - Key: "secret"
17 *
18 * PBKDF2:
19 * - Apparently it's HMAC-SHA1() * 8192
20 * - But see https://en.wikipedia.org/wiki/PBKDF2
21 * - PBKDF2, DK = PBKDF2(PRF, Password, Salt, count, dkLen)
22 * - WPA, DK = PBKDF2(HMAC−SHA1, passphrase, ssid, 4096, 256)
23 *
24 *
25 * Load byte array from URL: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data
26 *
27 * aircrack-ng sample WPA file: http://www.aircrack-ng.org/doku.php?id=wpa_capture
28 * Parsing .cap files: http://systemsarchitect.net/2014/03/12/parsing-binary-data-in-php-on-an-example-with-the-pcap-format/
29 *
30 * http://www.willhackforsushi.com/papers/80211_Pocket_Reference_Guide.pdf
31 *
32 * Details on 802.11 packet structures - http://www.studioreti.it/slide/802-11-Frame_E_C.pdf
33 *
34 *
35 * TODO:
36 * - Check bytes_.length >= header.length before parsing! (chocobo-new.cap is 'truncated', gives weird results)
37 * - Look at using ArrayBuffer + DataView internal JS structures.
38 * - Browser compatibility (ie, chrome, safari)
39 */
40
41
42/**
43 * Parse the given bytes of a Packet Capture (PCAP) file.
44 * Loads result into this.globalHeader (always) and list this.packetFrames (for known frame types).
45 *
46 * @param bytes (string? bytes?) Raw bytes from a .cap Pcap file.
47 * @param debug (boolean or function) If 'true': Dumps debug information to console.
48 * If 'false': Does not dump anything to console.
49 * If given a function, calls function with debug text.
50 */
51function CapFile(bytes, debug) {
52 if (debug) {
53 if (typeof debug === "boolean") {
54 // Default debug function
55 CapFile.debug = function(txt) {
56 console.log("[CapFile.js] " + txt);
57 }
58 }
59 else if (typeof debug === "function") {
60 CapFile.debug = debug;
61 }
62 else {
63 throw Error("Unexpected type of 'debug' option: " + (typeof debug));
64 }
65 }
66
67 this.bytes_ = bytes;
68 this.byteOffset_ = 0;
69 this.bytesTotal = this.bytes_.length;
70 //this.bytesHex = this.getHex(0, this.bytesTotal, " ");
71
72 this.parse();
73
74 delete this.bytes_;
75 delete this.byteOffset_;
76};
77
78// Configurations
79CapFile.useBigEndian = true;
80
81// Constants
82CapFile.MAGIC_NUMBER = 2712847316;
83CapFile.SUPPORTED_PCAP_VERSION = "2.4";
84CapFile.WLAN_HEADER_TYPE = 105;
85CapFile.GLOBAL_HEADER_LENGTH = 24;
86CapFile.PACKET_HEADER_LENGTH = 16;
87
88/**
89 * TODO: Log parse time for debug mode.
90 */
91CapFile.prototype.parse = function() {
92 // First chunk of bytes is global header.
93 this.byteOffset_ = 0;
94 this.globalHeader = CapFile.GlobalHeader.call(this);
95
96 // Ensure we can parse this Pcap file/ format version.
97 if (this.globalHeader.version !== CapFile.SUPPORTED_PCAP_VERSION) {
98 throw Error("Unsupported PCap File version (" + this.globalHeader.version + "). " +
99 "Unable to parse.");
100 }
101
102 // Restrict parsing to WLAN types.
103 if (this.globalHeader.headerType !== CapFile.WLAN_HEADER_TYPE) {
104 throw Error("Unsupported (non-WLAN) Pcap file header type (" + this.globalHeader.headerType + "). " +
105 "Unable to parse.");
106 }
107
108 // Skip past Blobal Header bytes.
109 this.byteOffset_ += CapFile.GLOBAL_HEADER_LENGTH;
110
111 // List of all identified frames in the cap file.
112 this.packetFrames = [];
113
114 var frame, skippedFrames = 0;
115 while (this.byteOffset_ < this.bytes_.length) {
116 frame = CapFile.WlanFrame.call(this);
117 if (frame && frame.name !== undefined && frame.name.indexOf("Unknown") === -1) {
118 // Only add known packet types to frames list.
119 this.packetFrames.push(frame);
120 }
121 else {
122 skippedFrames++;
123 }
124 }
125 if (CapFile.debug) {
126 var totalFrames = skippedFrames + this.packetFrames.length;
127 CapFile.debug("Parsed " + this.packetFrames.length + " known frames out of " + totalFrames + " total frames.");
128 }
129};
130
131/**
132 * Extract integer from bytes_ at current byteOffset_
133 *
134 * @param startIndex Index of first byte (added to byteOffset_)
135 * @param endIndex Index of last byte (added to byteOffset_)
136 * @param useBigEndian Override configuration, expect big-endian-style byte order (default: CapFile.useBigEndian)
137 * @param signed If integer should be signed (default: false)
138 *
139 * @return (int) Numeric-representation of data at byte location.
140 */
141CapFile.prototype.getInt = function(startIndex, endIndex, useBigEndian, signed) {
142 startIndex += this.byteOffset_;
143 endIndex += this.byteOffset_;
144
145 var intResult = 0, i, x;
146 if (useBigEndian == true || CapFile.useBigEndian) {
147 for (i = startIndex; i < endIndex; i++) {
148 intResult = intResult << 8;
149 x = this.bytes_.charCodeAt(i);
150 intResult = intResult | x;
151 }
152 } else {
153 for (i = endIndex - 1; i >= startIndex; i--) {
154 intResult = intResult << 8;
155 x = this.bytes_.charCodeAt(i);
156 intResult = intResult | x;
157 }
158 }
159
160 if (!signed) {
161 // convert to unsigned.
162 // See http://stackoverflow.com/a/1908655
163 intResult = (intResult >>> 0);
164 }
165 return intResult;
166};
167
168/**
169 * Extract hex characters from bytes_ at current byteOffset_
170 *
171 * @param startIndex (int) Index of first byte (added to byteOffset_)
172 * @param endIndex (int) Index of last byte (added to byteOffset_)
173 * @param byteSpacer (string) Separator between bytes (default: empty string)
174 * @param colSpacer (string) Separator between chunks of 8 bytes (default: empty string)
175 * @param rowSpacer (string) Separator between chunks of 16 bytes (default: empty string)
176 *
177 * @return (string) Hex-representation of data at byte location.
178 */
179CapFile.prototype.getHex = function(startIndex, endIndex, byteSpacer, colSpacer, rowSpacer) {
180 startIndex += this.byteOffset_;
181 endIndex += this.byteOffset_;
182
183 var byteList = [], hex, i, counter = 0;
184 if (byteSpacer) {
185 byteList.push("");
186 }
187
188 // Presume Big-endian for all hex value operations.
189 for (i = startIndex; i < endIndex; i++) {
190 hex = this.bytes_.charCodeAt(i).toString(16);
191 while (hex.length < 2) {
192 hex = "0" + hex;
193 }
194 counter++;
195 if (rowSpacer && counter % 16 == 0) {
196 hex += rowSpacer;
197 } else if (colSpacer && counter % 8 == 0) {
198 hex += colSpacer;
199 }
200 byteList.push(hex);
201 }
202
203 return byteList.join(byteSpacer || "");
204};
205
206/**
207 * Extract raw bytes from bytes_ at current byteOffset_
208 *
209 * @param startIndex (int) Index of first byte (added to byteOffset_)
210 * @param endIndex (int) Index of last byte (added to byteOffset_)
211 *
212 * @return (string? bytes?) Raw bytes of data at byte location.
213 */
214CapFile.prototype.getBytes = function(startIndex, endIndex) {
215 startIndex += this.byteOffset_;
216 endIndex += this.byteOffset_;
217
218 var byteList = [], hex, i;
219 for (i = startIndex; i < endIndex; i++) {
220 hex = this.bytes_.charAt(i);
221 byteList.push(hex);
222 }
223 if (CapFile.useBigEndian) {
224 byteList.reverse();
225 }
226 return byteList.join("");
227};
228
229/**
230 * Parses Pcap file global header (starting at byteOffset_, presumably "0").
231 *
232 * Requires reference to "this" CapFile object.
233 *
234 * More info on https://wiki.wireshark.org/Development/LibpcapFileFormat
235 *
236 * @return (object) containing:
237 * version (string) in format <major>.<minor> e.g. 2.4
238 * gmtOffset (signed int) Offset between packet timestamps and GMT timezone
239 * sigFigs (int) Accuracy of timestamps
240 * snapshotLength (int) Length of snapshot for the capture (in bytes)
241 * headerType (int) Link-Layer header type, e.g. LINKTYPE_IEEE802.11 = 105 (Wireless LAN)
242 */
243CapFile.GlobalHeader = function() {
244 // Presume big endian.
245 CapFile.useBigEndian = false;
246
247 // Set global endianess based on the magic number.
248 var magic_number = this.getInt(0, 4);
249 if (magic_number === CapFile.MAGIC_NUMBER) {
250 if (CapFile.debug) {
251 CapFile.debug("Using Little-Endian byte-encoding due to magic number: " + magic_number);
252 }
253 } else {
254 if (CapFile.debug) {
255 CapFile.debug("Using Big-Endian byte-encoding due to magic number: " + magic_number);
256 }
257 CapFile.useBigEndian = true;
258 magic_number = this.getInt(0, 4);
259 if (magic_number !== CapFile.MAGIC_NUMBER) {
260 throw Error("Can't read magic number! Got <" + magic_number + ">, but expecting <" + CapFile.MAGIC_NUMBER + ">");
261 }
262 }
263
264 var headers = {
265
266 // Version of cap file.
267 version: this.getInt(4, 6).toString(10) + "." + this.getInt(6, 8).toString(10),
268
269 // Difference between capfile timestamps and GMT (in seconds)
270 gmtOffset: this.getInt(8, 12, undefined, true),
271
272 // The accuracy of the capfile timestamps
273 sigFigs: this.getInt(12, 16),
274
275 // Length of snapshot for the capture.
276 // May cause "length" in PacketHeader to differ from "originalLength"
277 snapshotLength: this.getInt(16, 20),
278
279 // Link-layer header type. See http://www.tcpdump.org/linktypes.html
280 // e.g. LINKTYPE_IEEE802_11 = 105 (for Wireless LAN)
281 headerType: this.getInt(20, 24)
282
283 };
284
285 if (CapFile.debug) {
286 CapFile.debug("GlobalHeader (24 bytes):\n" + this.getHex(0, 24, " ", " ", "\n"));
287 }
288
289 return headers;
290};
291
292/**
293 * "Frame Builder". Parses entire frame at current bytesOffset_ location.
294 * Only supports detailed parsing of certain 802.11 WLAN frames - see CapFile.WlanFrame.* for more info
295 *
296 * Requires reference to "this" CapFile object -- call using CapFile.WlanFrame.call(this);
297 *
298 * @return (object) Frame information containing: {
299 * header: {
300 * timestamp: (Date) Timestamp of the packet as a Date object
301 * length: (int) Length of the packet/frame (in bytes)
302 * },
303 * frameControl: {
304 * version: (int) Frame version
305 * type: (int) Frame type (0=Management, 1=Control, 2=Data)
306 * subtype: (int) Frame subtype
307 * flags: {
308 * toDS: (boolean)
309 * fromDS: (boolean)
310 * moreFragments:(boolean)
311 * retry: (boolean)
312 * powerMgt: (boolean)
313 * moreData: (boolean)
314 * is_protected: (boolean)
315 * order: (boolean)
316 * }
317 * },
318 * duration: (int)
319 * addr1: (string) First address, as hex characters (no separator).
320 * }
321 *
322 * CONTROL FRAMES (type:1) will not contain any additional information.
323 *
324 * MANAGEMENT FRAMES (type:0) and DATA FRAMES (type:2) both contain more information: {
325 * addr2: (string) Second address, as hex characters (no separator).
326 * addr3: (string) Second address, as hex characters (no separator).
327 * fragmentNumber: (int)
328 * sequenceNumber: (int)
329 * }
330 *
331 * More info on MANAGEMENT frames: see CapFile.WlanFrame.Management
332 * More info on DATA frames: see CapFile.WlanFrame.Data
333 *
334 */
335CapFile.WlanFrame = function() {
336 var frame = {};
337
338 // Parse header
339 // Details on https://wiki.wireshark.org/Development/LibpcapFileFormat
340
341 // Convert timestamp to Date.
342 var timestampSec = this.getInt(0, 4);
343 var timestampUsec = this.getInt(4, 8);
344 var ts_usec = timestampSec * 1000;
345 ts_usec += (timestampUsec / 1000);
346 ts_usec += this.globalHeader.gmtOffset;
347 frame.header = {
348 timestamp: new Date(ts_usec),
349 length: this.getInt(8, 12),
350 originalLength: this.getInt(12, 16)
351 };
352
353 // Shift to frame body.
354 this.byteOffset_ += CapFile.PACKET_HEADER_LENGTH;
355
356 // Mark where this packet ends.
357 var endOfPacketOffset = this.byteOffset_ + frame.header.originalLength;
358
359 // Parse fields that are present in all Wlan frames.
360 // Details on https://en.wikipedia.org/wiki/IEEE_802.11#Layer_2_.E2.80.93_Datagrams
361 var frameControlBits = this.getInt(0, 1);
362 frame.frameControl = {
363 version: (frameControlBits >>> 0) & 0b11,
364 type: (frameControlBits >>> 2) & 0b11,
365 subtype: (frameControlBits >>> 4) & 0b1111
366 };
367
368 var frameControlFlags = this.getInt(1, 2);
369 frame.frameControl.flags = {
370 toDS: !!(frameControlFlags >>> 0 & 0b1),
371 fromDS: !!(frameControlFlags >>> 1 & 0b1),
372 moreFragments: !!(frameControlFlags >>> 2 & 0b1),
373 retry: !!(frameControlFlags >>> 3 & 0b1),
374 powerMgt: !!(frameControlFlags >>> 4 & 0b1),
375 moreData: !!(frameControlFlags >>> 5 & 0b1),
376 is_protected: !!(frameControlFlags >>> 6 & 0b1),
377 order: !!(frameControlFlags >>> 7 & 0b1)
378 };
379
380 frame.duration = this.getInt(2, 4);
381
382 frame.addr1 = this.getHex(4, 10);
383
384 // From here on, the fields may vary depending on the Frame Type (MANAGEMENT, CONTROL, DATA).
385 if (frame.frameControl.type === CapFile.WlanFrame.Types.CONTROL) {
386 // No other relevant data to parse.
387 } else {
388 // Capture similarities between Management and Data frames.
389 frame.addr2 = this.getHex(10, 16);
390 frame.addr3 = this.getHex(16, 22);
391 var fragSeq = this.getInt(22, 24);
392 frame.fragmentNumber = (fragSeq >>> 0) & 0b1111;
393 frame.sequenceNumber = (fragSeq >>> 4) & 0b111111111111;
394
395 var toDS = frame.frameControl.flags.toDS,
396 fromDS = frame.frameControl.flags.fromDS;
397 if (toDS && !fromDS) {
398 frame._bssid = frame.addr1;
399 frame._station = frame.addr2;
400
401 }
402 else if (!toDS && fromDS) {
403 frame._bssid = frame.addr2;
404 frame._station = frame.addr1;
405
406 }
407 else if (!toDS && !fromDS) {
408 frame._bssid = frame.addr3;
409 frame._station = undefined;
410
411 }
412 else if (toDS && fromDS) {
413 // No idea
414 frame._bssid = undefined;
415 frame._station = undefined;
416
417 }
418
419 // Skip to just past the sequence number.
420 this.byteOffset_ += 24;
421
422 if (frame.frameControl.type === CapFile.WlanFrame.Types.MANAGEMENT) {
423 // Parse frame in context of a Management Frame.
424 CapFile.WlanFrame.Management.call(this, frame, endOfPacketOffset);
425 }
426 else if (frame.frameControl.type === CapFile.WlanFrame.Types.DATA) {
427 // Parse frame in context of a Data frame.
428 CapFile.WlanFrame.Data.call(this, frame, endOfPacketOffset);
429 }
430 }
431
432 // Shift to end of frame.
433 this.byteOffset_ = endOfPacketOffset;
434
435 return frame;
436};
437
438CapFile.WlanFrame.Types = {
439 MANAGEMENT: 0,
440 CONTROL: 1,
441 DATA: 2
442};
443
444/**
445 * Adds any additional information about this Management packet to the given frame.
446 * Only a subset of Management frames are supported.
447 *
448 * Focus is on getting SSID from the Management frames' "tagged parameters".
449 * Other tagged parameters are read (and stored as Hex) but are not parsed.
450 *
451 * Requires reference to "this" CapFile object -- call using CapFile.WlanFrame.Management.call(this, ...);
452 *
453 * Increments CapFile.byteOffset_ to end of the Management frame.
454 *
455 * @param frame (object) Reference to the currently-parsed frame.
456 * @param endOfPacketOffset (int) The Offset, in relation to byteOffset_, in which this packet ends.
457 */
458CapFile.WlanFrame.Management = function(frame, endOfPacketOffset) {
459 // Management frames contain:
460 // 1. Fixed Parameters (variable length, depends on frameControl.subtype).
461 // 2. Tagged Parameters (variable length, defined in 'length' bytes).
462
463 var fixedParamLength;
464 if (frame.frameControl.subtype === 0) {
465 frame.name = "Association Request";
466 fixedParamLength = 4;
467 }
468 else if (frame.frameControl.subtype === 1) {
469 frame.name = "Association Response";
470 fixedParamLength = 6;
471 }
472 else if (frame.frameControl.subtype === 8) {
473 frame.name = "Beacon";
474 fixedParamLength = 12;
475 }
476 else if (frame.frameControl.subtype === 5) {
477 frame.name = "Probe Response";
478 fixedParamLength = 12;
479 }
480 else if (frame.frameControl.subtype === 11) {
481 frame.name = "Authentication";
482 fixedParamLength = 6;
483 }
484 else if (frame.frameControl.subtype === 12) {
485 frame.name = "Deauthentication";
486 fixedParamLength = 2;
487 }
488 else if (frame.frameControl.subtype === 13) {
489 frame.name = "Action";
490 fixedParamLength = 9;
491 }
492
493 if (!fixedParamLength) {
494 // Unable to parse tagged parameters without knowing fixed parameter length.
495 frame.name = "Unknown Management Frame subtype (" + frame.frameControl.subtype + ")";
496 return;
497 }
498
499 // TODO: Parse fixed parameters. Skipping for now.
500 this.byteOffset_ += fixedParamLength;
501
502
503 // Parse tagged parameters.
504 frame.taggedParameters = {};
505 while (this.byteOffset_ < endOfPacketOffset) {
506 var tag = {};
507 var tagIndex = this.getInt(0, 1);
508 var tagLength = this.getInt(1, 2);
509 if (tagIndex === 0) {
510 // SSID
511 var tagData = this.getBytes(2, 2 + tagLength);
512 frame.taggedParameters[tagIndex] = {
513 name: "SSID",
514 length: tagLength,
515 data: tagData
516 };
517 frame.description = "SSID: " + tagData;
518 }
519 else {
520 // Don't care about other tags.
521 var tagData = this.getHex(2, 2 + tagLength);
522 frame.taggedParameters[tagIndex] = {
523 name: "N/A",
524 length: tagLength,
525 data: tagData
526 };
527 }
528 // Shift to next tagged paramter (or end of packet).
529 this.byteOffset_ += tagLength + 2;
530 }
531};
532
533/**
534 * Adds any additional information about this Data frame to the given frame.
535 * Only a small subset of Data frames are supported.
536 *
537 * Focus is on getting EAPOL (WPA handshake-related) information.
538 *
539 * Requires reference to "this" CapFile object -- call using CapFile.WlanFrame.Data.call(this, ...);
540 *
541 * Increments CapFile.byteOffset_ to end of the Data frame (if known).
542 * Otherwise does not change CapFile.byteOffset_
543 *
544 * @param frame (object) Reference to the currently-parsed frame.
545 * @param endOfPacketOffset (int) The Offset, in relation to byteOffset_, in which this packet ends.
546 */
547CapFile.WlanFrame.Data = function(frame, endOfPacketOffset) {
548 if ((frame.frameControl.subtype & 0b111) !== 0) {
549 // Only support EAPOL (and EAPOL+QoS) packets.
550 frame.name = "Unknown Data Frame subtype (" + frame.frameControl.subtype + ")";
551 return;
552 }
553
554 if (frame.frameControl.flags.toDS && frame.frameControl.flags.fromDS) {
555 // toDS and fromDS are set, expect addr4
556 frame.addr4 = this.getHex(0, 6);
557 this.byteOffset_ += 6;
558 }
559
560 if ((frame.frameControl.subtype & 0b1000) === 8) {
561 // QoS flag is set. Expect QoS control field.
562 frame.qosControl = this.getHex(0, 2);
563 this.byteOffset_ += 2;
564 frame.name = "EAPOL (QoS)";
565 }
566 else {
567 frame.name = "EAPOL";
568 }
569
570 if (frame.frameControl.flags.order) {
571 // Expect (and skip) HT Control field.
572 this.byteOffset_ += 4;
573 }
574
575 // Skip Logical-Link Control bytes
576 this.byteOffset_ += 8;
577
578 frame.bytes = this.getHex(0, endOfPacketOffset - this.byteOffset_);
579
580 // Parse Data frame body -- expect 802.1x auth packet.
581 var authVersion = this.getInt(0, 1);
582 var authType = this.getInt(1, 2);
583 frame.auth = {
584 version: authVersion, // 1=802.1X-2001
585 type: authType, // 3=Key
586 authLength: this.getInt(2, 4, true, false),
587 keyDescriptorType: this.getInt(4, 5), // 2=EAPOL RSN Key
588 keyInfo: this.getInt(5, 7, true, false),
589 keyLength: this.getInt(7, 9, true, false),
590 replayCounter: this.getInt(9, 17, true, false),
591 keyNonce: this.getHex(17, 49),
592 keyIV: this.getHex(49, 65),
593 keyRSC: this.getHex(65, 73),
594 keyID: this.getHex(73, 81),
595 keyMIC: this.getHex(81, 97),
596 keyDataLength: this.getInt(97, 99, true, false)
597 };
598 frame.auth.keyInfoFlags = {
599 keyDescriptorVersion: (frame.auth.keyInfo >>> 0) & 0b111, // 2=AES Cipher, HMAC-SHA1 MIC
600 keyType: (frame.auth.keyInfo >>> 3) & 0b1, // 1=Pairwise Key
601 keyIndex: (frame.auth.keyInfo >>> 4) & 0b11,
602 install: !!((frame.auth.keyInfo >>> 6) & 0b1 ),
603 ack: !!((frame.auth.keyInfo >>> 7) & 0b1 ),
604 mic: !!((frame.auth.keyInfo >>> 8) & 0b1 ),
605 secure: !!((frame.auth.keyInfo >>> 9) & 0b1 ),
606 error: !!((frame.auth.keyInfo >>> 10) & 0b1 ),
607 request: !!((frame.auth.keyInfo >>> 11) & 0b1 ),
608 encrypted: !!((frame.auth.keyInfo >>> 12) & 0b1 )
609 };
610 this.byteOffset_ += 99;
611
612 if (frame.auth.keyDataLength > 0) {
613 frame.auth.keyData = this.getHex(0, frame.auth.keyDataLength);
614 this.byteOffset_ += frame.auth.keyDataLength;
615 }
616
617 // Set handshake number
618 if (frame.frameControl.flags.fromDS) {
619 // Either 1 or 3
620 if (frame.auth.keyInfoFlags.mic) {
621 frame.description = "Handshake (3 of 4)";
622 } else {
623 frame.description = "Handshake (1 of 4)";
624 }
625 } else {
626 if (frame.auth.keyInfoFlags.secure) {
627 frame.description = "Handshake (4 of 4)";
628 } else {
629 frame.description = "Handshake (2 of 4)";
630 }
631 }
632
633};
634
635/**
636 * Identify 4-way handshake(s), extract information required for calculating PMK.
637 */
638CapFile.prototype.extractPmkFields = function(givenSsid) {
639 // Look for SSID name in previous beacons/auth packets
640
641 var bssid_to_ssid = {};
642 var i, frame;
643 for (i = 0; i < this.packetFrames.length; i++) {
644 frame = this.packetFrames[i];
645 if (!frame.hasOwnProperty("taggedParameters")
646 || !frame.hasOwnProperty("_bssid")) {
647 continue;
648 }
649 var tags = frame.taggedParameters;
650 if (!tags.hasOwnProperty("0")) {
651 continue;
652 }
653 var tag = tags["0"];
654 if (!tag.hasOwnProperty("name") || tag.name !== "SSID") {
655 continue;
656 }
657 if (!givenSsid || tag.data === givenSsid) {
658 bssid_to_ssid[frame._bssid] = tag.data;
659 }
660 else if (CapFile.debug) {
661 CapFile.debug("Ignoring discovered SSID <" + tag.data + "> because it does not match given SSID <" + givenSSID + ">");
662 }
663 }
664
665 var handshakes = [];
666
667 // Iterate over all known BSSIDs
668 var bssids = [], ssid;
669 for (var bssid in bssid_to_ssid) {
670 if (!bssid_to_ssid.hasOwnProperty(bssid)) {
671 continue;
672 }
673 bssids.push(bssid);
674 ssid = bssid_to_ssid[bssid];
675
676 // Look for last 3 frames of handshake
677 if (CapFile.debug) {
678 CapFile.debug("Looking for handshake for bssid: " + bssid + ", ssid: " + ssid);
679 }
680
681 var fc, mic, ack, install, dataLength;
682 var hsSrcAddress, hsDstAddress, snonce, anonce, hsKeyLength, hsReplayCounter, hsMic, hsKeyDescriptorVersion;
683 for (i = 0; i < this.packetFrames.length; i++) {
684 frame = this.packetFrames[i];
685
686 // Filter by packet type.
687 fc = frame.frameControl;
688 if (fc.type !== 2 || (fc.subtype !== 0 && fc.subtype !== 8)) {
689 // Not an EAPOL WPA data frame, skip.
690 continue;
691 }
692
693 // Filter for the BSSID we're looking for.
694 if (frame._bssid !== bssid) {
695 if (CapFile.debug) {
696 CapFile.debug("Skipping frame #" + i + ": BSSID: " + frame._bssid + " is not from " + bssid);
697 }
698 continue;
699 }
700
701 // Store fields used in all handshakes.
702 mic = frame.auth.keyInfoFlags.mic;
703 ack = frame.auth.keyInfoFlags.ack;
704 install = frame.auth.keyInfoFlags.install;
705 dataLength = frame.auth.keyDataLength;
706
707 /* Handshake (2 of 4):
708 * mic:true
709 * ack:false
710 * install:false
711 * keyDataLength > 0)
712 *
713 * Extract:
714 * - keynonce (SNonce , the nonce from STATION)
715 */
716 if (mic && !ack && !install && dataLength > 0) {
717 // Reset variables from Handshakes #3 and #4
718 hsSrcAddress = hsDstAddress = anonce = hsKeyLength = hsReplayCounter = hsMic = hsKeyDescriptorVersion = undefined;
719
720 // Extract SNonce
721 snonce = frame.auth.keyNonce;
722 if (CapFile.debug) {
723 CapFile.debug("Handshake (2 of 4): Found SNonce: " + snonce);
724 }
725 continue;
726 }
727
728 /* Handshake (3 of 4):
729 * mic:true
730 * ack:true
731 * install:true
732 *
733 * Extract:
734 * - src_address (STATION)
735 * - dst_address (AP)
736 * - ANonce (from AP)
737 * - replay_counter (for Handshake 4 of 4)
738 */
739 if (mic && ack && install) {
740 if (!snonce) {
741 // Require Handshake #2
742 continue;
743 }
744
745 // Reset variables from Handshake #4
746 hsMic = hsKeyDescriptorVersion = undefined;
747
748 // Extract variables
749 hsSrcAddress = frame._station;
750 hsDstAddress = frame._bssid;
751 anonce = frame.auth.keyNonce;
752 hsReplayCounter = frame.auth.replayCounter;
753 hsKeyLength = frame.auth.keyLength;
754 if (CapFile.debug) {
755 CapFile.debug("Handshake (3 of 4): src: " + hsSrcAddress +
756 ", dst: " + hsDstAddress +
757 ", ANonce: " + anonce +
758 ", counter: " + hsReplayCounter);
759 }
760 continue;
761 }
762
763 /* Handshake (4 of 4):
764 * mic:true
765 * ack:false
766 * install:false
767 * replay_couner: <same as Handshake (3 of 4)>
768 * (And/Or) key_data_length == 0 (data === undefined)
769 *
770 * Extract:
771 * - MIC
772 * - "EAPOL frame"
773 */
774 if (mic && !ack && !install
775 && hsReplayCounter && hsReplayCounter === frame.auth.replayCounter
776 && dataLength === 0) {
777 if (!anonce) {
778 // Require handshake #3.
779 continue;
780 }
781
782 hsMic = frame.auth.keyMIC;
783 hsKeyDescriptorVersion = frame.auth.keyInfoFlags.keyDescriptorVersion;
784 var eapolFrameBytes = frame.bytes;
785 eapolFrameBytes = eapolFrameBytes.substring(0, eapolFrameBytes.length - 36);
786 for (var j = 0; j < 36; j++) {
787 eapolFrameBytes += "0";
788 }
789 if (CapFile.debug) {
790 CapFile.debug("Handshake (4 of 4): MIC: " + hsMic + ", eapolFrameBytes: " + eapolFrameBytes);
791 }
792
793 handshakes.push({
794 ssid: ssid,
795 bssid: bssid,
796 snonce: snonce,
797 anonce: anonce,
798 srcAddress: hsSrcAddress,
799 dstAddress: hsDstAddress,
800 keyLength: hsKeyLength,
801 mic: hsMic,
802 eapolFrameBytes: eapolFrameBytes,
803 keyDescriptorVersion: hsKeyDescriptorVersion
804 });
805 continue;
806 }
807 }
808 }
809
810 if (bssids.length === 0) {
811 throw Error("No SSIDs found");
812 }
813
814 if (handshakes.length === 0) {
815 throw Error("No handshakes found");
816 }
817
818 // TODO: Return all handshakes? Or just the first one?
819 if (CapFile.debug) {
820 CapFile.debug("Captured " + handshakes.length + " 4-way handshakes: " + JSON.stringify(handshakes));
821 CapFile.debug("Using first 4-way handshake captured: " + JSON.stringify(handshakes[0]));
822 }
823 return handshakes[0];
824
825};