Repository: secdev/scapy

Issue:
Unable to send 802.11 frames on Mac OS Catalina
There currently isn't a way on Mac OS at least on the newest version as of right now, Catalina 10.15.7 to send a packet. 
Ways I have tried:
I have tried already having the interface in monitor mode before sending the packet
I have used the monitor=True function EX: sendp(packet, monitor=True, iface='en0',  count=100, verbose=0)
  - when doing this the computer immediately crashes
  - it crashes when conf.use_pcap is both true and false

I have tried without being in monitor mode, but obviously that didn't work
I have tried all these methods with conf.use_pcap true and false

I have looked throughout the internet to find a solution, but I have been unable to find one. I have everything including the pcap module upgraded to the newest version. Unless there is another solution I am not considering, this needs to be patched.


Discussion:
I am assuming that you are trying to inject 802.11 frames. On macOS, this is not reliable, and we recommend to use Linux to efficilently inject frames.

Could you share a complete example that could allow us to reproduce the issue?
Sounds a lot like https://github.com/secdev/scapy/issues/2154 to me. I am not a Mac user, can't really help with that.
Here are the different ways in which I have tried to use it and I'll explain after each section of code what happens:

This is an example packet, I am using a de-authorization packet because I have another machine scanning for de-authorization packets and they don't come across my network very often making them easier to spot
```
packet = packet = RadioTap()/Dot11(type=8, subtype=12, addr1='b8:e8:56:2a:3a:64', addr2 = '94:6a:77:e9:c5:b2', addr3='94:6a:77:e9:c5:b2')/Dot11Deauth(reason=7)

conf.use_pcap = True
sendp(packet, inter=0.1, count=100, iface='en0', monitor=True)`
```
The above code just makes the computer crash and have to restart


this code has 2 processes running so that one can enable monitor mode and the other can send the packets
```
import platform
from multiprocessing import Process

def deauthLoop(interface, channel):
    conf.use_pcap = True
    packet = packet = RadioTap()/Dot11(type=8, subtype=12, addr1='b8:e8:56:2a:3a:64', addr2 = '94:6a:77:e9:c5:b2', addr3='94:6a:77:e9:c5:b2')/Dot11Deauth(reason=7)

    while values.handshake() == False:
        if platform.system() == 'Darwin':
            os.system("sudo /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport " +str(interface)+ " --channel="+str(channel))
        if platform.system() == 'Linux':
            os.system("iwconfig " +str(interface)+ " channel "+str(channel))
        time.sleep(5)
        sendp(packet, inter=0.1, count=100, iface=interface)

def main():
     p = Process(target=deauthLoop, args=('en0', 11,))
     p.start()
     conf.use_pcap=True
     sniff(monitor=True, iface='en0')
```

That code above plainly just doesn't send any packets I am monitoring for any packets on another computer that have the layer of Dot11Deauth. It does pick up on other Dot11Deauth packets but none sent from the Mac. Out of the hundreds that are sent by my computer, I am pretty confident that at least 1 would be picked up by the computer monitoring for them.

This is the code on the computer monitoring for them:

```
def sniffer(pkt):
    if pkt.haslayer(Dot11Deauth):
        print('DSTMAC', pkt[0].addr1)
        print('SRCMAC', pkt[0].addr2)
        print('BSSID    ', pkt[0].addr3)

sniff(iface='en0', prn=sniffer, monitor=True)
```

Also, yes I make sure they are on the same channel. Is there something wrong with my code that I am not doing?

Also sorry my code isn't coming out formatted the correct way
> Sounds a lot like #2154 to me. I am not a Mac user, can't really help with that.

No, its not, because my computer goes into monitor mode just fine, it is just unable to send packets
Please use triple back quotes for proper formatting.

Also that's totally #2154. Read it again
Thanks for the feedback. Could you provide a simpler reproducer (aka the minimum lines of code that leads to the crash)?

> Thanks for the feedback. Could you provide a simpler reproducer (aka the minimum lines of code that leads to the crash)?

This specific line leads to the crash:
`sendp(*and packet here*, iface=*interface on Mac OS*, monitor=True)`
The crash only happens when monitor = True

I also just did some research and found out the main wifi card on a mac doesn't support packet injection. I'm realizing it it probably the interface now more than a bug with scapy. Is there a built in function to check if the device supports packet injection? If not, if I were to write the function should I submit it as a potential patch? That way maybe setting `monitor = True` wouldn't crash the entire computer. Instead send the programmer a message saying the card doesn't support packet injection.
> Is there a built in function to check if the device supports packet injection? If not, if I were to write the function should I submit it as a potential patch...

There is none.
It's pretty amazing how inconsistent the monitor mode is. Not only is it extremely platform dependent, it's very dependent on both the wifi chip and the wifi drivers. You might still be able to figure out a function that will work for Mac, in which case feel free to submit a PR.

Even if they don't support injection, most cards however do support monitor mode to sniff packets, as you probably figured out already.

A last note: libpcap are best when it comes to monitor mode. While we do have a few tricks to make it work on some platforms, they're far ahead: if while using libpcap you get failures, there's likely little we can do on our end.

You should have a look at this 
https://gitlab.com/wireshark/wireshark/-/wikis/CaptureSetup/WLAN
It will give you a pretty good idea of how messy this all is...
AFAIK 802.11 frames injection is not supported on macOS, it just happened to work on older versions while activating the sniffing mode (aka monitor mode).

Could you confirm that sniffing work fine and that any frame sent will lead to a crash?

That will simplify debugging when I will get access to Catalina.

I finally managed to test this on Catalina (10.15). The following `sendp(Ether()/IP(dst="8.8.8.8")/ICMP(), iface="en0", monitor=True)` crashes the kernel. It does not crash on Mojave (10.14).

Here a simpler way to reproduce the crash:
```
sck = conf.L2socket(iface="en0", monitor=True)
sck.send(b"crash")
```
I wonder if we can do anything about that... open a radar maybe ? Or maybe we're doing something wrong but kernel crashes usually aren't a good sign.. I don't really have experience with Macs 😄 
The only way that I’ve thought of that may make it able to send packets is by finding an exploit with the kernel. Specifically the airport WiFi driver. Somewhat like a jailbreak for Mac OS. The thing that also sucks about Catalina when it comes to this topic is even if you buy an external WiFi driver such as one of the Alfa wifi drivers that support monitor mode and packet injection, no drivers for Catalina support it. Making it so that you can’t use scapy to turn them to monitor mode or have them use packet injection.
I think that the safest option is to forbid sending frames in monitor mode on macOS. We might consider removing monitor mode for receiving too.
I can see not allowing to send frames, but please don’t take out the ability to receive on monitor mode. Someone willing to use the pcap plus plus library for c++ that uses the pcap library on Mac OS will be able to do the same thing, except it wouldn’t apply to python. Which would make no sense in removing it from scapy especially since that function works. Instead, we should work together to find a solution to allowing frames to be sent using monitor mode that doesn’t crash the kernel.
There is no point in keeping code that do no work. Only 802.11 will be impacted, injecting Ethernet frames works fine and will still do after removing the monitor mode feature.
Did one of you tried using `conf.use_pcap = True` ?
I was reading the changes libpcap have done to  [their implementation](https://github.com/the-tcpdump-group/libpcap/blob/0574bb6bd25ded6a6eb8cfc015169dc26d133bcf/pcap-bpf.c) and I wonder if theirs work.
I’ve tried it before and it was the same effect of a crash.
@gpotter2 if I am correct, monitor mode (i.e. sniffing raw 802.11 frames) is not activated by default in libpcap. `conf.use_pcap = True` likely won't work. 
On linux, using
```python
conf.use_pcap = True
sniff(monitor=True)
```
uses libpcap in monitor mode, if you have already set the interface to monitor mode manually. We open the adapter differently in this case.

Resolving patch:
diff --git a/scapy/arch/bpf/supersocket.py b/scapy/arch/bpf/supersocket.py
--- a/scapy/arch/bpf/supersocket.py
+++ b/scapy/arch/bpf/supersocket.py
@@ -11,6 +11,7 @@
 import platform
 from select import select
 import struct
+import sys
 import time
 
 from scapy.arch.bpf.core import get_dev_bpf, attach_filter
@@ -83,12 +84,27 @@ def __init__(self, iface=None, type=ETH_P_ALL, promisc=None, filter=None,
         # Note: - trick from libpcap/pcap-bpf.c - monitor_mode()
         #       - it only works on OS X 10.5 and later
         if DARWIN and monitor:
-            dlt_radiotap = struct.pack('I', DLT_IEEE802_11_RADIO)
+            # Convert macOS version to an integer
             try:
-                fcntl.ioctl(self.ins, BIOCSDLT, dlt_radiotap)
-            except IOError:
-                raise Scapy_Exception("Can't set %s into monitor mode!" %
-                                      self.iface)
+                tmp_mac_version = platform.mac_ver()[0].split(".")
+                tmp_mac_version = [int(num) for num in tmp_mac_version]
+                macos_version = tmp_mac_version[0] * 10000
+                macos_version += tmp_mac_version[1] * 100 + tmp_mac_version[2]
+            except (IndexError, ValueError):
+                warning("Could not determine your macOS version!")
+                macos_version = sys.maxint
+
+            # Disable 802.11 monitoring on macOS Catalina (aka 10.15) and upper
+            if macos_version < 101500:
+                dlt_radiotap = struct.pack('I', DLT_IEEE802_11_RADIO)
+                try:
+                    fcntl.ioctl(self.ins, BIOCSDLT, dlt_radiotap)
+                except IOError:
+                    raise Scapy_Exception("Can't set %s into monitor mode!" %
+                                          self.iface)
+            else:
+                warning("Scapy won't activate 802.11 monitoring, "
+                        "as it will crash your macOS kernel!")
 
         # Don't block on read
         try:


Test patch: