View
2
Download
0
Category
Preview:
Citation preview
Catching Pink Dolphins using Libpcap via 802.11
Douglas Berdeaux
weaknetlabs@gmail.com
2013 © GNU – www.WeakNetLabs.com
Abstract
During my recent development of WARCARRIER, I thought that I had to write my own 802.11
protocol analyzer. So, I tried what came naturally to me – Perl – using the Net::PCAP libraries. This
proved to be fruitless since I desired more from the actual packet processing functions. I moved on to
SCAPY and LORCON with Python, but I realized the dependencies where starting to pile up and I wanted
to make my code as small as possible. Then, I decided to dive right into C programming and using the
Libpcap libraries for packet capture. This was incredibly easy with basic Ethernet packets on a switched
network, but for 802.11 (WiFi) packets, I was coming upon dead ends in documentation, I couldn’t
understand the syntax used in code snippets, and I was just plain lost. When I finally finished the
protocol analyzer code application, I just decided that Airodump-NG wasn’t too big of a dependency and
left it in the production code.
C is an amazing language, but its syntax is far different from what I was used to with Perl. Now, I
can capture the packets that I want and pull any data that I want from their headers in only a few easy
steps! This paper explores libpcap with the DLT_IEEE802_11_RADIO link layer type of packet. The
examples you read have been tested with the following hardware: ALFA AWUS036H rt8187 with the
latest driver from Compat-Drivers and the GNU Linux kernel. I realize that the code has a lot of room for
optimization, and I left out all of the functions that report errors for brevity. This is a very basic
introduction to the libpcap library for the purpose of sniffing beacon frames only – which should be
enough to get someone (that’s stuck in the same position I was in) onto the ground and running.
Frame Dissection
Our examples will deal solely with the beacon frames. In 802.11 wireless networking, An AP, or
Access point, can broadcast a type of packet called a management frame with a subtype of beacon. The
management frame is pretty much just a large header, there’s no data portion sent with it. All of its data
is in the header itself. The beacon is used by an AP for station synchronization and to express to stations
and potential stations what its attributes and technical capabilities are. By default, many AP
manufacturers set the beacon rate to about 100ms – which is ten times per second. This is what makes
wardriving possible. When you are traveling with your station computer with an 802.11 adapter in
promiscuous mode, it can easily sense these beacons and the software used for wardriving pulls the
relevant information out of them. There are many levels the beacon passes as it makes its way from the
physical layer of the antenna aperture up to the application layer where our software handles the
beacon frame.
There are many subtypes of management frames, not just beacons. So to keep track of these
types, we use numbers. As each frame is sent through the air, the header information is encoded as bits
in 1’s OR 0’s in the radio signal. These bits are grouped into bytes (8 bits to a byte). These bytes are then
grouped again into section of the header frame. One of these sections is just to relay the information as
to which type the frame is that is being sent. As you may have guessed, there are also more types that
just management frames since we need some way to send actual application data to and from the
stations! There are a total of three different types of 802.11 frames: management, control, and data
which are represented in the bytes as 00, 01, and 02 respectively. Our frame is a management frame of
subtype beacon. Beacons are represented as 08. So, if we are to dissect our packet at the application
layer when it is received, we can step through it starting at the beginning of the frame using what is
called an offset and see the 00 08 value for management:beacon. Let’s go deep and take a look at a
beacon frame in Wireshark, the packet analyzer.
Figure 0: the beacon frame dissected using Wireshark.
In figure 0, we can see the highlighted byte 80 that corresponds to “Subtype: 8” or simply
“beacon.” If you look even higher above, we see that the destination of the packet is to every address or
“Broadcast.” Beacons are multicast frames and are not sent to machines individually unless the 802.11
network is an iBSS or Ad-Hoc network. See the null byte 00 just before the 80? That’s how we know this
is a management packet. Everything in a frame header can be accessed by its hexadecimal form.
ESSID
See the ESSID “PinkDolphin” in the right side of the frame data output in the bottom portion of
the window in figure 0? That is represented in its ASCII form which we will need to convert from the
hexadecimal form. Let’s next highlight the ESSID in Wireshark and find its offset and see its hexadecimal
form.
Figure 1: The ESSID or “name” of the service set (802.11 network) in a Wireshark packet dissection.
In figure 1 above, we see the hexadecimal form on the bottom left and the ASCII version on the
right. If we count the bytes until we get to the 50 in the ESSID (the first highlighted value in the field on
the left side), we get 4 rows of 16 or 64 bytes.
If we convert all hexadecimal values starting at the offset + 1 (65)1, into ASCII values we get:
0x50 = ‘P’ 0x69 = ‘i’ 0x6e = ‘n’ 0x6b = ‘k’ 0x44 = ‘D’ 0x6f = ‘o’ 0x6c = ‘l’ 0x70 = ‘p’ 0x68 = ‘h’ 0x69 = ‘i’ 0x6e = ‘n’
Listing 0: hexadecimal values represented as ASCII for the ESSID.
1 When assigning a character pointer to the offset, we assign it to the 64the byte in the frame header. It’s first element, say
essid[0] will be the 65th
byte in the frame header – the second (essid[1]) will be the 66th
byte and so on.
Since the ESSID is of variable length, it is terminated by a byte value of 0x1 or simply 1 that we
can see just after the last character 6e (n) as 01. In C, we can make a pointer *essid start at the 0x50 value
or the ‘P’ and then use a simple while loop that increments n and continues until our essid[n] = 0x1. Then
we will have our ESSID from the 802.11 header! Well, it’s not really “ours.”2 This is actually a neighbor’s
protected AP. Why is this legal or okay to sniff the packets then? Well, we are just sniffing beacon
frames and not the user’s data. Remember the Google fiasco when they were wardriving for the
location of BSSIDs for global positioning purposes but they accidentally sniffed passwords and private
data too? Well, someone at Google decided it was unimportant enough to make their own protocol
sniffer for just the beacons alone – which is actually all they needed. And then they got in trouble. The
beacons again, are just public frames. That’s why they are not encrypted. In fact, no management
frames are encrypted in the 802.11 network standard as of the writing of this paper.
This is the only case in which the value of a field will be of a variable length that I can think of at
the moment. None of the fields are separated in any way, but we know by the protocol how long in
bytes they are and we can easily analyze the packets to find their offset from the beginning of the
header. The ESSID can range from 0 to 32 characters, or bytes as we saw in listing 0. This would be
represented as two whole rows in the Wireshark display on the lower left side. As a false sense of
security, these bytes can be hidden when administrators decide to “hide” the ESSID. There are three
packets which include the ESSID even when it is hidden, and those are Association Request, Probe
Request, and Probe Response. All of which are unencrypted management frames - and as a side note,
can be generated using the libpcap libraries.
2 Yet.
The Code
Now that we have a grasp as to what we need to do to parse out specific values of the 802.11
header, let’s dive into the C code. This will be a fair amount of intermediate code, but don’t let this deter
you from coding your own implementation or following along! I promise I will make this as easy as
possible and explain all that I can. First and foremost, we include the libpcap header file pcap.h in a
preprocessor directive:
#include <pcap.h>
Code listing 0: including the libpcap header file
Remember if we want to use the printf(); stdout function, we need the stdio.h file also.
#include <stdio.h>
Code listing 1: including the standard input/output header file
The packet capture functions use a data type of uint8_t which comes with the netinet/in.h
header files which we need to include: (my machine through errors without the header file)
#include <netinet/in.h> // needed for the uint8_t in
Code listing 2: including the net-internet header file
Now, we use a few global variables of special types from the pcap.h header for the creation and
writing of the packet capture (.pcap) file. Remember the packets are binary data to the computer. We
can’t just open them in a basic text editor and read them or write them so we use specific packet
capture handling functions from pcap.h to do this work for us.
pcap_t *capturehandle; /* packet capture handle */pcap_dumper_t *pcapfile; /* output file */pcap_t *filehandle; /* output file handle */char *filename = "output.cap"; /* output file name */
Code listing 3: packet capture file management from libpcap
Now, we can prototype the function we want to call when we receive a packet. Prototyping just
allows us to declare that a function exists elsewhere in the C code outside of main(); that will be called by
another function. The arguments to this function: u_char *args, const struct pcap_pkthdr *header, const
u_char *packet are simply for the packet that we will pass to it which another function3 will fill in for us.
// prototype the function to call per packetvoid sniff(u_char *args, const struct pcap_pkthdr *header, const u_char *packet); Code listing 4: prototyping the function that pcap_loop(); or pcap_dispatch(); will call per packet.
We also need a data type of struct to gather the uint8_t value of it_len.
3 pcap_dispatch();
// This is for the it_len value:struct radiotap_header { uint8_t it_rev; uint8_t it_pad; uint16_t it_len; };
Code listing 5: struct using the uint8_t data type for the length of the radiotap header using in calculating the offset
for other values within the header.
Now we can begin our main(); C function. The arguments for main(); which are int argc, char *argv[] are
the standard arguments for accepting command line input from the terminal. I will use these for
gathering the 802.11 NIC device from the user.
int main(int argc, char *argv[]){ const u_char *bssid; int offset = 0; struct radiotap_header *rtaphdr; char *erbuf; // A pointer to the error string char *dev; // A pointer to our device pcap_t *handle; // pointer to the header grabbed by pcap dev = argv[1]; // the device as an argument "mon0,wlan0,etc" // we grab out packet with pcap_open_live(); args are: // device, length in bytes to sniff, promisc mode boolen, // milliseconds to time out(0 = never), string to store error msg handle = pcap_open_live(dev, SNAP_LEN, 1, 0, erbuf); // descriptor // our data link type returned should be 127 for "LINKTYPE_IEEE802_11_RADIOTAP" printf("Type: %d\n",pcap_datalink(handle)); pcap_dispatch(handle, 1, sniff, NULL); // call sniff(); when we get a packet return(0); }
Code listing 6: The main(); C function that is called at runtime.
In main(); we set our integer data type variable of offset to 0. Then we design a struct data type
pointer rtaphdr. This will be used for the length variable used in sniff(); Next we create an error buffer
character array pointer and another for the device we are using. This device is passed to main(); as an
argument from the command line. We will run this application as ./sniffer <device> As an example in my
case I used ./sniffer mon0 after creating the VAP mon0 with airmon-ng start wlan0. Next the handle
variable is for file writing. In the sniff(); function, that we will define later, is the packet is a beacon we
save it to a file so we can analyze it with Wireshark later is need be.
Next, we run the function pcap_open_live(); from pcap.h and assign it’s returned value descriptor
to handle.
Then, we simply use the printf(); function from the stdio.h header file to display the frames Link
Layer Type which initially returned 127 for DLT_IEEE802_11_RADIO from our RTL8187 driver4. To do this
4 This is implementation specific. The values of the offset and DLT_ type may differ in another environment. We
can determine the DLT_ Link Layer Type by using the pcap_datalink(handle); function as in code listing 6 above.
we call the pcap_datalink(); function from the pcap.h header file and pass it the handle variable of our
packet. During my initial development, I was basically left in the dark and needed to feel my way
through the first captured packet with Wireshark.
Next, we call another pcap.h function pcap_dispatch(); which we pass the packet via the handle
variable, an integer 1 for packets to process, the function sniff(); to handle the packet and NULL because
we pass nothing to the callback routine. Let’s take a look at the sniff(); function now.
void sniff(u_char *args, const struct pcap_pkthdr *header, const u_char *packet){ const u_char *bssid; // character array for BSSID and ESSID const u_char *essid; int offset = 0; struct radiotap_header *rtaphdr; rtaphdr = (struct radiotap_header *) packet; offset = rtaphdr->it_len; if(packet[offset] == 0x80){ // subtype 8 for beacon starts at offset 27 // bssid = packet + offset + 10; // BSSID starts here in beacons bssid = packet + 36; // BSSID starts here in beacons essid = packet + 64; // ESSID starts here and ends with a simple 0x1 char *ssid; // Let's make a simple char array of the ESSID // let's construct the essid: unsigned int i = 0; while(essid[i] > 0x1){ // loop through bytes values start with essid[] printf("hex char: %x\n",essid[i]); // until we hit the 0x1 ssid[i] = essid[i]; // ssid[] string i++; // here would be good chance to filter with strcmp(); } ssid[i] = NULL; // terminate the string printf("ESSID string: %s\n", ssid); // print the stringprintf("BSSID: %02X:%02X:%02X:%02X:%02X:%02X\n", bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5]); // Below is more stuff to write the cap file: filehandle = pcap_open_dead(DLT_IEEE802_11_RADIO, BUFSIZ); pcapfile = pcap_dump_open(filehandle, filename); // open it for writing pcap_dump((u_char *) pcapfile, header, packet); // write the packet // Now we close the file stream so that it doesn't get corrupt: pcap_dump_close(pcapfile); pcap_close(filehandle); } }
Code listing 7: the sniff(); function that “handles” the packet by parsing the header, outputting information, and
writing the packet to file.
This function returns an integer value that can be matched to the table on the http://www.tcpdump.org/linktypes.html website.
The Dispatched Function
This is a large snippet of code, but it is incredibly easy to understand. I have loaded all of these
with comments to help me understand the code when modifying or troubleshooting it. The goal of this
sniff(); function, once dispatched, is simple. Find the ESSID and BSSID and print them, then print the
binary packet data to a file, close it, then return. The arguments passed to sniff(); from pcap_dispatch();
are obtained, and assigned in the first line. The next two lines are pointer objects to the first values of
the BSSID and ESSID which we need to calculate. Next, we define the offset as 0 which we will need to
change soon also and use in the calculation for the offsets of ESSID and BSSID. Now we create a pointer
and then assign it to a new struct of radiotap_header. This struct will be used to calculate the length of
the offset. We then assign offset to the length with uint16_t it_len from the struct.
Now that we know the offset, we can check the frame type and we do with packet[offset]. If it is
a beacon frame as we describe above 00 80 then we continue to process the packet. Checking out figure
1, we notice that the ESSID of the beacon frame is 64 bytes away from the beginning of the frame
header. So we add 64 onto the packet length and assign that value to the *essid pointer. As pointers in C
go, is that we can now step through the values in memory from that value until we get a 0x1 terminator
byte for the ESSID.
BSSID Retrieval
Figure 2: the BSSID has an offset of 16 x 2 rows + 4, or 36 byte offset.
We know that the BSSID is the MAC address of the AP and that a MAC address is 6 bytes. So we
need to find the BSSID in the frame, which we did in figure 2 and it starts 36 bytes away from the
beginning of the frame. We assign this to the *bssid pointer and we can now access the 6 bytes with
bssid[0] through bssid[5]. This is an aspect of C that proves to be most useful. Now, we create a new
pointer to a character array that we will assign the actual ESSID to *ssid[].
We create an integer token i and set it to 0, then use a while loop to gather all characters of the
ESSID, stopping with 0x1, and assign them to the *ssid[] character array. We then terminate the array so
truncate any leftover garbage from the assignment by adding the NULL character to the end of it.
Finally we print the ESSID and BSSID to the stdout (screen). We use the %02X hexadecimal
specifier with printf(); for the bytes of the BSSID and the %s string identifier to print the entire ESSID
character array to the screen. The remaining five lines simply write the packet binary data to the .cap file
and close it up. That’s all we need to do. We can repeat this process as much as we’d like to display any
values we’d like to from the packets.
Channel Frequency
Let’s try to get the channel information from the packet as well. The channel is stored in little
endian format, which means the bytes are swapped. Say we have channel 11, or 2.412GHz which is sent
as the value 2412. 2412 = 0x096c. It’s sent in the packet as we can see in Figure 3 as 6c 09. We can easily
multiply the 09 by 256 then add 09 to get the channel frequency5. We can also see that the offset from
the first byte is 18.
Figure 3: the channel has an offset of 16 x 2 rows + 4, or 36 byte offset.
Let’s try that in C programming.
const u_char *ch; ch = packet + 18; int chi = ch[1] * 256 + ch[0]; printf("channel: %i\n",chi);
Code listing 8: simple calculation of concatenated hexadecimal values 0x096c we first multiply the 09 by 256, then
add the 6c which yields 2412 – which then corresponds to the frequency 2.412GHz or channel 11 for 802.11
networking.
There’s no conversion needed, we can multiply and add the values directly from the packet. We multiply
the second byte to 256 because of the little endian format. Then we add the first byte. This will yield the
following output:
channel: 2412
Code listing 9: output form our printf(); function.
When re-compiled and ran from our example.
5 Frequency times 1000 for Ghz (in our case 2412, which is channel 11 and 2.412GHz)
Here is a simple example of this in action:
Figure 4: values stored in memory can be multiplied to integers without conversion in C programming.
Conclusion
With these small steps and these few lines of code we have successfully pulled the ESSID and
BSSID and frame type out of the packet! Once we find the offset and length of any field, we can then
create new offsets to determine their values and then do simple hexadecimal to ASCII or integer
conversion and display them with printf();
Remember that the fields are not separated by anything, so we need to either know or calculate
the lengths of each. This is simply how I came to understand the 802.11 packet structure. Wireshark is
the most useful network traffic analyzer out there and it’s free! If you copy all of the code snippets, save
for the added channel calculation portion6, and save to a .c file, you can compile it with gcc input.c –o
output –lpcap. Then run with a device as an argument, as I did with ./output mon0
6 Unless you knew where to put it.
References
TCPDUMP/Libpcap: Tcpdump,“TCPDUMP/LIBPCAP public repository”, Tcpdump, Changed by: , 20-May-
2013 http://www.tcpdump.org/
Google “wardriving fiasco”: Mathew J. Schwartz, “Google Wardriving: How Engineering Trumped
Privacy”,Mathew J. Schwartz http://www.informationweek.com/security/privacy/google-wardriving-
how-engineering-trumpe/232901230
Wireshark: the Wireshark Foundation, “Wireshark · Go Deep.”, the Wireshark Foundation,
ALFA AWUS036H rt8187: Amazon.com, “Amazon.com: Customer Reviews: Alfa AWUS036H Upgraded to
1000mW 1W 802.11b/g High Gain USB Wireless Long-Rang WiFi network Adapter with 5dBi Antenna -
for Wardriving & Range Extension”, Amazon.com, http://www.amazon.com/Alfa-AWUS036H-Upgraded-
Wireless-Long-Rang/product-reviews/B000QYGNKQ
Compat-Drivers: Mcgrof “Documentation/compat-drivers - Driver Backports Wiki” Mcgrof, last
modified on 25 September 2012, at 20:58 https://backports.wiki.kernel.org/index.php/Documentation/compat-drivers
Airodump-NG: Thomas D’otreppe, “airodump-ng [Aircrack-ng]”, Maintainer of Aircrack-NG Suite, Last
modified: 2012/05/08 15:18 by darkaudax. http://search.cpan.org/~saper/Net-Pcap-0.17/Pcap.pm
Perl Net::PCAP: Sébastien Aperghis-Tramoni, “Net::Pcap - Interface to pcap(3) LBL packet capture
library”,Sébastien Aperghis-Tramoni, http://search.cpan.org/~saper/Net-Pcap-0.17/Pcap.pm
Python LORCON: Dragorn, “LORCON wireless packet injection library”, Dragorn, unkown date,
http://search.cpan.org/~saper/Net-Pcap-0.17/Pcap.pm
Python SCAPY: Unknown (no meta data) “Scapy” unknown, unknown date (no meta data)
http://search.cpan.org/~saper/Net-Pcap-0.17/Pcap.pm
Recommended