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 m'étais promis d'écrire plus souvent sur ce blog en 2021, et en fin de compte je n'ai publié qu'un seul billet durant cette année. Peut-être que 2021 n'aura pas été l'année attendue, celle durant laquelle on pensait que tout allait s'arranger, que la situation sanitaire s'améliorerait et que l'on pourrait mettre cela de côté et reprendre une vie normale. Mais non.
2021 a été l'occasion de changer de boulot, d'organisation familiale, d'organisation personnelle. Je travaille désormais 80% du temps à la maison (un peu plus ces derniers temps), et m'occupe des enfants et des animaux de la maison, ce que je n'avais pas le temps de faire auparavant. Idem pour les activités extra-scolaires, les courses, les tâches ménagères (même si ma moitié dira que j'exagère et que je n'en fait pas tant que ça). Ce n'est pas pour me déplaire, loin de là, mais ça fait du changement.
L'arrivée d'une petite chienne dans la famille en août a ajouté un peu de piment mais nous avons désormais notre petite routine journalière, elle et moi, en plus de celle établie avec le chat de la maison (qui voit d'ailleurs ce chamboulement d'un mauvais oeil). Cela fait des journées assez remplies, mais au final plus satisfaisantes car je peux profiter des minis-moi plus qu'avant, tout en ayant moins de trajet pour le boulot.
La plupart des évènements cybersécurité de 2021 ont été annulés, reportés, ou transformés en évènements en ligne. Je parlais déjà en 2020 de mon ressenti sur les talks donnés à distance pour des conférences virtuelles, et 2021 a du coup été une année assez pauvre en talks. Les copains de Unlock Your Brain Harden Your System ont quand même réussi à organiser leur évènement courant novembre, et je dois avouer que ça a fait un bien fou de retrouver une scène, les potos speakers (coucou MaliciaRogue, Nono, Patrice) et d'avoir un vrai retour de vrais gens! Evènement qui était franchement top, encore bravo aux orgas =).
En parallèle, l'aventure Twitch continue et le rythme de deux streams par semaine en soirée (voire même la nuit) me convient. J'ai ainsi pu boucler plusieurs projets, dont celui des interphones WiFi et le hack d'un routeur WiFi, mais aussi tenter d'améliorer le setup pour être plus à l'aise et faciliter les différentes manipulations. Papa Noël a quant à lui apporté aussi du matériel plus que bienvenu, comme un contrôleur MIDI qui a vocation à devenir mon streamdeck principal et un support d'écran qui permet de libérer de la place sur le bureau (et c'était plus que nécessaire).
Je me rends compte que je n'ai pas correctement documenté les projets réalisés courant 2021, que j'ai encore une liste d'idées de trucs à faire longue comme le bras et que je m'organise toujours comme un sac.
Les bonnes résolutions de 2022 (qui seront difficilement tenues, mais faut essayer):
Sur ce, bonnee année 2022 à tous (avec un peu de retard) !
L'année 2021 démarre plutôt bien, et j'ai profité des premiers lives Twitch de l'année pour continuer la bidouille de minitels mais aussi pour tenter de convertir deux anciens téléphones à cadrans rotatifs en interphones sans-fil. Tandis que la modification des minitels suit son cours (je viens tout juste de souder le connecteur du clavier du Minitel), la conversion des vieux téléphones en interphones sans-fil se révèle plus problématique que prévu. Petit point rapide dans ce billet des soucis rencontrés, des solutions testées, et de l'avenir de ce projet.
Revenons deux minutes à l'origine de ce projet. On trainait en famille dans un vide-grenier (oui, à l'époque où c'était encore possible) et je suis tombé sur un de ces vieux téléphones à cadran rotatif, ce même type de téléphone que j'utilisais tout petit pour appeler mes grands-parents. Il faut avouer que composer le numéro à 8 chiffres -- dans cet ancien temps la numérotation n'était pas encore à 10 chiffres -- était vraiment amusant: on plaçait le doigt dans le numéro souhaité, puis on faisait tourner le cadran jusqu'en butée, et ce dernier revenait en position initiale en faisant un petit bruit de crécelle. Tout nostalgique que j'étais, j'en ai acheté deux exemplaires pour moins de 10€, avec en tête l'idée de les réhabiliter à la maison.
Dans un premier temps, on souhaitait s'en servir comme second téléphone fixe, mais il s'est avéré que la méthode de génération du numéro composé n'est plus supportée par les réseaux téléphoniques d'aujourd'hui. Le système de numérotation par impulsion de l'ancien temps ne peut donc fonctionner, et il est donc impossible de passer des appels avec ce type de téléphone. Du coup, pourquoi ne pas les transformer en interphones sans-fil ? Tant qu'à faire, autant intégrer de la modernitude dans ces vieilleries ! Et c'est d'un pratique pour appeler les enfants qui jouent dans la mezzanine pour leur demander de mettre le couvert, pardi 😇.
Tout excité à l'idée de réaliser ce projet (on était en 2020 à ce moment là), je teste dans un premier temps si les micros et les hauts-parleurs des téléphones fonctionnent, ce qui est le cas. Puis je connecte un micro à une entrée analogique d'un arduino Nano, et je code un petit programme qui échantillonne le signal en provenance du micro (amplifié avec un module lm386, histoire d'avoir un niveau de signal suffisant) et le retransmet sur le haut-parleur, lui aussi couplé à un module d'amplification similaire. Et miracle, ça fonctionne ! Bon d'accord, le son est pourri, mais ça fonctionne !
La voix humaine varie entre 40 et 1500 Hertz (d'après Musimem), et l'arduino Nano que j'utilisais, cadencé à 16 MHz, ne peut pas échantillonner plus vite que 615 384 échantillons à la seconde, ou plus simplement 615 kHz. C'est plutôt pas mal, mais la résolution sonore est limitée car le convertisseur analogique/numérique (ou ADC) ne produit des valeurs que sur 8 bits au vu de la vitesse d'échantillonnage requise. Ceci dit, le résultat n'était pas trop mal !
Le souci avec un système de communication bi-directionnelle comme un interphone, c'est qu'il faut gérer en même temps la conversion du signal audio en provenance du micro en échantillons numériques, l'envoi de ces échantillons, mais aussi la conversion des échantillons numériques reçus en signal analogique à destination du haut-parleur. Ca veut donc dire faire plein d'autres choses que de l'échantillonnage de la voix, ce qui réduit la vitesse d'échantillonnage effective.
Avec ce premier test réussi, j'ai tenté l'implémentation d'un projet existant (Long Range Arduino Based Walkie Talkie using nRF24L01 [EN]) visant à transmettre de la voix sur du 2.4 GHz, mais sans succès. Comme je m'y attendais, l'arduino voit sa vitesse d'échantillonnage réduite et la piètre résolution du convertisseur analogique/numérique n'arrange pas la qualité sonore. En cherchant un peu plus, je me rends alors compte que je ne suis pas le seul qui s'est cassé la tête sur ce type d'usage, comme le démontre le retour d'expérience de GreatScott!! sur Hacakday [EN].
Il va falloir tester autre chose.
Vu que pas mal de mes connaissances m'ont parlé en bien des systèmes produits par Espressif et notamment la plateforme ESP32, je me suis demandé s'il était possible que ces derniers fassent l'affaire. J'ai pu expérimenter le développement sur ESP32 avec mon projet de smartwatch programmable (la vénérable T-Watch 2020 v1 de Lilygo), et je dois avouer que ça m'a bien bluffé. Je décidais donc de donner sa chance à l'ESP32, et commandais un ESP32 NodeMCU sur Amazon pour prototyper tout ça.
C'est à ce moment là (donc début 2021, si vous suivez) que j'ai commencé à prototyper sur ESP32 lors de lives sur Twitch, et les premiers résultats ont été assez satisfaisants. J'ai réussi à échantillonner le son du micro et à l'envoyer sur le haut-parleur, mais la qualité était juste horrible. Ceci dit, c'était encore acceptable. C'est quand j'ai commencé à interfacer l'ESP32 à un service accessible en WiFi que les choses se sont gâtées. L'envoi du son sur le service distant fonctionnait, mais d'une part la qualité n'était pas merveilleuse du tout, mais de la donnée était perdue et il y avait un décalage énorme (entre 5 à 10 secondes) entre l'échantillonnage par l'ESP32 et la transmission via WiFi.
Alors oui, l'ESP32 ça fonctionne bien, mais il a beaucoup de mal à réaliser en même temps la conversion du signal audio en échantillons numériques et la transmission de ces derniers. Et encore, c'était sans intégrer la convesion numérique/analogique pour la restitution du son sur le haut-parleur. Bref, ça devenait mal engagé. Certes l'ESP32 NodeMCU ne coûte pas bien cher, mais il souffre de plusieurs défauts.
Le premier défaut concerne la conversion numérique/analogique (DAC): la résolution du DAC interne de l'ESP32 est de 8 bits, on ne peut donc coder que des valeurs de 0 à 256, qui constitueront autant de "paliers" dans la tension de sortie et donc une imprécision dans le signal audio restitué. Surtout que la conversion analogique/numérique (ADC) ne souffre pas de cette limitation, la résolution étant de 12 bits. La réduction de 12 bits à 8 bits altère le son de manière significative, et impacte de fait la qualité de la communication. Le second défaut concerne l'électronique du NodeMCU: l'entrée analogique est parasitée par une alimentation du circuit un peu mal fichue, et par l'absence de quelques composants permettant de filtrer l'audio entrant. C'est aussi ce qui ressort des tests qu'a effectué atomic14 dans sa vidéo youtube [EN]: le son est parasité, la qualité sonore très dégradée, et sans prendre en compte la transmission via le réseau !
Encore un fail, mais bon ça m'aura permis de bien prendre en main le dev sur ESP32.
Une des solutions possibles consiste à externaliser la conversion numérique/analogique et analogique/numérique, idéalement en employant des composants permettant d'échantillonner avec une bonne résolution (12 bits dans les deux cas), et fournissant un débit suffisant pour avoir une bonne qualité audio. On pourrait aussi envisager l'emploi d'un microphone I2S (qui fournit directement des échantillons numériques), mais je n'ai pas envie de perdre le grain du microphone du téléphone original.
La solution idéale pour le prototypage consiste à utiliser une carte adaptée, en l'occurence une esp32-lyrat-mini, compatible avec le framework esp-adf d'Espressif. Ce framework est dédié au traitement audio, et cette carte compatible intègre tout ce qu'il faut pour faire la conversion analogique/numérique et numérique/analogique via des composants externes. Elle intègre aussi des filtres adaptés, histoire d'avoir une meilleure qualité sonore. En bref, c'est vraiment une board faite pour cet usage. Comme un crétin, je ne pensais pas devoir en arriver là pour réaliser ce projet, mais je pense que je ne vais pas avoir d'autre choix. J'ai passé commande, on verra dans quelques semaines ce que ça donne 😀.