isno.fr

OpenProfalux

Piloter mes volets roulants Profalux 868 MHz depuis Home Assistant sans la clé constructeur, en clonant une télécommande existante avec un ESP32 et un module radio CC1101. La leçon du projet - j'ai chassé une clé de chiffrement pendant des jours avant de découvrir que le moteur accepte tout simplement qu'on rejoue une trame captée.

Code : github.com/Shad107/OpenProfalux

Sommaire
  1. Contexte
  2. Le vrai nœud du projet : KeeLoq, et pourquoi je m’en fiche
  3. Identification hardware
  4. Câblage pas à pas
  5. Comment est construite une trame
  6. Le firmware
  7. Apprendre une télécommande
  8. Piloter, et le modèle « appui + stop »
  9. Suivre la vraie télécommande
  10. Home Assistant
  11. Sauvegarde et mises à jour
  12. Pour les curieux : et si on voulait quand même la clé ?
  13. La suite

Contexte

J’ai cinq volets roulants Profalux, pilotés par des télécommandes murales et une télécommande multi-canal « Noé ». Ils marchent très bien, mais ils ne parlent qu’à leurs propres télécommandes 868 MHz. Aucune passerelle officielle vers Home Assistant, aucun scénario du genre « ferme tout au coucher du soleil ». Je voulais les intégrer à ma domotique sans changer les moteurs.

Ma télécommande murale Profalux MAI-EMPX, trois boutons montée / stop / descente, celle que le firmware va cloner

Trois chemins existaient :

La passerelle DEVMEL AirSend Duo, la solution commerciale multi-marques (=photo constructeur), celle que j'ai voulu éviter pour rester local et à ~15 €

Le vrai nœud du projet : KeeLoq, et pourquoi je m’en fiche

Les télécommandes Profalux utilisent KeeLoq (=le rolling code de Microchip, celui de la plupart des télécommandes de garage). Le principe : à chaque appui, un compteur s’incrémente, il est chiffré avec une clé de chiffrement dérivée d’une clé constructeur, et le résultat (le « hopping code », 32 bits qui changent à chaque fois) part sur l’air. Le récepteur, qui connaît la clé, déchiffre, vérifie que le compteur avance, et bouge le volet. Rejouer une vieille trame est censé échouer, puisque son compteur est « déjà passé ».

J’ai donc fait ce que fait tout le monde : chasser la clé constructeur. Analyse du firmware d’une passerelle, tables de clés, tentatives de déchiffrement sur des trames réelles capturées, lectures sur l’attaque slide/meet-in-the-middle de KeeLoq… Des jours. Rien de exploitable côté hobby - récupérer cette clé demande soit une fuite, soit une attaque physique par canal auxiliaire à plusieurs centaines d’euros.

Puis j’ai testé la chose la plus bête au banc, celle que j’aurais dû tester en premier :

rejouer une trame capturée. J’ai capté une trame « montée » de ma vraie télécommande, je l’ai réémise telle quelle avec le CC1101… et le volet est monté. Mieux : ça marche encore après avoir réutilisé la vraie télécommande entre-temps.

Autrement dit, le moteur Profalux n’applique pas la contrainte anti-rejeu du compteur. Une trame captée reste valable. Toute la saga cryptographique était sans objet pour le pilotage : je n’ai besoin d’aucune clé. Le firmware clone une télécommande par simple capture + rejeu.

C’est le genre de retournement qui fait râler sur le moment (=tout ce temps sur la crypto) et sourire après : le tell-tale, c’est que la solution la plus simple mérite d’être testée avant la plus savante.

Identification hardware

Rien à démonter ici, contrairement à un module à reverser : on construit l’émetteur. Deux composants.

Un piège dès l’achat : il existe des CC1101 en 433 MHz et en 868 MHz, et les deux se ressemblent comme deux gouttes d’eau. Profalux émet autour de 868, donc prendre la bonne bande : un module 433 ne captera rien. Le marquage de bande est souvent minuscule, d’où la photo ci-dessous du module exact que j’utilise.

Gros plan de mon CC1101 : le marquage « CC1101 868MHz Module » est lisible sur le bord gauche, avec l'antenne ressort en cuivre. Regarde les soudures sur la droite avant de juger.

Petite confession en passant : je ne suis pas le roi de la soudure, et mes pâtés d’étain sur les pastilles du CC1101 sont franchement dégueulasses. Une bonne partie de l’excuse tient au pas de 2 mm non standard du module (=voir plus haut) : le 2.54 classique ne rentre pas, et faute d’avoir un header 2 mm sous la main, j’ai étamé les fils un par un directement sur les pastilles, à la main. Ça finit rarement joli, mais ça conduit, et c’est tout ce qu’on demande à une soudure.

Câblage pas à pas

Le CC1101 se relie à l’ESP32 par six fils : les quatre du bus SPI (MISO, MOSI, SCK, CS), plus GDO0 (=la broche qui sort la donnée radio, indispensable), et l’alimentation.

⚠️ Le CC1101 est un composant 3.3 V. Jamais 5 V. L’alimenter en 5 V le détruit. Sur l’ESP32 on prend donc le rail 3V3, pas le 5V/VBUS.

Sur le M5Stack ATOM Lite, tous les signaux sont sur le header du bas, ce qui rend le câblage sans soudure. Voici le mapping que le firmware attend (=cible m5stack), avec un code couleur suggéré pour ne pas s’emmêler :

Signal CC1101Broche ATOM LiteCouleur du fil
MISOG33🟢
MOSIG23🟡
SCKG19🔵
CS (=CSN)G22
GDO0G25🟠
GDO2 (=optionnel)G21🟣
3V33V3🔴
GNDGND

Si tu utilises un ESP32 DevKit classique à la place (=cible external), le firmware attend le bus VSPI : MISO 19, MOSI 23, SCK 18, CS 5, GDO0 4, GDO2 2.

Piège classique : sur beaucoup de breakouts CC1101, GDO0 n’est pas aligné avec la sérigraphie évidente. C’est la broche data - si rien ne se capture, c’est le premier fil à re-vérifier. GDO2 est optionnel (=debug), on peut le laisser non branché au début.

Pour l’antenne, un bout de fil rigide de 8.6 cm (=quart d’onde à 868 MHz) soudé sur la pastille antenne suffit pour tester sur le bureau. Pour couvrir la maison, une vraie antenne 868 sur connecteur améliore beaucoup la portée.

Comment est construite une trame

Une trame Profalux, c’est du KeeLoq HCS301 pur. Au SDR, ma porteuse est à 868.425 MHz, modulation OOK (=on/off keying : la porteuse est présente ou absente), avec un élément de base T_E d’environ 455 µs. Sur l’air, dans l’ordre :

  1. Un préambule : une vingtaine d’alternances courtes (=~455 µs chacune) qui réveillent le récepteur et calent son horloge.
  2. Un en-tête : un long silence d’environ 4.4 ms qui marque le début des données.
  3. Le mot de code : 66 bits, chaque bit codé par la durée du niveau haut/bas (=période de bit ~1365 µs, soit 3·T_E).

Les 66 bits se découpent ainsi :

ChampBitsRôle
Hopping code32la partie chiffrée, change à chaque appui
Numéro de série28identifiant de la télécommande, en clair
Bouton4descente 0x01, stop 0x02, montée 0x04
État2batterie faible (VLOW) + répétition (RPT)

La seule partie chiffrée, c’est le hopping code (=32 bits). À l’intérieur, une fois déchiffré avec la clé, on trouve : un compteur de 16 bits qui s’incrémente à chaque appui, une partie du numéro de série (=10 bits, la « discrimination »), le code bouton et 2 bits de dépassement. C’est ce compteur chiffré qui fait le « rolling » : impossible à prédire sans la clé. Le reste de la trame (=série, bouton, état) est en clair.

Détail utile pour la position : le bit RPT distingue un maintien. Quand tu gardes le doigt appuyé, la télécommande répète la même trame (=même hopping code, compteur figé, RPT à 1). Le compteur ne s’incrémente qu’au relâchement suivi d’un nouvel appui.

Rien de tout ça n’est nécessaire pour utiliser le firmware (=il capte et rejoue les bits tels quels), mais c’est utile pour comprendre ce qui passe et pour la section suivante.

Le firmware

Le code est sur github.com/Shad107/OpenProfalux, dossier firmware/. C’est de l’ESP-IDF (=le SDK natif Espressif, pas Arduino ni ESPHome).

Compiler (=via Docker, sans rien installer localement) :

docker run --rm -e IDF_TARGET=esp32 -v "$PWD":/project/firmware -w /project/firmware \
  espressif/idf:v5.2.2 bash -c "idf.py set-target esp32 && idf.py build"

Flasher + moniteur série :

idf.py -p /dev/ttyUSB0 flash monitor

Premier démarrage. Le module n’a pas encore de Wi-Fi configuré, donc il ouvre son propre point d’accès :

  1. Un réseau Wi-Fi OpenProfalux_XXXX apparaît (=XXXX = fin de la MAC), ouvert (=pas de mot de passe, on se connecte directement).
  2. S’y connecter, ouvrir http://192.168.4.1, onglet Système : nom de l’appareil, SSID de la box (=laisse le mot de passe vide si ton réseau est ouvert, par exemple un SSID en authentification par MAC), adresse du broker MQTT (=un bouton « Détecter le broker » cherche Mosquitto en mDNS et remplit le champ tout seul).
  3. Enregistrer + redémarrer. Le module rejoint la box en Wi-Fi et se connecte à MQTT.

L'interface web embarquée d'OpenProfalux, onglet Volets : une carte par volet avec montée / stop / descente et la position estimée

Apprendre une télécommande

C’est le cœur du clone. Onglet Apprentissage de l’UI :

  1. On choisit l’action à enregistrer (=montée, stop, ou descente).
  2. On clique Écouter, puis on appuie une fois sur le bouton correspondant de la vraie télécommande physique.
  3. La trame captée s’affiche (=numéro de série, bouton, force du signal). On donne un nom à la télécommande (« Murale chambre »), et on affecte la trame au volet.

On répète pour les trois actions. Un volet peut avoir plusieurs télécommandes (=la murale et la Noé multi-canal) : il suffit de capturer chacune, le firmware mémorise tous les numéros de série qui pilotent ce volet.

Piloter, et le modèle « appui + stop »

Une subtilité Profalux qui change la logique de position : ce n’est pas un maintien. On tape montée, le moteur part tout seul vers le haut, et on tape stop quand on veut qu’il s’arrête. La position d’un volet à mi-course, c’est donc le temps entre l’appui montée et l’appui stop.

Le firmware reproduit exactement ça :

Comme il n’y a aucun retour du moteur (=on émet, on n’écoute pas sa position réelle), le pourcentage est une estimation par le temps. Elle dérive un peu et se recale automatiquement dès que le volet va à fond (0 % ou 100 %). Pour un volet, c’est largement suffisant au quotidien.

Suivre la vraie télécommande

Si quelqu’un utilise la télécommande murale physique, le firmware écoute aussi et met à jour la position dans Home Assistant : il voit passer la trame montée (=le volet part en haut), et la trame stop (=il fige la position estimée là où on s’est arrêté). Il faut pour ça que la télécommande ait été apprise (=son numéro de série est connu du volet).

Home Assistant

Une fois le MQTT configuré, chaque volet appris apparaît tout seul dans Home Assistant comme une entité cover (=via MQTT discovery) : ouvrir, fermer, stop, et une position en pourcentage. Pas de YAML à écrire côté HA.

Sauvegarde et mises à jour

Deux filets de sécurité que je tenais à avoir :

Pour les curieux : et si on voulait quand même la clé ?

Le rejeu suffit largement pour piloter, mais la question crypto reste amusante. Deux vraies pistes existent pour récupérer la clé de chiffrement d’une télécommande, si un jour l’envie prend.

1. L’attaque par canal auxiliaire (DPA / CPA). À chaque trame reçue, le récepteur KeeLoq déchiffre. Pendant ce calcul, sa consommation électrique fuit de l’information sur la clé. En mesurant finement cette consommation pendant le déchiffrement, puis en corrélant sur toutes les hypothèses de sous-clé, on remonte la clé complète en quelques centaines de traces (=Differential / Correlation Power Analysis). Le matériel de référence pour ça est un ChipWhisperer (le Husky, ou le Nano plus abordable pour débuter). C’est la méthode « propre » et fiable, mais elle demande un accès physique au circuit à sonder.

Le ChipWhisperer-Husky, l'outil de référence pour l'analyse de consommation DPA/CPA sur KeeLoq (=photo constructeur NewAE)

2. L’attaque slide / meet-in-the-middle (=Indesteege et al., 2008). Purement mathématique, sur les trames radio, sans toucher au matériel. Elle exploite la structure de KeeLoq (=528 tours d’une fonction très simple) mais réclame beaucoup de couples à clair connu, de l’ordre de 2^16 trames d’une même télécommande, pour une complexité finale autour de 2^44.5. Faisable sur un bon GPU, mais il faut d’abord collecter énormément de hopping codes distincts.

Et c’est exactement pourquoi le firmware a une option « capturer toutes les trames ». Quand on l’active, chaque trame distincte reçue est publiée en MQTT et dédupliquée par numéro de série. Sur des semaines, ça accumule tranquillement le jeu de données de hopping codes qui alimenterait une attaque slide. C’est une porte laissée ouverte pour plus tard, désactivée par défaut, et totalement inutile au pilotage.

La suite

Ce qui est fait : le firmware compile (=ESP-IDF v5.2.2, ~1.2 Mo), et le rejeu est validé au banc (=le moteur suit une trame rejouée, y compris après usage de la vraie télécommande). Reste à valider sur l’installation complète :

Matériel

Prix indicatifs relevés au moment du projet. Aucun lien affilié - je ne touche rien.