hak5-wifi-coconut/wifi_capture.c

501 lines
15 KiB
C

/*
* GPL-2.0-or-later
*
* Userspace port (C) 2019 Hak5 LLC
*
*/
#include <errno.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#ifndef _WIN32
#include <sys/time.h>
#include <unistd.h>
#else
#include <Windows.h>
#define sleep(x) Sleep(x*1000)
#define usleep(x) Sleep((x) < 1000 ? 1 : (x) / 1000)
#endif
#include "kernel/cfg80211.h"
#include "kernel/endian.h"
#include "kernel/ieee80211.h"
#include "kernel/ieee80211_radiotap.h"
#include <libusb.h>
#include "userspace/userspace.h"
struct wifi_test_context {
/*
* USB context
*/
struct userspace_wifi_context *context;
/*
* Device we plan to open
*/
struct userspace_wifi_probe_dev* probedev;
/*
* The device we're operating on
*/
struct userspace_wifi_dev *dev;
/*
* Runtime options
*/
bool disable_leds;
bool disable_blink;
bool invert_leds;
bool no_radiotap;
bool quiet;
bool diagnostics_mode;
bool diagnostics_only;
int device_number;
int channel;
/*
* pcap logging
*/
bool enable_pcap_log;
FILE *dump_filep;
int num_packets;
pthread_mutex_t pcap_mutex;
};
int open_wifi_device(struct wifi_test_context *test_context) {
int r;
r = userspace_wifi_device_open(test_context->context, test_context->probedev, &test_context->dev);
if (r != 0) {
printf("Failed to open device: %d %s\n", r, strerror(r));
return -1;
}
userspace_wifi_device_set_channel(test_context->context,
test_context->dev,
test_context->channel,
NL80211_CHAN_WIDTH_20_NOHT);
if (test_context->disable_leds)
userspace_wifi_device_set_led(test_context->context, test_context->dev, false);
else
userspace_wifi_device_set_led(test_context->context, test_context->dev, true);
if (!test_context->disable_blink)
userspace_wifi_device_enable_led_control(test_context->context, test_context->dev);
return 0;
}
int start_wifi_capture(struct wifi_test_context* test_context) {
/*
* If LEDs are inverted, turn off LEDs once we've enumerated all the radios.
*/
if (test_context->invert_leds)
userspace_wifi_device_set_led(test_context->context, test_context->dev, false);
userspace_wifi_device_start_capture(test_context->context, test_context->dev);
return 0;
}
#define DLT_IEEE802_11 105 /* IEEE 802.11 wireless */
#define DLT_IEEE802_11_RADIO 127 /* 802.11 plus radiotap radio header */
int open_pcap(const char *file, struct wifi_test_context *test_context) {
struct {
uint32_t magic_number; /* magic number */
uint16_t version_major; /* major version number */
uint16_t version_minor; /* minor version number */
int32_t thiszone; /* GMT to local correction */
uint32_t sigfigs; /* accuracy of timestamps */
uint32_t snaplen; /* max length of captured packets, in octets */
uint32_t network; /* data link type */
} pcap_hdr_t;
pcap_hdr_t.magic_number = 0xa1b2c3d4;
pcap_hdr_t.version_major = 2;
pcap_hdr_t.version_minor = 4;
pcap_hdr_t.thiszone = 0;
pcap_hdr_t.sigfigs = 0;
pcap_hdr_t.snaplen = 8192;
test_context->enable_pcap_log = true;
pthread_mutex_init(&test_context->pcap_mutex, NULL);
if (test_context->no_radiotap)
pcap_hdr_t.network = DLT_IEEE802_11;
else
pcap_hdr_t.network = DLT_IEEE802_11_RADIO;
if (strcmp(file, "-") == 0)
test_context->dump_filep = stdout;
else
test_context->dump_filep = fopen(file, "wb");
if (test_context->dump_filep == NULL) {
fprintf(stderr, "ERROR: Could not open dump: %d %s\n", errno, strerror(errno));
return -1;
}
fwrite(&pcap_hdr_t, sizeof(pcap_hdr_t), 1, test_context->dump_filep);
return 1;
}
int pcap_rx_packet(struct userspace_wifi_context *context,
struct userspace_wifi_dev *dev,
struct userspace_wifi_rx_signal *signal,
unsigned char *data, unsigned int len) {
struct wifi_test_context *test_context = (struct wifi_test_context *) context->local_data;
typedef struct {
uint32_t ts_sec; /* timestamp seconds */
uint32_t ts_usec; /* timestamp microseconds */
uint32_t incl_len; /* number of octets of packet saved in file */
uint32_t orig_len; /* actual length of packet */
} pcaprec_hdr_t;
pcaprec_hdr_t pcap_hdr;
struct timeval tv;
typedef struct {
uint16_t version;
uint16_t length;
uint32_t bitmap;
uint8_t flags;
uint8_t pad0;
uint16_t channel_freq;
uint16_t channel_flags;
uint8_t antsignal;
} _rtap_hdr;
_rtap_hdr rtap_hdr = {
.version = 0,
.length = htole16(sizeof(_rtap_hdr)),
.bitmap = htole32((1 << IEEE80211_RADIOTAP_FLAGS) |
(1 << IEEE80211_RADIOTAP_CHANNEL) |
(1 << IEEE80211_RADIOTAP_DBM_ANTSIGNAL)),
.flags = 0,
.channel_freq = htole16(ieee80211_channel_to_frequency(signal->channel, signal->band)),
.channel_flags = 0,
.antsignal = (u8) signal->signal,
};
if (!signal->crc_valid)
rtap_hdr.flags |= IEEE80211_RADIOTAP_F_BADFCS;
if (signal->short_gi)
rtap_hdr.flags |= IEEE80211_RADIOTAP_F_SHORTGI;
if (signal->band == NL80211_BAND_2GHZ)
rtap_hdr.channel_flags |= IEEE80211_CHAN_2GHZ;
else if (signal->band == NL80211_BAND_5GHZ)
rtap_hdr.channel_flags |= IEEE80211_CHAN_5GHZ;
/*
* Only blink if we're told to blink.
* Default LED state is "not inverted" (so on).
* If we're inverted, we let the timer override, so active LEDs stay
* ON longer as the timer gets extended; this looks better.
*/
if (!test_context->disable_blink)
userspace_wifi_device_blink_led(context, dev, 100000, !test_context->invert_leds, test_context->invert_leds);
if (test_context->diagnostics_mode) {
fprintf(stderr, "PACKET %u %u %d %02x %02x %02x %02x %02x %02x ...\n",
len, signal->channel, signal->signal, data[0], data[1], data[2], data[3], data[4], data[5]);
}
if (!test_context->enable_pcap_log)
return 1;
gettimeofday(&tv, NULL);
pcap_hdr.ts_sec = tv.tv_sec;
pcap_hdr.ts_usec = tv.tv_usec;
pthread_mutex_lock(&test_context->pcap_mutex);
if (test_context->no_radiotap) {
pcap_hdr.incl_len = pcap_hdr.orig_len = len;
fwrite(&pcap_hdr, sizeof(pcaprec_hdr_t), 1, test_context->dump_filep);
fwrite(data, len, 1, test_context->dump_filep);
} else {
pcap_hdr.incl_len = pcap_hdr.orig_len = sizeof(_rtap_hdr) + len;
fwrite(&pcap_hdr, sizeof(pcaprec_hdr_t), 1, test_context->dump_filep);
fwrite(&rtap_hdr, sizeof(_rtap_hdr), 1, test_context->dump_filep);
fwrite(data, len, 1, test_context->dump_filep);
}
test_context->num_packets++;
if (!test_context->quiet && test_context->num_packets % 100 == 0)
fprintf(stderr, "Got %d packets\n", test_context->num_packets);
pthread_mutex_unlock(&test_context->pcap_mutex);
return 1;
}
void print_device_list(struct userspace_wifi_probe_dev* devs) {
int dev_num = 0;
int i;
while (devs != NULL) {
fprintf(stderr, "DEVICE %d: Driver %s USB ID %x %x ",
dev_num, devs->driver_name, devs->device_id_match->idVendor, devs->device_id_match->idProduct);
for (i = 0; i < devs->usb_bus_path_len; i++) {
fprintf(stderr, "/%u", devs->usb_bus_path[i]);
}
fprintf(stderr, "\n");
dev_num++;
devs = devs->next;
}
}
struct userspace_wifi_probe_dev* find_device_by_number(int device_number, struct userspace_wifi_probe_dev* devs) {
int dev_num = 0;
if (devs == NULL)
return NULL;
if (device_number < 0)
return devs;
while (devs != NULL) {
if (dev_num == device_number)
return devs;
dev_num++;
devs = devs->next;
}
return NULL;
}
void print_usage(char *argv) {
printf("Userspace Wifi Test\n");
printf("Usage: %s [options] [output file]\n", argv);
printf("Options:\n");
printf(" --list List devices and exit\n"
" --device=X If you have multiple supported Wi-Fi devices, specify\n"
" which one to use\n"
" --channel=X Set Wi-Fi Channel (1-14)\n"
" --plain-dot11 Log plain 802.11 packets instead of radiotap\n"
" formatted packets with signal and channel\n"
" --disable-leds Go fully dark; don't enable any LEDs\n"
" --invert-leds Normally we enable all the LEDs\n"
" and blink during traffic; Invert only lights\n"
" when there is traffic.\n"
" --disable-blinking Disable blinking the LEDs on traffic\n"
" --quiet Disable most output\n"
" --diagnostics Diagnostic statistics mode\n"
" --diagnostics-only Enable diagnostics-only mode, disable pcap\n");
}
int main(int argc, char *argv[]) {
#define OPT_HELP 'h'
#define OPT_LIST 2
#define OPT_DEVNO 3
#define OPT_PLAINDOT11 5
#define OPT_DISABLELED 6
#define OPT_INVERTLED 7
#define OPT_DISABLEBLNK 8
#define OPT_QUIET 9
#define OPT_DIAG 10
#define OPT_DIAGONLY 11
#define OPT_CHANNEL 12
static struct option longopt[] = {
{ "help", no_argument, 0, OPT_HELP },
{ "list", no_argument, 0, OPT_LIST },
{ "device", required_argument, 0, OPT_DEVNO },
{ "plain-dot11", no_argument, 0, OPT_PLAINDOT11 },
{ "disable-leds", no_argument, 0, OPT_DISABLELED },
{ "invert-leds", no_argument, 0, OPT_INVERTLED },
{ "disable-blinking", no_argument, 0, OPT_DISABLEBLNK },
{ "quiet", no_argument, 0, OPT_QUIET },
{ "diagnostics", no_argument, 0, OPT_DIAG },
{ "diagnostics-only", no_argument, 0, OPT_DIAGONLY },
{ "channel", required_argument, 0, OPT_CHANNEL },
{ 0, 0, 0, 0 }
};
int option_idx = 0;
optind = 0;
opterr = 0;
int r;
bool list_only = false;
char *pcap_fname = NULL;
struct userspace_wifi_context *context;
struct userspace_wifi_probe_dev *probed;
int probed_count;
struct wifi_test_context test_context;
memset(&test_context, 0, sizeof(struct wifi_test_context));
test_context.device_number = -1;
test_context.channel = 1;
while ((r = getopt_long(argc, argv, "h", longopt, &option_idx)) != -1) {
switch (r) {
case OPT_HELP:
/* help */
print_usage(argv[0]);
exit(0);
break;
case OPT_LIST:
list_only = true;
break;
case OPT_DISABLELED:
test_context.disable_leds = true;
break;
case OPT_INVERTLED:
test_context.invert_leds = true;
break;
case OPT_DISABLEBLNK:
test_context.disable_blink = true;
break;
case OPT_QUIET:
test_context.quiet = true;
break;
case OPT_DEVNO:
if (sscanf(optarg, "%u", &test_context.device_number) != 1) {
print_usage(argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "Expected a number for --device, such as --device=5\n");
exit(1);
}
break;
case OPT_PLAINDOT11:
test_context.no_radiotap = true;
break;
case OPT_DIAG:
test_context.diagnostics_mode = true;
break;
case OPT_DIAGONLY:
test_context.diagnostics_only = true;
test_context.diagnostics_mode = true;
break;
case OPT_CHANNEL:
if (sscanf(optarg, "%u", &test_context.channel) != 1) {
print_usage(argv[0]);
fprintf(stderr, "\nExpected a number for --channel, such as --channel=6\n");
exit(1);
}
if (test_context.channel < 1 || test_context.channel > 14) {
print_usage(argv[0]);
fprintf(stderr, "\nExpected a channel between 1 and 14\n");
exit(1);
}
break;
}
}
if (!list_only && !test_context.diagnostics_only && optind >= argc) {
print_usage(argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "FATAL: Expected pcap file name\n");
exit(1);
} else if (!test_context.diagnostics_only && !list_only) {
pcap_fname = strdup(argv[optind]);
}
/*
* If LEDs are disabled, blinking is disabled
*/
if (test_context.disable_leds)
test_context.disable_blink = true;
r = userspace_wifi_init(&context);
if (r != 0) {
fprintf(stderr, "FATAL: Failed to initialize USB subsystem, %d\n", r);
exit(1);
}
context->local_data = &test_context;
test_context.context = context;
probed_count = userspace_wifi_probe(context, &probed);
if (probed_count == 0) {
#ifdef _WIN32
fprintf(stderr, "ERROR: No compatible USB Wi-Fi cards found. Make sure you have a supported Wi-Fi device "
"and that you have installed the required drivers via Zadig!\n");
#elif defined(__APPLE__)
/*
* Whine about root
*/
if (getuid() != 0) {
fprintf(stderr, "ERROR: No compatible USB Wi-Fi devices found. Not running as root. Root is often required for "
"raw USB access if you are not the primary user.\n");
} else {
fprintf(stderr, "ERROR: No compatible USB Wi-Fi devices found.\n");
}
#else
if (getuid() != 0) {
fprintf(stderr, "ERROR: No compatible USB Wi-Fi devices found. Not running as root. Root is typically required for "
"raw USB access; if you see no USB devices, try running as root\n");
} else {
fprintf(stderr, "ERROR No compatible USB Wi-Fi devices found.\n");
}
#endif
exit(1);
}
if (!test_context.quiet)
print_device_list(probed);
if (list_only)
goto end;
/* Look for the device we were asked for */
test_context.probedev = find_device_by_number(test_context.device_number, probed);
userspace_wifi_set_packet_cb(context, &pcap_rx_packet);
if (!test_context.diagnostics_only && !list_only) {
r = open_pcap(pcap_fname, &test_context);
if (r < 0) {
fprintf(stderr, "FATAL: Could not open pcap\n");
exit(1);
}
}
open_wifi_device(&test_context);
/*
* Activate capture
*/
if (!test_context.quiet)
fprintf(stderr, "Activating capture...\n");
start_wifi_capture(&test_context);
while (1) {
sleep(1);
}
end:
/* This makes win32 sad right now; not sure why
userspace_wifi_free_probe(probed);
userspace_wifi_free(context);
*/
return 0;
}