blob: 21a7b99c8ac22832cfe3539c87b6e6b9278d786b [file] [log] [blame]
#include <pcap.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <endian.h>
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
#define le_to_host16 le16toh
#define le_to_host32 le32toh
struct ieee80211_radiotap_hdr {
uint8_t it_version;
uint8_t it_pad;
uint16_t it_len;
uint32_t it_present;
} __attribute__ ((packed));
struct ieee80211_mgmt {
uint16_t frame_control;
uint16_t duration;
uint8_t da[6];
uint8_t sa[6];
uint8_t bssid[6];
uint16_t seq_ctrl;
union {
struct {
uint16_t cap;
uint16_t listen;
uint8_t variable[0];
} __attribute__ ((packed)) assoc_req;
struct {
uint8_t variable[0];
} __attribute__ ((packed)) probe_req;
} u;
} __attribute__ ((packed));
#define ASSOC_REQ 0
#define PROBE_REQ 4
/* from hostap/src/ap/taxonomy.c */
/* Copy a string with no funny schtuff allowed; only alphanumerics. */
static void no_mischief_strncpy(char *dst, const char *src, size_t n)
{
size_t i;
for (i = 0; i < n; i++) {
unsigned char s = src[i];
int is_lower = (s >= 'a' && s <= 'z');
int is_upper = (s >= 'A' && s <= 'Z');
int is_digit = (s >= '0' && s <= '9');
if (is_lower || is_upper || is_digit) {
/* TODO: if any manufacturer uses Unicode within the
* WPS header, it will get mangled here. */
dst[i] = s;
} else {
/* note that even spaces will be transformed to underscores,
* so 'Nexus 7' will turn into 'Nexus_7'. This is deliberate,
* to make the string easier to parse. */
dst[i] = '_';
}
}
}
static int get_wps_name(char *name, size_t name_len,
const u8 *data, size_t data_len)
{
/* Inside the WPS IE are a series of sub-IEs, using two byte IDs
* and two byte lengths. We're looking for the model name, if
* present. */
while (data_len >= 4) {
u16 id, elen;
id = (data[0] << 8) | data[1];
elen = (data[2] << 8) | data[3];
data += 4;
data_len -= 4;
if (elen > data_len) {
return 0;
}
if (id == 0x1023) {
/* Model name, like 'Nexus 7' */
size_t n = (elen < name_len) ? elen : name_len;
no_mischief_strncpy(name, (const char *)data, n);
return n;
}
data += elen;
data_len -= elen;
}
return 0;
}
static void ie_to_string(char *fstr, size_t fstr_len,
const u8 *ie, size_t ie_len)
{
size_t flen = fstr_len - 1;
char htcap[7 + 4 + 1]; // ",htcap:" + %04hx + trailing NUL
char htagg[7 + 2 + 1]; // ",htagg:" + %02hx + trailing NUL
char htmcs[7 + 8 + 1]; // ",htmcs:" + %08x + trailing NUL
char vhtcap[8 + 8 + 1]; // ",vhtcap:" + %08x + trailing NUL
char vhtrxmcs[10 + 8 + 1]; // ",vhtrxmcs:" + %08x + trailing NUL
char vhttxmcs[10 + 8 + 1]; // ",vhttxmcs:" + %08x + trailing NUL
#define MAX_EXTCAP 254
char extcap[8 + (2 * MAX_EXTCAP) + 1]; // ",extcap:" + hex + trailing NUL
char txpow[7 + 4 + 1]; // ",txpow:" + %04hx + trailing NUL
#define WPS_NAME_LEN 32
char wps[WPS_NAME_LEN + 5 + 1]; // room to prepend ",wps:" + trailing NUL
int num = 0;
memset(htcap, 0, sizeof(htcap));
memset(htagg, 0, sizeof(htagg));
memset(htmcs, 0, sizeof(htmcs));
memset(vhtcap, 0, sizeof(vhtcap));
memset(vhtrxmcs, 0, sizeof(vhtrxmcs));
memset(vhttxmcs, 0, sizeof(vhttxmcs));
memset(extcap, 0, sizeof(extcap));
memset(txpow, 0, sizeof(txpow));
memset(wps, 0, sizeof(wps));
fstr[0] = '\0';
while (ie_len >= 2) {
u8 id, elen;
char tagbuf[32];
char *sep = (num++ == 0) ? "" : ",";
id = *ie++;
elen = *ie++;
ie_len -= 2;
if (elen > ie_len) {
break;
}
if ((id == 221) && (elen >= 4)) {
/* Vendor specific */
int is_MSFT = (ie[0] == 0x00 && ie[1] == 0x50 && ie[2] == 0xf2);
if (is_MSFT && ie[3] == 0x04) {
/* WPS */
char model_name[WPS_NAME_LEN + 1];
const u8 *data = &ie[4];
size_t data_len = elen - 4;
memset(model_name, 0, sizeof(model_name));
if (get_wps_name(model_name, WPS_NAME_LEN, data, data_len)) {
snprintf(wps, sizeof(wps), ",wps:%s", model_name);
}
}
snprintf(tagbuf, sizeof(tagbuf), "%s%d(%02x%02x%02x,%d)",
sep, id, ie[0], ie[1], ie[2], ie[3]);
} else {
if ((id == 45) && (elen >= 2)) {
/* HT Capabilities (802.11n) */
u16 cap;
memcpy(&cap, ie, sizeof(cap));
snprintf(htcap, sizeof(htcap), ",htcap:%04hx",
le_to_host16(cap));
}
if ((id == 45) && (elen >= 3)) {
/* HT Capabilities (802.11n), A-MPDU information */
u8 agg;
memcpy(&agg, ie + 2, sizeof(agg));
snprintf(htagg, sizeof(htagg), ",htagg:%02hx", agg);
}
if ((id == 45) && (elen >= 7)) {
/* HT Capabilities (802.11n), MCS information */
u32 mcs;
memcpy(&mcs, ie + 3, sizeof(mcs));
snprintf(htmcs, sizeof(htmcs), ",htmcs:%08hx",
le_to_host32(mcs));
}
if ((id == 191) && (elen >= 4)) {
/* VHT Capabilities (802.11ac) */
u32 cap;
memcpy(&cap, ie, sizeof(cap));
snprintf(vhtcap, sizeof(vhtcap), ",vhtcap:%08x",
le_to_host32(cap));
}
if ((id == 191) && (elen >= 8)) {
/* VHT Capabilities (802.11ac), RX MCS information */
u32 mcs;
memcpy(&mcs, ie + 4, sizeof(mcs));
snprintf(vhtrxmcs, sizeof(vhtrxmcs), ",vhtrxmcs:%08x",
le_to_host32(mcs));
}
if ((id == 191) && (elen >= 12)) {
/* VHT Capabilities (802.11ac), TX MCS information */
u32 mcs;
memcpy(&mcs, ie + 8, sizeof(mcs));
snprintf(vhttxmcs, sizeof(vhttxmcs), ",vhttxmcs:%08x",
le_to_host32(mcs));
}
if (id == 127) {
/* Extended Capabilities */
int i;
int len = (elen < MAX_EXTCAP) ? elen : MAX_EXTCAP;
char *p = extcap;
p += snprintf(extcap, sizeof(extcap), ",extcap:");
for (i = 0; i < len; ++i) {
int lim = sizeof(extcap) - strlen(extcap);
p += snprintf(p, lim, "%02x", *(ie + i));
}
}
if ((id == 33) && (elen == 2)) {
/* TX Power */
u16 p;
memcpy(&p, ie, sizeof(p));
snprintf(txpow, sizeof(txpow), ",txpow:%04hx",
le_to_host16(p));
}
snprintf(tagbuf, sizeof(tagbuf), "%s%d", sep, id);
}
strncat(fstr, tagbuf, flen);
flen = fstr_len - strlen(fstr) - 1;
ie += elen;
ie_len -= elen;
}
if (strlen(htcap)) {
strncat(fstr, htcap, flen);
flen = fstr_len - strlen(fstr) - 1;
}
if (strlen(htagg)) {
strncat(fstr, htagg, flen);
flen = fstr_len - strlen(fstr) - 1;
}
if (strlen(htmcs)) {
strncat(fstr, htmcs, flen);
flen = fstr_len - strlen(fstr) - 1;
}
if (strlen(vhtcap)) {
strncat(fstr, vhtcap, flen);
flen = fstr_len - strlen(fstr) - 1;
}
if (strlen(vhtrxmcs)) {
strncat(fstr, vhtrxmcs, flen);
flen = fstr_len - strlen(fstr) - 1;
}
if (strlen(vhttxmcs)) {
strncat(fstr, vhttxmcs, flen);
flen = fstr_len - strlen(fstr) - 1;
}
if (strlen(txpow)) {
strncat(fstr, txpow, flen);
flen = fstr_len - strlen(fstr) - 1;
}
if (strlen(extcap)) {
strncat(fstr, extcap, flen);
flen = fstr_len - strlen(fstr) - 1;
}
if (strlen(wps)) {
strncat(fstr, wps, flen);
flen = fstr_len - strlen(fstr) - 1;
}
fstr[fstr_len - 1] = '\0';
}
int usage(const char *progname)
{
fprintf(stderr, "usage: %s -f pcap\n", progname);
exit(1);
}
int main(int argc, char **argv)
{
int opt;
pcap_t *handle;
char errbuf[PCAP_ERRBUF_SIZE];
struct pcap_pkthdr hdr;
const uint8_t *pkt;
const char *filename = NULL;
char mac[18];
char probe_sig[4096] = {0};
char assoc_sig[4096] = {0};
while ((opt = getopt(argc, argv, "f:")) != -1) {
switch(opt){
case 'f':
filename = optarg;
break;
default:
usage(argv[0]);
break;
}
}
if (filename == NULL) {
usage(argv[0]);
}
if ((handle = pcap_open_offline(filename, errbuf)) == NULL) {
perror("Cannot open pcap file");
exit(1);
}
while ((pkt = pcap_next(handle, &hdr)) != NULL) {
struct ieee80211_radiotap_hdr *rtap;
struct ieee80211_mgmt *mlme;
uint16_t fc;
int type, subtype;
uint8_t *ie;
size_t ie_len;
rtap = (struct ieee80211_radiotap_hdr *)pkt;
mlme = (struct ieee80211_mgmt *)(pkt + rtap->it_len);
fc = le_to_host16(mlme->frame_control);
type = (fc >> 2) & 0x0003;
subtype = (fc >> 4) & 0x000f;
if (type == 0 && subtype == ASSOC_REQ) {
ie = mlme->u.assoc_req.variable;
ie_len = hdr.caplen - (ie - (const uint8_t *)mlme) - rtap->it_len - 4;
ie_to_string(assoc_sig, sizeof(assoc_sig), ie, ie_len);
}
if (type == 0 && subtype == PROBE_REQ) {
snprintf(mac, sizeof(mac), "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx",
mlme->sa[0], mlme->sa[1], mlme->sa[2],
mlme->sa[3], mlme->sa[4], mlme->sa[5]);
ie = mlme->u.probe_req.variable;
ie_len = hdr.caplen - (ie - (const uint8_t *)mlme) - rtap->it_len - 4;
ie_to_string(probe_sig, sizeof(probe_sig), ie, ie_len);
}
}
printf("%s wifi4|probe:%s|assoc:%s\n", mac, probe_sig, assoc_sig);
if (strlen(probe_sig) && strlen(assoc_sig) && strlen(mac)) {
exit(0);
}
exit(1);
}