10.3. libpcap and 802.11 Wireless Networks
As shown in Table 10-2, libpcap supports packet capture from a wide variety of link types, including several link types related to 802.11 wireless networks. The Arpsniff tool we just demonstrated was designed to work only on Ethernet networks (or more specifically, Ethernet II networks). We check the link type of the network interface because we receive different types of packet frames from the interface depending on the link type reported. For example, Arpsniff is expecting to receive an Ethernet II frame, containing an ARP packet. In this case, we know the Ethernet II frame has a header consisting of 14 bytes, as shown in Figure 10-2.
Figure 10-2. Ethernet II header format
Had Arpsniff been capturing packets from an 802.11
wireless network, however, something such as the 802.11 packet header shown in Figure 10-3 would have been present.
Figure 10-3. Header format
In addition to expecting the correct packet framing for the data link type we are using, there is one other major obstacle to successful packet capture from wireless networks, and that is monitor mode.
10.3.1. 802.11 Monitor Mode
In 802.11 wireless networks you are generally interested in capturing all information on a particular frequency or channel, regardless of the network the traffic belongs to. Unfortunately, putting an 802.11 wireless card into promiscuous mode does not allow you to capture all packets on a channel; rather, it allows you to capture only the packets on the network you are attached to on that channel. To capture all packets on a channel, you need to put the card into a special mode known as monitor or rfmon mode.
Monitor mode is the mode for monitoring traffic, usually on a particular channel. A lot of wireless hardware is capable of entering monitor mode, but the ability to set the wireless hardware into monitor mode depends on support within the wireless driver. As such, you can force many cards into monitor mode in Linux, but in Windows you will probably need to write your own wireless network card driver.
Table 10-5 shows some ways to make common 802.11 wireless cards enter monitor mode. A good reference for cards capable of entering monitor mode is available from the web site for the Kismet tool, located at http://www.kismetwireless.net.
Table 10-5. Example commands to enter monitor mode
Driver/card
|
Operating system
|
Command
|
---|
Cisco Aironet
|
Linux
|
Echo "mode: y" > '/proc/driver/aironet/<device>/Config'
|
HostAP
|
Linux
|
iwconfig <device> mode monitor
|
Orinoco (patched)
|
Linux
|
iwpriv <device> monitor 1 <channel>
|
Madwifi
|
Linux
|
iwconfig <device> mode monitor
|
Wlan-ng
|
Linux
|
wlanctl-ng <device> lnxreq_wlansniff channel=<channel> enable=true
|
Radiotap
|
FreeBSD
|
ifconfig <device> monitor up
|
Another complication when capturing packets from a wireless card in monitor mode is that there is less consistency in packet-framing format. In addition to the 802.11 packet header shown in Figure 10-3, many wireless drivers return custom headers detailing a number of pieces of information about the captured packet, such as signal strength and noise values. The two most common of these are the Prism header and the AVS header. The Prism monitor mode header was originally developed as part of the linux-wlan-ng project (http://www.linux-wlan.com/), and was designed for use when developing drivers for the Prism II 802.11b card for Linux. Now this format is supported on a wide variety of drivers, and it is supported by libpcap as the DLT_PRISM_HEADER link type. The Prism monitor mode header is of the following format:
struct prism_value { uint32 did; uint16 status; uint16 len; uint32 data; }; struct prism_header { uint32 msgcode; uint32 msglen; u8char devname[16]; struct prism_value hosttime; struct prism_value mactime; struct prism_value channel; struct prism_value rssi; struct prism_value sq; struct prism_value signal; struct prism_value noise; struct prism_value rate; struct prism_value istx; struct prism_value frmlen; }; The AVS capture header is a newer development designed to replace the Prism monitor mode header. In addition to providing additional information, the AVS capture header can capture information about 802.11a and 802.11g packet-capture sources. The doc/capturefrm.txt file in the linux wlan-ng driver package is available from struct AVS_header { uint32 version; uint32 length; uint64 mactime; uint64 hosttime; uint32 phytype; uint32 channel; uint32 datarate; uint32 antenna; uint32 priority; uint32 ssi_type; int32 ssi_signal; int32 ssi_noise; uint32 preamble; uint32 encoding; }; Most recent wireless drivers that are capable of entering monitor mode support either a Prism monitor mode header or the AVS capture header, or both. Where possible you should use the AVS capture format, as this is better documented (i.e., it is documented, period) and is designed to be extensible to support newer technologies.
10.3.2. Adapting Arpsniff to 802.11
To adapt Arpsniff to capture information from a wireless packet-capture source, we need to make a few changes to the application logic. We assume the wireless device used in this example supports the AVS wireless capture format. First of all, we need to specify the sizes of some of the additional frames captured:
/* ugly shortcuts - Defining our header types */ #define ETH_HEADER_SIZE 14 #define AVS_HEADER_SIZE 64 /* AVS capture header size */ #define DATA_80211_FRAME_SIZE 24 /* header for 802.11 data packet */ #define LLC_HEADER_SIZE 8 /* LLC frame for encapsulation */ We are specifying additional header sizes because of the additional headers our ARP packet has when capturing from a wireless source due to RFC 1042 IP encapsulation, as shown in Figure 10-4.
Figure 10-4. ARP packet format on 802.11 from an AVS capture source
To determine the type of packet embedded in the 802.11 packet, we need to have a definition for the LLC header so that we can extract the ether_type value:
/* SNAP LLC header format */ struct snap_header { u_int8_t dsap; u_int8_t ssap; u_int8_t ctl; u_int16_t org; u_int8_t org2; u_int16_t ether_type; /* ethernet type */ } _ _attribute_ _ ((_ _packed_ _)); Now we can alter the process_packet function to work with a captured 802.11 packet from an AVS wireless source:
/* callback function to process a packet when captured */ void process_packet (u_char * args, const struct pcap_pkthdr *header, const u_char * packet) { struct ether_header *eth_header; /* in ethernet.h included by if_eth.h */ struct snap_header *llc_header; /* RFC 1042 encapsulation header */ struct ether_arp *arp_packet; /* from if_eth.h */ if (wired) /* global flag - wired or wireless? */ { eth_header = (struct ether_header *) packet; arp_packet = (struct ether_arp *) (packet + ETH_HEADER_SIZE); if (ntohs (eth_header->ether_type) != ETHERTYPE_ARP) return; } else { /* wireless */ llc_header = (struct snap_header *) (packet + AVS_HEADER_SIZE + DATA_80211_FRAME_SIZE); arp_packet = (struct ether_arp *) (packet + AVS_HEADER_SIZE + DATA_80211_FRAME_SIZE + LLC_HEADER_SIZE); if (ntohs (llc_header->ether_type) != ETHERTYPE_ARP) return; } printf ("Source: %d.%d.%d.%d\t\tDestination: %d.%d.%d.%d\n", arp_packet->arp_spa[0], arp_packet->arp_spa[1], arp_packet->arp_spa[2], arp_packet->arp_spa[3], arp_packet->arp_tpa[0], arp_packet->arp_tpa[1], arp_packet->arp_tpa[2], arp_packet->arp_tpa[3]); } You might have noticed that we have introduced a global flag called wired which we are going to use to determine a packet's framing type. We will set this further down in Arpsniff when we check the link type using pcap_datalink:
/* find out the datalink type of the connection */ if (pcap_datalink (handle) == DLT_EN10MB) { wired = 1; /* ethernet link */ } else { if (pcap_datalink (handle) == DLT_IEEE802_11_RADIO_AVS) { wired = 0; /* wireless */ } else { fprintf (stderr, "I don't support this interface type!\n"); exit (1); } } Once we have made the preceding changes, Arpsniff is ready to capture ARP packets from a wireless network interface in monitor mode. The full source code for the updated version of Arpsniff is included in Example 10-4.
Example 10-4. Arpsniff2 source code
#include <stdio.h> #include <unistd.h> #include <signal.h> #include <net/if.h> #include <pcap.h> #include <netinet/if_ether.h> /* ugly shortcut -- Ethernet packet headers are 14 bytes */ #define ETH_HEADER_SIZE 14 #define AVS_HEADER_SIZE 64 /* AVS capture header size */ #define DATA_80211_FRAME_SIZE 24 /* header for 802.11 data packet */ #define LLC_HEADER_SIZE 8 /* LLC frame for encapsulation */ /* SNAP LLC header format */ struct snap_header { u_int8_t dsap; u_int8_t ssap; u_int8_t ctl; u_int16_t org; u_int8_t org2; u_int16_t ether_type; /* ethernet type */ } __attribute__ ((__packed_ _)); /* for the sake of clarity we'll use globals for a few things */ char *device; /* device to sniff on */ int verbose = 0; /* verbose output about device */ pcap_t *handle; /* handle for the opened pcap session */ int wired=0; /* flag for wired/wireless */ /* gracefully handle a Control C */ void ctrl_c ( ) { printf ("Exiting\n"); pcap_breakloop (handle); /* tell pcap_loop or pcap_dispatch to stop capturing */ pcap_close(handle); exit (0); } /* usage */ void usage (char *name) { printf ("%s - simple ARP sniffer\n", name); printf ("Usage: %s [-i interface] [-l] [-v]\n", name); printf (" -i interface to sniff on\n"); printf (" -l list available interfaces\n"); printf (" -v print verbose info\n\n"); exit (1); } /* callback function to process a packet when captured */ void process_packet (u_char * args, const struct pcap_pkthdr *header, const u_char * packet) { struct ether_header *eth_header; /* in ethernet.h included by if_eth.h */ struct snap_header *llc_header; /* RFC 1042 encapsulation header */ struct ether_arp *arp_packet; /* from if_eth.h */ if (wired) /* global flag - wired or wireless? */ { eth_header = (struct ether_header *) packet; arp_packet = (struct ether_arp *) (packet + ETH_HEADER_SIZE); if (ntohs (eth_header->ether_type) != ETHERTYPE_ARP) return; } else { /* wireless */ llc_header = (struct snap_header *) (packet + AVS_HEADER_SIZE + DATA_80211_FRAME_SIZE); arp_packet = (struct ether_arp *) (packet + AVS_HEADER_SIZE + DATA_80211_FRAME_SIZE + LLC_HEADER_SIZE); if (ntohs (llc_header->ether_type) != ETHERTYPE_ARP) return; } printf ("Source: %d.%d.%d.%d\t\tDestination: %d.%d.%d.%d\n", arp_packet->arp_spa[0], arp_packet->arp_spa[1], arp_packet->arp_spa[2], arp_packet->arp_spa[3], arp_packet->arp_tpa[0], arp_packet->arp_tpa[1], arp_packet->arp_tpa[2], arp_packet->arp_tpa[3]); } int main (int argc, char *argv[]) { char o; /* for option processing */ char errbuf[PCAP_ERRBUF_SIZE]; /* pcap error messages buffer */ struct pcap_pkthdr header; /* packet header from pcap */ const u_char *packet; /* packet */ bpf_u_int32 netp; /* ip address of interface */ bpf_u_int32 maskp; /* subnet mask of interface */ char *filter = "arp"; /* filter for BPF (human readable) */ struct bpf_program fp; /* compiled BPF filter */ int r; /* generic return value */ pcap_if_t *alldevsp; /* list of interfaces */ while ((o = getopt (argc, argv, "i:vl")) > 0) { switch (o) { case 'i': device = optarg; break; case 'l': if (pcap_findalldevs (&alldevsp, errbuf) < 0) { fprintf (stderr, "%s", errbuf); exit (1); } while (alldevsp != NULL) { printf ("%s\n", alldevsp->name); alldevsp = alldevsp->next; } exit (0); case 'v': verbose = 1; break; default: usage (argv[0]); break; } } /* setup signal handler so Control-C will gracefully exit */ signal (SIGINT, ctrl_c); /* find device for sniffing if needed */ if (device == NULL) /* if user hasn't specified a device */ { device = pcap_lookupdev (errbuf); /* let pcap find a compatible device */ if (device == NULL) /* there was an error */ { fprintf (stderr, "%s", errbuf); exit (1); } } /* set errbuf to 0 length string to check for warnings */ errbuf[0] = 0; /* open device for sniffing */ handle = pcap_open_live (device, /* device to sniff on */ BUFSIZ, /* maximum number of bytes to capture per packet */ /* BUFSIZE is defined in pcap.h */ 1, /* promisc - 1 to set card in promiscuous mode, 0 to not */ 0, /* to_ms - amount of time to perform packet capture in milliseconds */ /* 0 = sniff until error */ errbuf); /* error message buffer if something goes wrong */ if (handle == NULL) /* there was an error */ { fprintf (stderr, "%s", errbuf); exit (1); } if (strlen (errbuf) > 0) { fprintf (stderr, "Warning: %s", errbuf); /* a warning was generated */ errbuf[0] = 0; /* re-set error buffer */ } if (verbose) { printf ("Using device: %s\n", device); /* printf ("Using libpcap version %s", pcap_lib_version); */ } /* find out the datalink type of the connection */ if (pcap_datalink (handle) == DLT_EN10MB) { wired = 1; /* ethernet link */ } else { if (pcap_datalink (handle) == DLT_IEEE802_11_RADIO_AVS) { wired = 0; /* wireless */ } else { fprintf (stderr, "I don't support this interface type!\n"); exit (1); } } /* get the IP subnet mask of the device, so we set a filter on it */ if (pcap_lookupnet (device, &netp, &maskp, errbuf) == -1) { fprintf (stderr, "%s", errbuf); exit (1); } /* compile the filter, so we can capture only stuff we are interested in */ if (pcap_compile (handle, &fp, filter, 0, maskp) == -1) { fprintf (stderr, "%s", pcap_geterr (handle)); exit (1); } /* set the filter for the device we have opened */ if (pcap_setfilter (handle, &fp) == -1) { fprintf (stderr, "%s", pcap_geterr (handle)); exit (1); } /* we'll be nice and free the memory used for the compiled filter */ pcap_freecode (&fp); if ((r = pcap_loop (handle, -1, process_packet, NULL)) < 0) { if (r == -1) /* pcap error */ { fprintf (stderr, "%s", pcap_geterr (handle)); exit (1); } /* otherwise return should be -2, meaning pcap_breakloop has been called */ } /* close our devices */ pcap_close (handle); }
If the wireless network is using encryption, we are not going to be able to intercept all traffic in a readable format. Unfortunately, we cannot be in monitor mode and have the wireless card decrypting data for us, so any data requiring decryption should be captured while not in monitor mode, or else the tool will have to implement decryption for the
captured data.
|