Page 1 of 1

[TUTO] Epsilon 16 - Mise en place d'un Environement Ghidra

Unread postPosted: 24 Jun 2021, 20:31
by M4x1m3
Epsilon 16 est sorti il y a deux jours. Beaucoup de personnes veulent essayer de comprendre comment fonctionnent le bootloader et le kernel de cette nouvelle version, qui change complètement la manière de fonctionner d'Epsilon. Nous allons donc voir comment mettre en place un environnement de Reverse Engineering prêt pour Epsilon 16 avec Ghidra.


Le Reverse Engineering est dans une zone grise juridique. La béta d'Epsilon 16 étant mise à disposition sans licence, elle doit être considérée comme étant sous strong copyright. Si vous découvrez des failles dans le kernel, nous vous encourageons à ne pas les rendre publiques, mais à contacter redgl0w, quentinguidee, moi-même, M4x1m3 ou critor.



1 - Ghidra, c'est quoi ?

Ghidra est un logiciel de Software Reverse Engineering libre, développé par la NSA. Il intègre notamment un désassembler supportant l'architecture Cortex-M (l'architecture matérielle de la Numworks) et un décompileur très puissant. Pour windows, l'installation se passe tout simplement depuis le site officiel. L'outil nécessite l'installation de Java. Pour linux, ghidra est inclue dans les dépôts de pas mal de distros.

2 - Obtention des binaires du firmware

Les binaires sont disponible au format DFU depuis le site de Numworks. Le format DFU étant un format conteneur, il faut effectuer une manipulation afin d'obtenir des fichiers .bin que l'on peut charger dans Ghidra.

Je vous mets donc à disposition un petit script python pour ça :
Show/Hide spoilerAfficher/Masquer le spoiler
Code: Select all
#!/bin/env python3

import struct
import sys

def read(fo, fw):
    l = struct.calcsize(fo);
    return struct.unpack(fo, fw.read(l));

def read_header(fw):
    magik, = read("5s", fw)

    if (magik != b"DfuSe"):
        return False, "Invalid magik number!", 0, 0

    version, = read("B", fw)

    if (version != 1):
        return False, "Unsupported file version!", 0, 0

    imagesize, = read("<I", fw);
    imagecount, = read("B", fw)

    return True, "OK!", imagecount, imagesize

def read_target(fw):
    magik, = read("6s", fw)

    if(magik != b"Target"):
        return False, "Invalid magik number!", "", 0, 0

    alternate, = read("B", fw)
    named, = read("<I", fw)
    name, = read("255s", fw)
    name = name.split(b'\0', 1)[0].decode("utf-8") if named != 0 else ""
    size, = read("<I", fw)
    elements, = read("<I", fw)

    return True, "OK!", name, size, elements

def read_element(fw):
    address, = read("<I", fw)
    size, = read("<I", fw)
    data, = read(str(size) + "s", fw)
    return address, size, data


internal_size = 64 * 1024
internal_addr = 0x8000000
internal = bytearray([255] * internal_size)

external_size = 8 * 1024 * 1024
external_addr = 0x90000000
external = bytearray([255] * external_size)


with open("firmware.dfu", "rb") as fw:

    ok, message, count, size = read_header(fw);

    if not ok:
        print("Error reading header:", message)
        sys.exit(1)

    print("Found", count, "image(s), total size:", size)

    for i in range(count):
        ok, message, name, size, elements = read_target(fw);

        if not ok:
            print("Error reading image:", message)
            sys.exit(2)

        print("  Image", i)
        print("    Found", elements, "element(s), total size:", size)

        for j in range(elements):
            addr, size, data = read_element(fw)


            if (addr >= internal_addr and addr + size <= internal_addr + internal_size):
                print("      Element", j, ": Address:", hex(addr), "Size:", hex(size), "<internal>")
                for k in range(size):
                    internal[k + addr - internal_addr] = data[k]
            elif (addr >= external_addr and addr + size <= external_addr + external_size):
                print("      Element", j, ": Address:", hex(addr), "Size:", hex(size), "<external>")
                for k in range(size):
                    external[k + addr - external_addr] = data[k]
            else:
                print("      Element", j, ": Address:", hex(addr), "Size:", hex(size), "<unknown>")

print("Saving...")

with open("internal.bin", "wb") as f:
    f.write(internal);
with open("external.bin", "wb") as f:
    f.write(external);

print("Done!")

Il s'utilise en le mettant dans le même dossier que le fichier .dfu, nommé firmware.dfu. Il produit deux fichiers binaires, internal.bin et external.bin, qui correspondent respectivement à la flash interne et à la flash externe.

3 - Installation de SVD-Loader

Nous allons maintenant installer le plugin SVD-Loader, qui va nous permettre de charger la description de tous les registres du processeur dans Ghidra, afin de grandement faciliter la compréhension du code. Commençons par télécharger le plugin. Nous devons extraire le contenue du dossier SVD-Loader-Ghidra-master de l'archive dans le dossier ghidra_scripts dans votre dossier utilisateur (créez le si il n'existe pas). Pensez aussi à télécharger le fichier STM32F730.sdv, il nous servira plus tard.

4 - Création du projet

Nous pouvons maintenant ouvrir Ghidra, et créer un nouveau projet, en allant dans File -> New Project. Nous choisirons un "Non-Shared Project", un dossier adéquat et l'appellerons Epsilon16, par exemple. Nous pouvons maintenant glisser le fihcier internal.bin dans le projet, pour le charger dans Ghidra. Le format se mettra normalement en "Raw Binary". Cliquons sur "..." à côté de Langage. Une fenêtre s'ouvre. Dans filter, taper "cortex", et choisir la ligne "ARM Cortex 32 Little", et faire OK. Cliquer sur "Options...", mettre "internal" dans "block name", et faire "OK", puis valider l'ajout (encore "OK).

Nous pouvons maintenant double cliquer sur "internal" pour ouvrir internal. Le désassembleur se lance, et vous propose d'analyser le fichier, dites non, car nous devons d'abord ajouter les miroirs d'internal et external.

4.1 - Memory Map

Nous allons ajouter external.bin au projet, en allant dans "File->Add to program...", et en choisissant notre fichier externa.bin. Allons dans "Options...", nommons le block "external" et définissons la Base Address à 90000000, puis faisons "OK" et validons l'ajout.

Nous allons maintenant ouvrir l'éditeur de mappage mémoire ("Window->Memory Map", ou l'icon en forme de stick de RAM).

Chez moi, internal s'est chargé avec comme nom de block "ram", si c'est votre cas, double-cliquez dessus et renommez le internal


Commençons par ajouter les miroirs d'internal. Boutton "+", mettre "Block Types" à "Byte Mapped" et laisser l'adresse source à 0. nommer le block "internal.mirror1", adresse de début mettre "200000" et taille mettre "0x10000". Cocher Read, Write et Execute. Faire la même chose en nommant "internal.mirror2" avec adresse de début "8000000", même taille et même coches. Ajoutons aussi la RAM, nommer le block "sram", adresse de début "20000000", taille à "0x40000". Cocher Read, Write et Execute et Uninitialized. Ajoutons aussi la mémoire OTP, block nommé "otp", adresse de début à "1FF07800", taille à "0x210". Cocher Read, Write et Uninitialized.

Vous devriez maintenant avoir cette configuration :

Image

Si c'est le cas, bravo. Sinon, jouez au jeu des 7 différences. Bonne chance :troll:

4.2 - SVD-Loader

Nous allons maintenant utiliser SDV-Loader pour charger le fichier SDV téléchargé plus haut. Ouvrons le gestionnaire de scripts (Window->Scripts manager, ou le bouton flèche blanc sur verte), et cliquons sur le dossier "leveldown-security", à gauche. Double-cliquons ensuite sur "SDV6Loader.py" pour l’exécuter. Il va vous demander un fichier SVD, choisissez le fichier STM32F730.svd téléchargé avant. SVD-Loader va charger le fichier. Cela peut prendre jusqu'à deux minutes. Vous pouvez fermer le Script Manager.

4.3 - Analyse

Maintenant que tout est prêt, nous allons demander à Ghidra d'analyser le firmware. Cliquer sur "Analysis->Auto analyse...". Cochez, en plus de ce qui l'est déjà, "Decompiler Parameter ID", et cliquer sur Analyze. Cette étape peut prendre beaucoup de temps, vue la taille du firmware.

Une fois que l'analyse est terminée, vous pouvez commencer à travailler. Référez vous à la documentation de Ghidra pour savoir comment vous en servir.

5 - Ce qui a déjà été compris

Grâce à mes recherches sur ce firmware, j'ai pu déduire plusieurs choses :

5.1 - Bootloader

Le bootloader n'est présent qu'une fois, dans la flash interne. Son rôle est d'initialiser les périphériques et la board, puis de charger le kernel. Il vérifie une signature (algorithme que je n'ai pas encore reconnu) et charge le kernel, sinon initialise l'écran et charge un stack USB. Le kernel est présent deux fois, en flash externe. Le bootloader jump sur le kernel une fois qu'il a vérifié sa signature, toujours en mode privilégié.

5.2 - Kernel

Le kernel est présent deux fois en flash externe, une fois en 0x90010000 et une autre fois en 0x90410000. La flash est donc coupée en deux. Le kernel s'accompagne de son userspace, le premier étant en 0x90050000, et le deuxième en 0x90450000. Le rôle du kernel est d'initialiser l'écran et de vérifier la signature de l'userland, de passer en mode user et de jump dedans. Il est aussi responsable de l'affichage des messages en cas de firmware tiers ou d'apps externes.

5.3 - Similitudes avec Epsilon 15

Le bootloader et le kernel intègrent tout les deux Ion. Il n'est pas improbable que, lorsqu'ils seront distribués sous formes de sources, les deux soient inclus dans le repo principal d'epsilon. On peut reconnaître plusieurs fonctions d'ion, notamment Ion::Board::init tout au début du bootloader.

5.4 - Cryptographie

L'algorithme utilisé est ED25519. C'est donc de la vérification de signature via SHA512 + cryptographie asymétrique par courbes éliptiques.

6 - Difficultés rencontrés

Le firmware a été compilé avec l'argument -Oz, le code est donc très optimisé pour le gain de place, ce qui le rend imbitable. Il m'a fallu plusieurs heures pour trouver la fonction qui gère la vérification de signature dans le bootloader. Je ne partagerais pas les adresses, pour des raisons de droits d'auteurs.

Le code du bootloader et du kernel sont de très bas niveau, interagisseant directement avec les registres du proco. Armez vous de la documentation de celui-ci (trouvable sur le site de Numworks) et de beaucoup de patience et de café.

Bon courage.

Re: [TUTO] Epsilon 16 - Mise en place d'un Environement Ghid

Unread postPosted: 24 Jun 2021, 21:01
by M4x1m3
Je tenais à rajouter quelque chose, à part. Cette version n'inclue pas encore de restriction au niveau du bootloader du STM32F730 (Reset + 6). C'est donc la version idéale pour faire ce genre de choses, bien que le SWD soit désactivé. Je tenterais de faire un patch binaire qui rétablit SWD pour ce firmware, afin de pouvoir faire de l'analyse dynamique.

Si vous voulez vous lancer dans ça, je vous recommande vraiment de le faire avec cette béta, car elle autorise le fait de revenir en arrière, grâce au-dit bootloader du STM32F730.

Re: [TUTO] Epsilon 16 - Mise en place d'un Environement Ghid

Unread postPosted: 24 Jun 2021, 22:20
by Lephe
À défaut d'informations techniques sur la Numworks, voilà une anecdote marrante avec Ghidra. L'émulation du processeur et des registres périphériques du MPU dans les émulateurs des calculatrices CASIO est optimisée sur le bout des ongles, avec de la LTO. À tel point que chaque fonction a une convention d'appel différente choisie spécifiquement selon ses besoins. Le décompilateur est bien sûr incapable de deviner les paramètres, ce qui oblige à traquer les registres utilisés sans initialisation et tous les détails du code assembleur rien que pour spécifier les paramètres de chaque fonction à la main.

Bon courage pour le RE ^^

Re: [TUTO] Epsilon 16 - Mise en place d'un Environement Ghid

Unread postPosted: 25 Jun 2021, 13:24
by parisse
Le reverse engineering a des fins d'interoperabilite (d'Omega ou/et de KhiCAS) est parfaitement autorise en France, et aux US aussi. Par contre, il ne peut pas etre utilise pour faire autre chose et il ne faut pas rendre public des informations protegees si ce n'est pas indispensable. Donc soyez vigilants sur ce que vous publiez sur ce fil.
Article L122-6 En savoir plus sur cet article...
Modifié par Loi n°94-361 du 10 mai 1994 - art. 4 JORF 11 mai 1994

Sous réserve des dispositions de l'article L. 122-6-1, le droit d'exploitation appartenant à l'auteur d'un logiciel comprend le droit d'effectuer et d'autoriser :

1° La reproduction permanente ou provisoire d'un logiciel en tout ou partie par tout moyen et sous toute forme. Dans la mesure où le chargement, l'affichage, l'exécution, la transmission ou le stockage de ce logiciel nécessitent une reproduction, ces actes ne sont possibles qu'avec l'autorisation de l'auteur ;

2° La traduction, l'adaptation, l'arrangement ou toute autre modification d'un logiciel et la reproduction du logiciel en résultant ;

3° La mise sur le marché à titre onéreux ou gratuit, y compris la location, du ou des exemplaires d'un logiciel par tout procédé. Toutefois, la première vente d'un exemplaire d'un logiciel dans le territoire d'un Etat membre de la Communauté européenne ou d'un Etat partie à l'accord sur l'Espace économique européen par l'auteur ou avec son consentement épuise le droit de mise sur le marché de cet exemplaire dans tous les Etats membres à l'exception du droit d'autoriser la location ultérieure d'un exemplaire.

Article L122-6-1 En savoir plus sur cet article...
Créé par Loi n°94-361 du 10 mai 1994 - art. 5 JORF 11 mai 1994
(https://www.legifrance.gouv.fr/codes/ar ... 0028345224)

I. Les actes prévus aux 1° et 2° de l'article L. 122-6 ne sont pas soumis à l'autorisation de l'auteur lorsqu'ils sont nécessaires pour permettre l'utilisation du logiciel, conformément à sa destination, par la personne ayant le droit de l'utiliser, y compris pour corriger des erreurs.

Toutefois, l'auteur est habilité à se réserver par contrat le droit de corriger les erreurs et de déterminer les modalités particulières auxquelles seront soumis les actes prévus aux 1° et 2° de l'article L. 122-6, nécessaires pour permettre l'utilisation du logiciel, conformément à sa destination, par la personne ayant le droit de l'utiliser.

II. La personne ayant le droit d'utiliser le logiciel peut faire une copie de sauvegarde lorsque celle-ci est nécessaire pour préserver l'utilisation du logiciel.

III. La personne ayant le droit d'utiliser le logiciel peut sans l'autorisation de l'auteur observer, étudier ou tester le fonctionnement de ce logiciel afin de déterminer les idées et principes qui sont à la base de n'importe quel élément du logiciel lorsqu'elle effectue toute opération de chargement, d'affichage, d'exécution, de transmission ou de stockage du logiciel qu'elle est en droit d'effectuer.

IV. La reproduction du code du logiciel ou la traduction de la forme de ce code n'est pas soumise à l'autorisation de l'auteur lorsque la reproduction ou la traduction au sens du 1° ou du 2° de l'article L. 122-6 est indispensable pour obtenir les informations nécessaires à l'interopérabilité d'un logiciel créé de façon indépendante avec d'autres logiciels, sous réserve que soient réunies les conditions suivantes :

1° Ces actes sont accomplis par la personne ayant le droit d'utiliser un exemplaire du logiciel ou pour son compte par une personne habilitée à cette fin ;

2° Les informations nécessaires à l'interopérabilité n'ont pas déjà été rendues facilement et rapidement accessibles aux personnes mentionnées au 1° ci-dessus ;

3° Et ces actes sont limités aux parties du logiciel d'origine nécessaires à cette interopérabilité.

Les informations ainsi obtenues ne peuvent être :

1° Ni utilisées à des fins autres que la réalisation de l'interopérabilité du logiciel créé de façon indépendante ;

2° Ni communiquées à des tiers sauf si cela est nécessaire à l'interopérabilité du logiciel créé de façon indépendante ;

3° Ni utilisées pour la mise au point, la production ou la commercialisation d'un logiciel dont l'expression est substantiellement similaire ou pour tout autre acte portant atteinte au droit d'auteur.

V. Le présent article ne saurait être interprété comme permettant de porter atteinte à l'exploitation normale du logiciel ou de causer un préjudice injustifié aux intérêts légitimes de l'auteur.

Toute stipulation contraire aux dispositions prévues aux II, III et IV du présent article est nulle et non avenue.

[Article equivalent aux US https://www.copyright.gov/title17/92chap12.html#1201 1201(f)
(f) Reverse Engineering.—(1) Notwithstanding the provisions of subsection (a)(1)(A), a person who has lawfully obtained the right to use a copy of a computer program may circumvent a technological measure that effectively controls access to a particular portion of that program for the sole purpose of identifying and analyzing those elements of the program that are necessary to achieve interoperability of an independently created computer program with other programs, and that have not previously been readily available to the person engaging in the circumvention, to the extent any such acts of identification and analysis do not constitute infringement under this title.

(2) Notwithstanding the provisions of subsections (a)(2) and (b), a person may develop and employ technological means to circumvent a technological measure, or to circumvent protection afforded by a technological measure, in order to enable the identification and analysis under paragraph (1), or for the purpose of enabling interoperability of an independently created computer program with other programs, if such means are necessary to achieve such interoperability, to the extent that doing so does not constitute infringement under this title.

(3) The information acquired through the acts permitted under paragraph (1), and the means permitted under paragraph (2), may be made available to others if the person referred to in paragraph (1) or (2), as the case may be, provides such information or means solely for the purpose of enabling interoperability of an independently created computer program with other programs, and to the extent that doing so does not constitute infringement under this title or violate applicable law other than this section.

(4) For purposes of this subsection, the term “interoperability” means the ability of computer programs to exchange information, and of such programs mutually to use the information which has been exchanged.