wifi.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. # Copyright (c) 2019-2020, Mathy Vanhoef <mathy.vanhoef@nyu.edu>
  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. #### Constants ####
  10. IEEE_TLV_TYPE_SSID = 0
  11. IEEE_TLV_TYPE_CHANNEL = 3
  12. IEEE_TLV_TYPE_RSN = 48
  13. IEEE_TLV_TYPE_CSA = 37
  14. IEEE_TLV_TYPE_FT = 55
  15. IEEE_TLV_TYPE_VENDOR = 221
  16. WLAN_REASON_DISASSOC_DUE_TO_INACTIVITY = 4
  17. WLAN_REASON_CLASS2_FRAME_FROM_NONAUTH_STA = 6
  18. WLAN_REASON_CLASS3_FRAME_FROM_NONASSOC_STA = 7
  19. #TODO: Not sure if really needed...
  20. IEEE80211_RADIOTAP_RATE = (1 << 2)
  21. IEEE80211_RADIOTAP_CHANNEL = (1 << 3)
  22. IEEE80211_RADIOTAP_TX_FLAGS = (1 << 15)
  23. IEEE80211_RADIOTAP_DATA_RETRIES = (1 << 17)
  24. #### Basic output and logging functionality ####
  25. ALL, DEBUG, INFO, STATUS, WARNING, ERROR = range(6)
  26. COLORCODES = { "gray" : "\033[0;37m",
  27. "green" : "\033[0;32m",
  28. "orange": "\033[0;33m",
  29. "red" : "\033[0;31m" }
  30. global_log_level = INFO
  31. def log(level, msg, color=None, showtime=True):
  32. if level < global_log_level: return
  33. if level == DEBUG and color is None: color="gray"
  34. if level == WARNING and color is None: color="orange"
  35. if level == ERROR and color is None: color="red"
  36. msg = (datetime.now().strftime('[%H:%M:%S] ') if showtime else " "*11) + COLORCODES.get(color, "") + msg + "\033[1;0m"
  37. print(msg)
  38. def change_log_level(delta):
  39. global global_log_level
  40. global_log_level += delta
  41. #### Back-wards compatibility with older scapy
  42. if not "Dot11FCS" in locals():
  43. class Dot11FCS():
  44. pass
  45. if not "Dot11Encrypted" in locals():
  46. class Dot11Encrypted():
  47. pass
  48. class Dot11CCMP():
  49. pass
  50. class Dot11TKIP():
  51. pass
  52. #### Linux ####
  53. def get_device_driver(iface):
  54. path = "/sys/class/net/%s/device/driver" % iface
  55. try:
  56. output = subprocess.check_output(["readlink", "-f", path])
  57. return output.decode('utf-8').strip().split("/")[-1]
  58. except:
  59. return None
  60. #### Utility ####
  61. def get_mac_address(interface):
  62. return open("/sys/class/net/%s/address" % interface).read().strip()
  63. def addr2bin(addr):
  64. return binascii.a2b_hex(addr.replace(':', ''))
  65. def get_channel(iface):
  66. output = str(subprocess.check_output(["iw", iface, "info"]))
  67. p = re.compile("channel (\d+)")
  68. m = p.search(output)
  69. if m == None: return None
  70. return int(m.group(1))
  71. def get_channel(iface):
  72. output = str(subprocess.check_output(["iw", iface, "info"]))
  73. p = re.compile("channel (\d+)")
  74. m = p.search(output)
  75. if m == None:
  76. return None
  77. return int(m.group(1))
  78. def set_channel(iface, channel):
  79. subprocess.check_output(["iw", iface, "set", "channel", str(channel)])
  80. def set_macaddress(iface, macaddr):
  81. # macchanger throws an error if the interface already has the given MAC address
  82. if get_macaddress(iface) != macaddr:
  83. subprocess.check_output(["ifconfig", iface, "down"])
  84. subprocess.check_output(["macchanger", "-m", macaddr, iface])
  85. def get_macaddress(iface):
  86. """This works even for interfaces in monitor mode."""
  87. s = get_if_raw_hwaddr(iface)[1]
  88. return ("%02x:" * 6)[:-1] % tuple(orb(x) for x in s)
  89. def get_iface_type(iface):
  90. output = str(subprocess.check_output(["iw", iface, "info"]))
  91. p = re.compile("type (\w+)")
  92. return str(p.search(output).group(1))
  93. def set_monitor_mode(iface, up=True, mtu=1500):
  94. # Note: we let the user put the device in monitor mode, such that they can control optional
  95. # parameters such as "iw wlan0 set monitor active" for devices that support it.
  96. if get_iface_type(iface) != "monitor":
  97. # Some kernels (Debian jessie - 3.16.0-4-amd64) don't properly add the monitor interface. The following ugly
  98. # sequence of commands assures the virtual interface is properly registered as a 802.11 monitor interface.
  99. subprocess.check_output(["ifconfig", iface, "down"])
  100. subprocess.check_output(["iw", iface, "set", "type", "monitor"])
  101. time.sleep(0.5)
  102. subprocess.check_output(["iw", iface, "set", "type", "monitor"])
  103. if up:
  104. subprocess.check_output(["ifconfig", iface, "up"])
  105. subprocess.check_output(["ifconfig", iface, "mtu", str(mtu)])
  106. def rawmac(addr):
  107. return bytes.fromhex(addr.replace(':', ''))
  108. def set_amsdu(p):
  109. if "A_MSDU_Present" in [field.name for field in Dot11QoS.fields_desc]:
  110. p.A_MSDU_Present = 1
  111. else:
  112. p.Reserved = 1
  113. def is_amsdu(p):
  114. if "A_MSDU_Present" in [field.name for field in Dot11QoS.fields_desc]:
  115. return p.A_MSDU_Present == 1
  116. else:
  117. return p.Reserved == 1
  118. #### Packet Processing Functions ####
  119. class DHCP_sock(DHCP_am):
  120. def __init__(self, **kwargs):
  121. self.sock = kwargs.pop("sock")
  122. self.server_ip = kwargs["gw"]
  123. super(DHCP_sock, self).__init__(**kwargs)
  124. def prealloc_ip(self, clientmac, ip=None):
  125. """Allocate an IP for the client before it send DHCP requests"""
  126. if clientmac not in self.leases:
  127. if ip == None:
  128. ip = self.pool.pop()
  129. self.leases[clientmac] = ip
  130. return self.leases[clientmac]
  131. def make_reply(self, req):
  132. rep = super(DHCP_sock, self).make_reply(req)
  133. # Fix scapy bug: set broadcast IP if required
  134. if rep is not None and BOOTP in req and IP in rep:
  135. if req[BOOTP].flags & 0x8000 != 0 and req[BOOTP].giaddr == '0.0.0.0' and req[BOOTP].ciaddr == '0.0.0.0':
  136. rep[IP].dst = "255.255.255.255"
  137. # Explicitly set source IP if requested
  138. if not self.server_ip is None:
  139. rep[IP].src = self.server_ip
  140. return rep
  141. def send_reply(self, reply):
  142. self.sock.send(reply, **self.optsend)
  143. def print_reply(self, req, reply):
  144. log(STATUS, "%s: DHCP reply %s to %s" % (reply.getlayer(Ether).dst, reply.getlayer(BOOTP).yiaddr, reply.dst), color="green")
  145. def remove_client(self, clientmac):
  146. clientip = self.leases[clientmac]
  147. self.pool.append(clientip)
  148. del self.leases[clientmac]
  149. class ARP_sock(ARP_am):
  150. def __init__(self, **kwargs):
  151. self.sock = kwargs.pop("sock")
  152. super(ARP_am, self).__init__(**kwargs)
  153. def send_reply(self, reply):
  154. self.sock.send(reply, **self.optsend)
  155. def print_reply(self, req, reply):
  156. log(STATUS, "%s: ARP: %s ==> %s on %s" % (reply.getlayer(Ether).dst, req.summary(), reply.summary(), self.iff))
  157. #### Packet Processing Functions ####
  158. # Compatibility with older Scapy versions
  159. if not "ORDER" in scapy.layers.dot11._rt_txflags:
  160. scapy.layers.dot11._rt_txflags.append("ORDER")
  161. class MonitorSocket(L2Socket):
  162. def __init__(self, iface, dumpfile=None, detect_injected=False, **kwargs):
  163. super(MonitorSocket, self).__init__(iface, **kwargs)
  164. self.pcap = None
  165. if dumpfile:
  166. self.pcap = PcapWriter("%s.%s.pcap" % (dumpfile, self.iface), append=False, sync=True)
  167. self.detect_injected = detect_injected
  168. self.default_rate = None
  169. def set_channel(self, channel):
  170. subprocess.check_output(["iw", self.iface, "set", "channel", str(channel)])
  171. def attach_filter(self, bpf):
  172. log(DEBUG, "Attaching filter to %s: <%s>" % (self.iface, bpf))
  173. attach_filter(self.ins, bpf, self.iface)
  174. def set_default_rate(self, rate):
  175. self.default_rate = rate
  176. def send(self, p, rate=None):
  177. # Hack: set the More Data flag so we can detect injected frames (and so clients stay awake longer)
  178. if self.detect_injected:
  179. p.FCfield |= 0x20
  180. # Control data rate injected frames
  181. if rate is None and self.default_rate is None:
  182. rtap = RadioTap(present="TXFlags", TXFlags="NOSEQ+ORDER")
  183. else:
  184. use_rate = rate if rate != None else self.default_rate
  185. rtap = RadioTap(present="TXFlags+Rate", Rate=use_rate, TXFlags="NOSEQ+ORDER")
  186. L2Socket.send(self, rtap/p)
  187. if self.pcap: self.pcap.write(RadioTap()/p)
  188. def _strip_fcs(self, p):
  189. """
  190. Scapy may throw exceptions when handling malformed short frames,
  191. so we need to catch these exceptions and just ignore these frames.
  192. This in particular happened with short malformed beacons.
  193. """
  194. try:
  195. return Dot11(raw(p[Dot11FCS])[:-4])
  196. except:
  197. return None
  198. def _detect_and_strip_fcs(self, p):
  199. # Older scapy can't handle the optional Frame Check Sequence (FCS) field automatically
  200. if p[RadioTap].present & 2 != 0 and not Dot11FCS in p:
  201. rawframe = raw(p[RadioTap])
  202. pos = 8
  203. while orb(rawframe[pos - 1]) & 0x80 != 0: pos += 4
  204. # If the TSFT field is present, it must be 8-bytes aligned
  205. if p[RadioTap].present & 1 != 0:
  206. pos += (8 - (pos % 8))
  207. pos += 8
  208. # Remove FCS if present
  209. if orb(rawframe[pos]) & 0x10 != 0:
  210. return self._strip_fcs(p)
  211. return p[Dot11]
  212. def recv(self, x=MTU, reflected=False):
  213. p = L2Socket.recv(self, x)
  214. if p == None or not (Dot11 in p or Dot11FCS in p):
  215. return None
  216. if self.pcap:
  217. self.pcap.write(p)
  218. # Hack: ignore frames that we just injected and are echoed back by the kernel
  219. if self.detect_injected and p.FCfield & 0x20 != 0:
  220. return None
  221. # Ignore reflection of injected frames. These have a small RadioTap header.
  222. if not reflected and p[RadioTap].len < 13:
  223. return None
  224. # Strip the FCS if present, and drop the RadioTap header
  225. if Dot11FCS in p:
  226. return self._strip_fcs(p)
  227. else:
  228. return self._detect_and_strip_fcs(p)
  229. def close(self):
  230. if self.pcap: self.pcap.close()
  231. super(MonitorSocket, self).close()
  232. # For backwards compatibility
  233. class MitmSocket(MonitorSocket):
  234. pass
  235. def dot11_get_seqnum(p):
  236. return p.SC >> 4
  237. def dot11_is_encrypted_data(p):
  238. # All these different cases are explicitly tested to handle older scapy versions
  239. return (p.FCfield & 0x40) or Dot11CCMP in p or Dot11TKIP in p or Dot11WEP in p or Dot11Encrypted in p
  240. def payload_to_iv(payload):
  241. iv0 = payload[0]
  242. iv1 = payload[1]
  243. wepdata = payload[4:8]
  244. # FIXME: Only CCMP is supported (TKIP uses a different IV structure)
  245. return orb(iv0) + (orb(iv1) << 8) + (struct.unpack(">I", wepdata)[0] << 16)
  246. def dot11_get_iv(p):
  247. """
  248. Assume it's a CCMP frame. Old scapy can't handle Extended IVs.
  249. This code only works for CCMP frames.
  250. """
  251. if Dot11CCMP in p:
  252. payload = raw(p[Dot11CCMP])
  253. return payload_to_iv(payload)
  254. elif Dot11TKIP in p:
  255. # Scapy uses a heuristic to differentiate CCMP/TKIP and this may be wrong.
  256. # So even when we get a Dot11TKIP frame, we should treat it like a Dot11CCMP frame.
  257. payload = raw(p[Dot11TKIP])
  258. return payload_to_iv(payload)
  259. if Dot11CCMP in p:
  260. payload = raw(p[Dot11CCMP])
  261. return payload_to_iv(payload)
  262. elif Dot11TKIP in p:
  263. payload = raw(p[Dot11TKIP])
  264. return payload_to_iv(payload)
  265. elif Dot11Encrypted in p:
  266. payload = raw(p[Dot11Encrypted])
  267. return payload_to_iv(payload)
  268. elif Dot11WEP in p:
  269. wep = p[Dot11WEP]
  270. if wep.keyid & 32:
  271. # FIXME: Only CCMP is supported (TKIP uses a different IV structure)
  272. return orb(wep.iv[0]) + (orb(wep.iv[1]) << 8) + (struct.unpack(">I", wep.wepdata[:4])[0] << 16)
  273. else:
  274. return orb(wep.iv[0]) + (orb(wep.iv[1]) << 8) + (orb(wep.iv[2]) << 16)
  275. elif p.FCfield & 0x40:
  276. return payload_to_iv(p[Raw].load)
  277. else:
  278. return None
  279. def dot11_get_priority(p):
  280. if not Dot11QoS in p: return 0
  281. return p[Dot11QoS].TID
  282. #### Crypto functions and util ####
  283. def get_ccmp_keyid(p):
  284. if Dot11WEP in p:
  285. return p.keyid
  286. return p.key_id
  287. def get_ccmp_payload(p):
  288. if Dot11WEP in p:
  289. # Extract encrypted payload:
  290. # - Skip extended IV (4 bytes in total)
  291. # - Exclude first 4 bytes of the CCMP MIC (note that last 4 are saved in the WEP ICV field)
  292. return raw(p.wepdata[4:-4])
  293. elif Dot11CCMP in p:
  294. return p[Dot11CCMP].data
  295. elif Dot11TKIP in p:
  296. return p[Dot11TKIP].data
  297. elif Dot11Encrypted in p:
  298. return p[Dot11Encrypted].data
  299. elif Raw in p:
  300. return p[Raw].load
  301. else:
  302. return None
  303. class IvInfo():
  304. def __init__(self, p):
  305. self.iv = dot11_get_iv(p)
  306. self.seq = dot11_get_seqnum(p)
  307. self.time = p.time
  308. def is_reused(self, p):
  309. """Return true if frame p reuses an IV and if p is not a retransmitted frame"""
  310. iv = dot11_get_iv(p)
  311. seq = dot11_get_seqnum(p)
  312. return self.iv == iv and self.seq != seq and p.time >= self.time + 1
  313. class IvCollection():
  314. def __init__(self):
  315. self.ivs = dict() # maps IV values to IvInfo objects
  316. def reset(self):
  317. self.ivs = dict()
  318. def track_used_iv(self, p):
  319. iv = dot11_get_iv(p)
  320. self.ivs[iv] = IvInfo(p)
  321. def is_iv_reused(self, p):
  322. """Returns True if this is an *observed* IV reuse and not just a retransmission"""
  323. iv = dot11_get_iv(p)
  324. return iv in self.ivs and self.ivs[iv].is_reused(p)
  325. def is_new_iv(self, p):
  326. """Returns True if the IV in this frame is higher than all previously observed ones"""
  327. iv = dot11_get_iv(p)
  328. if len(self.ivs) == 0: return True
  329. return iv > max(self.ivs.keys())
  330. def create_fragments(header, data, num_frags):
  331. # This special case is useful so scapy keeps the full "interpretation" of the frame
  332. # instead of afterwards treating/displaying the payload as just raw data.
  333. if num_frags == 1: return [header/data]
  334. data = raw(data)
  335. fragments = []
  336. fragsize = (len(data) + num_frags - 1) // num_frags
  337. for i in range(num_frags):
  338. frag = header.copy()
  339. frag.SC |= i
  340. if i < num_frags - 1:
  341. frag.FCfield |= Dot11(FCfield="MF").FCfield
  342. payload = data[fragsize * i : fragsize * (i + 1)]
  343. frag = frag/Raw(payload)
  344. fragments.append(frag)
  345. return fragments
  346. def get_element(el, id):
  347. if not Dot11Elt in el: return None
  348. el = el[Dot11Elt]
  349. while not el is None:
  350. if el.ID == id:
  351. return el
  352. el = el.payload
  353. return None
  354. def get_ssid(beacon):
  355. if not (Dot11 in beacon or Dot11FCS in beacon): return
  356. if Dot11Elt not in beacon: return
  357. if beacon[Dot11].type != 0 and beacon[Dot11].subtype != 8: return
  358. el = get_element(beacon, IEEE_TLV_TYPE_SSID)
  359. return el.info.decode()
  360. def is_from_sta(p, macaddr):
  361. if not (Dot11 in p or Dot11FCS in p):
  362. return False
  363. if p.addr1 != macaddr and p.addr2 != macaddr:
  364. return False
  365. return True
  366. def get_bss(iface, clientmac, timeout=20):
  367. ps = sniff(count=1, timeout=timeout, lfilter=lambda p: is_from_sta(p, clientmac), iface=iface)
  368. if len(ps) == 0:
  369. return None
  370. return ps[0].addr1 if ps[0].addr1 != clientmac else ps[0].addr2
  371. def create_msdu_subframe(src, dst, payload, last=False):
  372. length = len(payload)
  373. p = Ether(dst=dst, src=src, type=length)
  374. payload = raw(payload)
  375. total_length = len(p) + len(payload)
  376. padding = ""
  377. if not last and total_length % 4 != 0:
  378. padding = b"\x00" * (4 - (total_length % 4))
  379. return p / payload / Raw(padding)
  380. def find_network(iface, ssid):
  381. ps = sniff(count=1, timeout=0.3, lfilter=lambda p: get_ssid(p) == ssid, iface=iface)
  382. if ps is None or len(ps) < 1:
  383. log(STATUS, "Searching for target network on other channels")
  384. for chan in [1, 6, 11, 3, 8, 2, 7, 4, 10, 5, 9, 12, 13]:
  385. set_channel(iface, chan)
  386. log(DEBUG, "Listening on channel %d" % chan)
  387. ps = sniff(count=1, timeout=0.3, lfilter=lambda p: get_ssid(p) == ssid, iface=iface)
  388. if ps and len(ps) >= 1: break
  389. if ps and len(ps) >= 1:
  390. # Even though we capture the beacon we might still be on another channel,
  391. # so it's important to explicitly switch to the correct channel.
  392. actual_chan = orb(get_element(ps[0], IEEE_TLV_TYPE_CHANNEL).info)
  393. set_channel(iface, actual_chan)
  394. # Return the beacon that we captured
  395. return ps[0]
  396. return None