Note aux francophones: j'ai rédigé ce write-up initialement en anglais, et je n'ai pas eu/pris le temps de le traduire en français. Je sais, je commence mal l'année 2022 (mais lisez ce write-up, l'épreuve était vraiment sympa =)
During the ph0wn CTF that took place virtually on December the 3rd, a strange challenge called Wazabee were available with the following description:
Hint: Do some OSINT on the challenge author to find what this can be about.
Jean Reno has created a network surveillance startup, called Wazabee. He
installed this Android application (download it) on his smartphone and it
looks like it is transmitting something important. But what? Help him,
and you might get to play in Wasabi 2.
Install the application on an Android smartphone (it is not malicious)
that supports Extended advertising and LE 2M. You can easily test your phone's
capabilities with the nRF Connect application. Go in "Device information" and
check the corresponding labels are green.
Your smartphone does not support it? Too bad :=) Get one! Or find another way
to solve the challenge (there are several solutions).
The flag follows the usual format: ph0wn{ .... } where it can any printable
character between the brackets.
We are provided with an Android APK that must be run on a smartphone that supports Extended advertising and LE 2M.
Well, let's have a look at it.
First of all, I don't own a smartphone that supports Extended advertising and LE 2M but I know a little of Bluetooth Low Energy. Extended advertisements and a bitrate of 2 Mbps have been introduced in the version 5 of the Bluetooth core specification, that allows any compatible device to send BLE advertisements on all channels (instead of the only 3 advertising channels 37, 38, and 39 as specified in previous versions) at 2 Mbit per second.
Second, a hint is given in the description, telling us to do a background search on the author of this challenge (Romain Cayre). A quick search revealed that Romain Cayre published a paper written in French, at the French SSTIC conference, called "WazaBee : attaque de réseaux Zigbee par détournement de puces Bluetooth Low Energy" (or "WazaBee: attacking Zigbee networks by subverting Bluetooth Low Energy chips").
This paper presents a way to make a BLE 5 compatible chip send Zigbee frames by abusing the GFSK modulation used in Bluetooth Low Energy PHY. This paper contains the word "Wazabee" in its title (which sounds like "wasabi" in French -- also a French movie starring Jean Reno) and is about Bluetooth Low Energy (and Zigbee). Well, that's becoming interesting !
Once disassembled using Jadx, the interesting code is found in the radio.sploit.phownchallenge.SecondFragment class:
@Override // androidx.fragment.app.Fragment
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
final BluetoothLeAdvertiser advertiser = BluetoothAdapter.getDefaultAdapter()
.getBluetoothLeAdvertiser();
final View v = getView();
if (!adapter.isLe2MPhySupported()) {
Snackbar.make(v, "2M PHY not supported!", -1).show();
} else if (!adapter.isLeExtendedAdvertisingSupported()) {
Snackbar.make(v, "LE Extended Advertising not supported!", -1).show();
} else {
This code checks that the 2 Mbps bitrate and extended advertising are supported, and then builds a payload that is sent in bluetooth low energy advertisements:
AdvertisingSetParameters.Builder parameters = new AdvertisingSetParameters.Builder()
.setLegacyMode(false)
.setInterval(160)
.setTxPowerLevel(1)
.setPrimaryPhy(1)
.setSecondaryPhy(2);
String a = getString(R.string.a);
String b = getString(R.string.b);
String c = getString(R.string.c);
String d = getString(R.string.d);
String e = getString(R.string.e);
String out = "";
int x = 0;
while (out.length() != 64) {
if (x % 4 == 0) {
out = a + out + b;
}
if (x % 4 == 1) {
out = c + out + d;
}
if (x % 4 == 2) {
out = d + out + c;
}
if (x % 4 == 3) {
out = b + out + a;
}
x++;
}
AdvertiseData data = new AdvertiseData.Builder()
.addManufacturerData(
4660,
dewhiten(hexStringToByteArray(out + e), 17, 16)
).build();
The referenced strings are the following:
Let's write some Python code to generate the same advertising data:
# generate out
a = '1b'
b = '03'
c = '3a'
d = 'f7'
e = '70afb33965fc08453b9b03773970af3303f73a1b70afb339fc08c564f73a9b03' \
'03f73a1bb239702f70afb33970afb33908c5647cf73a9b034dc68f5070afb339' \
'c68f504cb239702f514cc60fb239702f4dc68f50f73a9b0303f73a1b70afb339' \
'c464fc08aeb33970b239702ff73a9b03f73a9b0370afb33965fc0845aeb33970' \
'464fc0870afb33970afb33970afb339aeb33970aeb33970514cc60f70afb3390'
'3f73a1bc68f504c70afb3393970af33'
x = 0
out = ''
while len(out)!=64:
if x%4==0:
out = a + out + b
if x%4==1:
out = c + out + d
if x%4==2:
out = d + out + c
if x%4==3:
out = b + out + a
x += 1
# Payload is dewhitened before sending, because it will be whitened
# again when sent by the BLE chip =)
payload = out + e
print('advertising payload:\n%s' % payload)
data = bytearray.fromhex(payload)
This script produces the following output:
advertising payload:
03f73a1b03f73a1b03f73a1b03f73a1b
03f73a1b03f73a1b03f73a1b03f73a1b
70afb33965fc08453b9b03773970af33
03f73a1b70afb339fc08c564f73a9b03
03f73a1bb239702f70afb33970afb339
08c5647cf73a9b034dc68f5070afb339
c68f504cb239702f514cc60fb239702f
4dc68f50f73a9b0303f73a1b70afb339
c464fc08aeb33970b239702ff73a9b03
f73a9b0370afb33965fc0845aeb33970
464fc0870afb33970afb33970afb339a
eb33970aeb33970514cc60f70afb3390
This data is what is sent in BLE advertisements as manufacturer data, but the meaning is still unknown. It's now time to dig into Cayre's paper to understand what is going on !
We won't cover all the details of Cayre's paper (so read it if you are looking for more information) but only the most interesting aspects that may be useful to decode this data.
Cayre found out that Bluetooth Low Energy PHY layer uses a Gaussian Frequency Shift Keying (GFSK) modulation that can be considered (in a way) as an Minimum Shift Keying (MSK) modulation. This MSK modulation when used with a 2 Mbit per second bitrate may be used to generate a RF signal (composed of I/Q samples) that fits the O-QPSK modulation used by Zigbee. In short, a BLE chip can send BLE data at 2 Mbps on a specific BLE channel that would be interpreted by a Zigbee receiver (using an O-QPSK demodulator) as a valid series of bits representing a Zigbee frame.
Cayre provides in his paper a specific table (table 2) that can be used to convert a block of 4 bits of Zigbee data into a series of 31 bits that would be interpreted as a PN sequence used in Zigbee PHY layer. And this is exactly what this Android application does: it sends a series of bits using MSK modulation that will be decoded as PN sequences and then turned into a series of 4-bit blocks by a Zigbee receiver and interpreted as such. For the record, the payload is dewhitened before sending because the BLE chip will whiten the data (and undo this dewhitening) before sending it, thus ensuring our payload is sent as-is.
Therefore, we need to do some bit-level magic with our recovered advertising payload in order to extract the MSK-encoded PN sequences with the help of some Python code:
# reverse lookup table from Cayre's paper (table 2)
msk_to_block = {
'1100000011101111010111001101100':'0000',
'1001110000001110111101011100110':'1000',
'0101100111000000111011110101110':'0100',
'0100110110011100000011101111010':'1100',
'1101110011011001110000001110111':'0010',
'0111010111001101100111000000111':'1010',
'1110111101011100110110011100000':'0110',
'0000111011110101110011011001110':'1110',
'0011111100010000101000110010011':'0001',
'0110001111110001000010100011001':'1001',
'1010011000111111000100001010001':'0101',
'1011001001100011111100010000101':'1101',
'0010001100100110001111110001000':'0011',
'1000101000110010011000111111000':'1011',
'0001000010100011001001100011111':'0111',
'1111000100001010001100100110001':'1111'
}
def msk_to_chip(seq):
assert seq in msk_to_block
return msk_to_block[seq]
def byte_to_bits(b):
return bin(b)[2:].rjust(8,'0')[::-1]
# generate out
a = '1b'
b = '03'
c = '3a'
d = 'f7'
e = '70afb33965fc08453b9b03773970af3303f73a1b70afb339fc08c564f73a9b03' \
'03f73a1bb239702f70afb33970afb33908c5647cf73a9b034dc68f5070afb339' \
'c68f504cb239702f514cc60fb239702f4dc68f50f73a9b0303f73a1b70afb339' \
'c464fc08aeb33970b239702ff73a9b03f73a9b0370afb33965fc0845aeb33970' \
'464fc0870afb33970afb33970afb339aeb33970aeb33970514cc60f70afb3390'
'3f73a1bc68f504c70afb3393970af33'
x = 0
out = ''
while len(out)!=64:
if x%4==0:
out = a + out + b
if x%4==1:
out = c + out + d
if x%4==2:
out = d + out + c
if x%4==3:
out = b + out + a
x += 1
# Payload is dewhitened before sending, because it will be whitened
again when sent by the BLE chip =)
payload = out + e
data = bytearray.fromhex(payload)
# Convert payload into bits
data_bits = ''
for c in data:
data_bits += byte_to_bits(c)
# Split by blocks of 31 bits (MSK-encoded PN sequences)
# and convert to corresponding 4-bit chip
chips = [data_bits[32*i:32*(i+1)] for i in range(int(len(data_bits)/32))]
output = ''.join([msk_to_chip(chip[:31]) for chip in chips])
This small script produces the following output:
00000000000000000000000000000000
11100101001010000000111000010110
00001100111011100111011011011110
10011100101111001101011000001110
00111010110001100110111001011010
00111110111011101010101010111110
0000100111101000
A little bit more of Python to convert this into bytes:
# Group output by 8 bits and rebuild data
nbytes = int(len(output)/8.)
output_bytes = [chr(int(output[8*i:8*(i+1)][::-1],2)) for i in range(nbytes)]
print(''.join(output_bytes))
And this displays the following text (the first null bytes are omitted): §ph0wn{9=kpcvZ|wU}. These bytes may correspond to a Zigbee beacon, but we don't need to decode it as the flag is clearly readable.
Je me suis récemment attaqué à la rétro-ingénierie du Ninja Badge de la DEFCON 18, et il se trouve que ce badge communique avec d'autres badges similaires via la couche MAC du protocole ZigBee (IEEE 802.15.4). Disposant d'un HackRF One, j'ai réussi à décoder les messages envoyés à l'aide de Gnuradio Companion, mais il m'était très difficile de coder un outil permettant de faire de l'envoi et de la réception en simultané, le HackRF One n'étant pas full-duplex. Il m'a donc fallu trouver un plan B.
Quand il s'agit de recevoir et de transmettre des paquets ZigBee, le framework Killerbee sauve pas mal la vie. Ce framework écrit en Python s'interface avec des équipements adaptés, et est en mesure de sniffer et injecter des paquets ZigBee. Enfin, quand on l'utilise avec des équipements qui le permettent. En effet, le seul device abordable qui était compatible avec ce framework est le RZ Raven d'ATMEL, dont la production a été arrêtée et qui est actuellement introuvable. Les équipements restants sont plus coûteux, comme l'ApiMote d'Attify (150$), le TelosB Mote, ou encore des cartes Silicon Labs compatibles (~300€). Par ailleurs, il existe bien un second équipement pas cher, une clé USB à base de TI CC2531, mais cette dernière ne supporte que le sniffing, ce qui ne me suffisait pas.
J'ai donc commandé une de ces clés USB à base de TI CC2531, et j'ai réussi à capturer des paquets ZigBee et à débuter l'analyse du protocole de communication du badge. Cependant, impossible d'envoyer de manière fiable des paquets, et donc d'interagir avec ce dernier. Je commençais sérieusement à douter du framework, et songeais à trouver une solution alternative. Il était clair à ce moment que personne n'avait implémenté le support de l'injection de paquet pour cette clé USB, malgré plusieurs demandes faites via des issues sur leur repository Github. La réponse apportée ? "Do it yourself", si je résume le propos. Eh bien faisons ça alors.
Ayant déjà développé sur des puces gérant des communications radio dans le cadre de mes recherches sur le protocole Bluetooth Low Energy, développer un firmware spécifique pour la puce TI CC2531 ne me faisait pas tellement peur. Par contre, si je pouvais éviter d'y passer trois semaines, ça m'arrangerait passablement. J'ai donc commencé à scruter le web à la recherche de kits de développement permettant de développer pour cette puce, avec potentiellement le support de la couche radio déjà disponible. Je suis assez rapidement tombé sur le code de Contiki, un micro-système d'exploitation qui supporte notamment la puce en question. Et pour lequel la couche radio est déjà implémentée, yippie !
Je me suis donc lancé dans le développement de ce firmware, en ajoutant une couche de communication par port série (exposé via la connectique USB) afin de piloter la puce à partir d'un ordinateur qui communique avec le code gérant la radio. La structure de Contiki, qui propose un semblant de processus, permet d'implémenter cela de manière très simple. Le premier test a pu être rapidement effectué, et s'est montré très concluant: j'arrivais à sniffer et transmettre des paquets ZigBee, autrement dit j'avais une bonne base de travail. J'ai bouclé le développement du firmware en moins d'une semaine, et mis le code à disposition (ainsi qu'une release pré-compilée) sur mon repository Github associé.
Développer un firmware c'est bien, mais faire en sorte que Killerbee puisse communiquer avec est encore mieux. En parallèle, j'ai donc implémenté un driver spécifique dans le framework Killerbee, capable de communiquer avec le firmware en question et qui supporte ainsi le sniffing et l'injection de paquets. Ce développement a été effectué sur la branche py3 du projet, qui n'est pas encore la version 3.0 officielle de Killerbee mais qui devrait l'être une fois que le portage sur Python 3.0 sera terminé, grâce à la réactivité de RiverLoopSec qui a poussé des modifications en attente quand je les ai sollicité sur Twitter (encore merci à eux !).
Ce driver est ainsi capable de sniffer des paquets ZigBee, avec pour seule limitation que ces paquets doivent avoir un FCS (une somme de contrôle) valide (il est possible d'ajouter le support des paquets invalides, mais je ne l'ai pas encore fait). Il est par ailleurs capable d'envoyer des paquets, là encore sans possibilité de contrôler le FCS. Mais c'est bien suffisant pour ce que j'ai à faire sur ce matériel. Il a d'ailleurs fallu trouver un petit nom à ce firmware, et celui proposé par tartofraise lors d'un live Twitch a été retenu: Bumblebee.
Le code du driver est intégré dans mon fork de Killerbee, disponible en suivant ce lien.
Le code source et une version pré-compilée du firmware ont été rendus publics sur mon dépôt Github associé, qui détaille par ailleurs plusieurs méthodes permettant d'installer ce dernier sur une clé TI CC2531. Basiquement, il vous faudra un programmateur CC-Debugger, ou un Raspberry Pi voire un Arduino. Une fois le firmware installé sur la clé, cette dernière est prête à l'emploi.
Sur l'ordinateur, installer killerbee à l'aide de l'outillage Python:
$ git clone --branch py3 https://github.com/virtualabs/killerbee.git
$ cd killerbee
$ python3 setup.py sdist
$ sudo pip3 install dist/killerbee-2.7.1.tgz
Killerbee est installé et prêt à l'emploi, y compris le support de Bumblebee. Vous pouvez désormais utiliser tous les outils classiques de Killerbee avec cette clé USB !
Si vous trouvez des bugs, ou souhaitez des améliorations, n'hésitez pas à soumettre des issues ou des feature requests, et pourquoi pas des pull requests !