Context
I've got a pcap file containing a bunch of beacon frames (in other words, I put my Wi-Fi adapter in monitor mode, started capturing while filtering on "wlan.fc.type_subtype == 0x08", and saved that).
Now, I want to, somehow, display specific fields of these packets. Among others:
I don't really care about the representation: plain text, xml, json, csv, X. I'm fine with it. I just don't want more data than I really need and the output needs to be meaningful to the human (wireshark newb) eye.
Eventually, I also want to filter the pcap to get a unique set and count the occurrences (some "|sort|uniq -c" will do), but let's not go there for now.
My solution so far
The first step could be, for example:
$ tshark -r capture.pcap -c 1 -T fields -e wlan_mgt.ssid -e wlan.ta -e wlan_mgt.ds.current_channel -e wlan_mgt.rsn.gcs.type -e wlan_mgt.rsn.pcs.type -e wlan_mgt.rsn.akms.type
MySSID XX:XX:XX:XX:XX:XX 2 4 4 2
After (manually) matching the numbers to their textual meaning, you get this:
This is what I'm looking for. But, as stated, I have to do it manually, which is not an option.
Question
Above you can see my current approach to the said goal. By doing
tshark -r capture.pcap -c 1 -T pdml
I get, for example (cutout):
<field name="wlan_mgt.rsn.pcs.list" showname="Pairwise Cipher Suite List 00-0f-ac (Ieee8021) AES (CCM)" size="4" pos="112" show="" value="">
<field name="wlan_mgt.rsn.pcs" showname="Pairwise Cipher Suite: 00-0f-ac (Ieee8021) AES (CCM)" size="4" pos="112" show="1027076" value="000fac04">
<field name="wlan_mgt.rsn.pcs.oui" showname="Pairwise Cipher Suite OUI: 00-0f-ac (Ieee8021)" size="3" pos="112" show="4012" value="000fac"/>
<field name="wlan_mgt.rsn.pcs.type" showname="Pairwise Cipher Suite type: AES (CCM) (4)" size="1" pos="115" show="4" value="04"/>
</field>
</field>
..., which tells me that tshark does have the information I need (in the form of the "showname" attribute).
Apparently, when working with "-T fields -e X", tshark outputs the value that's in the "show" attribute". I feel like I want what's behind the "showname" attribute. Unfortunately, after annoying google for a while I still don't know how or if this is even possible.
I'm also open to radically different ideas, but the main takeaway is that I can't part from the pcap file (which rules out iwlist, kismet, etc). I also preferably don't start writing search and replace rules to replace the meaningless numbers with their textual representation. I hope to solve it in cleaner way.
I kept messing with tshark for a while, until I decided that it couldn't be done. A little bit of programming using the amazing C++ library libtins got me where I needed to be.
The source is down below. Enjoy :)
#include <tins/tins.h>
#include <algorithm>
#include <iostream>
#include <map>
#include <string>
using namespace Tins;
using namespace std;
/*
* Container class for the data that is retrieved from the beacon.
*/
class Unit {
public:
/*
* Constructor. Parses the Dot11Beacon object and takes all the necessary
* data from it.
*/
Unit(Dot11Beacon& beacon);
Unit() = default;
unsigned getCount();
void incrementCount();
/*
* Prints this object onto the command line, in CSV format
*/
void print();
private:
string ssid;
string bssid;
unsigned channel;
unsigned count;
string gcs; // Group Cipher Suite
string pcs; // Pairwise Cipher Suite
string akm; // Authentication suite
/*
* Returns a string representation of a RSNInformation::CypherSuites enum value
*/
string type_to_string(const RSNInformation::CypherSuites& type);
/*
* Returns a string representation of a RSNInformation::AKMSuites enum value
*/
string type_to_string(const RSNInformation::AKMSuites& type);
};
Unit::Unit(Dot11Beacon& beacon) :
count {1} /* When this unit is created, it has been seen exactly once */ {
ssid = beacon.ssid();
bssid = beacon.addr3().to_string();
channel = unsigned(beacon.ds_parameter_set());
RSNInformation rsn;
for(const auto &opt : beacon.options()) {
if (opt.option() == Dot11::RSN) {
rsn = beacon.rsn_information();
// Put all authentication suite types in a string
const RSNInformation::akm_type& akmTypeList = rsn.akm_cyphers();
for (const auto& akmIt : akmTypeList) {
if (akm.size() == 0)
akm += type_to_string(akmIt);
else
akm += ";" + type_to_string(akmIt);
}
// Put all group cipher types in a string
const RSNInformation::CypherSuites& gcsType = rsn.group_suite();
gcs = type_to_string(gcsType);
// Put all pairwise ciphers in a string
const RSNInformation::cyphers_type& pcsTypeList = rsn.pairwise_cyphers();
for (const auto& pcsIt : pcsTypeList) {
if (pcs.size() == 0)
pcs += type_to_string(pcsIt);
else
pcs += ";" + type_to_string(pcsIt);
}
}
}
}
unsigned Unit::getCount() {
return count;
}
void Unit::incrementCount() {
count += 1;
}
void Unit::print() {
string ssid_to_print;
if (ssid.length() == 0) {
ssid_to_print = "<ZERO_LENGTH>";
} else if (!isprint(ssid[0])) {
ssid_to_print = to_string(static_cast<int>(ssid[0]));
} else {
ssid_to_print = ssid;
}
if (find(ssid_to_print.begin(), ssid_to_print.end(), ',') != ssid_to_print.end()) {
ssid_to_print = "\"" + ssid_to_print + "\"";
}
cout << ssid_to_print << ","
<< bssid << ","
<< to_string(channel) << ","
<< to_string(count) << ","
<< gcs << ","
<< pcs << ","
<< akm << endl;
}
string Unit::type_to_string(const RSNInformation::CypherSuites& type) {
switch (type) {
case RSNInformation::CypherSuites::CCMP:
return "CCMP";
break;
case RSNInformation::CypherSuites::TKIP:
return "TKIP";
break;
case RSNInformation::CypherSuites::WEP_104:
return "WEP_104";
break;
case RSNInformation::CypherSuites::WEP_40:
return "WEP_40";
break;
}
}
string Unit::type_to_string(const RSNInformation::AKMSuites& type) {
switch (type) {
case RSNInformation::AKMSuites::PMKSA:
return "PMKSA";
break;
case RSNInformation::AKMSuites::PSK:
return "PSK";
break;
}
}
/*
* Class that reads the pcap, keeps track of the units and writes out one
* beacon frame in pcap format for each unique AP it finds. This file is called
* "unique_beacons.pcap"
*/
class PCAPParser {
public:
/*
* Constructor. It takes the exact parameters that it will pas on to its
* FileSniffer object (a FileSniffer is actually just a file reader).
*/
PCAPParser(const string& pcapFilename, const string& filter);
/*
* Start reading the file.
*/
bool run();
/*
* Print CSV header and ask all of our collected Unit objects to print themselves
*/
void print();
private:
FileSniffer sniffer;
PacketWriter writer;
map<string, Unit> apMap; // stands for Access Point Map
bool handler(PDU&);
};
PCAPParser::PCAPParser(const string& pcapFilename, const string& filter) :
sniffer {pcapFilename, filter},
writer {"unique_beacons.pcap", PacketWriter::RADIOTAP} {
for (auto it = apMap.begin(); it != apMap.end(); it++) {
it->second.print();
}
}
bool PCAPParser::run() {
sniffer.sniff_loop( [this] (PDU& pdu) { return (bool) this->handler (pdu); } );
return true;
}
bool PCAPParser::handler(PDU& pdu) {
Dot11Beacon& beacon = pdu.rfind_pdu<Dot11Beacon>();
// An ESSID may span multiple BSSID's. Also, it's nice to keep track of what
// channels an AP has been on. Therefore, the combination of SSID, BSSID and
// channel is considered key.
const string& ssid = beacon.ssid();
const string& mac = beacon.addr3().to_string();
const unsigned channel = unsigned(beacon.ds_parameter_set());
const string key = ssid + mac + to_string(channel);
if (apMap.find(key) == apMap.end()) { // we've got a new one
Unit unit(beacon);
apMap[key] = unit;
writer.write(pdu);
} else {
apMap[key].incrementCount();
}
return true;
}
void PCAPParser::print() {
// Print the headers for the CSV output
cout << "SSID,BSSID,Current_channel,Count,Group_Cipher,Pairwise_Ciphers,Authentication_Suite" << endl;
// Ask each of the units to print themselves for the CSV output
for (auto it = apMap.begin(); it != apMap.end(); it++) {
it->second.print();
}
}
int main(int argc, char *argv[]) {
if(argc != 2) {
std::cout << "Usage: " << *argv << " <PCAP_FILE>\n";
return 1;
}
PCAPParser pcapParser(argv[1], "wlan type mgt subtype beacon");
pcapParser.run();
pcapParser.print();
}
Compile with:
g++ pcapreader.cpp -o pcapreader -O3 -std=c++11 -lpthread -ltins
The output is:
$ ./pcapreader capture.pcap
SSID,BSSID,Current_channel,Count,Group_Cipher, Pairwise_Ciphers,Authentication_Suite
MyWiFi,XX:XX:XX:XX:XX:XX,13,2,TKIP,TKIP;CCMP,PSK
...
...
Final note: if you open unique_beacons.pcap
, you may find a lot of [Malformed Packet]
. Apparently, a frame can still be successfully parsed if some of the tagged parameters are received wrongly. You could try to modify the code so that it only writes out frames to the pcap file that are completely intact.