Skip to main content

Extracting Access Point Names from Packet Captures

Years ago, while working as a Network Engineer, I did a bit of sniffing of our wireless access points. I noticed that some access point, mainly Cisco, broadcast the Access Point's name. I also noticed that the same access point will use a slightly different MAC Address (BSSID) for each SSID (ESSID). Typically the last nibble (half byte), or two, changes. I thought that was interesting, and moved on.

Now that I work as a penetration tester I want to correlate those access points, so I can tell exactly how many devices there are and the MAC addressing scheme. That way I can better identify something that is out of place, like a well place rogue.

Initially I did this by hand, and by hand means: teh suck!!!1! I knew there had to be a better way to do this, so I broke out scapy. I'll walk you through the process of creating a python script that extracts all the AP' MAC addresses, along with their corresponding Name and [E]SSID (if broadcast).

Let's start by looking a packet produced by a beacon.

The packet includes the name of the access point. But how do we extract it? Let's fire up scapy and check it out.

$ scapy
Welcome to Scapy (2.1.0)
>>> pkts=rdpcap("beacon-packet.pcap")
>>> p=pkts[0]
>>> p
<Dot11 subtype=8L type=Management proto=0L FCfield= ID=0 addr1=ff:ff:ff:ff:ff:ff
addr2=00:24:c4:d3:04:65 addr3=00:24:c4:d3:04:65 SC=6432 addr4=None |<Dot11Beacon
timestamp=339645573495 beacon_interval=102 cap=short-slot+ESS+privacy+short-preamble
|<Dot11Elt ID=SSID len=11 info='MyCorpESSID' |<Dot11Elt ID=Rates len=8 info='\x82
|<Dot11Elt ID=133 len=30 info='\n\x00\x8f\x00\x0f\x00\xff\x03Y\x00AP3\x00\x00\x00
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x006' |<Dot11Elt ID=150 len=6
info='\x00@\x96\x00\x11\x00' |<Dot11Elt ID=vendor len=6 info='\x00@\x96\x01\x01\x04'
|<Dot11Elt ID=vendor len=5 info='\x00@\x96\x03\x05' |<Dot11Elt ID=vendor len=5
info='\x00@\x96\x0b\t' |<Dot11Elt ID=vendor len=5 info='\x00@\x96\x14\x01' |<Dot11Elt
ID=vendor len=24 info="\x00P\xf2\x02\x01\x01\x80\x00\x03\xa4\x00\x00'\xa4\x00\x00
BC^\x00b2/\x00" |>>>>>>>>>>>>>>>>>>>>

The first packet in our capture is a beacon packet. It happens to contain the SSID and the AP's name. The BSSID (MAC) is really easy to extract (p.addr2). The ESSID is pretty easy to extract too (p[Dot11Elt].info). We still need to get that pesky Access Point Name, but it is nested in one of the Dot11Elt (802.11 Information Element). After trying this technique on multiple captures, at multiple sites, with multiple configurations, I found that the depth of the nested element is not consistent. This means we need to dig for it. Fortunately, we know the ID of the element that contains the AP's name, 133.

We can use this bit of code to find the property.

>>> while Dot11Elt in p:
... p = p[Dot11Elt]
... if p.ID == 133:
... print "found it: " +
... p = p.payload

found it:

I faked the output a bit since null and high numbered characters either don't display or display funny, but you get the point. After a bit of inspection, it appears the Name property is at offset 10 and is null terminated. We can use that to further refine our search by changing a few lines.

>>> p=pkts[0]
>>> while Dot11Elt in p:
... p = p[Dot11Elt]
... if p.ID == 133:
... ap =[10:]
... ap = ap[:ap.find("\x00")]
... print "found it: " + ap
... p = p.payload

found it: AP3

This works great, but we can't extract the ESSID from a hidden network since it doesn't broadcast its SSID in the beacon. Instead, we need to look for a probe response. While where at it, let's put together the code to grab both frame types.

Here is our original beacon again:
<Dot11  subtype=8L type=Management proto=0L FCfield= ID=0 addr1=ff:ff:ff:ff:ff:ff
addr2=00:24:c4:d3:04:65 addr3=00:24:c4:d3:04:65 SC=6432 addr4=None ...

Here is the header of a probe response:
<Dot11  subtype=5L type=Management proto=0L FCfield=retry ID=14849 addr1=00:20:00:6f:ab:30
addr2=00:24:c4:d3:04:65 addr3=00:24:c4:d3:04:65 SC=63328 addr4=None

We can use that to find packets that will contain the BSSID and AP name and possibly the ESSID.

>>> for p in pkts:
... if (p.subtype == 8L or p.subtype == 5L) and p.type == 0L:
... while Dot11Elt in p:
... p = p[Dot11Elt]
... if p.ID == 133:
... ap =[10:]
... ap = ap[:ap.find("\x00")]
... print "found it: " + ap
... p = p.payload
found it: AP3
found it: AP3
found it: AP4
found it: AP4
found it: AP2
found it: AP4

Now with a little extra python magic we end up with the attached script.

$ ./

usage: /pentest/wireless/ [-F directory containing pcaps] [-f pcapfipe] [-e ssid]
You must provide at least provide the directory or pcap file

We can then give it one or files and/or one or more directories containing .cap, .pcap, or .dump. Here is the resulting output:

$ ./ -f beacon-packet.pcap
00:24:c4:d3:04:65 MyCorpESSID AP3
00:24:c4:d2:d5:d1 AP4
00:24:c4:d2:d5:d5 MyCorpESSID AP4
00:24:c4:d2:23:41 AP2
00:24:c4:d2:d5:d2 AP4

This script writes the BSSID, ESSID, and AP Name from each beacon and probe response packet. If the output is sorted and only the unique rows are displayed we end up with this handy table.

$ ./ -f beacon-packet.pcap | sort -u
00:24:c4:d2:1d:91 AP5
00:24:c4:d2:1d:91 PRIV AP5
00:24:c4:d2:1d:92 AP5
00:24:c4:d2:1d:95 MyCorpESSID AP5
00:24:c4:d2:23:41 AP2
00:24:c4:d2:23:42 AP2
00:24:c4:d2:23:45 MyCorpESSID AP2
00:24:c4:d2:d5:d1 AP4
00:24:c4:d2:d5:d1 PRIV AP4
00:24:c4:d2:d5:d2 AP4
00:24:c4:d2:d5:d2 Voice AP4
00:24:c4:d2:d5:d5 MyCorpESSID AP4
00:24:c4:d2:ee:c0 MyCorpESSID
00:24:c4:d3:04:61 AP3
00:24:c4:d3:04:61 PRIV AP3
00:24:c4:d3:04:62 AP3
00:24:c4:d3:04:62 Voice AP3
00:24:c4:d3:04:65 MyCorpESSID AP3

You'll notice that some MAC addresses show up twice. That's because the beacon frame from the BSSID doesn't send it ESSID, so it shows up blank, but if a probe response frame is found the ESSID is populated.

We can see that PRIV SSID usually ends in 1, MyCorpESSID ends in 5, and Voice ends in 2. In this format it is really clear that BSSID 00:24:c4:d2:ee:c0 is out of place. It doesn't send the AP's name, and doesn't follow our typical pattern. This is an access point that should be looked into, either due to misconfiguration, or as a rogue.

Correlation FTW!


This is written for Python 2.6 and may require modifications for other versions of python.


import sys
import getopt
from scapy.all import *

def Usage():
print ("usage: " + sys.argv[0] + "[-F directory containing pcaps] [-f pcap file] [-e ssid] [-v]\nYou must provide at least provide the directory or pcap file\n-v shows the filename in the output")

def GetSsidAndApName(packet):

p = packet #.getlayer(Dot11Elt)

while Dot11Elt in p:
p = p[Dot11Elt]
if p.ID == 0:
if len( == 0 or[0] == "\x00":
if p.ID == 133:
ap =[10:]
ap = ap[:ap.find("\x00")]
p = p.payload

return (bssid, essid, ap)

files = []
essids = []

opts, args = getopt.getopt(sys.argv[1:], "f:F:e:v")
except getopt.GetoptError as err:
# print help information and exit:
print(err) # will print something like "option -a not recognized"

verbose = False
for opt, arg in opts:
if opt == '-h':
elif opt == '-F':
for item in os.listdir(arg):
fullpath = os.path.join(arg, item)
if os.path.isfile(fullpath) and ('.cap' in item or '.pcap' in item or '.dump' in item):
elif opt == '-f':
elif opt == '-e':
elif opt == '-v':
verbose = True

if len(files) == 0:

for f in files:
pcap = rdpcap(f)
for pckt in pcap:
if (pckt.subtype == 8L or pckt.subtype == 5L) and pckt.type == 0L:
t = GetSsidAndApName(pckt)
if not essids or t[1] in essids:
print t[0] + '\t' + t[1] + '\t' + t[2] + (('\t' + f) if verbose else '')
except Exception, e:
print e
print "Send error to: tim[at]"


Popular posts from this blog

3 Years of DirecTV User-Agent Command Injection

I found a bug in one of my DirecTV devices in 2015 after I got DirecTV. DirecTV didn't have a bug bounty program at that time so I used it as a demo in my classes. When AT&T bought DirecTV it then fell under AT&T's bug bounty, which is awarded quarterly. I forgot to submit it in the first post-merger quarger (2015Q4), so I submitted it 2016Q1. Screenshots from the bug bounty submission are at the bottom of this post.

EDIT: I in NO way want to steal the thunder from Ricky Lawshae and or diminish his hard work (although I think we can both agree this is about the lamest work either of use have ever had to do ;) ). My goal is to show how long crappy bugs like this sit.

I got bored a few years ago and decided to see what services the devices in my house expose.  A simple Nmap scan found listening web server on one of the devices. I browsed to it and I see this:

This is one of my DirecTV devices (I have DirecTV, solely so I can watch my precious Green Bay Packers on Sundays.…

Doll Hacking: The Good, The Bad(words) and the Ugly (features)

The age of internet connected toys is upon us. Increasingly, we are seeing children's toys connected to the internet, commonly through an app. I recently purchased a My Friend Cayla ( for uh…testing. I wanted to test the security of the device to see how safe it is for children.

In short, the toy does a good job of protecting children from inappropriate content, but any device (phone, tablet, laptop) can connect to the toy and play or record audio. That last bit scares me. The only protection against recording and arbitrary sound output is that only one device can be connected at a time. An opportunistic bad guy would only need to wait for the tablet or phone to go out of range or run out of battery.
Initial TestingI first needed to get a basic understanding of what Cayla can do and how she works. So I turned her on, connected her to my iPad via Bluetooth, and played with the app.

She began speaking and asking questions about me and my day. She is quite …