Écoute du signal d'un Babyphone via RTL-SDR

Introduction

Depuis quelque semaines j’ai la chance d’être l’heureux “possesseur” d’un bébé. Pour surveiller le petit bonhomme, nous avons investi dans un babyphone tout ce qu’il y a de plus classique, c’est-à-dire avec un émetteur et un récepteur. Vu que je n’ai plus beaucoup l’occasion de dormir, qu’une RTL-SDR trainait sur le bureau, et que je ne connais pas très bien GNURadio. Je me suis fixé pour objectif de créer un circuit virtuel à l’aide de GNURadio Companion afin d’avoir un récepteur virtuel pour le babyphone avec les mêmes “fonctionnalités”. Néanmoins, avant de rentrer dans le vif du sujet, on va éclaircir quelques termes.

Une SDR est une radio définie par logiciel (Software Defined Radio). Il s’agit d’un appareil capable d’émettre et/ou de recevoir des signaux radio avec un circuit de traitement qui n’est pas géré par le hardware. Pour résumer, il est possible avec ces petites bêtes de “programmer” le traitement que l’on veut faire du signal à partir d’un ordinateur sans avoir à modifier les composants de la carte électronique. C’est le logiciel GNURadio Companion, qui dans le cadre de cet article permettra de “coder” le traitement des signaux envoyés par le babyphone. GNURadio Companion, abrégé GRC, est une interface graphique à GNURadio qui permet de facilement créer un circuit de traitement du signal en positionnant des blocs de traitement (filtrage, modulation, codage, etc.) et en les reliant les uns aux autres.

GNURadio Companion

Maintenant que nous avons défini les termes “complexes”, voici le plan d’action pour arriver à créer un récepteur logiciel pour le babyphone :

  • Identifier les fonctionnalités du récepteur
  • Trouver la fréquence et la modulation utilisées par l’émetteur
  • Créer le “circuit” de traitement GNURadio permettant d’émuler le récepteur

Étape 1 : Identifier les fonctionnalités du récepteur

Le récepteur du babyphone est plutôt simple, sans fonctionnalités exotiques. Au niveau des entrées, nous avons un réglage de volume permettant d’éteindre le récepteur lorsqu’il est sur 0. Un switch permettant de sélectionner le canal 1 ou 2 pour les communications. Et pour ce qui est des sorties, nous avons un hautparleur pour entendre ce qui se passe et une série de signaux lumineux pour avoir le niveau de “puissance” du signal.

Étape 2 : Trouver la fréquence et la modulation de l’émetteur

Il existe plusieurs méthodes permettant de trouver la fréquence de fonctionnement d’un émetteur. Il est tout d’abord possible de voir si une indication sur l’appareil affiche une fréquence de fonctionnement. Si ce n’est pas le cas, nous pouvons toujours nous retrancher sur la notice ou la documentation du constructeur. Dans notre cas, le site du constructeur indique un fonctionnement à 865MHz sans plus d’information. Pour un début c’est pas mal, malheureusement l’émetteur permet de sélectionner deux canaux. Il y a donc de forte chance que plusieurs fréquences soit utilisées.

Vu que le constructeur nous indique un fonctionnement à 865MHz, nous allons tenter d’effectuer un balayage fréquentiel à l’aide de l’outil rtl_power et de notre RTL-SDR. L’objectif ici, est de mesurer la puissance des signaux sur chaque fréquence, et donc de détecter les plages occupées.

Pour effectuer ce balayage, nous allons donc utiliser la commande rtl_power -f 840M:880M:100k res.csv. Celle-ci nous permet de scanner entre 860 et 880MHz avec un pas de 100kHz et d’enregistrer les mesures relevées dans un fichier nommé res.csv. Je laisse le script tourner pendant quelques minutes, tout en changeant le canal de l’émetteur de temps en temps.

A la fin de l’enregistrement, nous obtenons un fichier CSV de quelques kilo-octets pas forcément très lisible. Nous allons donc utiliser un script python permettant de rendre le résultat plus… graphique.

FFT

Nous voyons en jaune sur l’image les signaux reçus par la clé RTL-SDR. Nous apercevons très nettement deux bandes de fréquences en fonction du canal choisi. Nous pouvons donc valider que les fréquences utilisées par l’émetteur semblent être aux alentours de 864,3MHz pour le canal 1 et 863,2MHz pour le canal 2. Il nous manque donc maintenant la modulation pour pouvoir tenter de faire quelque chose avec GNURadio.

Pour la modulation, nous allons passer par l’outil GQRX, qui est une interface permettant de facilement manipuler une SDR. Nous allons donc pouvoir écouter les fréquences et voir si un signal audible sort avec les démodulations intégrées à l’outil.

En se mettant sur la fréquence 864.3MHz dans GQRX, on remarque très rapidement sur le spectre fréquentiel que l’on est “à côté” du signal.

Fréquence canal 1

En réalité, le premier canal semble être sur 864.44MHz et le second sur 863.25MHz. En sélectionnant le mode “WFM Mono”, on entend de suite ce qui est envoyé par l’émetteur.

WIN!

Maintenant que l’on a toutes les informations que l’on veut, nous allons pouvoir passer sur GNURadio Companion et tenter d’implémenter les fonctionnalités du récepteur dans un circuit virtuel.

Étape 3 : Création du circuit dans GNURadio

Pour notre circuit GNURadio, nous allons utiliser les blocs suivants :

  • Une source RTL-SDR pour capter le signal
  • Un bloc power squelch pour nettoyer le bruit
  • Un filtre passe bas
  • Un démodulateur WBFM
  • Une sortie audio pour entendre ce que l’on capte

Avec ces éléments, nous sommes en mesure de récupérer et traiter le signal venant de l’émetteur. Mais nous allons y ajouter quelques widgets, permettant de gérer graphiquement notre récepteur virtuel. Avec QT, nous pouvons ajouter un *QT GUI Chooser", pour que je puisse sélectionner le canal de communication, et un QT GUI Range afin que je puisse régler le volume à l’aide d’un slider.

Après avoir mis tout ce petit monde sur le plan et relier tout comme il faut, voici le circuit :

Circuit GNURadio

Une fois exécuté, nous sommes en mesure d’entendre les sons captés par l’émetteur et l’interface nous permet de gérer le volume ou bien de sélectionner le canal.

récepteur virtuel

Conclusion

Maintenant que l’on connait le fonctionnement du récepteur et celui du GNURadio Companion, pourquoi ne pas tenter de faire peur à la maman ? Et si la fois prochaine, nous tentions d’émettre un signal en se faisant passer pour l’émetteur du bébé ?

Ressources :

T-Watch 2020: TV-B-Gone

La dernière fois, nous avons vu comment obtenir un environnement pour la T-Watch2020 avec Micropython et LVGL. Cette fois, nous allons jouer avec l’emeteur infrarouge de la montre afin de voir s’il est possible d’en faire un TV-B-Gone.

Qu’est ce qu’un TV-B-Gone

Vu que Wikipedia l’écrit très bien sur la page dédiée au TV-B-Gone, nous allons reprendre sa présentation :

Le TV-B-Gone est une télécommande universelle dont l’unique fonction est d’éteindre et d’allumer les téléviseurs. Le concept a été réalisé en open source et est commercialisé par Mitch Altman, ingénieur dans la Silicon Valley. Il s’agit d’un détournement de la télécommande universelle : le TV-B-Gone parcourt les différentes fréquences utilisées par les différentes marques en émettant pour chacune d’entre elles le signal correspondant à la commande d’arrêt du téléviseur à l’aide d’une diode infrarouge. Cette télécommande a été pensée pour permettre d’éteindre les téléviseurs dans les lieux publics afin de ne plus subir en permanence la publicité ou les émissions télévisées, notamment dans les fast-foods, les magasins ou encore les files d’attente. Ce gadget provoque l’engouement des activistes anti-télévision ou antipub, qui organisent régulièrement des « raids » dans les magasins d’électroménager pour éteindre en quelques pressions des murs entiers de téléviseurs en démonstration.

Plusieurs implémentations existent pour Arduino, mais il ne semble pas y avoir d’existant pour du Micropython. Nous allons donc voir comment fonctionne une télécommande et comment on peut le réimplementer en Python.

Comment fonctionne l’émission infrarouge d’une télécommande

Pour faire simple une télécommande encode une valeur en signaux lumineux. Cet encodage n’est pas universel et plus ou moins chaque constructeur utilise le sien. Par exemple, SONY propose un encodage simple. Il définit tout d’abord un temps T à 600µs qui lui permet d’encoder différentes valeurs. Si il doit encoder un 1, alors il envoie un signal lumineux de 2T puis une absence de signal de 1T, soit 1200µs à l’état haut et 600µs à l’état bas. Si il doit encoder un zero, alors il envoie un signal lumineux de 1T et une absence de signal de 1T, soit 600µs à l’état haut et 600µs à l’état bas. Le schéma suivant résume l’encodage utilisé par SONY :

Schéma de cypress.com présentant l’encodage SONY

Comme on peut le voir sur le schéma, un signal “START” de 2400µs à l’état haut et 600µs à l’état bas est utilisé pour débuter la communication avant d’encoder un message.

Quasiment tous les protocoles utilisent ce fonctionnement, seules les temps hauts et bas changent. Afin de pouvoir faire quelque chose d’universel, nous n’allons pas prendre en compte chaques particularités des protocoles. Nous ne ferons qu’alterner des temps hauts et temps bas qui seront interpreté par le récepteur IR de notre télévision. C’est d’ailleurs ce fonctionnement que l’on retrouve sur tout un tas d’implémentation du TVBGone que ce soit sur Arduino ou d’autres plateformes.

Utiliser la diode infrarouge depuis Micropython

La diode infrarouge de la T-Watch est reliée à la Pin 13 de l’esp32. Du coup, nous pouvons facilement la piloter. Pour envoyer un signal brut, nous pouvons tester quelque chose comme ça :

from machine import Pin
from utime import sleep_ms

ir = Pin(13, Pin.OUTPUT)
raw_signal = {'freq': 38000, 'codes': [600, 600, 1200, 600]}
codes = raw_signal.get('codes')
for i in range(0, len(codes), 2):
    ir.on()
    sleep_ms(codes[i])
    ir.off()
    sleep_ms(codes[i+1])

C’est très basique, mais ça permet de tenter une implémentation en pure python. Après plusieurs essais, il s’est avéré qu’une latence assez importante s’est fait ressentir et que le signal n’était pas interpreté correctement. Du coup, j’ai modifié légérement la bibliothèque “ir”, développée en C, présente dans le firmware afin qu’elle soit en mesure d’envoyer des données brutes. Le fork est téléchargeable ici. Pour l’utiliser depuis Micropython c’est assez simple :

from ir import rmtlib_raw_send
raw_signal = {'freq': 38000, 'codes': [600, 600, 1200, 600]}
freq = raw_signal.get('freq')
codes = raw_signal.get('codes')
rmtlib_raw_send(freq, codes)

Après plusieurs tests, on est sur quelque chose de beaucoup plus concluant. J’ai donc créé une bibliothèque TVBGone que l’on va pouvoir utiliser depuis notre montre :

wget https://forge.tedomum.net/-/snippets/46/raw -O tvbgone.py
ampy -p /dev/ttyUSB0 put tvbgone.py

Depuis l’interpreteur python de votre montre, vous devriez pouvoir lancer votre TVBGone avec :

picocom /dev/ttyUSB0 -b115200
>> from tvbgone import TVBgone
>> TVBgone().start()

Créer une interface

Afin de rendre l’utilisation du TVBGone plus simple, nous allons créer une simple interface à l’aide de LVGL. Celle-ci est juste composée d’un bouton qui lance l’attaque lorsqu’on appuie dessus et qui affiche un spinner le temps du l’attaque.

Le code de l’interface est le suivant :

import lvgl as lv
import ttgo
import _thread
from IR import TVBgone


class Button:
    def __init__(self, app):
        self.btn = lv.btn(app)
        self.btn.align(lv.scr_act(), lv.ALIGN.CENTER, 0, 0)
        self.btn.set_event_cb(self.onevent)

        self.text_label = lv.label(self.btn)
        self.text_label.set_text("Click to start")

        self.spinner = lv.spinner(app)
        self.spinner.align(lv.scr_act(), lv.ALIGN.CENTER, 0, 0)
        self.spinner.set_hidden(True)

    def onevent(self, btn, event):
        if event == lv.EVENT.CLICKED:
            #self.text_label.set_text("In progress")
            #self.btn.set_state(self.btn.STATE.DISABLED)
            self.btn.set_hidden(True)
            self.spinner.set_hidden(False)
            _thread.start_new_thread(self.attack, ())
            
    def attack(self):
        TVBgone().start()
        self.spinner.set_hidden(True)
        self.btn.set_hidden(False)

# Init watch
watch = ttgo.Watch()

# Init lvgl
lv.init()
watch.lvgl_begin()

# Enable backlight
watch.tft.backlight_fade(100)

# Init interface
scr = lv.obj()
win = lv.win(scr)
win.set_title("TVBGone PoC")
Button(win)
lv.scr_load(scr)

Si tout se passe bien vous devriez avoir un résultat équivalent au mien: https://mastodon.tedomum.net/@y0no/104785810302905250

Nous avons donc un TV-B-Gone très élémentaire mais fonctionnel. Plusieurs améliorations peuvent être envisageable :

  • Possibilité d’arrêter l’attaque en cours
  • Selection du payload Europe ou US
  • Gestion des signaux plus exotiques

Ressources :