Rhinocéros à l'intérieur du chat - exécutez le firmware dans l'émulateur Kopycat


Lors de la réunion 0x0A DC7831 DEF CON Nizhny Novgorod du 16 février, nous avons présenté un rapport sur les principes de base de l'émulation de code binaire et notre propre développement - un émulateur de plates-formes matérielles Kopycat .


Dans l'article, nous décrirons le lancement du micrologiciel de l'appareil dans l'émulateur, démontrerons l'interaction avec le débogueur et effectuerons une petite analyse dynamique du micrologiciel.


Contexte


Il y a longtemps dans une galaxie très lointaine


Il y a quelques années, dans notre laboratoire, il était nécessaire d'étudier le micrologiciel de l'appareil. Le firmware a été compressé, décompressé par le chargeur de démarrage. Il l'a fait d'une manière très confuse, déplaçant plusieurs fois les données en mémoire. Oui, et le firmware lui-même a alors activement interagi avec les périphériques. Et tout cela sur le noyau MIPS.


Pour des raisons objectives, les émulateurs existants ne nous convenaient pas, mais je voulais quand même exécuter le code. Ensuite, nous avons décidé de faire notre propre émulateur, qui fera un minimum et permettra de déballer le firmware principal. Nous avons essayé - il s'est avéré. Nous avons pensé, que faire si nous ajoutons des périphériques afin d'exécuter également le firmware principal. Ce n'était pas très douloureux - et ça a fonctionné aussi. Nous avons réfléchi à nouveau et avons décidé de créer un émulateur à part entière.


Le résultat a été un émulateur de systèmes informatiques Kopycat .



Pourquoi Kopycat?

Il y a un jeu de mots.


  1. copycat (anglais, n. [ˈkɒpɪkæt]) - copycat, imitator
  2. cat (anglais, n. [ˈkæt]) - un chat, un chat - un animal préféré de l'un des créateurs du projet
  3. Lettre "K" - du langage de programmation Kotlin

Kopycat


Lors de la création de l'émulateur, des objectifs absolument spécifiques ont été définis:


  • la possibilité de créer rapidement une nouvelle périphérie, module, cœur de processeur;
  • la possibilité d'assembler un appareil virtuel à partir de divers modules;
  • la possibilité de charger toutes les données binaires (firmware) dans la mémoire du périphérique virtuel;
  • la capacité de travailler avec des instantanés (instantanés de l'état du système);
  • la possibilité d'interagir avec l'émulateur via le débogueur intégré;
  • beau langage moderne à développer.

En conséquence, Kotlin a été choisi pour l'implémentation, l'architecture de bus (c'est lorsque les modules communiquent entre eux via des bus de données virtuels), JSON comme format de description de périphérique et GDB RSP comme protocole d'interaction avec le débogueur.


Le développement se poursuit depuis un peu plus de deux ans et se poursuit activement. Pendant ce temps, les cœurs de processeur MIPS, x86, V850ES, ARM et PowerPC ont été mis en œuvre.


Le projet prend de l'ampleur et il est temps de le présenter au grand public. Nous ferons une description détaillée du projet plus tard, mais nous allons maintenant nous concentrer sur l'utilisation de Kopycat.


Pour les plus impatients - la version promo de l'émulateur peut être téléchargée ici .


Rhino dans l'émulateur


Rappelons que plus tôt pour la conférence SMARTRHINO-2018, un appareil de test "Rhinocéros" a été créé pour la formation aux techniques de reverse engineering. Le processus d'analyse statique du micrologiciel a été décrit dans cet article .


Essayons maintenant d'ajouter des «haut-parleurs» et d'exécuter le firmware dans l'émulateur.


Nous aurons besoin de:
1) Java 1.8
2) Python et le module Jep pour utiliser Python à l'intérieur de l'émulateur. L'assemblage WHL du module Jep pour Windows peut être téléchargé ici .


Pour Windows:
1) com0com
2) PuTTY


Pour Linux:
1) socat


Vous pouvez utiliser Eclipse, IDA Pro ou radare2 en tant que client GDB.


Comment ça marche?


Afin d'exécuter le firmware dans l'émulateur, vous devez "assembler" un périphérique virtuel, qui est un analogue d'un périphérique réel.


Le véritable appareil ("rhinocéros") peut être représenté dans un schéma fonctionnel:


Circuit de l'appareil réel

L'émulateur a une structure modulaire et le périphérique virtuel final peut être décrit dans un fichier JSON.


JSON sur 105 lignes
{ "top": true, // Plugin name should be the same as file name (or full path from library start) "plugin": "rhino", // Directory where plugin places "library": "user", // Plugin parameters (constructor parameters if jar-plugin version) "params": [ { "name": "tty_dbg", "type": "String"}, { "name": "tty_bt", "type": "String"}, { "name": "firmware", "type": "String", "default": "NUL"} ], // Plugin outer ports "ports": [ ], // Plugin internal buses "buses": [ { "name": "mem", "size": "BUS30" }, { "name": "nand", "size": "4" }, { "name": "gpio", "size": "BUS32" } ], // Plugin internal components "modules": [ { "name": "u1_stm32", "plugin": "STM32F042", "library": "mcu", "params": { "firmware:String": "params.firmware" } }, { "name": "usart_debug", "plugin": "UartSerialTerminal", "library": "terminals", "params": { "tty": "params.tty_dbg" } }, { "name": "term_bt", "plugin": "UartSerialTerminal", "library": "terminals", "params": { "tty": "params.tty_bt" } }, { "name": "bluetooth", "plugin": "BT", "library": "mcu" }, { "name": "led_0", "plugin": "LED", "library": "mcu" }, { "name": "led_1", "plugin": "LED", "library": "mcu" }, { "name": "led_2", "plugin": "LED", "library": "mcu" }, { "name": "led_3", "plugin": "LED", "library": "mcu" }, { "name": "led_4", "plugin": "LED", "library": "mcu" }, { "name": "led_5", "plugin": "LED", "library": "mcu" }, { "name": "led_6", "plugin": "LED", "library": "mcu" }, { "name": "led_7", "plugin": "LED", "library": "mcu" }, { "name": "led_8", "plugin": "LED", "library": "mcu" }, { "name": "led_9", "plugin": "LED", "library": "mcu" }, { "name": "led_10", "plugin": "LED", "library": "mcu" }, { "name": "led_11", "plugin": "LED", "library": "mcu" }, { "name": "led_12", "plugin": "LED", "library": "mcu" }, { "name": "led_13", "plugin": "LED", "library": "mcu" }, { "name": "led_14", "plugin": "LED", "library": "mcu" }, { "name": "led_15", "plugin": "LED", "library": "mcu" } ], // Plugin connection between components "connections": [ [ "u1_stm32.ports.usart1_m", "usart_debug.ports.term_s"], [ "u1_stm32.ports.usart1_s", "usart_debug.ports.term_m"], [ "u1_stm32.ports.usart2_m", "bluetooth.ports.usart_m"], [ "u1_stm32.ports.usart2_s", "bluetooth.ports.usart_s"], [ "bluetooth.ports.bt_s", "term_bt.ports.term_m"], [ "bluetooth.ports.bt_m", "term_bt.ports.term_s"], [ "led_0.ports.pin", "u1_stm32.buses.pin_output_a", "0x00"], [ "led_1.ports.pin", "u1_stm32.buses.pin_output_a", "0x01"], [ "led_2.ports.pin", "u1_stm32.buses.pin_output_a", "0x02"], [ "led_3.ports.pin", "u1_stm32.buses.pin_output_a", "0x03"], [ "led_4.ports.pin", "u1_stm32.buses.pin_output_a", "0x04"], [ "led_5.ports.pin", "u1_stm32.buses.pin_output_a", "0x05"], [ "led_6.ports.pin", "u1_stm32.buses.pin_output_a", "0x06"], [ "led_7.ports.pin", "u1_stm32.buses.pin_output_a", "0x07"], [ "led_8.ports.pin", "u1_stm32.buses.pin_output_a", "0x08"], [ "led_9.ports.pin", "u1_stm32.buses.pin_output_a", "0x09"], [ "led_10.ports.pin", "u1_stm32.buses.pin_output_a", "0x0A"], [ "led_11.ports.pin", "u1_stm32.buses.pin_output_a", "0x0B"], [ "led_12.ports.pin", "u1_stm32.buses.pin_output_a", "0x0C"], [ "led_13.ports.pin", "u1_stm32.buses.pin_output_a", "0x0D"], [ "led_14.ports.pin", "u1_stm32.buses.pin_output_a", "0x0E"], [ "led_15.ports.pin", "u1_stm32.buses.pin_output_a", "0x0F"] ] } 

Faites attention au paramètre du firmware dans la section params - c'est le nom du fichier qui peut être téléchargé sur le périphérique virtuel en tant que firmware.


Un périphérique virtuel et son interaction avec le système d'exploitation principal peuvent être représentés comme suit:


Circuit émulé

L'instance de test actuelle de l'émulateur implique une interaction avec les ports COM du système d'exploitation principal (débogage UART et UART pour le module Bluetooth). Il peut s'agir de ports réels auxquels les périphériques sont connectés ou de ports COM virtuels ( com0com / socat est juste pour cela ) .


Il existe actuellement deux façons principales d'interagir avec l'émulateur de l'extérieur:


  • Protocole GDB RSP (respectivement, supportant ce protocole, outils - Eclipse / IDA / radare2);
  • ligne de commande interne de l'émulateur (Argparse ou Python).

Ports COM virtuels


Afin d'interagir avec l'UART du périphérique virtuel sur la machine locale via le terminal, vous devez créer quelques ports COM virtuels connectés. Dans notre cas, un port utilise un émulateur, et le second un programme terminal (PuTTY ou écran):


Ports COM virtuels

Utilisation de com0com


Les ports COM virtuels sont configurés avec l'utilitaire d'installation à partir du kit com0com (la version de la console est C: \ Program Files (x86) \ com0com \ setup.exe, ou la version GUI est C: \ Program Files (x86) \ com0com \ setupg.exe ) :


Configurer les ports COM virtuels

Cochez les cases Activer le dépassement de tampon pour tous les ports virtuels créés, sinon l'émulateur attendra une réponse du port COM.


Utiliser socat


Sur les systèmes UNIX, les ports COM virtuels sont automatiquement créés par l'émulateur à l'aide de l'utilitaire socat, pour cela il suffit de spécifier le préfixe socat: dans le nom du port lors du démarrage de l'émulateur.


Interface de ligne de commande interne (Argparse ou Python)


Étant donné que Kopycat est une application console, l'émulateur fournit deux options permettant à l'interface de ligne de commande d'interagir avec ses objets et variables: Argparse et Python.


Argparse est l'interface CLI intégrée à Kopycat, elle est toujours disponible pour tout le monde.


Une CLI alternative est l'interpréteur Python. Pour l'utiliser, vous devez installer le module Jep Python et configurer l'émulateur pour qu'il fonctionne avec Python (l'interpréteur Python installé sur le système principal de l'utilisateur sera utilisé).


Installer le module Jep Python


Sous Linux, Jep peut être installé via pip:


 pip install jep 

Pour installer Jep sous Windows, vous devez d'abord installer le SDK Windows et le Microsoft Visual Studio correspondant. Nous avons simplifié un peu votre tâche et créé des assemblys WHL JEP pour les versions actuelles de Python pour Windows, afin que le module puisse être installé à partir d'un fichier:


 pip install jep-3.8.2-cp27-cp27m-win_amd64.whl 

Pour vérifier l'installation de Jep, vous devez exécuter la ligne de commande:


 python -c "import jep" 

En réponse, un message doit être reçu:


 ImportError: Jep is not supported in standalone Python, it must be embedded in Java. 

Dans le fichier de commandes de l'émulateur pour votre système ( kopycat.bat pour Windows, kopycat pour Linux), ajoutez le paramètre supplémentaire Djava.library.path à la liste des paramètres DEFAULT_JVM_OPTS - il doit contenir le chemin d'accès au module Jep installé.


Par conséquent, pour Windows, vous devriez obtenir une ligne comme celle-ci:


 set DEFAULT_JVM_OPTS="-XX:MaxMetaspaceSize=256m" "-XX:+UseParallelGC" "-XX:SurvivorRatio=6" "-XX:-UseGCOverheadLimit" "-Djava.library.path=C:/Python27/Lib/site-packages/jep" 

Lancement de Kopycat


L'émulateur est une application JVM de console. Le lancement s'effectue via le script de ligne de commande du système d'exploitation (sh / cmd).


Commande à exécuter sous Windows:


 bin\kopycat -g 23946 -n rhino -l user -y library -p firmware=firmware\rhino_pass.bin,tty_dbg=COM26,tty_bt=COM28 

La commande à exécuter sous Linux à l'aide de l'utilitaire socat:


 ./bin/kopycat -g 23946 -n rhino -l user -y library -p firmware=./firmware/rhino_pass.bin,tty_dbg=socat:./COM26,tty_bt=socat:./COM28 

  • -g 23646 - Port TCP qui sera ouvert pour l'accès au serveur GDB;
  • -n rhino - le nom du module principal du système (assemblage de périphérique);
  • -l user - le nom de la bibliothèque pour rechercher le module principal;
  • -y library - le chemin pour rechercher les modules inclus dans le périphérique;
  • firmware\rhino_pass.bin - chemin vers le fichier du firmware;
  • COM26 et COM28 sont des ports COM virtuels.

Le résultat sera l' Argparse > Python > (ou Argparse > ):


 18:07:59 INFO [eFactoryBuilder.create ]: Module top successfully created as top 18:07:59 INFO [ Module.initializeAndRes]: Setup core to top.u1_stm32.cortexm0.arm for top 18:07:59 INFO [ Module.initializeAndRes]: Setup debugger to top.u1_stm32.dbg for top 18:07:59 WARN [ Module.initializeAndRes]: Tracer wasn't found in top... 18:07:59 INFO [ Module.initializeAndRes]: Initializing ports and buses... 18:07:59 WARN [ Module.initializePortsA]: ATTENTION: Some ports has warning use printModulesPortsWarnings to see it... 18:07:59 FINE [ ARMv6CPU.reset ]: Set entry point address to 08006A75 18:07:59 INFO [ Module.initializeAndRes]: Module top is successfully initialized and reset as a top cell! 18:07:59 INFO [ Kopycat.open ]: Starting virtualization of board top[rhino] with arm[ARMv6Core] 18:07:59 INFO [ GDBServer.debuggerModule ]: Set new debugger module top.u1_stm32.dbg for GDB_SERVER(port=23946,alive=true) Python > 

Interaction avec IDA Pro


Pour simplifier les tests, nous utilisons le firmware Rhino comme fichier ELF (les méta-informations y sont enregistrées) comme fichier source pour l'analyse dans IDA.


Vous pouvez également utiliser le micrologiciel principal sans méta-informations.


Après avoir démarré Kopycat dans IDA Pro, dans le menu Debugger, accédez à l'élément " Switch debugger ... " et sélectionnez " Remote GDB debugger ". Ensuite, configurez la connexion: menu Débogueur - Options de processus ...


Définissez les valeurs:


  • Application - toute valeur
  • Nom d'hôte: 127.0.0.1 (ou l'adresse IP de la machine distante sur laquelle Kopycat s'exécute)
  • Port: 23946

Configuration de la connexion au serveur GDB

Maintenant, le bouton de démarrage du débogage est disponible (touche F9):



Appuyez dessus - il se connecte au module de débogage dans l'émulateur. IDA passe en mode débogage, des fenêtres supplémentaires deviennent disponibles: informations sur les registres, sur la pile.


Nous pouvons maintenant utiliser toutes les fonctionnalités standard du travail avec le débogueur:


  • exécution pas à pas des instructions ( Step into et Step over - touches F7 et F8, respectivement);
  • démarrer et suspendre l'exécution;
  • création de points d'arrêt sur le code et les données (touche F2).

Se connecter au débogueur ne signifie pas démarrer le code du firmware. La position actuelle pour l'exécution doit être l'adresse 0x08006A74 - le début de la fonction Reset_Handler . Si vous faites défiler la liste ci-dessous, vous pouvez voir l'appel à la fonction principale . Vous pouvez placer le curseur sur cette ligne (adresse 0x08006ABE ) et effectuer l'opération Exécuter jusqu'au curseur (touche F4).


image


Ensuite, vous pouvez appuyer sur F7 pour accéder à la fonction principale .


Si vous exécutez la commande Continue process (touche F9), la fenêtre "Please wait" apparaîtra avec un seul bouton Suspend :



Lorsque Suspend est enfoncé, l'exécution du code du firmware est suspendue et peut être poursuivie à partir de la même adresse dans le code où il a été interrompu.


Si vous continuez à exécuter le code, dans les terminaux connectés aux ports COM virtuels, vous pouvez voir les lignes suivantes:


image


image


La présence de la chaîne "State bypass" indique que le module Bluetooth virtuel est passé en mode de réception de données depuis le port COM de l'utilisateur.


Maintenant, dans le terminal Bluetooth (sur la figure - COM29), vous pouvez entrer des commandes conformément au protocole Rhino. Par exemple, la chaîne "mur-mur" revient à la commande "MEOW" dans le terminal Bluetooth:




Imite pas complètement


Lors de la construction d'un émulateur, vous pouvez choisir le degré de détail / émulation d'un appareil. Ainsi, par exemple, le module Bluetooth peut être émulé de différentes manières:


  • appareil entièrement émulé avec un ensemble complet de commandes;
  • Les commandes AT sont émulées et le flux de données est reçu du port COM du système principal;
  • le périphérique virtuel fournit une redirection complète des données vers un périphérique réel;
  • comme un simple talon qui renvoie toujours «OK».

Dans la version actuelle de l'émulateur, la deuxième approche est utilisée - le module Bluetooth virtuel effectue la configuration, après quoi il passe en mode proxy de données du port COM du système principal vers le port UART de l'émulateur.



Considérez la possibilité d'une instrumentation simple du code si une partie de la périphérie n'est pas implémentée. Par exemple, si aucun temporisateur n'est créé pour contrôler le transfert de données dans DMA (la vérification est effectuée dans la fonction ws2812b_wait située à 0x08006840 ), le micrologiciel attendra toujours l'indicateur occupé situé à 0x200004C4 pour réinitialiser la ligne de données DMA:



Nous pouvons contourner cette situation en réinitialisant manuellement le drapeau occupé immédiatement après l'avoir défini. Dans IDA Pro, vous pouvez créer une fonction Python et l'appeler en point d'arrêt, et le point d'arrêt doit être défini dans le code après avoir écrit la valeur 1 dans l'indicateur occupé .


Gestionnaire de points d'arrêt


Créez d'abord une fonction Python dans l'IDA. Menu Fichier - Commande de script ...


Ajoutez un nouvel extrait dans la liste de gauche, donnez-lui un nom (par exemple, BPT ),
dans la zone de texte à droite, nous entrons le code de fonction:


 def skip_dma(): print "Skipping wait ws2812..." value = Byte(0x200004C4) if value == 1: PatchDbgByte(0x200004C4, 0) return False 


Après cela, cliquez sur Exécuter et fermez la fenêtre de script.


Passons maintenant au code à 0x0800688A , définissons le point d'arrêt (touche F2), modifiez-le (menu contextuel Modifier le point d'arrêt ... ), n'oubliez pas de définir le type de script - Python:




Si la valeur actuelle de l'indicateur occupé est 1, la fonction skip_dma doit être exécutée dans la ligne de script:



Si vous exécutez le micrologiciel pour exécution, le code du gestionnaire de points d'arrêt peut être vu dans l'IDA dans la fenêtre Sortie sur la ligne Skipping wait ws2812... Maintenant, le firmware n'attendra pas pour réinitialiser le drapeau occupé .


Interaction émulateur


Il est peu probable que l'émulation à des fins d'émulation suscite joie et plaisir. C'est beaucoup plus intéressant si l'émulateur aide le chercheur à voir les données en mémoire ou à établir l'interaction des flux.


Nous montrons comment établir dynamiquement l'interaction des tâches RTOS. Tout d'abord, suspendez l'exécution du code s'il est en cours d'exécution. Si vous passez à la fonction bluetooth_task_entry dans la branche de traitement des commandes "LED" (adresse 0x080057B8 ), vous pouvez voir ce qui est créé en premier, puis un message est envoyé à la file d'attente système ledControlQueueHandle .


image


Vous devez définir un point d'arrêt pour accéder à la variable ledControlQueueHandle située à 0x20000624 et continuer à exécuter le code:



Par conséquent, il s'arrêtera d'abord à l'adresse 0x080057CA avant d'appeler la fonction osMailAlloc , puis à 0x08005806 avant d'appeler la fonction osMailPut , puis après un certain temps à l'adresse 0x08005BD4 (avant d'appeler la fonction osMailGet ), qui appartient à la fonction leds_task_entry (tâche LED), c'est-à-dire il y avait un interrupteur de tâche, et maintenant le contrôle a reçu la tâche LED.


image


D'une manière si simple, vous pouvez établir comment les tâches RTOS interagissent les unes avec les autres.


Bien sûr, en réalité, l'interaction des tâches peut être plus compliquée, mais l'utilisation d'un émulateur pour suivre cette interaction devient moins difficile.


Ici, vous pouvez regarder une courte vidéo du lancement de l'émulateur et de l'interaction avec IDA Pro.


Lancement avec Radare2


Vous ne pouvez pas ignorer un outil universel tel que Radare2.


Pour vous connecter à l'émulateur à l'aide de r2, la commande ressemblera à ceci:


 radare2 -A -a arm -b 16 -d gdb://localhost:23946 rhino_fw42k6.elf 

Maintenant, démarrer ( dc ) et suspendre l'exécution (Ctrl + C) sont disponibles.


Malheureusement, pour le moment dans r2, il y a des problèmes lorsque vous travaillez avec un serveur gdb matériel et un balisage de mémoire, pour cette raison, les points d'arrêt et les étapes (la commande ds ) ne fonctionnent pas. Nous espérons que cela sera corrigé dans un proche avenir.


Lancement avec Eclipse


L'une des options d'utilisation de l'émulateur consiste à déboguer le micrologiciel de l'appareil en cours de développement. Pour plus de clarté, nous utiliserons également le firmware Rhino. Vous pouvez télécharger les sources du firmware à partir d'ici .


Nous utiliserons Eclipse de la suite System Workbench pour STM32 comme IDE.


Pour que le micrologiciel directement compilé dans Eclipse soit chargé dans l'émulateur, vous devez ajouter le paramètre firmware=null à la commande de démarrage de l'émulateur:


 bin\kopycat -g 23946 -n rhino -l user -y library -p firmware=null,tty_dbg=COM26,tty_bt=COM28 

Configuration de débogage


Dans Eclipse, sélectionnez le menu Exécuter - Configurations de débogage .... Dans la fenêtre qui s'ouvre, dans la section Débogage matériel GDB , vous devez ajouter une nouvelle configuration, puis spécifier le projet et l'application en cours de débogage dans l'onglet "Principal":



Dans l'onglet Débogueur, vous devez spécifier la commande GDB:
${openstm32_compiler_path}\arm-none-eabi-gdb


Et entrez également les paramètres de connexion au serveur GDB (hôte et port):



Les paramètres suivants doivent être spécifiés dans l'onglet "Démarrage":


  • cochez la case Charger l'image (pour que l'image du micrologiciel assemblé soit chargée dans l'émulateur);
  • activez la coche Charger les symboles ;
  • ajoutez une commande de démarrage: set $pc = *0x08000004 (définissez la valeur de la mémoire à l'adresse 0x08000004 dans le registre PC - l'adresse du 0x08000004 est stockée).

Veuillez noter que si vous ne souhaitez pas télécharger le fichier du micrologiciel à partir d'Eclipse, vous n'avez pas besoin de spécifier les paramètres des commandes Charger l'image et Exécuter .



Après avoir cliqué sur Déboguer, vous pouvez travailler en mode débogage:


  • exécution de code étape par étape
  • interaction avec les points d'arrêt

Remarque Eclipse a, hmm ... quelques fonctionnalités ... et vous devez vivre avec elles. Par exemple, si le message «Aucune source disponible pour« 0x0 »» apparaît lors du démarrage du débogueur, exécutez la commande Step (F5)



Au lieu d'une conclusion


L'émulation de code natif est une chose très intéressante. Pour un développeur de périphériques, il devient possible de déboguer le firmware sans véritable périphérique. Pour le chercheur - la possibilité d'effectuer une analyse de code dynamique, ce qui n'est pas toujours possible même avec un appareil.


Nous voulons fournir aux spécialistes un outil qui serait pratique, modérément simple et qui n'a pas pris beaucoup d'efforts et de temps à configurer et à lancer.


Écrivez dans les commentaires sur votre expérience d'utilisation des émulateurs matériels. Nous vous invitons à une discussion et serons heureux de répondre à vos questions.

Source: https://habr.com/ru/post/fr445798/


All Articles