Itamae
802.11 radiotap and MPDU parser
Install / Use
/learn @wraith-wireless/ItamaeREADME
itamae 0.1.2: 802.11 parser

1 DESCRIPTION:
Itamae is a raw (packed binary data) 802.11 parser. Consider the OSI model:
+-------------+
| Application |
+-------------+
| Presentation|
+-------------+
| Session |
+-------------+
| Transport |
+-------------+
| Network | /+-----------+
+-------------+/ | MSDU (LLC)|
| Data-Link | +-----------+
+-------------+\ | MPDU (MAC)|
| Physical | \+-----------+
+-------------+
Layer 2, the Data-Link layer can be subdivided into the MAC Service Data Unit (MSDU) or IEEE 802.2 Logical Link Control and the MAC Protocol Data Unit (MPDU). Itamae is concerned with parsing the MPDU or 802.11 frame and parsing meta-data about the Layer 1, Physical layer as found in <a href="http://www.radiotap.org">Radiotap</a>. ATT, itamae does not support Prism or AVS at Layer 1 and it does not parse anything at the LLC sublayer or above. In the future, I plan on extending it into the Network layer and include 802.1X parsing.
Itamae is not intended to be a substitute for Scapy. Use Scapy if you
- need to parse TCP/IP,
- need to craft packets, or
- need to inject packets.
Itamae is intended to meet a niche set of goals and is ideal if
- speed and efficiency is a requirement,
- you only need 802.11 support, and
- you are only parsing, not building packets.
When parsing raw data, Itamae is six times faster than Scapy and has a reduced overhead in terms of object size because it uses minimal classes. However, unlike Scapy, Itamae does not offer socket support (you'll have to bind and sniff your own sockets) and it is not layered. See Section 3: Using for an explanation.
a. Why the name?
I chose itamae and other sushi related names because there are only so many ways to write dot11. With this naming convention, itamae can coexist with other python network frame parsers (if you don't import *). I chose sushi because it is the art of 'raw' cheffing.
2. INSTALLING:
a. Requirements
Itamae requires Python 2.7. I have no plans on supported Python 3.x as doing so makes the code ugly. If at such a time as Python 3 becomes the defacto standard, I will move it to Python 3. Itamae has been tested on Linux but, as of yet, has not been tested on Windows or MAC OS.
b. Install from Package Manager
Install itamae through PyPI:
sudo pip install itamae
c. Install from Source
Itamae can also be installed from source. Download from http://wraith-wireless.github.io/itamae or https://github.com/wraith-wireless/itamae. Once downloaded, extract the files and from the itamae directory run:
sudo python setup.py install
3. USING
Before using Itamae, you'll need a wireless card in monitor mode and a raw socket. You can use iw (or, shameless plug follows <a href="https://github.com/wraith-wireless/PyRIC">PyRIC</a>) to create a virtual interface and use the Python socket module to bind the raw socket. Before showing Itamae examples, let's set up our card and socket. You'll need to be root to do so.
>>> import pyric.pyw as pyw
>>> import socket
>>> card = pyw.getcard('wlan0')
>>> pyw.phyadd(card,'mon0','monitor')
>>> sock = socket.socket(socket.AF_PACKET,
... socket.SOCK_RAW,
... socket.htons(0x0003))
>>> sock.bind((card.dev,0x0003))
With the raw socket ready, we can read the raw frames and parse them with Itamae.
>>> from itamae.mpdu import MAX_MPDU
>>> raw = sock.recv(MAX_MPDU)
Before describing how to parse with Itamae, it is best to describe how the RTAP object and MPDU object are handled. Each is a wrapper around a dict that exposes certain fields using the '.' operator. And for each, the respective parse function takes a byte stream and returns the appropriate layer dict. Unlike Scapy and other protocol parsers, Itamae does not parse and/or treat RTAP and MPDU as a layered hierarchy. That is, the parsed MPDU is not an object contained within the radiotap object.
For the following examples we will be parsing three frames as shown below:
>>> len(raw1), raw1
(171, "\x00\x00\x12\x00.H\x00\x00\x00$l\t\xc0\x00\xb5\x01\x00\x00\x88A0\x00
\x04\xa1Q\xd0\xdc\x0f\xb04\x95n0\x02\x04\xa1Q\xd0\xdc\x0f\x00<\x00\x00\x07\x08
\x00 \n\x00\x00\x00\xa9\xe6\xfc\x98 T\xe4\xed\xf5\x01w`\xe76\x18@D.'\xaf:;\xa3
\xff\xf2\xb8\x88J\xe8\xeeL\x84\xaf\x08$\x1e\x87\xbc\x8e\xa0\x8e\x86\xd1\xce\xa26
\x84\xa4.\xf5#\xff\xc07`\xd4\xb2\xe4\xaf\n\x01\xcby\x9e4\xb5\xac:0a]\x9d\xfb
\xbf5X\xb3\xc5-f\xca0\xb77~4\xd5\xbf9\x8d\xf3oZ\xcb\xe6>t\xd35\x01\x1c%\x19
\x8cD+\xd6\xc7W\x81\xcb\xd6\x97O.\xde\x07\x11")
>>>
>>> len(raw2), raw2
(153, "\x00\x00\x15\x00*H\x08\x00\x00\x00\x9e\t\x80\x04\xc3\x01\x00\x00\x07
\x00\x05\x88\xc1,\x00\x08\x86;C\xf2h\x949\xe5i\xcf\x0b\xff\xff\xff\xff\xff
\xff\x90\x1a\x00\x00\xc0\xff\x00\xc0RM\x00 \x0b\x00\x00\x00!\xd0M$6\x15\x8f5;
\xaf\xc1\xee_\xae\x84 >\xc72\xdaJ-\xb6\xb61\x85+\xa1\xe4\xd1ys\xe9B\xe4\x8b%
\xaa\xe0j\xdf\x86\x04\xe6\x88\x89g\x11\x85\xb4\x0f\xbcI'Df\xcd\xbf\x83\xf5
\x10\xec\x1b\xa1FMD\x81\xcb\xbe\xa9qO\xc3\xb8\xecL\xb6[:\xe2h\xd5\x13\xbd
\xdd\x94\xd6\xe2\xa6)\xd8\x9b\xab")
>>>
>>> len(raw3), raw3
(38, '\x00\x00\x12\x00.H\x00\x00\x100l\t\xc0\x00\xbe\x03\x00\x00\xb4\x00\x80
\x00\xac\xb5}\x8d;0<F\xd8~\x0e\xdd\xc2U0\xde')
As can be seen from above the sizes of the frames are 171 bytes, 153 bytes and 38 bytes respectively.
Itamae now implements a single function to parse both Radiotap and MPDU:
>>> import itamae.sushi as sushi
>>> dR1,DM1,l3,err = sushi.bento(raw1)
>>> dR1
{'vers': 0, 'antenna': 1, 'rx-flags': 0, 'antsignal': -75, 'rate': 36, 'flags': 0,
'present': ['flags', 'rate', 'channel', 'antsignal', 'antenna', 'rx-flags'],
'channel': [2412, 192], 'size': 18}
>>> dM1
{'qos': {'tid': 0, 'a-msdu': 0, 'ack-policy': 0, 'eosp': 0, 'txop': 0},
'err': [], 'stripped': 8, 'addr1': '04:a1:51:d0:dc:0f',
'seqctrl': {'seqno': 960, 'fragno': 0}, 'addr2': 'b0:34:95:6e:30:02',
'addr3': '04:a1:51:d0:dc:0f', 'offset': 34, 'duration': {'dur': 48, 'type': 'vcs'},
'l3-crypt': {'mic': '\xcb\xd6\x97O.\xde\x07\x11', 'rsrv': '\x00', 'pn5': '\x07',
'pn1': '\x08', 'pn0': '\x07', 'pn3': '\x00', 'pn2': '\n', 'type': 'ccmp',
'pn4': '\x00', 'key-id': {'ext-iv': 1, 'rsrv': 0, 'key-id': 0}},
'framectrl': {'subtype': 8, 'vers': 0, 'type': 2,
'flags': {'md': 0, 'mf': 0, 'o': 0, 'r': 0, 'fd': 0, 'pf': 1, 'td': 1, 'pm': 0}},
'present': ['framectrl', 'duration', 'addr1', 'addr2', 'addr3', 'seqctrl', 'qos', 'l3-crypt']}
>>> l3
"\xa9\xe6\xfc\x98 T\xe4\xed\xf5\x01w`\xe76\x18@D.'\xaf:;\xa3\xff\xf2\xb8\x88J
\xe8\xeeL\x84\xaf\x08$\x1e\x87\xbc\x8e\xa0\x8e\x86\xd1\xce\xa26\x84\xa4.\xf5#\xff
\xc07`\xd4\xb2\xe4\xaf\n\x01\xcby\x9e4\xb5\xac:0a]\x9d\xfb\xbf5X\xb3\xc5-f\xca0
\xb77~4\xd5\xbf9\x8d\xf3oZ\xcb\xe6>t\xd35\x01\x1c%\x19\x8cD+\xd6\xc7W\x81"
But to better understand how it works, let us begin parsing the Radiotap with rdiotap.parse() which returns a RTAP object. RTAP always exposes three fields: the version, the size and the present list. RTAP will also expose certain other commonly found fields via the '.' operator and for all others, the bracket(s) operator will work. Parsing raw1 we get:
>>> import itamae.radiotap as rtap
>>> dR1 = rtap.parse(raw1)
>>> dR1
{'size': 18, 'vers': 0, 'antenna': 1, 'rx-flags': 0, 'antsignal': -75, 'rate': 36,
'flags': 0, 'channel': [2412, 192], 'present': ['flags', 'rate', 'channel',
'antsignal', 'antenna', 'rx-flags']}
>>> dR1.vers,dR1.size,dR1.present
(0, 18, ['flags', 'rate', 'channel', 'antsignal', 'antenna', 'rx-flags'])
>>>
So far, we have read a raw frame of 171 bytes (which as we will see later is a data frame) off our monitor interface and parsed the radiotap "layer". We can print the version (0), size, in bytes, (18) and the list of present fields (flags, rate, channel, antsignal, antenna and rx-flags). Let us continue with the present fields (see http://www.radiotap.org/defined-fields for a listing of all defined fields):
- flags: frame properties. see http://www.radiotap.org/defined-fields/Flags
- rate: TX/RX data rate (in 500Kbps)
- channel: a set (list) where the first item is the frequency and the second is the channel flags
- antsignal: received signal power in dBM
- antenna: the antenna index starting at 0.
- rx-flags: received frame properties. see http://www.radiotap.org/defined-fields/RX%20flags
We could get the field values using the bracket operator on the returned RTAP object or as mentioned above use the '.' operator. Below, we show both methods.
>>> print "Antenna: via '[]' = {0} & '.' = {1}".format(dR1['antenna'],dR1.antenna)
Antenna: via '[]' = 1 & '.' = 1
>>>
>>> print "Signal Strength: via '[]' = {0} & '.' = {1}".format(dR1['antsignal'],dR1.rss)
Signal Strength: via '[]' = -75 & '.' = -75
>>>
>>> print "Flags: via '[]' = {0} & '.' = {1}".format(radiotap.flags(dR1['flags']),dR1.flags)
Flags: via '[]' = [] & '.' = []
>>>
>>> print "Data rate: via '[]' = {0} & '.' = {1}".format(dR1['rate'],dR1.rate)
Data rate: via '[]' = 36 & '.' = 18.0
Antenna (the index 0-based of the antenna that read the signal) and Signal strength (strength of the received signal in dBm) are straightforward. Regardless of which method you use, you will get the same result. Looking at flags, notice that by using the brackets, additional manipulation was required to get the same result as using the '.' operator. This is because dR['flags'] returns the value of the flags field whereas dR.flags returns a list of flags present in the flags fi
