5fd5c45 · 3 years ago 4 commits
  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};