Bon, il est temps pour moi de faire le point sur Twitter. De savoir si oui ou non, je vais désactiver mon compte et pour quelles raisons. Ce que je vais y perdre ou gagner, les conséquences à court et moyen terme. Au moment où vous lisez ce début de billet (et au moment même où je l'ai écrit), je ne me suis pas encore décidé. Normalement à la fin de celui-ci (tout en bas de cette page), je devrais m'être décidé, avec en prime les arguments qui ont éclairé ma décision.
J'ai créé mon compte Twitter en mars 2009, il y a donc de cela plus de 13 ans. Je ne sais plus qui m'en avait parlé, mais cela devait certainement être dans mon cercle de potes de l'époque, via IRC. Twitter avait le vent en poupe, était relativement simple à prendre en main, et semblait un très bon moyen de relayer mes billets de blog à une époque où les flux RSS étaient encore bien employés. C'était aussi un bon moyen de se tenir informé, de faire de la veille technique. Mon premier tweet date d'octobre 2009, lors d'une mission qui se déroulait à Tahiti (histoire de narguer les collègues je suppose):
C'était aussi le moment où la Hadopi a été créée (1er janvier 2010) et où on a bien rigolé avec ses déboires (Albanel, site web KO, etc.). J'ai même retrouvé un tweet sexiste au possible de mon moi de l'époque (et je m'en excuse d'avance)...
Punaise, ça me fait tout drôle de relire mes anciens tweets. J'étais jeune et con, c'est sans appel. Je retrouve pas mal d'histoires, des échanges avec des twittos comme Stéphane Bortzmeyer, Bluetouff, Guillaume Champeau. Nostalgie. 13 ans. Forcément, j'ai un attachement pour cette plateforme à laquelle j'ai participé durant toutes ces années, j'y ai investi du temps et y ai fait de belles rencontres.
Les années passant, j'ai continué d'utiliser Twitter pour promouvoir mes billets de blog et mes projets de développement, puis mes participations à diverses conférences ainsi que mes différents talks. Les billets de blog se sont fait de plus en plus discrets, de plus en plus espacés jusqu'au point où je publiais trois billets par an. Si vous regardez le nombre de billets publiés pour 2022, je pense qu'on ne doit pas en être loin non plus 😅. Mon usage de Twitter a évolué, je restais sur ce réseau social pour les échanges et les personnes, la promotion de mes bidouilles et talks, la publication de slides ou de liens Github, pour communiquer avec mes amis, collègues et connaissances. J'y restais parce que bon nombre de personnes gravitant dans le milieu de la cybersécurité y étaient présentes, que je pouvais y faire ma veille relativement facilement.
Je me sers encore aujourd'hui de Twitter comme source d'information, de source d'alertes en temps réel concernant la cybersécurité principalement (mais pas que). J'y tweete mes participations à des conférences, les annonces de mes streams sur Twitch, et mes humeurs et réactions du moment. Récemment, cela a beaucoup concerné la plateforme Twitter elle-même et les décisions prises par Elon. Au point qu'à chaque fois que j'y retourne, c'est pour chercher quelle nouvelle bonne ou mauvaise idée a eu Elon, les réactions des différentes communautés aux coups de tête du dirigeant. J'arrive à y faire ma veille, mais c'est plus laborieux qu'auparavant. Tout est entrecoupé de tweets sponsorisés, de tweets promus ou mis en avant que je pourrais aimer (non), et il devient difficile de trouver la substantifique moëlle.
Entre temps, en 2018, j'ai créé un compte Mastodon sur l'instance de La Quadrature du Net, Mamot. Je ne sais plus exactement pour quelle raison — l'apparition d'une alternative à Twitter ou des incertitudes sur l'avenir de la plateforme, toujours est-il que j'ai testé pendant quelques semaines mais rapidement laissé tomber car seuls quelques connaissances étaient dessus et la majorité des pouets que je voyais passer étaient dans des langues étrangères que je ne maîtrise pas. C'était clairement un réseau social de niche en 2018. C'est donc tout naturellement que je suis resté où mes amis et connaissances se trouvaient, à savoir sur Twitter.
Avance rapide en 2022, et l'annonce du rachat effectif de Twitter par Elon Musk. J'ai lu la nouvelle sur Twitter, durant un petit-déjeuner avec un collègue lors d'une participation à une conférence. Je n'ai pas pu m'empêcher de rire intérieurement tellement cela semblait improbable. Un milliardaire rachète Twitter, un peu forcé par ses précédentes frasques (Elon avait fait une offre et tentait ensuite d'esquiver l'achat autant que possible), et se retrouve du jour au lendemain à la tête d'un réseau social. "Let that sink in". Gérer un réseau social, ce n'est pas juste gérer une entreprise et faire en sorte qu'elle soit rentable mais bel et bien comprendre les responsabilités quant aux règles en vigueur sur ce dernier. Se rendre compte qu'une décision concernant ce que les utilisateurs ont le droit de faire (ou non) impacte des millions de personnes et est perçue de différente façon en fonction des pays et des cultures. Au vu du passif de Musk, et notamment sur la plateforme Twitter lors des élections américaines, cela présageait du mouvement.
A ce moment précis donc, une fois rentré de la conférence, j'ai repris en main mon compte Mastodon. L'occasion de voir que la plateforme avait beaucoup évoluée par rapport à celle que j'ai testée en 2018, et que de nombreuses personnes étaient en train d'arriver suite aux dernières évolutions sur Twitter. Musk remercie 75% des employés, et Mastodon accueille une vague de tweetos qui cherchent un plan B. Les premières décision d'Elon sont assez radicales, je décide de prendre le temps de voir comment Twitter va évoluer et de poster à la fois sur Twitter et Mastodon. Et je me donne un mois ou deux pour faire le point.
L'heure est donc venue.
J'ai toujours été réticent à quitter Twitter, pour une simple raison je crois: l'audience. Après plus de 13 ans, j'ai plus de 5000 followers. C'est autant de personnes qui recevront mes tweets. Cela donne de la visibilité, qui m'est nécessaire dans le cadre de mon travail. Je suis passé d'un compte Twitter purement personnel à un compte visible (à une certaine échelle), qui me sert aussi à promouvoir mes talks et outils tout comme certaines publications et participations de mon employeur à des évènements. Avec Mastodon, je repars de zéro sans certitude que l'audience suivra.
Cependant, depuis que j'utilise Mastodon je retrouve l'ambiance qu'il y avait en 2009 sur Twitter. Je découvre des personnes intéressantes à suivre, des informations et échanges intéressants, et récemment beaucoup de personnes de la cybersécurité ont créé des comptes Mastodon sur une instance spécialisée dans ce domaine (https://infosec.exchange). J'y retrouve donc les personnes que je suivais sur Twitter, qui sont actives depuis quelques mois sur Mastodon, et qui pour certaines d'entre elles publient comme moi sur les deux réseaux. Et bien sûr le tout sans publicité ni pouets sponsorisés, sans algorithme décidant ce qui peut être intéressant pour moi.
La décision est donc prise: je n'utiliserai plus que Mastodon et laisserai Twitter de côté. Mon compte Twitter restera actif, car j'utilise ce compte pour m'authentifier sur certains services et qu'il indique où me trouver sur Mastodon. Au lieu de donner 8$ à une plateforme dont les règles vont dépendre de résultats de sondages, je préfère faire un don récurrent à La Quadrature du Net qui s'occupe (entre autres) de l'instance Mastodon qui m'héberge. Bye Twitter, so long and thanks for all the fish !
Cela doit bien faire un an que le langage Rust me fait de l'oeil. Rust se veut rapide, fiable, économe en mémoire, mais aussi sécurisé par design. En effet, il incorpore des mécanismes évitant au développeur les erreurs habituelles pouvant aboutir à des vulnérabilités applicatives: adieu buffer overflows, use-after-free, etc. Le langage commence à être mature (il a été créé il y a plus de dix ans), et possède une bonne communauté, ce qui est plutôt pratique (et on aura l'occasion d'en reparler plus bas dans ce billet).
Bref, je me suis donc dit qu'il était temps en 2022 de tester ce langage par la pratique, histoire de mieux appréhender ses concepts et subtilités. Ce billet de blog est un premier retour à chaud de mon début d'apprentissage du Rust, qui s'est principalement déroulé en live sur ma chaîne Twitch, ainsi que mes premières impressions au regard des autres langages auxquels je suis habitué (globalement C et Python).
Rien de mieux pour tester un langage que de se lancer dans un petit projet pratique. En ce qui me concerne (et après une rapide recherche pour m'assurer de la faisabilité), je me suis décidé à développer un logiciel pour interfacer mon AKAI APC mini reçu à Noël avec OBS Studio. De cette façon, je pourrai piloter OBS (mon logiciel de streaming) avec ce magnifique clavier MIDI, tout en ayant un retour visuel sur ce dernier. Franchement, je trouvais ce projet relativement ambitieux, vu que cela implique de communiquer avec le clavier MIDI et OBS Studio via une websocket. Aucune idée de comment réaliser cela de prime abord, mais je savais que Rust avait des modules (des crates) pour gérer cela.
J'ai suivi dans un premier temps le tutorial officiel (qui d'ailleurs a sa traduction non-officielle en français disponible sur https://jimskapt.github.io/rust-book-fr/, merci jimskapt !), histoire de découvrir les principales fonctionnalités et de faire des parallèles avec celles du C ou encore de C++. Mauvaise idée d'ailleurs, de faire des parallèles avec d'autres langages, mais je m'en suis rendu compte assez rapidement.
Au final, il m'aura fallu quelques soirées de coding pour arriver à un résultat probant, alors que je pensais sincèrement que j'allais galérer. Bon d'accord, j'avais aussi de l'aide de certaines personnes sur le chat Twitch (encore un grand merci à speace, lilymonade, jimskapt et ceux que j'oublie), et cela a certainement aussi joué sur le temps de développement.
La première chose qui m'a étonné lorsque j'ai débuté le tutorial est la prédominance de cargo. Cargo est l'outil qui fait le café et bien plus en Rust, qui permet de démarrer un projet, de vérifier le code, de le compiler, de gérer les dépendances, etc... Je ne suis d'habitude pas super fan de ce genre d'outils (type bower, grunt et consors), mais je dois avouer qu'avoir un outil "officiel" permettant de gérer tout cela est très pratique. Cargo utilise bien sûr le compilateur de Rust (rustc) pour les phases de compilation et de vérification (enfin je crois), qui est aussi très intéressant.
En effet, le compilateur aide énormément l'utilisateur, contrairement à gcc et ses erreurs cryptiques. En cas de problème, il essaie même de proposer des solutions pour le corriger, ce qui est très appréciable ! Cela m'a permis de résoudre tout seul quelques erreurs dans le code, gratifiant quand on débute. Plusieurs profils de compilation sont proposés, pour permettre le débogage (avec toutes les informations de débogage dans le binaire) ou la release du logiciel (qui favorise les performances à l'exécution).
Enfin, Rust dispose d'un magasin de bibliothèques collaboratives (https://crates.io) à la sauce npm ou pypi, qui fournit tout un ensemble de bibliothèques (ou crates) qui peuvent être utilisées en Rust. C'est à mon avis le seul point noir de cet écosystème, car tout comme sur npm ou pypi il faut bien analyser les différentes crates pour éviter de tomber sur une qui soit bien maintenue et ayant bonne réputation. On n'est pas à l'abri de la disparition d'une crate ou de dérives que l'on a pu voir sur npm (sabotage ou piratage de bibliothèques).
Je ne vais pas couvrir en détail l'ensemble des fonctionnalités qu'offre le langage Rust (car je suis loin de les avoir toutes testées :p), juste mentionner celles qui m'ont marquées. Et on commence par un type particulier, Option. Quand je l'ai utilisé la première fois, je ne me suis pas rendu directement compte de son utilité, mais celle-ci s'est révélée au fur et à mesure que j'apprenais ce langage.
En Rust, il n'y a pas de None ou de nil, aucun mot-clé particulier permettant de spécifier qu'une variable ne contient juste rien. Le type Option fournit un genre de conteneur qui peut ne rien contenir ou contenir une valeur particulière. La subtilité est là toutefois: on a un type dédié que l'on peut utiliser pour des variables ou paramètres qui peuvent être vide, et donc optionnels. Prenons un exemple en python:
def greet_user(username):
if username is not None:
print('Hello, %s !' % username)
else:
print('Hello, anonymous !')
greet_user('virtualabs')
greet_user(None)
La définition de la fonction ne permet pas de déterminer a priori que le paramètre username peut avoir comme valeur None. L'équivalent en Rust est à mon sens plus lisible car la définition de la fonction précise, grâce à l'usage de ce type Option, que le paramètre est optionnel:
fn greet_user(username: Option<&str>) {
match username {
Some(x) => println!("Hello, {} !", x),
None => println!("Hello, anonymous !")
}
}
fn main() {
greet_user(Some("virtualabs"));
greet_user(None);
}
Et il en est de même pour les fonctions qui peuvent retourner un Option afin que la fonction appelante puisse déterminer si la fonction appelée retourne un résultat ou non, comme cet exemple tiré de la documentation officielle:
fn divide(numerator: f64, denominator: f64) -> Option<f64> {
if denominator == 0.0 {
None
} else {
Some(numerator / denominator)
}
}
La seconde fonctionnalité qui m'a bluffé est le pattern matching implémenté de base dans Rust. Cela fonctionne comme un switch dans lequel les différents cas de figure sont des motifs particuliers pour lequel un bloc de code sera exécuté si la variable passée en entrée correspond à ce dernier. Cela m'a permis par exemple de parser des codes MIDI en quelques lignes de Rust:
fn from_midi(message: &[u8]) -> Option<Message> {
match message {
[0x90, id@0..=63, ..] => Some(Message::Button{id:*id}),
[0x90, id@64..=71, ..] => Some(Message::SliderButton{id:*id}),
[0xB0, id@48..=57, value, ..] => Some(Message::Slider{id:*id - 48, value:*value}),
_ => None
}
}
On retrouve d'ailleurs dans ce code l'utilisation du type Option, combiné au mécanisme de pattern matching. La fonction en question analyse le message passé en paramètre et en fonction de la valeur de ses premiers octets retourne une structure automatiquement complétée avec des valeurs issues du motif correspondant. Certes la syntaxe est quelque peu complexe (en particulier le id@0..=63 par exemple), mais c'est très puissant ! Ce qui m'amène à parler de la troisième fonctionnalité que j'apprécie de plus en plus: les énumérations.
En Rust, les énumérations sont plus flexibles qu'en C par exemple, car elles permettent d'associer des variants à une énumération spécifique. Pour reprendre l'exemple des messages MIDI ci-dessus, Message est une énumération définie comme suit:
enum Message {
/// Normal button has been pressed.
Button{id:u8},
/// Slider value has changed.
Slider{id:u8, value:u8},
/// Slider button has been pressed.
SliderButton{id:u8},
}
Cette énumération permettra par la suite, grâce au pattern matching, de pouvoir dispatcher les différents messages MIDI décodés par notre précédente fonction from_midi(), comme le montre le code suivant:
match message {
/* Button has been pressed (switch scenes and video sources). */
Message::Button{id} => {
/* Here some code to handle button press event. */
},
/* Slider button has been pressed (handle mute/unmute). */
Message::SliderButton{id} => {
/* Here some code to handle slider button event. */
},
/* Slider value has been changed (set volume). */
Message::Slider{id,value} => {
/* Here some code to handle slider value change event. */
},
}
Bref, ces quelques fonctionnalités m'ont pas mal séduit dans le langage Rust, bien qu'il me reste encore un bon bout de chemin à faire dans mon apprentissage.
Rust utilise des mécanismes spécifiques afin d'éviter toute erreur de manipulation de la mémoire ou de références (pointeurs) sur des zones mémoires allouées, et ces derniers imposent au développeur de penser à des choses auxquelles il ne pensait pas auparavant. Et cela commence par la mutabilité des variables: il est nécessaire de préciser dans le code qu'une variable est mutable, c'est-à-dire que sa valeur va changer au cours de sa durée de vie, via l'emploi du mot-clé mut.
C'est un coup de main à prendre, mais c'est dans ce genre de situation que le compilateur est bienveillant: il indique précisemment le problème et une solution possible, comme le montre l'exemple ci-dessous:
fn main() {
let a: i32 = 2;
a =3;
}
Cet exemple génère une erreur à la compilation:
error[E0384]: cannot assign twice to immutable variable `a`
--> src/main.rs:3:9
|
2 | let a: i32 = 2;
| -
| |
| first assignment to `a`
| help: consider making this binding mutable: `mut a`
3 | a = 3;
| ^^^^^ cannot assign twice to immutable variable
For more information about this error, try `rustc --explain E0384`.
Le compilateur donne une indication sur la résolution du problème, qui consiste à déclarer la variable a mutable.
Autre particularité de Rust et autre prise de tête: la possession des variables. Quand on passe une variable à une fonction, cette fonction prend la possession de cette dernière et le code appelant la perd. Cela signifie que la variable en question n'existe plus une fois la sous-fonction appelée. Le souci en tant que développeur, c'est de suivre la possession de différentes variables dans le flux d'instructions exécutées afin d'éviter de faire une erreur.
On est alors vite tenté de cloner les différentes variables (la fonction appelée prenant possession de cette dernière) ou tout simplement passer l'ensemble des paramètres de fonction par référence, ce qui permet de conserver la possession de la variable et de la "prêter" à la sous-fonction appelée. Un peu comme si l'on passait un pointeur sur la variable en question. Et c'est là que ça commence à piquer, car le borrow checker de Rust entre en action. Son objectif ? S'assurer que les différentes références passées à des sous-fonctions (ou dans des sous-blocs de code) son intègres et ne posent pas de souci lors de l'exécution. Et la lutte avec le borrow checker commence...
On doit alors entrer dans de la syntaxe un peu avancée, et jouer avec la durée de vie des différentes variables (qu'il faut renseigner dans le code grâce à une syntaxe spécifique), et donc se prendre la tête avec les dépendances entre références passées à diverses fonctions. J'ai perdu pas mal de temps avec ces subtilités, et j'avoue ne pas encore les maîtriser au moment où je rédige ce billet.
Avant de sauter directement à la conclusion, je tiens à rappeler que je suis un total novice en Rust, qui a juste développé son premier projet dans ce langage. Mon code doit certainement hérisser le poil des devs Rust confirmés, mais il fonctionne et déjà rien que ça c'est pour moi une petite victoire :).
De mon point de vue, l'apprentissage de Rust nécessite d'oublier certains concepts d'autres langages car ces derniers sont revisités; et bien sûr d'apprendre les particularités qui font de Rust un langage de développement sécurisé (oui, c'est bien des notions de mutabilité, possession et durée de vie des variables dont je parle ici). C'est assez difficile, surtout pour un développeur C/C++ qui ne se souciait pas de cela dans ses codes. Il m'a fallu un peu de temps pour prendre en main ces spécificités, mais une fois ces dernières à peu près maîtrisées cela devient plus facile. En résumé, la marche à franchir lors de la première prise en main de Rust est assez haute.
Cependant, on trouve relativement facilement des bibliothèques de fonctions (crates dans l'éco-système Rust) que l'on peut utiliser simplement, comme on le ferait en Python. J'ai ainsi pu installer plusieurs crates pour mon projet, et les interconnecter pour réussir à faire ce que je voulais. J'ai eu l'impression que ce n'était pas beaucoup plus compliqué à faire qu'en Python, bien que cela m'ait pris plus de temps à développer car étant novice. On notera aussi certaines crates qui changent la vie comme serde ou anyhow ! Plusieurs mécanismes de Rust sont très appréciables et permettent de bien structurer son code, et les performances sont au rendez-vous.
Rust est à mon avis un langage très intéressant, que ce soit pour ses aspects liés à la sécurité du code produit, ses performances ou ses fonctionnalités (pattern matching FTW). Je ne le recommanderai toutefois pas au débutants, car il suppose tout de même que le développeur qui apprend Rust maîtrise déjà certains concepts, par contre si vous êtes rodés en C ou C++ et que vous souhaitez faire du code plus sécurisé c'est le moment de s'y essayer !
NB: Je suis toujours en cours d'apprentissage de Rust, mon avis sur Rust peut changer dans l'avenir, et est très certainement actuellement biaisé par l'attrait de la nouveauté.
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.