Browse Source

Compatibility with new scapy versions

Mathy 5 years ago
parent
commit
6ac7beacc5
5 changed files with 79 additions and 27 deletions
  1. 5 3
      README.md
  2. 1 1
      hostapd/hostapd.conf
  3. 2 2
      krackattack/krack-test-client.py
  4. 68 21
      krackattack/libwifi.py
  5. 3 0
      krackattack/requirements.txt

+ 5 - 3
README.md

@@ -7,9 +7,9 @@ Remember that our scripts are not attack scripts! You will need the appropriate
 Our scripts were tested on Kali Linux. To install the required dependencies on Kali, execute:
 
 	apt-get update
-	apt-get install libnl-3-dev libnl-genl-3-dev pkg-config libssl-dev net-tools git sysfsutils python-scapy python-pycryptodome
+	apt-get install libnl-3-dev libnl-genl-3-dev pkg-config libssl-dev net-tools git sysfsutils python-scapy python-pycryptodome virtualenv
 
-Then **disable hardware encryption** using the script `./krackattack/disable-hwcrypto.sh`. It's recommended to reboot after executing this script. After plugging in your Wi-Fi NIC, use `systool -vm ath9k_htc` or similar to confirm the nohwcript/swcrypto/hwcrypto parameter has been set. We tested our scripts with an Intel Dual Band Wireless-AC 7260 and a TP-Link TL-WN722N v1 on Kali Linux.
+Then **disable hardware encryption** using the script `./krackattack/disable-hwcrypto.sh`. It's recommended to reboot after executing this script. If you want to confirm this script work, execute `systool -vm ath9k_htc` or similar after plugging in your Wi-Fi NIC to confirm the nohwcript/swcrypto/hwcrypto parameter has been set. We tested our scripts with an Intel Dual Band Wireless-AC 7260 and a TP-Link TL-WN722N v1 on Kali Linux.
 
 Finally compile our modified hostapd instance:
 
@@ -19,11 +19,13 @@ Finally compile our modified hostapd instance:
 
 Remember to disable Wi-Fi in your network manager before using our scripts. After disabling Wi-Fi, execute `sudo rfkill unblock wifi` so our scripts can still use Wi-Fi.
 
+To assure you're using the correct version of scapy, you can create a virtualenv with the dependencies listed in `krackattack/requirements.txt`.
+
 # Testing Clients
 
 First modify `hostapd/hostapd.conf` and **edit the line `interface=` to specify the Wi-Fi interface** that will be used to execute the tests. Note that for all tests, once the script is running, you must let the device being tested connect to the **SSID testnetwork using the password abcdefgh**. You can change settings of the AP by modifying `hostapd/hostapd.conf`. In all tests the **client must use DHCP to get an IP** after connecting to the Wi-Fi network. This is because some tests only start after the client has requested an IP using DHCP!
 
-You should now run the following tests:
+You should now run the following tests located in the `krackattacks/` directory:
 
 1. **`./krack-test-client.py --replay-broadcast`**. This tests whether the client acceps replayed broadcast frames. If the client accepts replayed broadcast frames, this must be patched first. If you do not patch the client, our script will not be able to determine if the group key is being reinstalled (because then the script will always say the group key is being reinstalled).
 2. **`./krack-test-client.py --group --gtkinit`**. This tests whether the client installs the group key in the group key handshake with the given receive sequence counter (RSC). See section 6.4 of our [follow-up research paper(https://papers.mathyvanhoef.com/ccs2018.pdf)] for the details behind this vulnerability.

+ 1 - 1
hostapd/hostapd.conf

@@ -5,7 +5,7 @@
 # management frames with the Host AP driver); wlan0 with many nl80211 drivers
 # Note: This attribute can be overridden by the values supplied with the '-i'
 # command line parameter.
-interface=wlp0s20u2
+interface=wlan0
 
 # In case of atheros and nl80211 driver interfaces, an additional
 # configuration parameter, bridge, may be used to notify hostapd if the

+ 2 - 2
krackattack/krack-test-client.py

@@ -298,7 +298,7 @@ class KRAckAttackClient():
 	def handle_replay(self, p):
 		"""Replayed frames (caused by a pairwise key reinstallation) are rejected by the kernel. This
 		function processes these frames manually so we can still test reinstallations of the group key."""
-		if not Dot11WEP in p: return
+		if not dot11_is_encrypted_data(p): return
 
 		# Reconstruct Ethernet header
 		clientmac = p.addr2
@@ -343,7 +343,7 @@ class KRAckAttackClient():
 			self.reset_client_info(clientmac)
 
 		# Inspect encrypt frames for IV reuse & handle replayed frames rejected by the kernel
-		elif p.addr1 == self.apmac and Dot11WEP in p:
+		elif p.addr1 == self.apmac and dot11_is_encrypted_data(p):
 			if not clientmac in self.clients:
 				self.clients[clientmac] = ClientState(clientmac, options=options)
 			client = self.clients[clientmac]

+ 68 - 21
krackattack/libwifi.py

@@ -23,6 +23,19 @@ def log(level, msg, color=None, showtime=True):
 	print (datetime.now().strftime('[%H:%M:%S] ') if showtime else " "*11) + COLORCODES.get(color, "") + msg + "\033[1;0m"
 
 
+#### Back-wards compatibility with older scapy
+
+if not "Dot11FCS" in locals():
+	class Dot11FCS():
+		pass
+if not "Dot11Encrypted" in locals():
+	class Dot11Encrypted():
+		pass
+	class Dot11CCMP():
+		pass
+	class Dot11TKIP():
+		pass
+
 #### Packet Processing Functions ####
 
 class DHCP_sock(DHCP_am):
@@ -76,12 +89,12 @@ class MitmSocket(L2Socket):
 
 	def send(self, p):
 		# Hack: set the More Data flag so we can detect injected frames (and so clients stay awake longer)
-		p[Dot11].FCfield |= 0x20
+		p.FCfield |= 0x20
 		L2Socket.send(self, RadioTap()/p)
 
 	def _strip_fcs(self, p):
-		# Scapy can't handle the optional Frame Check Sequence (FCS) field automatically
-		if p[RadioTap].present & 2 != 0:
+		# Older scapy can't handle the optional Frame Check Sequence (FCS) field automatically
+		if p[RadioTap].present & 2 != 0 and not Dot11FCS in p:
 			rawframe = str(p[RadioTap])
 			pos = 8
 			while ord(rawframe[pos - 1]) & 0x80 != 0: pos += 4
@@ -99,33 +112,62 @@ class MitmSocket(L2Socket):
 
 	def recv(self, x=MTU):
 		p = L2Socket.recv(self, x)
-		if p == None or not Dot11 in p: return None
+		if p == None or not (Dot11 in p or Dot11FCS in p):
+			return None
 
 		# Hack: ignore frames that we just injected and are echoed back by the kernel
-		if p[Dot11].FCfield & 0x20 != 0:
+		if p.FCfield & 0x20 != 0:
 			return None
 
 		# Strip the FCS if present, and drop the RadioTap header
-		return self._strip_fcs(p)
+		if Dot11FCS in p:
+			return p
+		else:
+			return self._strip_fcs(p)
 
 	def close(self):
 		super(MitmSocket, self).close()
 
 def dot11_get_seqnum(p):
-	return p[Dot11].SC >> 4
+	return p.SC >> 4
+
+def dot11_is_encrypted_data(p):
+	# All these different cases are explicitly tested to handle older scapy versions
+	return (p.FCfield & 0x40) or Dot11CCMP in p or Dot11TKIP in p or Dot11WEP in p or Dot11Encrypted in p
+
+def payload_to_iv(payload):
+	iv0 = payload[0]
+	iv1 = payload[1]
+	wepdata = payload[4:8]
+
+	# FIXME: Only CCMP is supported (TKIP uses a different IV structure)
+	return ord(iv0) + (ord(iv1) << 8) + (struct.unpack(">I", wepdata)[0] << 16)
 
 def dot11_get_iv(p):
-	"""Scapy can't handle Extended IVs, so do this properly ourselves (only works for CCMP)"""
-	if Dot11WEP not in p:
-		log(ERROR, "INTERNAL ERROR: Requested IV of plaintext frame")
-		return 0
+	"""
+	Assume it's a CCMP frame. Old scapy can't handle Extended IVs.
+	This code only works for CCMP frames.
+	"""
+	if Dot11CCMP in p or Dot11TKIP in p or Dot11Encrypted in p:
+		# Scapy uses a heuristic to differentiate CCMP/TKIP and this may be wrong.
+		# So even when we get a Dot11TKIP frame, we should treat it like a Dot11CCMP frame.
+		payload = str(p[Dot11Encrypted])
+		return payload_to_iv(payload)
+
+	elif Dot11WEP in p:
+		wep = p[Dot11WEP]
+		if wep.keyid & 32:
+			# FIXME: Only CCMP is supported (TKIP uses a different IV structure)
+			return ord(wep.iv[0]) + (ord(wep.iv[1]) << 8) + (struct.unpack(">I", wep.wepdata[:4])[0] << 16)
+		else:
+			return ord(wep.iv[0]) + (ord(wep.iv[1]) << 8) + (ord(wep.iv[2]) << 16)
+
+	elif p.FCfield & 0x40:
+		return payload_to_iv(p[Raw].load)
 
-	wep = p[Dot11WEP]
-	if wep.keyid & 32:
-		# FIXME: Only CCMP is supported (TKIP uses a different IV structure)
-		return ord(wep.iv[0]) + (ord(wep.iv[1]) << 8) + (struct.unpack(">I", wep.wepdata[:4])[0] << 16)
 	else:
-		return ord(wep.iv[0]) + (ord(wep.iv[1]) << 8) + (ord(wep.iv[2]) << 16)
+		log(ERROR, "INTERNAL ERROR: Requested IV of plaintext frame")
+		return 0
 
 def get_tlv_value(p, type):
 	if not Dot11Elt in p: return None
@@ -144,14 +186,19 @@ def dot11_get_priority(p):
 #### Crypto functions and util ####
 
 def get_ccmp_payload(p):
-	# Extract encrypted payload:
-	# - Skip extended IV (4 bytes in total)
-	# - Exclude first 4 bytes of the CCMP MIC (note that last 4 are saved in the WEP ICV field)
-	return str(p.wepdata[4:-4])
+	if Dot11WEP in p:
+		# Extract encrypted payload:
+		# - Skip extended IV (4 bytes in total)
+		# - Exclude first 4 bytes of the CCMP MIC (note that last 4 are saved in the WEP ICV field)
+		return str(p.wepdata[4:-4])
+	elif Dot11CCMP in p or Dot11TKIP in p or Dot11Encrypted in p:
+		return p[Dot11Encrypted].data
+	else:
+		return p[Raw].load
 
 def decrypt_ccmp(p, key):
 	payload   = get_ccmp_payload(p)
-	sendermac = p[Dot11].addr2
+	sendermac = p.addr2
 	priority  = dot11_get_priority(p)
 	iv        = dot11_get_iv(p)
 	pn        = struct.pack(">I", iv >> 16) + struct.pack(">H", iv & 0xFFFF)

+ 3 - 0
krackattack/requirements.txt

@@ -0,0 +1,3 @@
+pkg-resources==0.0.0
+pycryptodomex==3.9.4
+scapy==2.4.3