July 12, 2016

ESP8266 and beacon frames

In a previous post I mentioned a hack by kripthor about sending fake beacon frames with the ESP8266. The original code was limited to fixed 6 character long SSIDs and I wanted to extend it to arbitrary length.

Understanding the beacon frame

So the original code was sending this frame:

uint8_t packet[128] = { 0x80, 0x00, 0x00, 0x00,
/*4*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
/*10*/ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
/*16*/ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
/*22*/ 0xc0, 0x6c,
/*24*/ 0x83, 0x51, 0xf7, 0x8f, 0x0f, 0x00, 0x00, 0x00,
/*32*/ 0x64, 0x00,
/*34*/ 0x01, 0x04,
/* SSID */
/*36*/ 0x00, 0x06, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72,
0x01, 0x08, 0x82, 0x84,
0x8b, 0x96, 0x24, 0x30, 0x48, 0x6c, 0x03, 0x01,
/*56*/ 0x04};


Later in every iteration it put the randomly generated source MAC address to 10-15th and 16-21th element, the SSID to the 83-43th element and the channel to the 56th element.
But what this packet really is? I found this very good article on beacon frames. Basically in the beginning it has a header with fixed size fields, and then the frame body has variable length fields, some of them are optional.
Let's start with the header. Based on this image from the above mentioned article we can identify the fields:
uint8_t packet[128] = { 
0x80, 0x00, //frame control
0x00, 0x00, //duration
/*4*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
//DA - destination address, broadcast in this case
/*10*/ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,//SA - source address, will be overwritten later
 /*16*/ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,//BSSID - same as SA in this case, will be overwritten later
 /*22*/ 0xc0, 0x6c,//Seq-ctl
//Frame body starts here
/*24*/ 0x83, 0x51, 0xf7, 0x8f, 0x0f, 0x00, 0x00, 0x00,//timestamp
 /*32*/ 0x64, 0x00,//beacon interval
 /*34*/ 0x01, 0x04,//capability info
 /* SSID */
/*36*/ 0x00, 0x06, 0x72, 0x72, 0x72, 0x72, 0x72, 0x72,
0x01, 0x08, 0x82, 0x84,
0x8b, 0x96, 0x24, 0x30, 0x48, 0x6c, 0x03, 0x01,
/*56*/ 0x04}; 


First part of the frame body was also easy, since it was fixed length. SSID was tricky, since it's variable length, so simply counting the bytes won't work. Fortunately the above mentioned article has a good description on the matter: the first byte is the type of the field (0x00 meaning SSID), then the next byte is the length of the field (0x06 = 6 character) and then the 6 character long name of the SSID.
But based on this logic the next element has a type of 0x01 and a length of 0x08. Based on the article 0x01 means 'Supported rates', which is a mandatory field, so just leave it there. If we count the 8 characters we see that the field ends with 0x6c. So the next and last field has the id 0x03, length 0x01 and the content is 0x04. I couldn't find a field with id 0x03 in the article, but based on the fact, that 0x04 is overwritten with the channel number, I guess it's used for that. So the whole packet looks like this:

uint8_t packet[128] = { 
0x80, 0x00, //frame control
0x00, 0x00, //duration
/*4*/ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
//DA - destination address, broadcast in this case
/*10*/ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,//SA - source address, will be overwritten later
 /*16*/ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,//BSSID - same as SA in this case, will be overwritten later
 /*22*/ 0xc0, 0x6c,//Seq-ctl
//Frame body starts here
/*24*/ 0x83, 0x51, 0xf7, 0x8f, 0x0f, 0x00, 0x00, 0x00,//timestamp
 /*32*/ 0x64, 0x00,//beacon interval
 /*34*/ 0x01, 0x04,//capability info
 /* SSID */
0x00,//ID meaning SSID
0x06,//length
0x72, 0x72, 0x72, 0x72, 0x72, 0x72,//SSID name
0x01,//ID meaning Supported rates
0x08,//length
0x82, 0x84, 0x8b, 0x96, 0x24, 0x30, 0x48, 0x6c,//Supported rates
0x03,//ID meaning channel (?)
0x01,//length
0x04//will be overwritten later with the actual channel
}; 

It sounds easy now, but I spent some time figuring it out, so I hope it helps others :)

The code

I implemented some features like sending beacon frames given the SSID, sending random SSIDs giving only the desired length and sending fuzzed beacon frames. This last feature gets a string (for example 'test') and it will send beacon frames with a given number of SSIDs starting with the string ('test') and ending with some whitespaces (spaces and/or tabs), so when someone scans for wifi they will see a lot of networks popping up with the same name (actually the name differs in the whitespaces in the end, but usually that's not displayed):
My code is available on Github. Feel free to contribute :)
Mark