Nous présentons à votre attention la deuxième partie de l'article sur la rétro-ingénierie du firmware du dispositif Flashing Rhino basé sur un atelier à la conférence
SMARTRHINO-2018 .
Dans la première partie de l' article, le micrologiciel du périphérique a été chargé dans le désassembleur IDA et une analyse initiale des commandes de protocole du périphérique a été effectuée. Les commandes individuelles ont été testées sur un appareil fonctionnel.
Dans la deuxième partie, une analyse des tâches restantes du firmware sera effectuée.
Permettez-moi de vous rappeler qu'après avoir analysé la tâche Bluetooth en termes de contrôle des LED, il a été décidé de passer à la tâche LED, car la tâche initiale consiste à créer une application pour contrôler les LED, et pour cela une compréhension détaillée du fonctionnement du firmware est nécessaire.
Le fichier du firmware est disponible pour étude indépendante.
Toutes les informations sont fournies à des fins éducatives uniquement.Sous le chat, il y a beaucoup de rhinocéros clignotants.
Tâche LED
En bref: une analyse complète de la tâche responsable de la commutation des LED. Analyse des types de données et des variables globales.La tâche LED est représentée par la fonction
x_leds_task , située à
0x08005A08
.
En plus des étranges lignes «J'ai une super puissance ...» dans la fonction principale de la tâche LED, vous pouvez faire attention à la ligne
«teinte> max: changer l'éclat \ r \ n» .

En même temps, nous voyons une situation familière - (WORD *) (v26 + 4). Dans le menu contextuel de la variable v26, sélectionnez l'élément "Convertir en struct *", puis indiquez la structure créée précédemment:

Étant donné que
v5 = v26
, nous répétons l'opération
«Convertir en struct *» pour la variable v5.
Nous continuons de structurer le code et les données. Définissez la représentation hexadécimale partout. Renommer:
- v5 - led ;
- v6 - idx ;
- v8 - hue_1 ;
- v9 - hue_2 ;
- v26 - _led ;
Le code s'améliore. Mais certaines variables font toujours mal à l'œil, par exemple, la variable v23:


Apparemment, la v23 est un tableau de 4 octets.idx est l'indice de la LED; cet index est ajouté à l'adresse de base; de cette façon, l'accès est fait aux éléments avec les mêmes déplacements - c'est ainsi que se comportent les tableaux.
On assigne le type
char v23[4]
et on le renomme
leds_smth , le code devient plus joli:

Vous pouvez également noter que le résultat de la fonction x_queue_recv est retourné à la variable v25:
x_queue_recv(&v25, leds_queue, 1000);
Mais il se peut que la façon dont les données dont vous avez besoin se
trouvent dans la structure
_led ne soit pas claire. Le fait est que les variables v25 et _led
sont situées à proximité de la pile - cela peut être compris par le fait que dans la décompilation, elles sont écrites sur des lignes adjacentes. L'emplacement des variables sur la pile peut être vu dans une fenêtre séparée si vous double-cliquez sur la variable:

Il s'agit probablement d'une structure, ou le compilateur a effectué une optimisation. Ainsi, on peut affirmer que les données de la tâche Bluetooth sont transmises à la tâche LED. Pour le savoir plus précisément, je vais vérifier sur l'appareil - pour la LED zéro via Bluetooth j'enverrai les valeurs
0x208 ,
0x2D0 ,
0x398 ,
0x3E9 , qui pourraient être remarquées dans le code:

Les résultats de la vérification de la valeur de teinte sur l'appareil:
- 0x208 - les LED ont cessé de commuter en douceur et ont été réglées dans les couleurs: rouge, vert, bleu, violet;
- 0x2D0 - les LED ont recommencé à commuter;
- 0x398 - rien n'a changé;
- 0x3E9 - rien n'a changé.
Si vous regardez à nouveau le code, vous pouvez voir que la valeur 0x398 peut être associée logiquement à une valeur inférieure à 0x167 (différentes valeurs sont définies pour l'élément de tableau
leds_smth ). Par conséquent, je vais effectuer cette vérification: d'abord, je vais mettre la première LED sur vert (teinte = 0x78,
LED 010078FF20
), tandis que les trois autres LED continuent de changer de couleur.
Maintenant, je vais
LED 010398FFFF
protocole Bluetooth
LED 010398FFFF
- après cela, la première LED est passée en mode de commutation de couleur générale.
Ainsi, la valeur de teinte de 0x398 réinitialise la valeur de couleur statique, ce qui signifie que le tableau leds_smth contient des drapeaux (0 ou 1) pour les LED à occuper:
- 0 - la LED n'est pas occupée, participe à une commutation de couleur fluide ( teinte = 0x398 );
- 1 - la LED est occupée, l'utilisateur a défini une couleur statique ( teinte <= 0x167 ).
Renommez leds_smth en
leds_busy .
Le but du bloc de code suivant devrait maintenant devenir clair:

Le cycle des lignes 83-101 effectue une mosaïque de couleurs lisse avec une étape de changement de couleur de 5:
v12 += 5
. Si la LED a une couleur statique allumée, alors cette LED ne participe pas à la mosaïque. Après le cycle, il y a des lignes d'inclusion à court terme de toutes les LED.
Renommer:
- sub_800678A - x_led_set_hsv ;
- v12 - hue_step ;
- v13, v17, v18, v19 - led0_busy , led1_busy , led2_busy , led3_busy ;
- v11, v20, v21, v22 - teinte0 , teinte1 , teinte2 , teinte3 ;
- dword_200004C4 - led_control .
La fonction sub_80039FE effectue vraisemblablement un timeout (sinon les LED n'ont pas commuté en douceur, mais instantanément), appelons-la
x_sleep , et la variable v16 est
led_timeout .
Le but de la fonction sub_8006934 n'est pas encore évident, mais il est utilisé partout après avoir défini la couleur sur les LED - vous pouvez l'appeler
x_led_fix_color .
Après ces renommages, il est facile de comprendre la fonction
sub_8006944 (appelée dans la branche hue <= 0x167):

Il effectue simplement une vérification supplémentaire pour déterminer la couleur de la LED. Renommez la fonction sub_8006944 en
x_led_set_hsv_wrap (suffixe
_wrap - une explication qu'il s'agit d'un «wrapper» sur une autre fonction) et définissez-lui le prototype suivant:
signed int __fastcall x_led_set_hsv_wrap(int led_control, signed int idx, int hue, char sat, char val)
Revenons d'un niveau à la fonction x_leds_task. Une fois de plus en regardant le code, vous pouvez constater que la branche "teinte> 0x3E8" a commencé à ressembler à ceci:

En d'autres termes, une valeur de teinte supérieure à 0x3E8 devrait modifier le délai d'expiration de la mosaïque colorée. Je vérifierai en envoyant certaines valeurs à l'appareil:
- teinte = 0x3E9 - les LED ont commencé à basculer rapidement:

- hue = 0xFFFF - les LED ont commencé à basculer très lentement:

Lorsque vous quittez le cycle principal de la tâche LED, la fonction
sub_8003C44 est
utilisée , qui est également utilisée dans la fonction sub_8005070:

Renommer:
- sub_8005070 - x_freeMsg ;
- sub_8003C44 - x_free_queue .
Plus loin dans la tâche LED, la branche suivante ne peut qu'attirer l'attention:

Vous pouvez essayer d'exécuter la commande
LED B816D8D90000FFFF
. Mais si vous vous souvenez que seuls 2 caractères sont pris comme index LED, une tentative pour atteindre ce code sera évidemment infructueuse. Laissez ce fil pour plus tard. Renommez la fonction sub_8004AE8 en
x_mad_blinking , et il est temps de corriger la signature de la fonction
x_printf (la dernière fois que j'ai écrit la mauvaise signature):
void x_printf(const char *format, ...)
Le cycle principal de la tâche LED a été démonté, mais il y a toujours un code au tout début de la tâche.
Regardons le code:

À la ligne 49, il est très probable que les LED soient vérifiées pour la disponibilité et, en cas d'erreur, un appel est effectué vers la fonction sub_8004BBC, qui désactive les interruptions et démarre une boucle infinie dans laquelle la ligne «../Drivers/STM32F0xx_HAL_Driver/Src/stm32f0xx_hal_gpio.c» est utilisée. Il s'agit très probablement d'une fonction d'
affirmation ou similaire.
Renommer:
- sub_8004BBC - x_gpio_assert ;
- sub_800698C - x_check_gpio .
Le but de la fonction
sub_8006968 deviendra clair si vous regardez attentivement l'appareil lorsqu'il est allumé:
Les quatre LED s'allument ensemble en premier rouge, puis vert, puis bleu. Après cela, ils sont définis par couleur: 0-rouge, 1-vert, 2-bleu, 3-violet. Et c'est seulement alors qu'ils commencent à changer de mosaïque.
Étant donné que la mosaïque commence dans le cycle de tâches principal, il est logique que les lignes 58-61 avant le cycle principal soient responsables de l'inclusion à court terme de différentes couleurs sur les LED, et les lignes 52-56 sont chargées de définir le rouge-vert-bleu sur toutes les LED à la fois. Renommez la fonction sub_8006968 en
x_led_all_set_rgb (RGB - purement sur une intuition, selon les arguments passés).
Curiosités dans la tâche LED
En bref: définir la fonctionnalité du code contenant des lignes étranges. Génération d'un mot de passe pour l'appareil.Passons maintenant au tout début de la fonction x_leds_task:
"Eraze" ,
"gen" ,
"flash" ,
"reset" - pourquoi tout cela ???
Essayons de le comprendre.
Soit sub_80066BC
x_leds_task_init .
Regardons sub_8006B38:

Memset d'eau pure, d'accord?

Retour à x_leds_task. Quelque chose ne va pas avec le type de variable v24:

L'IDA a fait une petite erreur avec le type, mais un commentaire avec une marque de pile nous aide. Entre les variables v24 et v25 jusqu'à 12 octets (0x44 - 0x38). Par conséquent, nous renommons v24 en
buf et attribuons le type
unsigned __int8 buf[12]
(Ida avertira que le nouveau type de données est plus grand que l'ancien - nous sommes d'accord).
Ensuite. Fonction sub_8004CE4:

Renommez
a1 en
buf ,
v1 en
_buf .
Fonction sub_8006B26:

L'avez-vous reconnue?
Et si sans maquillage?
Bien sûr,
memcpy . Renommer.
Le but de la fonction sub_8004CE4 est alors d'obtenir des données à l'adresse
0x08007C00 . Soit dit en passant, cette adresse est dans la plage d'adresses de la mémoire flash du microcontrôleur (et du firmware, en particulier). Renommez sub_8004CE4 en
x_read_data_0x08007C00 .
Ligne de fonction X_leds_task 36:
if ( (unsigned int)buf[0] - 65 > 0x19 )
Modifiez l'affichage des données (touche R sur le numéro 65, touche H sur le numéro 0x19):
if ( (unsigned int)buf[0] - 'A' > 25 )
Après un peu de réflexion, vous pouvez comprendre qu'il s'agit d'un tel test de la gamme de l'alphabet latin AZ.
Ensuite, en utilisant les invites sous forme de chaînes de format, renommez:
- sub_8004C10 - x_erase ;
- sub_80059C8 - x_gen ;
- sub_8004C84 - x_flash .
La fonction sub_8003C66 ne fait rien de remarquable - elle n'augmente que certaines variables globales - renommez sub_8003C66 en
x_smth_inc .
La fonction
x_erase n'accepte aucun argument - cela peut être vérifié dans le désassembleur:

Dans x_erase, l'adresse familière 0x08007C00 est utilisée et trois fonctions inconnues sont accessibles:

Un
rapide coup d'œil sur ces trois fonctions, nous voyons qu'elles accèdent à des adresses dans la plage
0x40022000 - 0x400223FF . La documentation du microcontrôleur indique clairement qu'il s'agit de la gamme
«FLASH Interface» . Autrement dit, la fonction x_erase efface un morceau de mémoire flash - génial!
Apparemment, la fonction x_flash écrit dans la mémoire flash, après avoir vérifié la longueur de la ligne à écrire (au fait, les arguments a2 et a3 sont superflus ici - nous allons aider Idea):

Et tout cela se passe dans le "dispositif d'éclairage" ???
Et la fonction
x_gen ? Après un rapide coup d'œil et en renommant les variables, cela ressemblera à ceci:

La fonction
sub_8006CB4 ressemble à ceci:

Et
sub_8006D10 - comme ceci:

Ne retenez pas le désir de rechercher sur Internet ces constantes indécemment belles:
0xABCD ,
0x1234 ,
0xE66D ,
0xDEEC ,
0x4C957F2D et
0x5851F42D . Si Internet n'est pas encore complètement banni, vous trouverez probablement ces constantes dans la source
des fonctions aléatoires . Pas étonnant que la fonction parent s'appelle x_gen.
C'est aussi une situation très typique: appelez srand () avant la boucle, et appelez random () dans la boucle, alors renommez-le:
- sub_8006D10 - x_rand;
- sub_8006CB4 - x_srand.
Un lecteur curieux, en examinant la fonction
sub_8005098 , peut découvrir d'
où vient la graine pour la fonction srand.
Ainsi, la fonction x_gen génère
une chaîne aléatoire de la taille spécifiée .
Une fois la ligne générée écrite dans la mémoire flash, l'appareil redémarre:

Cela ressemble à une sorte de redémarrage bizarre. Mais si nous regardons la liste des tâches de cet appareil, nous trouverons «watchdogTask» parmi eux. Évidemment, s'il y a une "tâche bloquée", le chien de garde redémarre.
La tâche LED à l'exception du mode MadBlinking peut être considérée comme analysée.
Regardons à travers les lignes quelles sont les autres tâches du système:

Après avoir restauré les liens vers les chaînes dans le code, vous pouvez voir cette image:

Tout d'abord, il existe un lien vers une chaîne avec le nom de tâche, puis un lien vers la fonction de tâche principale. Et ils sont utilisés dans la fonction
principale , où ces tâches sont lancées:

Exécutons les renommages manquants:
- sub_80050FC - x_sensor_task ;
- sub_8004AAC - x_watchdogTask ;
- sub_8005440 - x_uartRxTask .
Tâche de surveillance
Le chien de garde des tâches ne fait rien de particulièrement intéressant:

Renommer:
- dword_200003F8 - wd_variable ;
- sub_8001050 - x_update_wd_var .
Tâche UART
En bref: recherchez des données et des fonctions qui ont des liens à partir de différentes fonctions. Détermination de leur objet.Un rapide coup d'œil à la tâche UART vous permet de détecter l'envoi de données vers une file d'attente inconnue définie par la variable
unk_200003EC :

Après avoir restauré les liens vers cette variable via la recherche binaire, nous verrons qu'en plus de x_uartRxTask, elle est utilisée dans le principal (là, la file d'attente est créée, apparemment) et dans la fonction inconnue jusqu'ici
sub_80051EC :

Renommer:
- sub_80051EC - x_recvMsg_uart_queue ;
- unk_200003EC - uart_queue .
Voir les références croisées à x_recvMsg_uart_queue:
- sub_8005250;
- x_bluetooth_task.
Tout d'abord, consultez la fonction
sub_8005250 :

Après réflexion, renommez:
- unk_2000034C - cmd_count ;
- a1 - cmd ;
- v4 - _cmd ;
- v6 est rsp ;
- sub_8005250 - x_bluetooth_cmd .
Voyons maintenant où x_bluetooth_cmd est toujours utilisé. Tous les liens supplémentaires uniquement à partir de la tâche Bluetooth, il est temps d'y revenir.
Retour à la tâche Bluetooth
En bref: l'analyse finale de la tâche Bluetooth. Recherchez une autorisation sans mot de passe.
Si vous regardez les endroits où la fonction
sub_8006A84 est
utilisée , et que vous n'êtes pas trop paresseux et que vous regardez dans ses entrailles, il n'y aura aucun doute - c'est
calloc . C'est logique - pour recevoir des données dans le tampon, vous devez d'abord créer ce tampon.
Maintenant
sub_8006DBC . Regardons-le (les variables ont déjà été renommées):

En rappelant les fonctions de la bibliothèque C standard pour travailler avec des chaînes, nous verrons
strstr (rechercher une sous-chaîne) ici et la renommer hardiment.
Passons en revue le code de la fonction x_bluetooth_task -
peut -
être que quelque chose a changé ici depuis la dernière visite . Dans le processus, nous nommons les variables:
- v2 - _state ;
- v3 - data_len .
Il y a une fonction
sub_80052E2 juste à côté. Par analogie avec les fonctions qui tirent des nombres d'une commande Bluetooth, il extrait une chaîne d'une longueur spécifiée - appelons-la
x_get_str .
Nous continuons:
- v26 - isEcho ;
- v6 - meow_str ;
- v10 - uart_cmd_byte ;
- v11 - uart_cmd_str ;
- v12 - str_0 ;
- v13 - str_1 ;
- v14 - format_str ;
- sub_8000F5C - x_blink_small_led .
Terminez avec un changement de nom rapide:
- v19 - mot de passe ; (car il y a des lignes sur l'autorisation et le mot de passe à côté)
- sub_8004CC0 - x_check_password ;
- sub_8006AF4 - x_free (puisque mot de passe, cmd et bt_args sont des pointeurs vers des objets dynamiques (vérifiez cela!), la mémoire doit être libérée après leur utilisation);
- sub_8006DAC - x_strcpy (vérifiez-le!).
Explorez maintenant les branches
READ ,
WRIT ,
AUTP ,
SETP .
Comme l'a montré un test sur un appareil en cours d'exécution, une autorisation est requise pour les commandes READ, WRIT, SETP. Une tentative d'autorisation avec la commande AUTP nous amène à la fonction
x_check_password pour vérifier le mot de passe:

Il s'avère que la longueur du mot de passe doit être de 8 caractères et le mot de passe est comparé (dans la fonction sub_8006B08) avec des octets à l'adresse
0x08007C00 - où la chaîne générée de caractères aléatoires AZ est stockée.
Il s'avère que, sans connaître le mot de passe, nous ne pouvons pas nous connecter à l'appareil. Eh bien, ou presque ne peut pas ...
Faites attention à l'endroit où la variable
auth_flag est
utilisée :

Il s'avère qu'il est utilisé non seulement dans la tâche Bluetooth. Et ici, nous n'avons tout simplement pas encore regardé la tâche Sensor. On y va.
Tâche du capteur
En bref: que fait le bouton tactile?Conformément aux meilleures pratiques de programmation, l'ensemble de la tâche Capteur tient dans un seul écran IDA. Et cela ne peut que se réjouir:

Ligne à ligne ...
- "TSC% d \ r \ n" - cette ligne devrait vous faire penser au contrôleur de détection tactile pour les microcontrôleurs STM32;
- "AUTH BTN \ r \ n" - bouton d'autorisation ???
- "SET AUTH% d \ r \ n" - définir l'indicateur d'autorisation?
Voyons comment l'appareil se comportera si vous appuyez sur le bouton tactile (avez-vous réalisé que le rhinocéros sur la jambe a un bouton tactile?):

En appuyant brièvement, la petite LED rouge s'allume. Avec une pression longue, cette LED s'allume pendant longtemps.
Si nous corrélons cela avec le code, nous pouvons supposer que la fonction
sub_8000708 est une fonction pour obtenir l'heure actuelle. Ensuite, si la différence entre l'heure actuelle et le début du toucher du capteur est supérieure à 1000 (1 seconde), la LED s'allume pendant
0xEA60 millisecondes (1 minute). Mais la variable auth_flag est d'un grand intérêt, qui est mise à 1 avec un appui long sur le bouton tactile, donnant à l'
attaquant l' accès à l'administrateur du "luminaire" l'accès à des fonctions privilégiées.
Ainsi, après autorisation «par bouton», vous pouvez lire le mot de passe stocké dans l'appareil (commande READ), écrire dans la RAM (fonction WRIT) ou définir un nouveau mot de passe (SETP).
Clignotement fou
En bref: une étrange branche de code Mad Blinking peut-elle être exécutée?Revenons à la tâche Bluetooth et faisons un nouveau changement de nom.
- v21 - vip_smth (on ne sait pas encore ce qu'il y a);
- v22 - vip_str (chaîne de taille inconnue, extraite des arguments);
- v23 - mad_led - attribuez "Convertir en struct *" et spécifiez struct_LED .
Et ici, nous voyons le numéro
0xB816D8D9 (il a été trouvé dans la première partie de l'article dans la tâche Bluetooth) comme index de la LED. Ce code sera exécuté si la vérification est effectuée:
if ( sub_8005520(vip_str, vip_smth) == 0x46F70674 )
Renommez sub_8005520 en
x_vip_check et jetez-y un œil:

Étant donné que le premier argument est une chaîne (au moins la chaîne est passée à cette fonction), ce code montre que le deuxième argument est la longueur de cette chaîne (ou la longueur qui doit être traitée). Renommer:
Regardons la fonction
sub_8000254 :

Regardez maintenant
sub_8000148 . Voici son début:

Ce n'est qu'un tiers de la fonction ... Mmmm ... Miam! Un creuseur expérimenté verra facilement ici ...
Quoi?opération de division entière.
Comment le dénicher?Si vous faites un effort, vous pouvez accéder à x_printf à partir de la fonction sub_8000254 (via plusieurs autres fonctions). Un point important à souligner à ce stade est que généralement toutes les fonctions standard sont assez standard . Cela signifie que vous pouvez essayer de trouver dans le domaine public au moins du code source de la fonction étudiée, afin que l'étude soit plus productive.
Donc, nous prenons la source de printf, puis nous regardons vfprintf , en le comparant avec le code du firmware étudié. En utilisant le code source, nous sortons de la fonction itoa et concluons que la fonction sub_8000254 est l'opérateur opérateur% (en prenant le reste de la division), et cette terrible fonction longue n'est rien de plus que de prendre la partie entière de la division (opération div).
Une question légitime peut se poser - pourquoi? Le fait est qu'il ne peut pas y avoir d'opérations DIV, MOD dans un microcontrôleur particulier, par conséquent, le compilateur substitue l'appel de fonctions individuelles à la place de ces opérateurs. Au fait, voici quelques autres
fonctions mathématiques .
N'oubliez pas de renommer en creusant.
Ainsi, la fonction
x_vip_check , calcule ... Et ce sera votre
devoir .
Soit dit en passant, si vous exécutez la commande
VIP correcte, nous obtenons un «rhinocéros dans la discothèque»:

Bref rapport sur le firmware
Le firmware de l'appareil est basé sur le système d'exploitation en temps réel FreeRTOS. Le système a les tâches suivantes:
- Tâche Bluetooth . Traite les commandes qui se présentent sous forme de texte via Bluetooth.
- Tâche LED . Contrôle les LED de couleur en fonction des commandes Bluetooth.
- Tâche du capteur . Allume la LED rouge, permet une autorisation à court terme sans mot de passe sur l'appareil.
- Tâche UART . Vous permet d'interagir avec le module Bluetooth via le port UART interne (utilisé pour initialiser Bluetooth).
- Tâche de surveillance . Garde la trace des gels.
L'étude n'a pas pris en compte la possibilité de lire les données du port UART (contacts Tx / GND).
Résumé
Lors de la master class lors de la conférence, seule la fonctionnalité principale de contrôle des LED a été démontée. Les participants les plus actifs ont été présentés avec leurs «rhinocéros» expérimentaux.
À mon avis, les «rhinocéros» ont produit un plan décent pour un cours de formation sur l'ingénierie inverse et la recherche de vulnérabilité. Une caractéristique de la disposition peut être la possibilité de changer le firmware autant de fois que vous le souhaitez, chaque cours a son propre firmware. Contrairement à l'analyse d'un fichier exécutable, le firmware inversé vous permet de mieux comprendre:
- comment travailler avec l'IDA;
- principes d'interaction entre le firmware et l'appareil;
- Principes de fonctionnement du RTOS.
Un grand merci à tous ceux qui ont lu jusqu'à la fin!