Following the AirTight “hole196” announcement affecting WPA/WPA2-Enterprise networks (see my WillHackForSushi article), I was speaking with a friend who told me how attractive yet naive I am regarding how clients respond to LAN traffic sent to the broadcast address with unicast payloads. Essentially, I was unaware as to how a client would handle a unicast upper-layer payload (such as a TCP SYN request) when delivered to a broadcast MAC address.
So, mull it over I did, with the help of Scapy. I wanted to experience first-hand how different clients behaved when they receive unicast IP payloads, sent to a layer 2 broadcast address. This is a very straightforward test with Scapy, sent in these examples over a wired LAN card.
First, I start Scapy and create an Ethernet header in the variable “p”, using the broadcast destination address, and the source address of my Linux box running Scapy. The type code 0x0800 indicates that the next payload is an IP packet:
$ sudo scapy
>>> p = Ether(dst="ff:ff:ff:ff:ff:ff", src="00:18:8b:ad:2a:c7", type=0x0800)
Next, I add an IP header, specifying the source of my Linux box and the destination of my Windows 7 box. I add an ICMP payload as well (using the default type/code of ICMP Echo Request) and display the packet contents:
>>> p /= IP(src="172.16.0.109", dst="172.16.0.113")
>>> p /= ICMP()
>>> p
<Ether dst=ff:ff:ff:ff:ff:ff src=00:18:8b:ad:2a:c7 type=0x800 |<IP
frag=0 proto=icmp src=172.16.0.109 dst=172.16.0.113 |<ICMP |>>>
With my finished packet, I call the srp1() Scapy function to send and receive the packet (using srp1() as opposed to sr1() since I am sending a layer 2 packet) and display the associated packet response:
>>> srp1(p)
Begin emission:
Finished to send 1 packets.
.*
Received 2 packets, got 1 answers, remaining 0 packets
<Ether dst=00:18:8b:ad:2a:c7 src=00:21:5c:7e:70:c3 type=0x800 |<IP
version=4L ihl=5L tos=0x0 len=28 id=31066 flags= frag=0L ttl=128
proto=icmp chksum=0x6888 src=172.16.0.113 dst=172.16.0.109 options=[]
|<ICMP type=echo-reply code=0 chksum=0xffff id=0x0 seq=0x0 |<Padding
load='\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|>>>>
Much to my surprise, the Windows 7 host responds to my unicast Echo Request sent to the broadcast LAN address with an ICMP Echo Reply. Next I wondered if the Windows 7 target validated the Ethernet source address. I created a second packet, similar to the first but this time using the broadcast LAN address as the source:
>>> p = Ether(dst="ff:ff:ff:ff:ff:ff", src="ff:ff:ff:ff:ff:ff", type=0x0800)
>>> p /= IP(src="172.16.0.109", dst="172.16.0.113")
>>> p /= ICMP()
>>> srp1(p)
Begin emission:
Finished to send 1 packets.
...*
Received 4 packets, got 1 answers, remaining 0 packets
<Ether dst=00:18:8b:ad:2a:c7 src=00:21:5c:7e:70:c3 type=0x800 |<IP
version=4L ihl=5L tos=0x0 len=28 id=31603 flags= frag=0L ttl=128
proto=icmp chksum=0x666f src=172.16.0.113 dst=172.16.0.109 options=[]
|<ICMP type=echo-reply code=0 chksum=0xffff id=0x0 seq=0x0 |<Padding
load='\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|>>>>
Again the target responds to the Echo Request. Note that the response goes to the correct unicast MAC address, as opposed to the source included in the Echo Request packet, indicating that Windows uses the ARP cache to populate the Ethernet header as opposed to just swapping the address fields in the originating packet.
I was surprised to learn this LAN addressing technique also works with TCP:
>>> p = Ether(dst="ff:ff:ff:ff:ff:ff", src="00:18:8b:ad:2a:c7", type=0x0800)
>>> p /= IP(src="172.16.0.109", dst="172.16.0.113")
>>> p /= TCP(sport=65000, dport=135)
>>> srp1(p)
Begin emission:
Finished to send 1 packets.
...........*
Received 12 packets, got 1 answers, remaining 0 packets
<Ether dst=00:18:8b:ad:2a:c7 src=00:21:5c:7e:70:c3 type=0x800 |<IP
version=4L ihl=5L tos=0x0 len=44 id=545 flags=DF frag=0L ttl=128
proto=tcp chksum=0x9fac src=172.16.0.113 dst=172.16.0.109 options=[]
|<TCP sport=loc_srv dport=65000 seq=4159458989L ack=1 dataofs=6L
reserved=0L flags=SA window=8192 chksum=0xda0c urgptr=0 options=[('MSS',
1460)] |<Padding load='\x00\x00' |>>>>
I continued this testing against other clients as well. Windows Vista systems behave the same as my Windows 7 target; I did not have a Windows XP box handy for testing. Interestingly, Linux 2.4, Linux 2.6 and OS X 10.6 systems do NOT behave the same as Windows. These systems reject LAN broadcast traffic sent to unicast IP addresses.
What does this mean? First, Scapy rocks, and you should check our Judy’s Scapy class with SANS. Second, leveraging the “hole196” vulnerability, a malicious insider could send traffic to other WLAN clients without going through the LAN, evading wired IDS systems (but otherwise not creating an additional network exposure). I have a nagging feeling this behavior is not good (tm) otherwise, but I haven’t figured out why yet. If you have an idea, please share it in the comments below, or drop me a note. Thanks!
-Josh