From 2c838fea489c333f72cf7a1c0b044ad9632cc56f Mon Sep 17 00:00:00 2001 From: threadpoolx Date: Sun, 5 Jul 2026 20:43:12 +0530 Subject: [PATCH 1/4] Fix Scapy 2.5+ and Python 3.10+ compatibility Scapy 2.5.0 removed L2Socket, attach_filter, and get_if_raw_hwaddr from its wildcard export and moved scapy.arch.common.get_if_raw_hwaddr; add explicit imports so the tool works on current Kali/Debian installs. - libwifi/wifi.py: import L2Socket and attach_filter from scapy.arch; wrap _rt_txflags mutation in try/except for forward compatibility; fix get_ccmp_payload str->bytes (Python 3 correctness); fix create_msdu_subframe padding initialised as str instead of bytes; use raw strings for regex patterns (SyntaxWarning in Python 3.12+) - fraginternals.py: fallback import for get_if_raw_hwaddr; add 0.5s sleep + clear error message after virtual monitor interface creation to mitigate mt76 null-pointer dereference (issue #60); raw string for regex - requirements.txt: replace 2020-era exact pins with >= minimums, drop Python 3.7-only packages (importlib-metadata, zipp, py), require scapy>=2.5.0 - pysetup.sh: guard BPF sed patch so it doesn't fail when file absent - fragattack.py: suppress DeprecationWarnings from scapy.layers.ipsec Co-Authored-By: Claude Sonnet 4.6 --- research/fragattack.py | 3 ++- research/fraginternals.py | 19 ++++++++++++++++--- research/libwifi/wifi.py | 18 +++++++++++------- research/pysetup.sh | 8 ++++++-- research/requirements.txt | 27 +++++++++++---------------- 5 files changed, 46 insertions(+), 29 deletions(-) diff --git a/research/fragattack.py b/research/fragattack.py index 6091f4e64..019c7e219 100755 --- a/research/fragattack.py +++ b/research/fragattack.py @@ -5,7 +5,8 @@ # See README for more details. # Note that tests_*.py files are imported automatically -import glob, importlib, argparse +import glob, importlib, argparse, warnings +warnings.filterwarnings("ignore", category=DeprecationWarning) from fraginternals import * # ----------------------------------- Main Function ----------------------------------- diff --git a/research/fraginternals.py b/research/fraginternals.py index 09d054ecd..28bfd005b 100644 --- a/research/fraginternals.py +++ b/research/fraginternals.py @@ -9,7 +9,10 @@ import os.path from wpaspy import Ctrl from scapy.contrib.wpa_eapol import WPA_key -from scapy.arch.common import get_if_raw_hwaddr +try: + from scapy.arch.common import get_if_raw_hwaddr +except ImportError: + from scapy.arch import get_if_raw_hwaddr FRAGVERSION = "1.3" @@ -779,7 +782,17 @@ def configure_interfaces(self): scapy.arch.get_if_index(self.nic_mon) except IOError: subprocess.call(["iw", self.nic_mon, "del"], stdout=subprocess.PIPE, stdin=subprocess.PIPE) - subprocess.check_output(["iw", self.nic_iface, "interface", "add", self.nic_mon, "type", "monitor"]) + try: + subprocess.check_output(["iw", self.nic_iface, "interface", "add", self.nic_mon, "type", "monitor"], + stderr=subprocess.PIPE) + except subprocess.CalledProcessError as ex: + log(ERROR, "Failed to create virtual monitor interface {}: {}".format(self.nic_mon, ex.stderr.decode().strip())) + log(ERROR, "Try using --inject {} to skip virtual interface creation.".format(self.nic_iface)) + quit(1) + # Give the kernel time to initialise the new virtual interface before use. + # Some drivers (e.g. mt76) crash with a null-pointer dereference if we + # open a socket on the interface immediately after iw returns. + time.sleep(0.5) # 2.A Remember whether to need to use injection workarounds. driver = get_device_driver(self.nic_mon) @@ -1242,7 +1255,7 @@ def handle_wpaspy(self, msg): # When using a separate interface to inject, switch to correct channel self.follow_channel() - p = re.compile("Associated with (.*)") + p = re.compile(r"Associated with (.*)") bss = p.search(msg).group(1) self.station.handle_connecting(bss) diff --git a/research/libwifi/wifi.py b/research/libwifi/wifi.py index 3bd229b21..94829439c 100644 --- a/research/libwifi/wifi.py +++ b/research/libwifi/wifi.py @@ -3,6 +3,7 @@ # This code may be distributed under the terms of the BSD license. # See README for more details. from scapy.all import * +from scapy.arch import L2Socket, attach_filter from Crypto.Cipher import AES from datetime import datetime import binascii @@ -102,7 +103,7 @@ def addr2bin(addr): def get_channel(iface): output = str(subprocess.check_output(["iw", iface, "info"])) - p = re.compile("channel (\d+)") + p = re.compile(r"channel (\d+)") m = p.search(output) if m == None: return None return int(m.group(1)) @@ -123,7 +124,7 @@ def set_macaddress(iface, macaddr): def get_iface_type(iface): output = str(subprocess.check_output(["iw", iface, "info"])) - p = re.compile("type (\w+)") + p = re.compile(r"type (\w+)") return str(p.search(output).group(1)) def set_monitor_mode(iface, up=True, mtu=1500): @@ -220,9 +221,12 @@ def print_reply(self, req, reply): #### Packet Processing Functions #### -# Compatibility with older Scapy versions -if not "ORDER" in scapy.layers.dot11._rt_txflags: - scapy.layers.dot11._rt_txflags.append("ORDER") +# Compatibility with older Scapy versions — _rt_txflags may not exist or already contain ORDER +try: + if "ORDER" not in scapy.layers.dot11._rt_txflags: + scapy.layers.dot11._rt_txflags.append("ORDER") +except AttributeError: + pass class MonitorSocket(L2Socket): def __init__(self, iface, dumpfile=None, detect_injected=False, **kwargs): @@ -384,7 +388,7 @@ 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]) + return bytes(p.wepdata[4:-4]) elif Dot11CCMP in p: return p[Dot11CCMP].data elif Dot11TKIP in p: @@ -484,7 +488,7 @@ def create_msdu_subframe(src, dst, payload, last=False): payload = raw(payload) total_length = len(p) + len(payload) - padding = "" + padding = b"" if not last and total_length % 4 != 0: padding = b"\x00" * (4 - (total_length % 4)) diff --git a/research/pysetup.sh b/research/pysetup.sh index decca233d..13c5d2ec3 100755 --- a/research/pysetup.sh +++ b/research/pysetup.sh @@ -9,6 +9,10 @@ source venv/bin/activate pip install wheel pip install -r requirements.txt -# Fix a bug in scapy that isn't fixed in the PyPI version yet. For background see +# Fix a bug in scapy<=2.4.3 that isn't fixed in the PyPI version yet. For background see # https://github.com/secdev/scapy/commit/46fa40fde4049ad7770481f8806c59640df24059 -sed -i 's/find_library("libc")/find_library("c")/g' venv/lib/python*/site-packages/scapy/arch/bpf/core.py +# (no-op on scapy>=2.4.4 where this was already fixed upstream) +BPF_CORE="venv/lib/python*/site-packages/scapy/arch/bpf/core.py" +for f in $BPF_CORE; do + [ -f "$f" ] && sed -i 's/find_library("libc")/find_library("c")/g' "$f" +done diff --git a/research/requirements.txt b/research/requirements.txt index 9551abc2e..6f9dabf37 100644 --- a/research/requirements.txt +++ b/research/requirements.txt @@ -1,16 +1,11 @@ -attrs==19.3.0 -importlib-metadata==1.5.0 -more-itertools==8.2.0 -mpmath==1.1.0 -packaging==20.1 -pluggy==0.13.1 -py==1.10.0 -pycryptodome==3.9.7 -pyparsing==2.4.6 -pytest==5.3.5 -scapy==2.4.3 -simpy==3.0.11 -six==1.14.0 -sympy==1.5.1 -wcwidth==0.1.8 -zipp==3.0.0 +attrs>=21.0.0 +mpmath>=1.2.0 +packaging>=21.0 +pluggy>=1.0.0 +pycryptodome>=3.9.7 +pyparsing>=3.0.0 +pytest>=7.0.0 +scapy>=2.5.0 +simpy>=4.0.0 +six>=1.16.0 +sympy>=1.9.0 From 5ec2b281434a50cc5e517b4916816595a48fd003 Mon Sep 17 00:00:00 2001 From: threadpoolx Date: Sun, 5 Jul 2026 21:03:09 +0530 Subject: [PATCH 2/4] Fix Scapy 2.7 runtime compatibility (WPA_key, get_if_index, get_if_raw_hwaddr) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three more Scapy APIs removed in 2.5–2.7 that cause ImportError or AttributeError at startup on current Kali installs: - scapy.contrib.wpa_eapol.WPA_key removed: fall back to scapy.layers.eap.EAPOL_KEY and reconstruct the key_info integer from its individual bit fields via _eapol_key_info() - scapy.arch.get_if_index removed: monkey-patch it back onto scapy.arch using SIOCGIFINDEX ioctl so configure_interfaces() can check whether the virtual monitor interface already exists - scapy.arch.get_if_raw_hwaddr removed: fall back to SIOCGIFHWADDR ioctl in both fraginternals.py and libwifi/wifi.py Tested: test-injection.py passes all checks on ath9k_htc with Scapy 2.7 and Python 3.13; fragattack.py --help and startup run cleanly. Co-Authored-By: Claude Sonnet 4.6 --- research/fraginternals.py | 63 ++++++++++++++++++++++++++++++++++----- research/libwifi/wifi.py | 19 ++++++++++++ 2 files changed, 74 insertions(+), 8 deletions(-) diff --git a/research/fraginternals.py b/research/fraginternals.py index 28bfd005b..bd1962fdf 100644 --- a/research/fraginternals.py +++ b/research/fraginternals.py @@ -8,11 +8,57 @@ import abc, sys, socket, struct, time, subprocess, atexit, select, copy import os.path from wpaspy import Ctrl -from scapy.contrib.wpa_eapol import WPA_key +try: + from scapy.contrib.wpa_eapol import WPA_key + def _eapol_key_info(pkt): + return pkt[WPA_key].key_info +except ImportError: + # Scapy 2.5+ removed scapy.contrib.wpa_eapol; EAPOL key handling is + # now in scapy.layers.eap as EAPOL_KEY with individual bit fields. + from scapy.layers.eap import EAPOL_KEY as WPA_key + def _eapol_key_info(pkt): + ek = pkt[WPA_key] + return (ek.key_descriptor_type_version | + (ek.key_type << 3) | + (ek.install << 6) | + (ek.key_ack << 7) | + (ek.has_key_mic << 8) | + (ek.secure << 9) | + (ek.error << 10) | + (ek.request << 11) | + (ek.encrypted_key_data << 12) | + (ek.smk_message << 13)) try: from scapy.arch.common import get_if_raw_hwaddr except ImportError: - from scapy.arch import get_if_raw_hwaddr + try: + from scapy.arch import get_if_raw_hwaddr + except ImportError: + # Scapy 2.7+ removed get_if_raw_hwaddr on Linux; implement via ioctl + import socket as _socket, struct as _struct, fcntl as _fcntl + def get_if_raw_hwaddr(iface): + SIOCGIFHWADDR = 0x8927 + s = _socket.socket(_socket.AF_INET, _socket.SOCK_DGRAM, 0) + try: + d = _struct.pack('256s', iface[:15].encode()) + res = _fcntl.ioctl(s.fileno(), SIOCGIFHWADDR, d) + return _struct.unpack_from('H6s', res, 16) + finally: + s.close() + +# scapy.arch.get_if_index was removed in Scapy 2.7; patch it back in via ioctl +if not hasattr(scapy.arch, 'get_if_index'): + import fcntl as _fcntl + def _get_if_index(iface): + SIOCGIFINDEX = 0x8933 + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, 0) + try: + d = struct.pack('256s', iface[:15].encode()) + res = _fcntl.ioctl(s.fileno(), SIOCGIFINDEX, d) + return struct.unpack_from('I', res, 16)[0] + finally: + s.close() + scapy.arch.get_if_index = _get_if_index FRAGVERSION = "1.3" @@ -495,11 +541,12 @@ def trigger_eapol_events(self, eapol): # Track return value of possible trigger Action function result = None - key_type = eapol.key_info & 0x0008 - key_ack = eapol.key_info & 0x0080 - key_mic = eapol.key_info & 0x0100 - key_secure = eapol.key_info & 0x0200 - key_request = eapol.key_info & 0x0800 + key_info = _eapol_key_info(eapol) + key_type = key_info & 0x0008 + key_ack = key_info & 0x0080 + key_mic = key_info & 0x0100 + key_secure = key_info & 0x0200 + key_request = key_info & 0x0800 # Detect Msg3/4 assumig WPA2 is used --- XXX support WPA1 as well is_msg3_or_4 = key_secure != 0 @@ -540,7 +587,7 @@ def handle_eapol_tx(self, eapol, dstmac): # - Some routers such as the RT-AC51U do the 4-way rekey HS in plaintext. plaintext = self.options.rekey_plaintext - if WPA_key in eapol and eapol[WPA_key].key_info & 2048: + if WPA_key in eapol and _eapol_key_info(eapol) & 0x0800: plaintext = False self.send_mon(eapol, plaintext=plaintext) diff --git a/research/libwifi/wifi.py b/research/libwifi/wifi.py index 94829439c..416240cda 100644 --- a/research/libwifi/wifi.py +++ b/research/libwifi/wifi.py @@ -8,6 +8,25 @@ from datetime import datetime import binascii +# get_if_raw_hwaddr was removed in Scapy 2.7; provide ioctl fallback for Linux +if 'get_if_raw_hwaddr' not in dir(): + try: + from scapy.arch.common import get_if_raw_hwaddr + except ImportError: + try: + from scapy.arch import get_if_raw_hwaddr + except ImportError: + import socket as _socket, struct as _struct, fcntl as _fcntl + def get_if_raw_hwaddr(iface): + SIOCGIFHWADDR = 0x8927 + s = _socket.socket(_socket.AF_INET, _socket.SOCK_DGRAM, 0) + try: + d = _struct.pack('256s', iface[:15].encode()) + res = _fcntl.ioctl(s.fileno(), SIOCGIFHWADDR, d) + return _struct.unpack_from('H6s', res, 16) + finally: + s.close() + #### Constants #### FRAME_TYPE_MANAGEMENT = 0 From 800bf67b567269012452ffd3b3d4117a517f57d5 Mon Sep 17 00:00:00 2001 From: threadpoolx Date: Sun, 5 Jul 2026 21:15:31 +0530 Subject: [PATCH 3/4] Fix build against OpenSSL 3.0+ (missing provider.h include) OSSL_PROVIDER_try_load() is used under #if OPENSSL_VERSION_NUMBER >= 0x30000000L but was never included, causing an implicit-function-declaration error on OpenSSL 3.0+ (tested on 3.6.3). Add the include under the same guard. Co-Authored-By: Claude Sonnet 4.6 --- src/crypto/crypto_openssl.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/crypto/crypto_openssl.c b/src/crypto/crypto_openssl.c index 4a54ab0e5..8ae79e493 100644 --- a/src/crypto/crypto_openssl.c +++ b/src/crypto/crypto_openssl.c @@ -23,6 +23,9 @@ #include #include #endif /* CONFIG_ECC */ +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#endif /* OpenSSL version >= 3.0 */ #include "common.h" #include "utils/const_time.h" From 12cecffbc83c2ba39aa2bf46af7007426791139d Mon Sep 17 00:00:00 2001 From: threadpoolx Date: Sun, 5 Jul 2026 21:21:17 +0530 Subject: [PATCH 4/4] Fix eapol-amsdu default actions: pass 1 inject action not 2 EapolAmsduTest.__init__ enforces exactly 1 inject action but fragattack.py was passing 2 by default, causing an immediate quit(1) on every invocation of eapol-amsdu and eapol-amsdu-bad without custom --actions. Co-Authored-By: Claude Sonnet 4.6 --- research/fragattack.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/research/fragattack.py b/research/fragattack.py index 019c7e219..6ba21132e 100755 --- a/research/fragattack.py +++ b/research/fragattack.py @@ -91,8 +91,7 @@ def prepare_tests(opt): elif opt.testname in ["eapol-amsdu", "eapol-amsdu-bad"]: freebsd = opt.testname.endswith("-bad") actions = str2actions(stractions, - [Action(Action.StartAuth, enc=False), - Action(Action.StartAuth, enc=False)]) + [Action(Action.StartAuth, enc=False)]) test = EapolAmsduTest(REQ_ICMP, actions, freebsd, opt) elif opt.testname == "linux-plain":