BLE Advertisement Replay Attack & Spoof Detection

Problem

In order to maximize battery life, BLE sensors may send data as non-connectable undirected advertisements, the classic beacon type transmission. In this use case, there is a vulnerability that advertisements can be captured by a third party and replayed. MAC addresses can be easily spoofed, leading to unreliable data on the observer end.

Project

The example BLE Gateway and BLE Sensor is an attempt at mitigating the vulnerability to replay attacks when using beacon type advertisements. It's written for nRF51822 hardware with no onboard NVRAM. The lack of permanent storage weakens this scheme because of start-up conditions. I'll also point out where this scheme is not a proof against replay attacks, only detecting and mitigating certain types of attempts

Import programBLE_Gateway

BLE ADV Gateway, converts advertisement to proper JSON serial output

Import programBLE_Sensor

BLE ADV sensor for 2-pin interrupt (i.e. window/door sensor w/ reed switch)

The assumption is that sensors even in sleep mode retain memory and timers. On the nRF51822, this is reasonable, as the sleep current is only 4uA. On power-on, sensors capture their power-on time as zero. Subsequent advertisements embed a 2-byte value indicating the number of seconds since power-on. This "spoof clock" resets at 1800 seconds (or 30 minutes), due to the limitations of the mbed implementation of timers on the nRF51822. Every new advertisement is basically timestamped with when that advertisement event happened relative to the sensor's "birthday", with a birthday periodicity of 1800 seconds.

When the observer reads the sensor data and timestamp, it can calculate when the sensor's birthday is relative to it's own timer. The observer stores an array of MAC address and birthday pairs. A sensor's advertisement is considered non-suspicious if the sensor's birthday calculated from the advertisement's timestamp matches this array. On powerup with a blank array, the gateway has no other option than to trust all new MAC address advertisements.

/media/uploads/electronichamsters/gateway_clock_01sm.png

But it's expected that sensors would need to have their batteries switched out eventually. Any power off-to-on cycle will reset the birthday of the sensor and so its advertisements would have timestamps that conflict with the observer's recorded birthday for that MAC address. I extended the observer's birthday records with a secondary set of birthdays. When there is a conflict, the new birthday is recorded into this second record. If there are 3 consecutive transmissions where the birthdays are consistent, then I can consider this sensor's birthday as the new primary birthday and start accepting data timed at this new birthday. The birthday records are implemented as a FIFO.

I don't change the time values on every repeated advertisement, and the gateway may receive any of the several repeated advertisements during the advertisement interval. As a result, the gateway must accept a several seconds long range around the birthday time. This also accounts for timer drift between sensors and gateways before resyncing. It does mean that the spoof detection is weaker than a scheme where every advertisement packet has an updated time and modules have an actual external real time clocks with greater accuracy.

/media/uploads/electronichamsters/gateway_clock_02.png

Here's my advertisement organization

AES ArrayADV ArrayFull AdvertisementDesignationValueFunction
nanaByte 0AD1 Length0x02AD1 is 2 bytes long
nanaByte 1AD1 Type0x01AD1 Data interpreted as flag
nanaByte 2AD1 Data 00x06AD1 flag indicates non-connectable undirected
nanaByte 3AD2 Length0x1BAD2 is 27 bytes (0x1B) long (rest of this data)
nanaByte 4AD2 Type0xFF0xFF mean Manufacturer Specific Data
na0Byte 5AD2 Data 0ADV_Data[0]"our device" flag, MAC[3]
na1Byte 6AD2 Data 1ADV_Data[1]"out device" flag, MAC[2]
na2Byte 7AD2 Data 2ADV_Data[2]"out device" flag, MAC[1]
na3Byte 8AD2 Data 3ADV_Data[3]"out device" flag, MAC[0]
na4Byte 9AD2 Data 4ADV_Data[4]battery voltage json MSB, ie 3 in 3.14
na5Byte 10AD2 Data 5ADV_Data[5]battery voltage json
na6Byte 11AD2 Data 6ADV_Data[6]battery voltage json
na7Byte 12AD2 Data 7ADV_Data[7]battery voltage json LSB, ie 4 in 3.14
na8Byte 13AD2 Data 8ADV_Data[8]reserved
na9Byte 14AD2 Data 9ADV_Data[9]reserved
010Byte 15AD2 Data 10ADV_Data[10] Encryptedspoof - clock high byte
111Byte 16AD2 Data 11ADV_Data[11] Encryptedspoof - clock low byte
212Byte 17AD2 Data 12ADV_Data[12] EncryptedXmit_Cnt - increments per transmit event, 0-255
313Byte 18AD2 Data 13ADV_Data[13] EncryptedJSON[0]
414Byte 19AD2 Data 14ADV_Data[14] EncryptedJSON[1]
515Byte 20AD2 Data 15ADV_Data[15] EncryptedJSON[2]
616Byte 21AD2 Data 16ADV_Data[16] EncryptedJSON[3]
717Byte 22AD2 Data 17ADV_Data[17] EncryptedJSON[4]
818Byte 23AD2 Data 18ADV_Data[18] EncryptedJSON[5]
919Byte 24AD2 Data 19ADV_Data[19] EncryptedJSON[6]
1020Byte 25AD2 Data 20ADV_Data[20] EncryptedJSON[7]
1121Byte 26AD2 Data 21ADV_Data[21] EncryptedJSON[8]
1222Byte 27AD2 Data 22ADV_Data[22] EncryptedJSON[9]
1323Byte 28AD2 Data 23ADV_Data[23] EncryptedJSON[10]
1424Byte 29AD2 Data 24ADV_Data[24] EncryptedJSON[11]
1525Byte 30AD2 Data 25ADV_Data[25] EncryptedJSON[12]

The first 5 bytes are the standard AD1 block for beacons, indicating the advertisement is unconnectable undirected. AD2 block is our manufacturer specific custom advertisement where we pack data. Bytes 5-8 are our indicator for the observer being able to identify "our sensor" vs. other beacons using a pattern matching scheme. The observer will only process the data (perform AES decrypt and output data over serial) if these 4 bytes match our pattern. The alternative is to know which MAC addresses to look for ahead of time. This would make deployment more complicated. The pattern matching is a compromise between security and ease of deployment. It imposes a weakness to the replay detection because the size of the spoof checking array is finite. We can only keep track of, say 50, unique MAC's and their birthdays. Beyond that, the FIFO starts dropping old MAC addresses, and malicious advertisements can then be injected. We'll detect when this happens, but we're not able to protect against it in an automated way. A solution for this is to have an external step to validate MAC addresses and protect those from dropping off the FIFO. But that's a lot of work.

Bytes 9-12 hold the battery voltage. I didn't want to take up more of the AES encrypted block for something that's not very sensitive.

The first two encrypted bytes, Byte 15-16, hold the number of seconds since the sensor's birthday.

Byte 17 holds the transmit counter. This helps the gateway differentiate between repeated transmissions of the same advertisement data, and not send those out the uart.

Byte 18-30 hold JSON formatted sensor data. Using JSON isn't very byte-efficient. But it pretty flexible and the scheme works well with MQTT topic naming.


Please log in to post comments.