/==============================================================\ | | | ANALYSE D'UN TÉLÉPHONE CHIFFRÉ | | | | RAPPORT D'INVESTIGATION NUMÉRIQUE | | | \==============================================================/ Auteur : Nicolas Iooss, auditeur technique au bureau audit de l'ANSSI, formateur pour le centre de formation continue SecureSphere by Epita, membre de l'équipe de développement de SELinux, membre du conseil d'administration de l'association des anciens élèves et diplômés de l'École polytechnique ainsi que de l'association loi 1901 Polytechnique.org. NB. Ce document texte contient quelques formules mathématiques qui deviennent plus lisibles quand celui-ci est interprété au format Markdown. Ce document respecte ainsi le format Markdown, qui utilise des marqueurs spécifiques pour les liens hypertextes (comme ), les formules mathématiques (comme $e = 2^{16} + 1$) et le texte utilisant une police d'écriture à chasse fixe (`comme ceci`). Afin de produire un document PDF ou une page HTML, les commandes suivantes peuvent être utilisées : pandoc -f markdown solution.txt -o solution.pdf pandoc -f markdown solution.txt -o solution.html Introduction ============ Suite au signalement d'un individu suspecté de s'attaquer à la communauté sécurité informatique française avec notamment l'intention de lui nuire, son téléphone a été saisi. Il devrait contenir les preuves nécessaires, mais la présence de plusieurs couches de chiffrement empêche d'accéder aux données. Face à cette impasse, les enquêteurs en charge de ce dossier ont publié un appel à l'aide sur , en mettant à disposition une copie de la mémoire flash du téléphone accompagnée d'une trace de consommation de courant lors du démarrage du téléphone. Ayant pris connaissance de cette publication, j'ai procédé à l'analyse des fichiers. Après avoir récupéré une première clé de chiffrement utilisée au démarrage du téléphone, j'ai pu le démarrer dans un environnement virtualisé en suivant les instructions techniques qui ont été fournies. Cet environnement contient trois fichiers chiffrés, protégés par des mécanismes différents. Le premier est protégé par un système utilisant un composant matériel spécifique permettant de saisir une combinaison secrète sur un clavier à quatre boutons. En simulant ce système, il est possible de tester toutes les combinaisons possibles afin de trouver la secrète. Le fichier déchiffré est une application C++ qui vérifie un mot de passe donné en paramètre. Cette vérification de mot de passe utilise un algorithme de chiffrement non standard mais réversible (la clé de chiffrement est intégrée à l'application). Le mot de passe que j'ai ainsi retrouvé est suffisant pour déchiffrer le second fichier, ainsi qu'un système d'exploitation exécuté dans le "monde sécurisé" du processeur ARM. Le nouveau fichier déchiffré est de nouveau une application qui vérifie un mot de passe. Toutefois l'algorithme de vérification employé n'est pas implémenté dans l'application, mais dans un bloc de données chiffrées qui est transmis au système du "monde sécurisé". L'analyse de ce système d'exploitation révèle que celui-ci interprète des instructions spécifiques contenues dans le bloc de données transmis. Ces instructions implémentent un algorithme de chiffrement pour vérifier le mot de passe saisi. L'inversion de cet algorithme conduit à la récupération du mot de passe attendu, nécessaire au déchiffrement du troisième fichier. Ce dernier fichier est une archive des données du téléphone du suspect. Comme celui-ci n'utilise pas d'application de messagerie sécurisée pour communiquer, les SMS qu'il a reçus ou envoyés sont présents en clair dans l'archive. Le contenu des discussions SMS révèle que le suspect est impliqué non seulement dans l'affaire de l'intoxication alimentaire qui a frappée les participants du SSTIC en juin 2018, mais aussi dans des attaques récentes visant à détourner l'attention des chercheurs en sécurité informatique. Un message mentionne une adresse électronique en @challenge.sstic.org, qu'il était demandé de trouver. Le présent document donne une description détaillée de la manière dont j'ai obtenu ces preuves. 1. Chiffrement de la mémoire flash ================================== 1.1. Démarrage de la virtualisation ----------------------------------- L'annonce publiée est accompagnée d'une archive contenant des fichiers issus du téléphone qui a été saisi. Cette archive est hébergée sur Elle contient les fichiers suivants : * `flash.bin`, la mémoire Flash du téléphone saisi ; * `power_consumption.npz`, une trace de la consommation électrique enregistrée lors du démarrage du téléphone ; * `README`, des instructions techniques ; * `rom.bin`, la mémoire ROM du téléphone saisi. Les instructions du troisième fichier décrivent une ligne de commande qui permet de démarrer un environnement virtualisé qui devrait simuler le démarrage du téléphone saisi : qemu-system-aarch64 -nographic -machine virt,secure=on -cpu max -smp 1 -m 1024 -bios rom.bin -semihosting-config enable,target=native -device loader,file=./flash.bin,addr=0x04000000 Cette ligne de commande utilise la version de l'émulateur QEMU pour processeur d'architecture ARMv8 ("aarch64"). Le paramètre `-machine virt,secure=on` définit comment sont organisés la mémoire et les périphériques du téléphone virtuel. La ligne de commande contient également une option inhabituelle, `-semihosting`. Cette option permet à la machine virtuelle d'effectuer des actions directement sur l'hôte telles que lire et écrire des fichiers, et exécuter des commandes arbitraires. L'implémentation de cette fonctionnalité est accessible sur . Comme les programmes contenus sur le téléphone du suspect pourraient tirer parti de cette fonctionnalité pour compromettre mon environnement de travail, je retire cette option de la ligne de commande. Par ailleurs, j'ajoute des options dans la ligne de commande de QEMU permettant d'exécuter des commandes de supervision sur l'émulateur, telle que la lecture du contenu de la mémoire : -chardev socket,id=monitor,path=monitor.sock,server,nowait -monitor chardev:monitor Avec cela, le téléphone ne démarre pas. Il affiche le message suivant puis reste bloqué indéfiniment : ########################################## # virtual environment detected # # QEMU 3.1+ is needed # ########################################## NOTICE: Booting SSTIC ARM Trusted Firmware Afin de diagnostiquer ce qui bloque le démarrage, je me suis connecté à l'interface de supervision de QEMU, et y ai lancé un serveur GDB (le débogueur standard du projet GNU) : $ socat STDIO,cfmakeraw,isig=1 UNIX:monitor.sock QEMU 3.1.0 monitor - type 'help' for more information (qemu) gdbserver Waiting for gdb connection on device 'tcp::1234' Puis dans un autre terminal, j'ai lancé une session GDB : $ aarch64-linux-gnu-gdb -q -ex 'target remote localhost:1234' Remote debugging using localhost:1234 warning: No executable has been specified and target does not support determining executable automatically. Try using the "file" command. (gdb) info threads Id Target Id Frame * 1 Thread 1 (CPU#0 [halted ]) 0x0000000000003e3c in ?? () (gdb) x/i $pc => 0x3e3c: b 0x3e38 (gdb) x/2i 0x3e38 0x3e38: wfi => 0x3e3c: b 0x3e38 (gdb) info registers [...] x30 0x480c 18444 sp 0x0 0x0 pc 0x3e3c 0x3e3c [...] La machine virtuelle est donc dans une boucle infinie située à l'adresse 0x3e38. De plus, la pile ne semble pas encore avoir été initialisée (sp vaut 0) et le Link Register (x30, le registre contenant l'adresse de retour) vaut 0x480c. À quoi correspondent ces adresses ? Pour le savoir, j'ai cherché à connaître l'organisation de la mémoire utilisée par QEMU. Cette information se trouve dans son code, accessible par exemple sur : ```c static const MemMapEntry a15memmap[] = { /* Space up to 0x8000000 is reserved for a boot ROM */ [VIRT_FLASH] = { 0, 0x08000000 }, [VIRT_CPUPERIPHS] = { 0x08000000, 0x00020000 }, /* GIC distributor and CPU interfaces sit inside the CPU peripheral space */ [VIRT_GIC_DIST] = { 0x08000000, 0x00010000 }, [VIRT_GIC_CPU] = { 0x08010000, 0x00010000 }, [VIRT_GIC_V2M] = { 0x08020000, 0x00001000 }, [VIRT_GIC_HYP] = { 0x08030000, 0x00010000 }, [VIRT_GIC_VCPU] = { 0x08040000, 0x00010000 }, /* The space in between here is reserved for GICv3 CPU/vCPU/HYP */ [VIRT_GIC_ITS] = { 0x08080000, 0x00020000 }, /* This redistributor space allows up to 2*64kB*123 CPUs */ [VIRT_GIC_REDIST] = { 0x080A0000, 0x00F60000 }, [VIRT_UART] = { 0x09000000, 0x00001000 }, [VIRT_RTC] = { 0x09010000, 0x00001000 }, [VIRT_FW_CFG] = { 0x09020000, 0x00000018 }, [VIRT_GPIO] = { 0x09030000, 0x00001000 }, [VIRT_SECURE_UART] = { 0x09040000, 0x00001000 }, [VIRT_SMMU] = { 0x09050000, 0x00020000 }, [VIRT_MMIO] = { 0x0a000000, 0x00000200 }, /* ...repeating for a total of NUM_VIRTIO_TRANSPORTS, each of that size */ [VIRT_PLATFORM_BUS] = { 0x0c000000, 0x02000000 }, [VIRT_SECURE_MEM] = { 0x0e000000, 0x01000000 }, [VIRT_PCIE_MMIO] = { 0x10000000, 0x2eff0000 }, [VIRT_PCIE_PIO] = { 0x3eff0000, 0x00010000 }, [VIRT_PCIE_ECAM] = { 0x3f000000, 0x01000000 }, [VIRT_MEM] = { 0x40000000, RAMLIMIT_BYTES }, /* Additional 64 MB redist region (can contain up to 512 redistributors) */ [VIRT_GIC_REDIST2] = { 0x4000000000ULL, 0x4000000 }, [VIRT_PCIE_ECAM_HIGH] = { 0x4010000000ULL, 0x10000000 }, /* Second PCIe window, 512GB wide at the 512GB boundary */ [VIRT_PCIE_MMIO_HIGH] = { 0x8000000000ULL, 0x8000000000ULL }, }; ``` Cet extrait définit des zones mémoires par leurs adresses de début et taille. Compte tenu des tailles des fichiers `rom.bin` et `flash.bin`, et de l'option `-device loader,file=./flash.bin,addr=0x04000000` qui est utilisée dans la ligne de commande de QEMU, j'obtiens que : * la zone allant de 0x00000000 à 0x08000000 (exclus) est réservée à la mémoire flash et à la mémoire d'amorçage (boot ROM) ; * la zone allant de 0x00000000 à 0x000078b1 contient `rom.bin` ; * la zone allant de 0x04000000 à 0x06237938 contient `flash.bin` ; Donc le problème de démarrage de l'environnement virtualisé se situe dans le code de `rom.bin`. Il est probable que ce code fasse appel aux fonctionnalités de semihosting que j'ai désactivées. Je suis donc contraint de remettre l'option adéquate dans la ligne de commande du démarrage. N'ayant pas envie de donner aux programmes du suspect la possibilité d'accéder à l'ensemble de mon système, j'encapsule tout cela dans un bac à sable Firejail. La ligne de commande que j'exécute devient alors : firejail \ --caps.drop=all \ --net=none \ --no3d \ --nodvd \ --noexec=/tmp --noexec="$HOME" \ --nogroups \ --nonewprivs \ --noroot \ --nosound \ --notv \ --novideo \ --private=$(pwd) \ --private-bin=qemu-system-aarch64 \ --private-cache \ --private-dev \ --private-etc=EMPTY \ --private-lib \ --private-opt=EMPTY \ --private-srv=EMPTY \ --private-tmp \ --protocol=unix,inet \ --seccomp \ --shell=none \ --x11=none \ -- \ qemu-system-aarch64 \ -nographic \ -machine virt,secure=on \ -cpu max -smp 1 -m 1024 \ -bios rom.bin \ -semihosting-config enable,target=native \ -device loader,file=./flash.bin,addr=0x04000000 \ -netdev user,id=network0,hostfwd=tcp:127.0.0.1:5555-192.168.200.200:22 -net nic,netdev=network0 \ -chardev socket,id=monitor,path=monitor.sock,server,nowait -monitor chardev:monitor La machine virtuelle s'arrête immédiatement, après avoir affiché deux nouveaux messages : KEYSTORE: keystore doesn't exist ERROR: KEYSTORE: Can't read keystore, reset keystore, try to boot again La machine virtuelle a en effet créé un fichier nommé keystore, certainement en utilisant les fonctionnalités de semihosting. Une nouvelle exécution de la machine virtuelle donne des messages différents : ########################################## # virtual environment detected # # QEMU 3.1+ is needed # ########################################## NOTICE: Booting SSTIC ARM Trusted Firmware KEYSTORE: AES Key is still encrypted, need decryption KEYSTORE: Need RSA key to decrypt KEYSTORE: RSA private exponent is not set, please set it in the keystore or enter hex value : Il faut trouver une clé privée RSA afin de démarrer le téléphone virtualisé. 1.2. Tous les chemins mènent à la ROM ------------------------------------- Parmi les fichiers obtenus jusqu'à présent : * `rom.bin` contient ce qui semble être du code ARM64, ainsi que les messages affichés ; * `flash.bin` semble être chiffré, après un entête d'un format que je ne connais pas mais qui contient beaucoup de zéros ; * keystore contient ce qui semble être deux enregistrements de "clés". Le contenu de keystore encodé en hexadécimal est le suivant : $ xxd -a keystore 00000000: 4b45 5900 0000 0000 0100 0000 0001 0000 KEY............. 00000010: d072 dd3f 639c 2d0e 1ff6 9ab9 6578 2f66 .r.?c.-.....ex/f 00000020: 8d7b ffac 766a eeab 5da8 4ae3 73f8 c0dc .{..vj..].J.s... 00000030: aa18 c6bd cbd5 51bb 012f 42af c0f3 fc4a ......Q../B....J 00000040: ad43 8c21 2ff8 a4e5 9705 3cd6 0b54 ac48 .C.!/.....<..T.H 00000050: 4714 fbae 059c 97f8 138c ede9 a800 60d3 G.............`. 00000060: c816 288f f2c7 7d82 081e 586f 73ee 78e0 ..(...}...Xos.x. 00000070: 6f69 d7d5 7596 acf1 959d 6f3e d00d 2cdd oi..u.....o>..,. 00000080: b11d ecf2 5a37 9def 4dce 3b26 4ad7 e819 ....Z7..M.;&J... 00000090: efbe adde 0000 0000 0000 0000 0000 0000 ................ 000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 00000110: 4b45 5900 0000 0000 0300 0000 8000 0000 KEY............. 00000120: c5a8 7bbe 1d22 9bdb 2b67 2811 598f 30f5 ..{.."..+g(.Y.0. 00000130: 0dc7 8e6b e853 08dc 876f 752d 9bd2 1e2f ...k.S...ou-.../ 00000140: d142 6017 1871 5a85 40cc 6df3 7787 9d7e .B`..qZ.@.m.w..~ 00000150: b2e8 16a5 2203 08a9 6409 a906 ffb6 7fdb ...."...d....... 00000160: b794 e631 a4e0 8119 2aa0 3f5e 5b76 7737 ...1....*.?^[vw7 00000170: 418c 8b97 8bee d6e6 8b44 0c79 d257 8175 A........D.y.W.u 00000180: a44b 9134 c712 b719 fda4 c44f 7dff 91bf .K.4.......O}... 00000190: fcdb 455a bf57 2b6a 3ba3 3792 c093 7bf9 ..EZ.W+j;.7...{. 000001a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 00000540: 0000 0000 0000 0000 0000 0000 0000 0000 ................ Afin de comprendre son contenu, il semble nécessaire d'analyser le code de `rom.bin`. La compréhension de l'organisation de la mémoire (dans la section précédente) a permis de déterminer que le contenu de ce fichier est chargé au début de la mémoire de l'environnement virtualisé (à l'adresse 0x00000000). En utilisant un logiciel de rétro-ingénierie comme Ghidra, il est possible d'identifier la fonction qui affiche les premiers messages, en 0x00001198. Cette fonction appelle ensuite celle en 0x00000de8. C'est cette fonction qui lit le fichier keystore ou initialise son contenu. Pour réaliser ces opérations, d'autres fonctions sont appelées jusqu'à réaliser un appel système de semihosting dans la fonction en 0x000037fc : 000037fc 00 00 5e d4 hlt 0xf000 ; Semihosting call 00003800 c0 03 5f d6 ret L'analyse permet de comprendre que le fichier keystore contient 5 emplacements ("slots") de clés. Chaque emplacement comprend un entête de 16 octets (avec un identifiant "`KEY\0`", un type de clé qui vaut 1, 2 ou 3, et une taille) suivi du contenu de l'emplacement, d'au plus 256 octets. Initialement, seuls les emplacements 0 et 1 sont initialisés. * Clé 0 : type 1, taille 256, contenu de 128 octets constants suivis de 4 octets correspondant à 0xDEADBEEF en petit-boutant, et 124 octets nuls. * Clé 1 : type 3, taille 128, contenu de 128 octets constants. Si le type de la clé 1 est 3, la fonction en 0x00000de8 affiche un message indiquant qu'une clé AES est chiffrée et tente de la déchiffrer. Pour cela, la seconde partie de la clé 0 est comparée avec 0xDEADBEEF et en cas d'égalité, il est demandé d'entrer un exposant privé de clé RSA. La fonction 0x00001470 utilise ensuite cet exposant pour déchiffrer le contenu de la clé 1, en utilisant un algorithme similaire à `RSAES-PKCS1-v1_5` (défini dans la RFC 3447, ). Si le déchiffrement réussit et que le résultat fait 32 octets, celui-ci est utilisé pour remplacer la clé 1, en utilisant le type 2. En résumé, la clé 0 est une clé RSA dont le module de 1024 bits (128 octets) est défini par `rom.bin` et l'exposant privé est demandé. La clé 1 est une clé AES256 (de 32 octets) qui a été chiffrée avec la clé publique associée à la clé 0. Il s'agit maintenant de trouver l'exposant privé de la clé 0. 1.3. Un algorithme non-conforme au RGS -------------------------------------- La première couche de chiffrement du téléphone virtualisé utilise l'algorithme `RSAES-PKCS1-v1_5` avec une clé RSA de 1024 bits. Cela n'est pas conforme au Référentiel Général de Sécurité (), dont l'annexe B1 "Règles et recommandations concernant le choix et le dimensionnement des mécanismes cryptographiques" contient : RègleFact-1. La taille minimale du module est de 2048 bits, pour une utilisation ne devant pas dépasser l'année 2030. RègleFact-2. Pour une utilisation au-delà de 2030, la taille minimale du module est de 3072 bits. Ici, le module de la clé 0 du keystore est (en base 16) : N = 0xD072DD3F639C2D0E1FF69AB965782F668D7BFFAC766AEEAB5DA84AE373F8C0DC AA18C6BDCBD551BB012F42AFC0F3FC4AAD438C212FF8A4E597053CD60B54AC48 4714FBAE059C97F8138CEDE9A80060D3C816288FF2C77D82081E586F73EE78E0 6F69D7D57596ACF1959D6F3ED00D2CDDB11DECF25A379DEF4DCE3B264AD7E819 Si j'avais un ordinateur quantique avec une puissance de calcul suffisante, l'algorithme de Shor permettrait d'obtenir les facteurs premiers de ce module. Ici, N = PQ avec : P = 0xD3E9BB53E6643B63E3455A47AF385334D7206BFD1AB17D4E19335ABFA86551A6 2290DC476616A01FB9D60FD0844449D0D9F03ADAC53A049B7345CE706F760219 Q = 0xFBD0A045E00ED117D0A7E3467E4D661FF89F4FB0DDCBD39C5B83A2BDA17AA7C7 8F0B586D87B7BCAF9283586097470F01E1971436A0A4517B3D0EE1C010C9D601 En supposant que la clé publique utilise l'exposant par défaut de la bibliothèque OpenSSL, E = $2^{16} + 1 = 65537$, l'exposant privé correspondant, noté D, serait obtenu en calculant l'inverse modulaire de E modulo PPCM(P-1, Q-1) (le plus petit commun multiple de P-1 et Q-1) : E = 0x10001 D = 0x09CA2137AB48100A222868E114E1C17923A32B916810D2D36FBF3A002767CAF7 FA119690DE982EC1D577089EDC352137C91E1B604A22576D346BE1ACEC448E60 3356095F560D8F905865D5A8A02E8F3BFC7203A453C6C4C41C1F3E23522412EA 7ABD440060BC20BE517A4D4A009C99CEA774CAC6A0372AB382C6CCE6C8FC5E01 L'emplacement de clé 1 dans le keystore contient ce message chiffré : C = clé 1 chiffrée = 0xC5A87BBE1D229BDB2B672811598F30F50DC78E6BE85308DC876F752D9BD21E2F D142601718715A8540CC6DF377879D7EB2E816A5220308A96409A906FFB67FDB B794E631A4E081192AA03F5E5B767737418C8B978BEED6E68B440C79D2578175 A44B9134C712B719FDA4C44F7DFF91BFFCDB455ABF572B6A3BA33792C0937BF9 Le déchiffrement de ce message avec l'exposant privé calculé donnerait : déchiffré = C^D modulo N = 0x0002BD74B53CD4DC751B3A9EE03B1FC01FF15DFEA567E7E4D4F1647AD539B90E 79202FD961A1ADE815CAB5A93EB1CAAFFDEE1F6B8FF8AAAD246EB6D2A8F9FED3 16907B0335E5C9E7ECF192CE37F4118BE990A38F148591E004A352BBFA6A5A00 53535449437B613934376436393830636366376238376362386437633234367D Le déchiffré commence par "0002" et a un octet nul à la fin de l'avant-dernière ligne. Ceci correspond à un bourrage de chiffrement PKCS#1 v1.5 valide (qui est l'algorithme utilisé en `RSAES-PKCS1-v1_5`). La clé AES déchiffrée est alors constituée des 32 derniers octets du message déchiffré (ce qui correspond à la dernière ligne ci-dessus). Cette clé correspond en ASCII au texte suivant : SSTIC{a947d6980ccf7b87cb8d7c246} Tout cela est conditionné par la possession d'un ordinateur quantique doté d'une puissance de calcul suffisante pour factoriser un nombre de 1024 bits. Sans une telle machine, il est nécessaire de procéder autrement pour obtenir la clé, par exemple en exploitant une faiblesse dans l'implémentation des algorithmes cryptographiques. Dans `rom.bin`, l'algorithme d'exponentiation modulaire fait fuiter les bits de l'exposant privé dans la consommation électrique. La trace de consommation de courant enregistrée peut être affichée par le programme Python suivant : ```py import numpy import matplotlib.pyplot as plt data = numpy.load('power_consumption.npz')['arr_0'] plt.plot(data) plt.show() ``` En comptant les bits entre les positions approximatives 700000 et 1490000 de la trace, un autre exposant privé est obtenu : D = 0x23d87cdf97bb95abe6273c384190c765f552ab86f6de30a8db74435c95e6e313 8f54af689812d8f9359cf0f4d453a0c11ec68ce470216c09e74c8947adaf23e9 02415d61ddf2c0ffe459cbb40f7de42bdb7cd14093100a570e8c29819765e2d8 d276f86471b52ac29aa2ce2bb72cd45006279e82bec253ae9675fe45824f6001 Il s'agit de l'inverse de l'exposant public E = 65537 modulo (P-1)(Q-1). Ce nouveau D est donc aussi un exposant privé utilisable pour l'algorithme RSA. Il est alors possible de retrouver les valeurs de P et Q en calculant l'ordre des éléments inversibles modulo N, qui est un diviseur de (DE - 1), puis en tirant parti du fait que cet ordre soit un diviseur de (P-1)(Q-1) = (N+1)-(P+Q), donc que (P+Q) soit congru à (N+1) modulo cet ordre. Quoiqu'il en soit, en entrant n'importe lequel des exposants privés obtenus, la machine virtuelle démarre un système Linux et donne accès à un terminal dans lequel l'utilisateur connecté est `root`. Voici un extrait des messages du démarrage : KEYSTORE: Key read: HEXDUMP : ----------------------------------------------- 09 ca 21 37 ab 48 10 0a 22 28 68 e1 14 e1 c1 79 23 a3 2b 91 68 10 d2 d3 6f bf 3a 00 27 67 ca f7 fa 11 96 90 de 98 2e c1 d5 77 08 9e dc 35 21 37 c9 1e 1b 60 4a 22 57 6d 34 6b e1 ac ec 44 8e 60 33 56 09 5f 56 0d 8f 90 58 65 d5 a8 a0 2e 8f 3b fc 72 03 a4 53 c6 c4 c4 1c 1f 3e 23 52 24 12 ea 7a bd 44 00 60 bc 20 be 51 7a 4d 4a 00 9c 99 ce a7 74 ca c6 a0 37 2a b3 82 c6 cc e6 c8 fc 5e 01 ----------------------------------------------- KEYSTORE: Decrypting ... +---------+-+-+-+--+----+-+-+-+-+-+----+---+-+--... Bravo, envoyez le flag SSTIC{a947d6980ccf7b87cb8d7c246} à l'adresse challenge2019@sstic.org pour valider votre avancée NOTICE: Loading image id=1 NOTICE: BL1: Booting BL2 KEYSTORE: BL2 got key : key_type:0x02, key_len:0x20 HEXDUMP : ----------------------------------------------- 53 53 54 49 43 7b 61 39 34 37 64 36 39 38 30 63 63 66 37 62 38 37 63 62 38 64 37 63 32 34 36 7d ----------------------------------------------- NOTICE: Loading image id=3 KEYSTORE: bad keystore magic KEYSTORE: BL2 got key : key_type:0x02, key_len:0x20 HEXDUMP : ----------------------------------------------- 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 44 ----------------------------------------------- NOTICE: Loading image id=4 KEYSTORE: BL2 got key : key_type:0x02, key_len:0x20 HEXDUMP : ----------------------------------------------- 53 53 54 49 43 7b 61 39 34 37 64 36 39 38 30 63 63 66 37 62 38 37 63 62 38 64 37 63 32 34 36 7d ----------------------------------------------- NOTICE: Loading image id=5 NOTICE: BL1: Booting BL31 ERROR: Secure-OS not not available : need decryption key UEFI firmware (version built at 00:01:39 on Feb 25 2019) EFI stub: Booting Linux Kernel... EFI stub: Generating empty DTB EFI stub: Exiting boot services and installing virtual address map... [ 0.000000] Booting Linux on physical CPU 0x0000000000 [0x411fd070] [ 0.000000] Linux version 5.0.3 (david@polylaptop) #37 SMP PREEMPT Mon Mar 25 10:26:28 CET 2019 [ 0.000000] efi: Getting EFI parameters from FDT: [ 0.000000] efi: EFI v2.70 by EDK II ... ################################## # Welcome to SSTIC challenge # ################################## VM IP : 192.168.200.200/24 USER : root PASSWORD : sstic Note: use /root/tools/add_key.py to unlock safes KEYSTORE: bad keystore magic [w] No key for safe_01 KEYSTORE: bad keystore magic [w] No key for safe_02 KEYSTORE: bad keystore magic [w] No key for safe_03 2. De l'électronique en mode agile ================================== La machine virtuelle démarre un système Linux basé sur la distribution Buildroot 2018.11. Le dossier /root contient trois dossiers nommés safe_01, safe_02 et safe_03, qui contiennent chacun un fichier chiffré nommé .encrypted. Il existe aussi un dossier /root/tools qui contient trois scripts Python : * add_key.py permet d'ajouter une clé au keystore, si elle est correcte (la vérification calcule l'empreinte de la clé par une fonction de hachage et la compare avec des empreintes écrites dans le script) ; * decrypt_containers.py déchiffre le contenu des dossiers safe_... si les clés associées sont disponibles ; * keystore.py implémente l'accès au keystore en utilisant un périphérique particulier, /dev/sstic. La lecture de ces fichiers permet de comprendre que : * le fichier dans safe_01 est chiffré en AES256-CBC avec la clé 2 ; * le fichier dans safe_02 est chiffré en AES256-CBC avec la clé 3 ; * le fichier dans safe_03 est chiffré en AES256-CBC avec la clé 4 ; Il s'agit donc de couches cryptographiques successives qu'il va falloir casser. Pour obtenir la clé 2 ouvrant safe_01, un script Python /root/get_safe1_key.py a été placé dans la machine virtuelle, accompagné par une image /root/schematics.png. Le script a pour objet de vérifier une combinaison de touches sur 4 boutons. Pour cela il interagit avec un périphérique sécurisé, qui réalise des opérations sur trois entrées (A, B et OP) en fonction des boutons qui sont appuyés. En pratique, cela ressemble à un travail inachevé : le script incorpore une fonction nommée `secure_device` avec une listes d'éléments décrivant son implémentation. ```py def secure_device(a,b,op): out = 0 """ TODO : - Implémentation de la communication avec le secure element * Entrées A et B (A0 = bit de poids faible) * Entrée OP * Sortie Out (Out0 = bit de poids faible) * Les boutons permettent à l'utilisateur de rentrer sa combinaison secrète pour le déchiffrement - Supprimer les docs de conception """ return out ``` Le dernier point est particulièrement intéressant : les « docs de conception » dont il est question correspondent à /root/schematics.png. Cette image représente un circuit logique connectant des portes (ET, OU, OU EXCLUSIF) aux entrées et sorties. Voici la traduction en Python des opérations effectuées par le circuit : ```py def secure_device(a,b,op): op = op ^ (BUTTONS_STATE & 3) # B1 et B2 if (BUTTONS_STATE >> (3-1)) & 1: # B3 a = ((a << 1) | (a >> 7)) & 0xff # Rotate-Left 1 if (BUTTONS_STATE >> (4-1)) & 1: # B4 b = ((b << 1) | (b >> 7)) & 0xff # Rotate-Left 1 if op == 0: return a & b if op == 1: return a | b if op == 2: return a ^ b if op == 3: return (a + b) & 0xff ``` Tester l'ensemble des combinaisons possibles prend un temps raisonnable et permet de trouver la combinaison de 8 appuis successifs sur les boutons : étape 1 : boutons 1110 => état 0x8f étape 2 : boutons 0101 => état 0xa4 étape 3 : boutons 0011 => état 0xdf étape 4 : boutons 0010 => état 0xa9 étape 5 : boutons 1011 => état 0xd4 étape 6 : boutons 0110 => état 0xed étape 7 : boutons 0100 => état 0xbb étape 8 : boutons 0110 => état 0xf0 Cela produit une clé AES que j'ajoute au keystore de la manière suivante : # /root/tools/add_key.py 5fb3a83d1fd97137076019ad6e96c6a3 66fb6b32618d162e00cdee9bad427a8a [+] Key with key_id 00000002 ok [+] Key added into keystore [+] Envoyez le flag SSTIC{5fb3a83d1fd97137076019ad6e96c6a3 66fb6b32618d162e00cdee9bad427a8a} à l'adresse challenge2019@sstic.org pour valider votre avancée [+] Container /root/safe_01/.encrypted decrypted to /root/safe_01/decrypted_file Comme je m'y attendais, il s'agissait de la clé 2 et celle-ci permet de déchiffrer le fichier de safe_01. 3. Des elfes et des nains ========================= 3.1. Trouve moi si tu peux -------------------------- Le nouveau fichier obtenu, /root/safe_01/decrypted_file, est un programme exécutable dans Linux, pour l'architecture ARM64 : # file /root/safe_01/decrypted_file /root/safe_01/decrypted_file: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, for GNU/Linux 3.7.0, BuildID[sha1]=5b5be1337d13c986d0e21441d771a36e41a34d17, stripped Ce programme contient une chaîne de caractères qui ressemble à un flag, mais celui-ci n'est pas considéré comme valide : # strings -10 /root/safe_01/decrypted_file ... That's the correct flag :) Usage : %s SSTIC{congolexicomatisation} # /root/safe_01/decrypted_file 'SSTIC{congolexicomatisation}' Not good Le programme modifie donc certainement la chaîne de caractères avant de l'utiliser. L'analyse dans Ghidra révèle un programme très simple : la fonction `main` (en 0x00402e68) vérifie le nombre d'arguments du programme et appelle la fonction en 0x00402e34 pour vérifier le flag. La fonction en 0x00402e34 lève une exception contenant le flag : ```c void FUN_00402e34(char *argv_1) { char **ppcVar1; ppcVar1 = (char **)__cxa_allocate_exception(8); *ppcVar1 = argv_1; /* WARNING: Subroutine does not return */ __cxa_throw(ppcVar1,typeinfo,0); } ``` La fonction `main` a défini des récupérateurs pour certaines exceptions. Ghidra indique en particulier : try { // try from 00402eb4 to 00402eb7 has its CatchHandler @ 00402f20 00402eb4 e0 ff ff 97 bl FUN_00402e34 } // end try from 00402eb4 to 00402eb7 Puis : catch(type#1 @ 00413db8) { ... } // from try @ 00402eb4 with catch @ 00402f20 00402f20 e1 03 1c aa mov x1,x28 00402f24 80 00 00 d0 adrp x0,0x414000 00402f28 00 e0 02 91 add x0,x0,#0xb8 00402f2c 01 00 00 f9 str x1,[x0] 00402f30 80 00 00 d0 adrp x0,0x414000 00402f34 00 e0 02 91 add x0,x0,#0xb8 00402f38 00 00 40 f9 ldr x0,[x0] try { // try from 00402f3c to 00402f3f has its CatchHandler @ 00402f48 00402f3c 3d ff ff 97 bl puts } // end try from 00402f3c to 00402f3f Ces instructions assembleur appellent simplement la fonction `puts` avec comme paramètre la valeur du registre x28. Celui-ci semble être initialisé par du code situé juste avant, qui compare quelque chose avec "SSTIC{congolexicomatisation}" et met dans x28 l'adresse 0x004030b8 (correspondant à "Not good") ou 00403098 ("That's the correct flag :)") selon le résultat. Ceci se révèle être trompeur, car la comparaison n'est jamais exécutée. J'ai pu m'en assurer en démarrant le programme dans le débogueur GDB et posant des points d'arrêts idoines, qui ne sont jamais déclenchés. Pourtant, le programme affiche bien "Not good" quand il est exécuté avec un argument qui n'est pas le flag attendu. Comment est initialisé le registre x28 afin de produire ce résultat ? 3.2. Rembobiner la pile avec les informations du nain ----------------------------------------------------- Afin de suivre quelles instructions sont exécutées par le programme, et espérer ainsi découvrir où l'initialisation du registre x28 a lieu, une approche brutale consiste à lancer GDB, à exécuter la commande `si` ("step-instruction") en répétition, et à contempler la sortie : yes si | gdb -q \ -ex "set disassemble-next-line on" \ -ex "set pagination off" \ -ex "tbreak *0x00402e68" \ -ex "r" \ --args decrypted_file "SSTIC{congolexicomatisation}" Cela ne fonctionne pas. La trace obtenue se termine par : (gdb) 0x0000ffffb1bfc178 in std::get_unexpected() () from /usr/lib64/libstdc++.so.6 => 0x0000ffffb1bfc178 <_ZSt14get_unexpectedv+4>: 00 80 41 f9 ldr x0, [x0, #768] (gdb) 0x0000ffffb1bfc17c in std::get_unexpected() () from /usr/lib64/libstdc++.so.6 => 0x0000ffffb1bfc17c <_ZSt14get_unexpectedv+8>: 00 fc df c8 ldar x0, [x0] (gdb) Not good [Inferior 1 (process 870) exited normally] L'instruction ldar (Load-Acquire) perturbe le fonctionnement du single-stepping de GDB. Afin de passer outre, il est nécessaire de poser un point d'arrêt juste après cette instruction (en `_ZSt14get_unexpectedv+12`). De plus, comme la résolution des symboles prend du temps et ne devrait pas perturber l'exécution, la commande suivante permet de l'esquiver dans la trace d'exécution : ```sh ( echo 'tbreak *0x402E68' ; echo commands ; echo 'break *(_ZSt14get_unexpectedv+12)' ; echo 'catch syscall' ; echo end ; echo 'break *0x402F60' ; echo commands ; echo c ; echo end ; echo r ; echo 'break *_dl_fixup' ; echo commands ; echo finish ; echo end ; yes si ) | gdb -q -ex "set disassemble-next-line on" \ -ex "set pagination off" \ --args decrypted_file "SSTIC{congolexicomatisation}" ``` En filtrant sur l'usage du registre x28, les lignes suivantes apparaissent : (gdb) 0x0000ffffa4a8954c in _Unwind_RaiseException () from /lib64/libgcc_s.so.1 => 0x0000ffffa4a8954c <_Unwind_RaiseException+356>: fb 73 47 a9 ldp x27, x28, [sp, #112] ... (gdb) 0x0000000000402f20 in ?? () => 0x0000000000402f20: e1 03 1c aa mov x1, x28 La valeur de x28 provient donc de la fonction `_Unwind_RaiseException`. De plus, la trace obtenue montre que l'exécution passe étrangement beaucoup de temps dans les fonctions de rembobinage liées à la levée d'une exception (comme `_Unwind_Find_FDE`). Dans l'implémentation de C++ pour Linux, lorsqu'une fonction appelle une autre fonction qui déclenche une exception et que celle-ci est rattrapée dans la première, certains registres doivent être rétablis à leurs valeurs précédant l'appel de la fonction (par exemple sp, le registre indiquant où se trouve la pile). Pour cela, les bibliothèques standard utilisent des informations au format DWARF placées dans les sections `.eh_frame_hdr` et `.eh_frame` d'un fichier ELF (ainsi que dans l'entrée `GNU_EH_FRAME` du Program Header). Le contenu de ces sections nanesques peut être affiché avec `objdump --dwarf`. Pour la fonction `main`, le résultat suivant est obtenu : 000000b0 0000000000000024 000000a0 FDE cie=00000014 pc=0000000000402e68..0000000000402f64 Augmentation data: a3 00 00 00 DW_CFA_advance_loc: 1 to 0000000000402e69 [SP,#var_40]! DW_CFA_def_cfa_offset: 64 DW_CFA_offset: r29 (x29) at cfa-64 DW_CFA_offset: r30 (x30) at cfa-56 DW_CFA_advance_loc: 2 to 0000000000402e6b DW_CFA_offset: r19 (x19) at cfa-48 DW_CFA_advance_loc: 59 to 0000000000402ea6 DW_CFA_restore: r30 (x30) DW_CFA_restore: r29 (x29) DW_CFA_restore: r19 (x19) DW_CFA_def_cfa_offset: 0 DW_CFA_nop DW_CFA_nop DW_CFA_nop Il s'agit d'une structure habituelle, qui indique que les registres x29 et x30 (aka. "frame pointer" et "stack pointer") ont été sauvegardés sur la pile, ainsi que le registre x19. Pour la fonction appelée par `main` qui lève une exception, le contenu est moins habituel : 00000090 000000000000001c 00000094 FDE cie=00000000 pc=0000000000402e34..0000000000402e68 DW_CFA_advance_loc: 1 to 0000000000402e35 DW_CFA_def_cfa_offset: 32 DW_CFA_offset: r29 (x29) at cfa-32 DW_CFA_offset: r30 (x30) at cfa-24 DW_CFA_val_expression: r28 (x28) (DW_OP_skip: -12222) DW_CFA_nop DW_CFA_nop À la place d'instructions permettant de restaurer x29 et x30, une instruction qui définit la valeur de x28 est présente. Comme cette valeur est ensuite affichée, il s'agit certainement de la manière dont le programme vérifie l'argument qui lui est donné. Le paramètre `(DW_OP_skip: -12222)` permet de renvoyer l'interprétation de l'expression 12222 octets avant celle-ci. En décodant le contenu de la section `.eh_frame`, j'ai retrouvé les adresses des instructions DWARF : 0x004031F8 FDE (Frame Description Entry) 0x00403209 DW_CFA_advance_loc: 1 to 0000000000402e35 0x0040320A DW_CFA_def_cfa_offset: 32 0x0040320C DW_CFA_offset: r29 (x29) at cfa-32 0x0040320E DW_CFA_offset: r30 (x30) at cfa-24 0x00403210 DW_CFA_val_expression: r28 (x28) (DW_OP_skip: -12222) 0x00403216 DW_CFA_nop 0x00403217 DW_CFA_nop La cible du `DW_OP_skip` se trouve donc en 0x00403216-12222 = 0x00400258. En effet, les données chargées à cette adresse peuvent être interprétées comme une expression DWARF valide, de plusieurs kilo-octets (jusqu'en 0x00401983). 3.3. Renversement de l'algorithme --------------------------------- L'expression DWARF en 0x00400258 commence par les instructions suivantes : 0x400258 DW_OP_reg31 (r31) 0x400259 DW_OP_const1u: 0xa8 0x40025b DW_OP_plus 0x40025c DW_OP_deref 0x40025d DW_OP_const1u: 0x8 0x40025f DW_OP_plus 0x400260 DW_OP_deref Cela déréférence une valeur de 64 bits à l'adresse (r31 + 0xa8), considère cette valeur comme une adresse, et déréférence une seconde valeur située 8 octets plus loin. Ceci permet en pratique d'obtenir l'adresse de la chaîne de caractères passée en paramètre du programme exécuté (`argv[1]` dans un programme C). L'expression DWARF vérifie que le caractère en position 32 est nul, ce qui correspond à une chaîne de caractères d'au plus 32 caractères. Si c'est le cas, elle effectue des opérations qui correspondent au chiffrement de l'entrée du programme. Enfin, elle évalue une expression et renvoie l'adresse de "Not good" ou de "That's the correct flag :)" selon le résultat. Le jeu d'instruction utilisé par DWARF produit du code très volumineux. Afin de le simplifier, j'ai transformé l'expression en programme C que j'ai ensuite simplifié. J'ai introduit les structures suivantes afin de rendre plus compréhensible les traitements effectués : ```c struct U32_PAIR { union { struct { uint32_t dwLow; uint32_t dwHigh; }; uint64_t qwValue; }; }; struct U64_PAIR { struct U32_PAIR low; // 64 bits at offset 0 struct U32_PAIR high; // 64 bits at offset 8 }; struct U64_DOUBLEPAIR { struct U64_PAIR p00; // 128 bits at offset 0 struct U64_PAIR p16; // 128 bits at offset 16 }; ``` Avec cela, l'expression DWARF devient : ```c uint64_t *p_input = (uint64_t *)argv[1]; struct U64_DOUBLEPAIR blk256 = { .p00 = {.low.qwValue = p_input[0], .high.qwValue = p_input[1]}, .p16 = {.low.qwValue = p_input[2], .high.qwValue = p_input[3]}, }; // 0x4002cf unsigned int iterations; for (iterations = 0; iterations != 4; iterations++) { encrypt15_4003ac(blk256.p16, &blk256.p00); encrypt15_4003ac(blk256.p00, &blk256.p16); } // 0x400285 if ((((blk256.p16.high.qwValue ^ 0x658302a68e8e1c24) + (blk256.p16.low.qwValue ^ 0xdc7564f1612e5347)) + (blk256.p00.high.qwValue ^ 0xd9c69b74a86ec613)) + (blk256.p00.low.qwValue ^ 0x65850b36e76aaed5)) { return "Not good"; } return "That's the correct flag :)"; ``` Avec les fonctions suivantes : ```c void encrypt15_4003ac(const struct U64_PAIR key, struct U64_PAIR *pBlk) { struct U32_PAIR pair; unsigned int i_round; for (i_round = 0; i_round != 0xf; i_round++) { struct U64_PAIR roundKeys = KDF_4003b6(key, 1 + i_round); pair = encrypt6_4004ea(pBlk->high); pBlk->low.dwLow = rol32(pBlk->low.dwLow ^ pair.dwLow, 4) ^ pair.dwHigh; pBlk->low.dwHigh = rol32(pBlk->low.dwHigh ^ pair.dwHigh, 14) ^ pair.dwLow; pBlk->low.dwLow = roundKeys.low.dwLow ^ pBlk->low.dwLow; pBlk->low.dwHigh = roundKeys.low.dwHigh ^ pBlk->low.dwHigh; pair = encrypt6_4004ea(pBlk->low); pBlk->high.dwHigh = ror32(pBlk->high.dwHigh ^ pair.dwLow ^ pair.dwHigh, 14); pBlk->high.dwLow = ror32(pBlk->high.dwLow ^ pair.dwHigh, 6); pBlk->high.dwLow = roundKeys.high.dwLow ^ pBlk->high.dwLow; pBlk->high.dwHigh = roundKeys.high.dwHigh ^ pBlk->high.dwHigh; } } struct U32_PAIR encrypt6_4004ea(const struct U32_PAIR input) { unsigned int i; struct U32_PAIR result = input; for (i = 0; i < 6; i ++) { result.dwLow ^= result.dwHigh + DWORDS_0x400648[i * 2]; result.dwHigh ^= result.dwLow | DWORDS_0x400648[i * 2 + 1]; } return result; } struct U64_PAIR KDF_4003b6(const struct U64_PAIR key, unsigned int round) { unsigned int i; struct U64_PAIR rnd_key = key; for (i = 0; i < 4; i++) { // 0x40030c uint32_t x = rnd_key.low.dwLow ^ (rnd_key.low.dwHigh + rnd_key.high.dwLow); uint32_t y = x + DWORDS_0x4006b4[rnd_key.high.dwHigh & 0xff]; rnd_key.low.dwLow = y; rnd_key.low.dwHigh &= rnd_key.high.dwLow; rnd_key.high.dwLow -= x; rnd_key.high.dwHigh = y ^ (rnd_key.high.dwHigh >> 8); } for (i = 0; i < round; i++) { // 0x40055c uint32_t x = rnd_key.low.dwHigh + 0x45786532; uint32_t y = function_400ab5( ((x ^ rnd_key.high.dwLow) & 0x80000000) ? 0x84653217 : 0x17246549); uint64_t z = rol32(rnd_key.high.dwHigh, 4); rnd_key.low.dwLow = (DWORDS_0x400678[i] ^ rnd_key.low.dwLow) - x; rnd_key.low.dwHigh = y ^ x ^ z; rnd_key.high.dwLow = x ^ rnd_key.high.dwLow; rnd_key.high.dwHigh = z; } return rnd_key; } ``` Afin de calculer une entrée valide, il suffit d'inverser les fonctions de chiffrement. La réciproque de la fonction `encrypt15_4003ac` est : ```c void decrypt15_4003ac(const struct U64_PAIR key, struct U64_PAIR *pBlk) { struct U32_PAIR pair; int i_round; for (i_round = 0xe;i_round >= 0; i_round--) { struct U64_PAIR roundKeys = KDF_4003b6(key, 1 + i_round); pair = encrypt6_4004ea(pBlk->low); pBlk->high.dwLow = roundKeys.high.dwLow ^ pBlk->high.dwLow; pBlk->high.dwHigh = roundKeys.high.dwHigh ^ pBlk->high.dwHigh; pBlk->high.dwLow = rol32(pBlk->high.dwLow, 6) ^ pair.dwHigh; pBlk->high.dwHigh = rol32(pBlk->high.dwHigh, 14) ^ pair.dwLow ^ pair.dwHigh; pair = encrypt6_4004ea(pBlk->high); pBlk->low.dwLow = roundKeys.low.dwLow ^ pBlk->low.dwLow; pBlk->low.dwHigh = roundKeys.low.dwHigh ^ pBlk->low.dwHigh; pBlk->low.dwLow = ror32(pBlk->low.dwLow ^ pair.dwHigh, 4) ^ pair.dwLow; pBlk->low.dwHigh = ror32(pBlk->low.dwHigh ^ pair.dwLow, 14) ^ pair.dwHigh; } } ``` En utilisant cette fonction, j'obtiens comme entrée valide : SSTIC{Dw4rf_VM_1s_co0l_isn_t_It} Il s'agit de la clé AES256 qui permet de déchiffrer le contenu de safe_02 : # /root/tools/add_key.py SSTIC{Dw4rf_VM_1s_co0l_isn_t_It} [+] Key with key_id 00000003 ok [+] Key added into keystore [+] Envoyez le flag SSTIC{Dw4rf_VM_1s_co0l_isn_t_It} à l'adresse challenge2019@sstic.org pour valider votre avancée [+] Container /root/safe_02/.encrypted decrypted to /root/safe_02/decrypted_file [w] You must reboot in order to decrypt Secure OS Au redémarrage de l'environnement virtualisé, un nouveau message s'affiche : NOTICE: BL1: Booting BL31 NOTICE: BL31: Initializing BL32 NOTICE: Booting Secure-OS Cette étape permet donc de déchiffrer un système d'exploitation sécurisé. 4. Le monde qui cherche à être sécurisé ======================================= 4.1. La plongée hors du monde normal ------------------------------------ Le fichier qui a été déchiffré dans /root/safe_02/decrypted_file est de nouveau un programme ELF ARM64 pour Linux. Sa fonction `main` ressemble à ceci : ```c int main(int argc, char **argv) { uint8_t input[32]; int fd; unsigned int result; if (argc != 2 || strlen(argv[1]) != 64) { printf("usage: %s [32-bytes-key-hex-encoded]\n", argv[0]); return 1; } if (!parse_hex(argv[1], &input, 32)) { puts("can't decode hex"); return 1; } fd = open("/dev/sstic", 0); ioctl(fd, 0xC0105300, &{&DATA_44DBD8, 0x101010}); ioctl(fd, 0xC0105301, &{input, 0x20}); while (1) { ioctl(fd, 0xC0105302, 0); result = ioctl(fd, 0xC0105303, 0); switch (result & 0xffff) { case 1: if (result & 0xffff0000) { puts("Loose"); } else { puts("Win"); } return 0; case 0xffff: puts("Failure"); return 0; } } } ``` Pour comprendre comment est traitée l'entrée du programme, il est nécessaire d'analyser l'implémentation de /dev/sstic. Quel module noyau intervient ? Voici des commandes exécutées dans l'environnement virtualisé et leurs résultats. # ls -l /dev/sstic crw------- 1 root root 248, 0 Apr 1 13:37 /dev/sstic # grep 248 /proc/devices 248 sstic # lsmod Module Size Used by Not tainted sstic 16384 0 # ls -l /lib/sstic.ko -rw-r--r-- 1 root root 10072 Mar 28 22:24 /lib/sstic.ko Ces commandes indiquent que /dev/sstic est un fichier de type caractère qui est pris en charge par un module nommé "sstic", dont le code est présent dans /lib. La gestion des opérations IOCTL sur /dev/sstic est implémentée dans la fonction `sstic_ioctl`. Celle-ci se contente de récupérer les arguments éventuels des commandes, appelle `__arm_smccc_smc`, et transmet le résultat. Il s'agit donc d'un passe-plat vers un appel SMC (Secure Monitor Call). En environnement ARM, un SMC invoque des fonctions du "monde sécurisé" depuis le "monde normal". Ce mécanisme est spécifié dans un document hébergé sur . Ce document indique en particulier la convention d'appel en AArch64 : * le registre w0 (de 32 bits) contient un identifiant de fonction ; * les registres x1 à x6 contiennent les paramètres de la fonction appelée ; * les résultats de l'appel sont écrits dans les registres x0 à x3 ; * les valeurs des registres x18 à x30 ainsi que les pointeurs de pile SP_EL0 et SP_ELx sont conservées. Voici la table de correspondance entre les IOCTL de /dev/sstic et les SMC utilisés : +------------+------------+----------------------------------------------------+ | IOCTL | SMC | Paramètres et description | +============+============+====================================================+ | 0xC0105300 | 0x83010004 | Gros bloc de données et taille (0x101010). | | | | Usage inconnu. | +------------+------------+----------------------------------------------------+ | 0xC0105301 | 0xF2005003 | L'IOCTL est utilisé avec un bloc de 32 octets, | | | répété | découpé en 8 mots de 4 octets lors du passage | | | 8 fois | en SMC. Paramètres du SMC : index (de 0 à 7) | | | | et mot de 32 bits. | +------------+------------+----------------------------------------------------+ | 0xC0105302 | 0xF2005001 | Aucun paramètre, et la valeur de retour n'est | | | | pas utilisée. | +------------+------------+----------------------------------------------------+ | 0xC0105303 | 0xF2005002 | Le SMC renvoie deux valeurs, x1 et x2. | | | | La valeur de retour de l'IOCTL est | | | | `(x1 & 0xffff) | ((x2 & 0xffff) << 16)` | +------------+------------+----------------------------------------------------+ | 0xC0105304 | 0xF2003000 | Cette opération inconnue est implémentée dans | | | | sstic.ko. | +------------+------------+----------------------------------------------------+ | 0xC1105305 | 0x83010005 | Cette opération est utilisée dans | | | | /root/tools/keystore.py afin de charger le contenu | | | | d'un emplacement du keystore. | | | | Paramètre : structure d'emplacement du keystore | | | | indiquant l'identifiant et la taille de la clé. | +------------+------------+----------------------------------------------------+ | 0xC1105306 | 0x83010006 | Cette opération est utilisée dans | | | | /root/tools/keystore.py afin d'ajouter une clé au | | | | keystore. | | | | Paramètre : structure d'emplacement du keystore. | +------------+------------+----------------------------------------------------+ Les appels SMC utilisent des codes (identifiant une fonction appelée) dans deux catégories, décrites dans la spécification d'appel : * ceux en 0x8301xxxx correspondent à la catégorie « Fast Call, SMC32, OEM Service Calls, reserved bit 1 » ; * ceux en 0xF200xxxx correspondent à la catégorie « Fast Call, SMC64, Trusted OS Calls ». Comme l'environnement virtualisé est capable d'utiliser les SMC pour utiliser le keystore, le code du monde sécurisé est chargé en mémoire, et je suis sensé pouvoir y accéder afin de comprendre le rôle de chaque SMC. Où se trouve-t-il ? 4.2. La confiance sécurisée selon les processeurs ARM ----------------------------------------------------- L'usage des SMC (Secure Monitor Call) témoigne de la présence d'un "monde sécurisé" ("Secure World" en anglais). Il s'agit d'une notion de la technologie "TrustZone" d'ARM : deux systèmes d'exploitation s'exécutent simultanément, l'un étant de confiance mais proposant peu de fonctionnalités (le "Secure OS"), l'autre étant plus classique (le "Rich OS", Linux par exemple). Cela est par exemple utilisé dans des systèmes utilisant des mécanismes de chiffrement avec des clés protégées : le "Secure OS" implémente des opérations de chiffrement et de déchiffrement qui sont utilisables par le "Rich OS" sans que ce dernier ne puisse connaître les clés de chiffrement utilisées. Ceci permet de concentrer sur le "Secure OS" les efforts de protection des secrets cryptographiques. L'Internet regorge d'explications plus détaillées concernant TrustZone, avec des diagrammes plutôt clairs, tels que ceux présents sur . ARM propose une implémentation de référence pour bénéficier de TrustZone, le "Trusted Firmware-A" (TF-A). Le code et la documentation de celle-ci sont disponibles sur . Quand un système dérivé de cette implémentation est utilisé, le "monde sécurisé" est démarré en premier. Un chargeur d'amorçage simple (dans la ROM du System-On-Chip par exemple) démarre un second, qui démarre ce qui devient le "Secure OS" d'un côté, et le "Rich OS" dans le "monde normal". Les différents composants utilisés ont une désignation qui indique leurs positions respectives dans la séquence d'amorçage (d'où le préfixe "BL" des noms, pour "BootLoader"). La terminologie employée par le TF-A est documenté sur : * "BL1", aussi appelé "Boot ROM", désigne le code présent dans la ROM, exécuté dans le "monde sécurisé" à l'amorçage du processeur. Il est généralement non-inscriptible et a pour rôle de charger le code du BL2, de vérifier son intégrité, et de lui passer la main. * "BL2", aussi appelé "Trusted Boot Firmware", désigne le code qui charge les composants suivants (BL31, BL32 et BL33). Il est localisé dans un emplacement qu'il est possible de mettre à jour, comme la mémoire Flash d'un système. * "BL31", aussi appelé "EL3 Runtime Firmware", "EL3 Monitor Firmware" ou "Trusted OS Dispatcher", désigne le code qui gère les transitions entre le "monde normal" et le "monde sécurisé". Ceci signifie qu'en pratique, c'est ce code qui prend en charge les SMC. * "BL32", aussi appelé "Secure-EL1 Payload", "Trusted Execution Environment" (TEE) ou "Trusted OS", désigne le code qui s'exécute dans le "monde sécurisé" en général. Le "monde normal" peut uniquement interagir avec ce code par le biais de SMC, que le BL31 transmet au BL32. * "BL33" désigne le système qui est démarré dans le "monde normal", par exemple un système d'exploitation utilisant le noyau Linux. Voici un schéma positionnant chacun de ces éléments dans les niveaux de privilège de l'architecture ARM ("Execution Level" ; EL0 est le moins privilégié et EL3 est le plus privilégié) : +----------------------------+-------------------------------------+ | "Monde sécurisé" | "Monde normal" | +----------------------------+-------------------------------------+ | Secure-EL0 : "Trusted App" | EL0 : applications | +----------------------------+-------------------------------------+ | Secure-EL1 : "Secure OS" | EL1 : système d'exploitation (BL33) | | (BL32) +-------------------------------------+ | | EL2 : hyperviseur éventuel (BL33) | +----------------------------+-------------------------------------+ | EL3 : "Dispatcher" (BL31) | | (Amorçage : "Boot ROM" (BL1) -> "Trusted Boot Firmware" (BL2)) | +------------------------------------------------------------------+ 4.3. Déballage du micrologiciel ------------------------------- Après avoir digéré la documentation du Trusted Firmware-A d'ARM, la séquence d'amorçage du téléphone chiffré devient plus claire : * `rom.bin` contient le BL1. Son code est responsable du déchiffrement et du démarrage du BL2 (comme l'indique le message "NOTICE: BL1: Booting BL2"). * `flash.bin` contient les autres codes d'amorçage (BL2, BL31, BL32 et BL33). * BL2 déchiffre BL31 et BL33 avec une clé AES (celle à l'emplacement 1 du keystore) et BL32 avec la dernière clé AES obtenue (celle à l'emplacement 3). Avant d'avoir retrouvé cette clé, la séquence de démarrage contenait les messages suivants : NOTICE: BL1: Booting BL31 ERROR: Secure-OS not not available : need decryption key UEFI firmware (version built at 00:01:39 on Feb 25 2019) EFI stub: Booting Linux Kernel... Au passage, la double-négation "not not available" est bien étrange. Avec la dernière clé, ces messages sont devenus : NOTICE: BL1: Booting BL31 NOTICE: BL31: Initializing BL32 NOTICE: Booting Secure-OS UEFI firmware (version built at 00:01:39 on Feb 25 2019) EFI stub: Booting Linux Kernel... Ces messages correspondent à la séquence d'amorçage du TF-A. * BL33 correspond au système Linux accessible en ligne de commande. Comment déchiffrer le code des différents BL ? En comprenant la structure du fichier `flash.bin`. Celui-ci commence par les octets suivant, en hexadécimal : 01 00 64 aa 78 56 34 12 00 00 00 00 00 00 00 00 Cela correspond à deux nombres de 32-bit, 0xAA640001 et 0x12345678, suivis de zéros. Le premier nombre ressemble à l'assemblage de "AArch64" et "1". En le cherchant dans le code du TF-A, je trouve les définitions suivantes dans include/tools_share/firmware_image_package.h : ```c /* This is used as a signature to validate the blob header */ #define TOC_HEADER_NAME 0xAA640001 typedef struct fip_toc_header { uint32_t name; uint32_t serial_number; uint64_t flags; } fip_toc_header_t; ``` Ceci correspond parfaitement au début du contenu de `flash.bin`. Ce fichier est donc un firmware au format "Firmware Image Package" (FIP). Après un entête de 16 octets correspondant à la structure `fip_toc_header`, le fichier contient une liste d'entrées décrivant le contenu, la "Table Of Contents" (TOC) : ```c typedef struct fip_toc_entry { uuid_t uuid; uint64_t offset_address; uint64_t size; uint64_t flags; } fip_toc_entry_t; ``` Les entrées de `flash.bin` sont les suivantes : +----------------------------------------------+------------+-----------+-------+ | UUID (et nom usuel) | offset | size | flags | +==============================================+============+===========+=======+ | 0becf95f-224d-4d3e-a5-44-c39d81c73f0a (BL2) | 0x000000d8 | 0x9430 | 0 | +----------------------------------------------+------------+-----------+-------+ | 6d08d447-fe4c-4698-9b-95-2950cbbd5a00 (BL31) | 0x00009508 | 0x90a0 | 0 | +----------------------------------------------+------------+-----------+-------+ | 89e1d005-dc53-4713-8d-2b-500a4b7a3e38 (BL32) | 0x000125a8 | 0x5370 | 0 | +----------------------------------------------+------------+-----------+-------+ | a7eed0d6-eafc-4bd5-97-82-9934f234b6e4 (BL33) | 0x00017918 | 0x2220020 | 0 | +----------------------------------------------+------------+-----------+-------+ | 00000000-0000-0000-00-00-000000000000 | 0x02237938 | 0 | 0 | +----------------------------------------------+------------+-----------+-------+ Chaque partie du fichier est chiffrée en AES256-CBC avec des clés connues, aucun bourrage, et des vecteurs d'initialisation explicites (ie. les 16 premiers octets de chaque partie est le vecteur d'initialisation utilisé pour chiffrer le reste). J'ai ainsi pu extraire et déchiffrer chaque composant de `flash.bin`. Afin de procéder à leur analyse, il est nécessaire de savoir où ces composants sont chargés. Le TF-A indique quelques heuristiques à ce sujet, mais en pratique tout dépend de la plateforme utilisée (et le code pourrait être écrit de sorte à ne pas dépendre de la position à laquelle il est chargé). Dans le cas présent, les composants sont chacun chargés à une adresse fixe, qu'il faut donc trouver. Les messages affichés au démarrage sont préfixés par un niveau ("INFO", "ERROR", etc.). Ces niveaux sont définis par un tableau qui est présent en mémoire dans chacun des programmes. Par exemple, BL32 contient : 000040a0: c840 200e 0000 0000 d240 200e 0000 0000 .@ ......@ ..... 000040b0: dc40 200e 0000 0000 e640 200e 0000 0000 .@ ......@ ..... 000040c0: f040 200e 0000 0000 4552 524f 523a 2020 .@ .....ERROR: 000040d0: 2000 4e4f 5449 4345 3a20 2000 5741 524e .NOTICE: .WARN 000040e0: 494e 473a 2000 494e 464f 3a20 2020 2000 ING: .INFO: . 000040f0: 5645 5242 4f53 453a 2000 0000 0000 0000 VERBOSE: ....... Les messages reconnaissables sont précédés par une suite d'entiers de 64 bits dont les 16 bits de poids faible correspondent aux adresses des messages : +-----------------------------+-------------+----------------------+ | Position relative dans BL32 | Message | Entier correspondant | +=============================+=============+======================+ | 0x000040c8 | "ERROR: " | 0x0E2040C8 | +-----------------------------+-------------+----------------------+ | 0x000040d2 | "NOTICE: " | 0x0E2040D2 | +-----------------------------+-------------+----------------------+ | 0x000040dc | "WARNING: " | 0x0E2040DC | +-----------------------------+-------------+----------------------+ | 0x000040e6 | "INFO: " | 0x0E2040E6 | +-----------------------------+-------------+----------------------+ | 0x000040f0 | "VERBOSE: " | 0x0E2040F0 | +-----------------------------+-------------+----------------------+ Il est donc raisonnable d'en déduire que BL32 est chargé à l'adresse 0x0E200000. En procédant de même avec les autres composants, j'obtiens : +-----------+------------------+------------------------------------+ | Composant | Adresse de début | Adresse de fin (dernier octet + 1) | +===========+==================+====================================+ | BL2 | 0x0E00B000 | 0x0E014420 | +-----------+------------------+------------------------------------+ | BL31 | 0x0E030000 | 0x0E039090 | +-----------+------------------+------------------------------------+ | BL32 | 0x0E200000 | 0x0E205360 | +-----------+------------------+------------------------------------+ Toutes ces adresses correspondent à la zone marquée `VIRT_SECURE_MEM` dans le code de QEMU, comme je pouvais m'y attendre pour le code "monde sécurisé", dont l'accès est ainsi interdit depuis le "monde normal". Ces adresses peuvent également être retrouvée en analysant directement le code. D'une part, l'adresse où BL2 est chargé se trouve dans BL1 (`rom.bin`), dans une structure de type `image_desc_t` située à l'adresse 0x0E04E000 (il s'agit de la variable `bl2_img_desc` du fichier `plat/common/plat_bl1_common.c` du TF-A). En remarquant que cette adresse correspond au début de la section `.data` qui est initialisée à partir des données en 0x000077C0 dans `rom.bin`, son contenu est lisible directement et contient effectivement l'adresse 0x0E00B000. D'autre part, BL2 contient les adresses où sont chargés BL31, BL32 et BL33 dans le tableau nommé `bl2_mem_params_descs` dans le fichier `plat/qemu/qemu_bl2_mem_params_desc.c` du TF-A. Ce tableau est situé à l'adresse 0x0E014000 une fois que BL2 est chargé en 0x0E00B000. Sa lecture permet de retrouver les adresses de chargement de BL31 et de BL32 ainsi que de trouver que BL33 est chargé à l'adresse 0x60000000. Quoiqu'il en soit, ceci permet d'analyser enfin le code de BL31 et de BL32. 4.4. La remontée dans le monde sécurisée ---------------------------------------- La section 4.1 a déterminé que /root/safe_02/decrypted_file fait appel à des SMC pour vérifier l'entrée fournie au programme (en passant par un module noyau). La section 4.2 a présenté l'implémentation de référence TF-A d'ARM, dans laquelle les SMC sont pris en charge par le BL31 au niveau exécution EL3. Ensuite la section 4.3 a retrouvé les composants du TF-A dans les fichiers fournis (`rom.bin` et `flash.bin`) ainsi que les adresses où ceux-ci sont chargés. Il est dès lors possible d'analyser l'implémentation de BL31 et BL32 dans un logiciel de rétro-ingénierie comme Radare2/Cutter, IDA ou Ghidra. Le code de BL31 est organisé de la manière suivante. * Le point d'entrée (`bl31_entrypoint` dans TF-A, situé en 0E030000) : * initialise les zones mémoires nécessaires (.bss, .data, etc.) ; * retrouve les informations correspondant à BL32 et BL33 grâce à une structure initialisée par BL2 qui lui est transmise (`bl31_early_platform_setup2` dans plat/qemu/qemu_bl31_setup.c, en 0E0308B8) ; * configure la MMU pour l'EL3 (en appelant `qemu_configure_mmu_el3`, en 0E034960) ; * appelle `bl31_main` (en 0E030A94) ; * retourne en restaurant les registres. * La fonction principale (`bl31_main` dans TF-A, en 0E030A94) : * appelle `bl31_platform_setup` (plat/qemu/qemu_bl31_setup.c dans TF-A, en 0E0309D0), qui initialise le "GIC CPU" (Generic Interrupt Controller) et les "distributor interfaces" ; * appelle `runtime_svc_init` (en 0E030D38), qui enregistre des services décrits par le tableau global `rt_svc_descs` situé en 0E0386F0 ; * effectue des opérations nécessaires pour lancer BL32 quand `bl31_entrypoint` finira. J'ai observé toutefois une différence dans l'implémentation de la fonction `bl31_plat_arch_setup` entre TF-A (dans plat/qemu/qemu_bl31_setup.c) et le BL31 étudié (en 0E030958). Un appel à la fonction suivante a été ajouté : ```c // Paramètres : // adresse physique, adresse virtuelle, taille, attributs mmap_add_region(0x0E053000, 0x0E053000, 0x00102000, MT_RW); ``` Ceci a pour effet de rendre disponible en lecture, écriture et exécution pour le "monde sécurisé" (les attributs `MT_EXECUTE` et `MT_SECURE` sont implicites) la région mémoire située de 0E053000 à 0E155000. Comme la taille de cette région, 0x00102000, correspond incroyablement bien à la taille des données transmises par SMC dans /root/safe_02/decrypted_file, 0x101010, il est possible que cette région serve à recueillir les données transmises. Lorsqu'un SMC est utilisé, la fonction de BL31 qui le prend en charge dépend de paramètres qui sont décrits dans le tableau `rt_svc_descs` qui a été trouvé en suivant le flot d'exécution. Ce tableau contient les éléments suivants : +----------------+---------+-------+-----------------+-----------------------+ | Nom | OEN | Type | Initialisation | Prise en charge | +================+=========+=======+=================+=======================+ | `arm_arch_svc` | 0 | Fast | 0 | 0E030E44 | | | | | | `arm_arch_svc_` ... | | | | | | ... `smc_handler` | +----------------+---------+-------+-----------------+-----------------------+ | `std_svc` | 4 | Fast | 0E030EFC | 0E030F20 | | | | | `std_svc_setup` | `std_svc_smc_handler` | +----------------+---------+-------+-----------------+-----------------------+ | `oem_svc` | 3 | Fast | 0E030FDC | 0E032014 | | | | | `oem_svc_setup` | `oem_svc_smc_handler` | +----------------+---------+-------+-----------------+-----------------------+ | `tspd_std` | 0x32 à | Yield | 0 | 0E034158 | | | 0x3f | | | `tspd_smc_handler` | | | inclus | | | | +----------------+---------+-------+-----------------+-----------------------+ Le champ "OEN" (Owning Entity Numbers) sert à identifier le propriétaire d'un SMC. Il se retrouve dans les bits 24 à 29 des identifiants de fonctions. Dans la section 4.1, j'ai énuméré les SMC utilisés par le module noyau sstic.ko. Ils correspondent aux OEN suivants : * Ceux en 0x8301xxxx ont pour OEN 3, "OEM Service Calls". Cela correspond à la ligne `oem_svc` du tableau. Ces SMC sont donc traités par la fonction en 0E032014 dans BL31, nommée `oem_svc_smc_handler`. * Ceux en 0xF200xxxx ont pour OEN 0x32 : "Trusted OS Calls". Cela correspond à la ligne `tspd_std` du tableau. Ces SMC sont donc traités par la fonction en 0E034158 dans BL31, nommée `tspd_smc_handler`. Les fonctions `..._smc_handler` implémentent un certain nombre de SMC pour respecter des spécifications (par exemple `oem_svc_smc_handler` renvoie un UUID pour le SMC 0x8300ff01 défini comme `OEM_SVC_UID`). Je concentre donc mon analyse sur les autres SMC, spécifiques au BL31 étudié. Le premier SMC employé par /root/safe_02/decrypted_file est le 0x83010004, avec en paramètre un bloc de 0x101010 octets et sa taille. Pour traiter ce SMC, `oem_svc_smc_handler` appelle une fonction en 0E031034, que j'appelle `oem_smc_handler` (car une telle fonction existe dans un fichier du TF-A, plat/mediatek/common/custom/oem_svc.c). Voici un extrait décompilé de cette fonction : ```c uintptr_t oem_smc_handler( uint32_t smc_fid, u_register_t x1, u_register_t x2, u_register_t x3, u_register_t x4, void *cookie, uint64_t *handle, u_register_t flags) { unsigned int smc_function_id = smc_fid & 0xffff; /* ... */ if ( (smc_function_id == 4) && (x2 < 0x101011) && (0x3fffffff < x1) && (x1 + x2 < 0xf0000001) && (x1 <= x2 + x1) ) { memcpy(0x0E053000, (void *)x1, x2); flush_dcache_range(0x0E053000, x2); *handle = 0; return handle; } /* ... */ } ``` Les conditions permettent de vérifier les limites d'une zone mémoire commençant en x1 et de taille x2 : celle-ci doit faire au plus 0x101010 octets et être située entre 40000000 et F0000000, ce qui correspond à la RAM accessible par le "monde normal". Le SMC 0x83010004 permet donc de copier des données du "monde normal" dans la zone située en 0E053000. À proximité, les SMC 0x83010005 et 0x83010006 invoquent les opérations de semihosting permettant de charger ou écrire un emplacement du keystore. Ceci correspond bien au fonctionnement attendu, compte tenu du fait que ces SMC sont invoqués par /root/tools/keystore.py. D'autres SMC sont implémentés dans `oem_smc_handler`, mais ne semblent pas être utilisés par le monde normal. Je reviendrai dessus dans la section suivante. Les autres SMC employés par /root/safe_02/decrypted_file sont de la catégorie "Trusted OS Calls". Ces SMC sont implémentés par la fonction `tspd_smc_handler` en 0E034158 (services/spd/tspd/tspd_main.c du TF-A). Le code correspondant est celui d'un passe-plat vers le niveau Secure-EL1 : * Quand le BL31 reçoit du "monde normal" un SMC 0xf200500x, il sauvegarde les registres, écrit les arguments x1 et x2 dans une structure `tspd_sp_context` et passe la main à un point d'entrée `tsp_vectors->fast_smc_entry` ou `tsp_vectors->yield_smc_entry` selon les cas, en Secure-EL1. * Ces points d'entrée correspondent à un "Test Secure-EL1 Payload" (TSP), implémenté dans BL32. Le code correspondant peut à son tour émettre des SMC, pris en charge par le BL31 : * le SMC 0xf2001000 (`TSP_GET_ARGS`) permet de récupérer le contenu des arguments x1 et x2 qui ont été écrits dans `tspd_sp_context` ; * le SMC 0xf200500x correspondant à l'appel initial permet d'indiquer la fin du traitement, en fournissant des valeurs de retour. * Lorsque le BL31 reçoit du "monde sécurisé" un SMC 0xf200500x, il sauvegarde les registres du "monde sécurisé", restaure ceux du "monde normal", et y assigne dans les registres x0, x1 et x2 les trois paramètres du SMC (qui correspondent ainsi aux valeurs de retour du SMC) avant de rendre la main au "monde normal". Ainsi, le cœur des traitements induits par /root/safe_02/decrypted_file se déroule en Secure-EL1, dans BL32. 4.5. Une nouvelle couche de virtualisation ------------------------------------------ Le point d'entrée de BL32 est similaire à celui de BL31 : il initialise des zones mémoires, la MMU du Secure-EL1, et appelle `tsp_main` en 0E200230. La principale différence est le fait qu'il termine par un SMC 0xf2000000 (`TSP_ENTRY_DONE`) avec en paramètre une table de vecteurs (`tsp_vector_table`, en 0E200090), afin que le BL31 puisse acheminer les SMC qu'il reçoit. Les SMC sont reçus par les deux premières entrées de cette table, et sont traités par la fonction en 0E200C08 (`tsp_smc_handler` dans TF-A). Cette fonction et celle qu'elle appelle, en 0E2005A4, constituent le cœur des traitements induits par /root/safe_02/decrypted_file. Tout le reste n'existe que pour permettre l'exécution de ce code dans en Secure-EL1. Ces fonctions implémentent 3 SMC. En voici le pseudo-code : ```c if (smc_function == 0xf2005001) { x0 = SMC(0x83010001, 0xf, 0, 0); *(uint64_t*)0x0E215a80 = x0; *(uint64_t*)0x0E215810 = (*(uint64_t*)x0) & 0x00ffffff; return {0, 0}; } else if (smc_function == 0xf2005002) { value = 0; result = fct_0E2005A4(*(uint64_t*)0x0E215810, &value); return {result, value}; } else if (smc_function == 0xf2005003) { // Récupère les paramètres avec TSP_GET_ARGS=0xf2001000 {x0, x1} = SMC(0xf2001000); ((uint32_t*)0x00100020)[x0] = x1 & 0xffffffff; if (3 <= x0 < 7) { SMC(0x83010003, x0 - 3, x1, 0); } SMC(0x83010002, 0xf, 0, 0); return {0, 0}; } ``` Ce code utilise des SMC 0x8301xxxx, qui sont traités par le BL31 comme des "OEM Service Calls". La fonction qui implémente ce traitement a été étudiée en partie dans la section précédente, et les nouveaux SMC nécessitent de l'étudier à nouveau. En rapprochant le pseudo-code obtenu et la manière dont les SMC sont utilisés par /root/safe_02/decrypted_file, il apparaît que le BL32 implémente une machine virtuelle pour un jeu d'instructions sur 24 bits utilisant 16 registres de 32 bits : * Le SMC 0x83010001 permet de lire la valeur d'un registre indiqué en paramètre (dans x1). * Le SMC 0x83010002 permet d'assigner la valeur de x2 dans le registre indiqué par x1. * Le SMC 0x83010003 permet de définir des clés de chiffrement qui permettent de chiffrer les registres. Ces registres sont ainsi maîtrisés par BL31. * Le dernier registre (d'identifiant 0xf) est un pointeur d'instruction. Le SMC 0xf2005001 permet de charger une instruction à exécuter. * Le SMC 0xf2005002 permet d'exécuter effectivement une instruction qui a été chargée. Ce traitement renvoie deux valeurs, qui sont combinées par sstic.ko puis séparées par /root/safe_02/decrypted_file : * La première (nommée `result` dans le pseudo-code) indique si l'exécution continue (0), s'arrête (1) ou est en erreur (0xffff). * Si l'exécution s'arrête, la seconde (nommée `value`) contient un code de retour qui peut signifier une réussite (0) ou un échec (valeur non nulle). * Le SMC 0xf2005003, utilisé pour transmettre les 32 octets passés en paramètre à /root/safe_02/decrypted_file, copie ces données en 00100020 et définit certaines de ces données comme clé de chiffrement utlisée par BL31 pour chiffrer les registres de cette nouvelles machine virtuelle. Ce SMC réinitialise aussi le pointeur d'instruction à zéro. Il est étrange d'utiliser des adresses aussi basses que zéro ou 00100020, car s'il s'agissait d'adresses physiques, elles correspondraient à une partie de la ROM. Il s'agit donc probablement d'une mémoire virtuelle qui a été allouée à l'adresse nulle et qui contient les instructions à exécuter. Cette hypothèse est d'autant plus prometteuse que 00100020 est inférieur à 00102000, qui est la taille de la région allouée en 0E053000 par BL31 pour y copier les 0x101010 octets transmis par /root/safe_02/decrypted_file. En bref, il est raisonnable d'envisager que la zone située à l'adresse physique 0E053000 et de taille 00102000 soit transposée à l'adresse nulle dans la mémoire virtuelle utilisée par BL32. Le SMC 0x83010004 permet alors de charger un programme pour la machine virtuelle implémentée par BL32. Quelles sont les instructions de cette machine virtuelle ? L'analyse de BL31 et BL32 permet de répondre à cela. La réponse à cette question m'a pris du temps à établir, car il se trouve que l'implémentation de la machine virtuelle mélange du code ARM32 et ARM64, et même du code ARM32-Thumb. De plus le BL31 utilise des instructions peu habituelles du jeu d'instruction ARM64 : les instructions scalaires et SVE (Scalable Vector Extension); Voici un pseudo-code qui permet de décoder une instruction de la machine virtuelle, initialement dans la variable `opcode` de 24 bits : ```c // Décomposition de l'instruction highkind = opcode >> 20; // bits 20 à 23 w1819 = (opcode >> 18) & 3; // bits 18 et 19 regidx14 = (opcode >> 14) & 0xf; // bits 14 à 17 regidx10 = (opcode >> 10) & 0xf; // bits 10 à 13 imm16 = opcode & 0xffff // bits 0 à 15 imm14 = opcode & 0x3fff // bits 0 à 13 Si highkind == 0xc : index = (REG[regidx14] >> 2) % 8; secret_value = "pr.a.rfg.cnf.fv.snpvyr@ffgvp.bet"; // = rot13("ce.n.est.pas.si.facile@sstic.org") memcpy(0x00100000 + 4*index, secret_value + 4*index, 4); Si highkind == 0xa : exit(REG[0]); Si highkind == 0xd : *(uint32_t*)0x09010008 = 0; Si highkind == 0xe : if (*(uint32_t*)0x09010000 > 5) goto imm14; Si w1819 == 0 : Si highkind == 0 : REG[regidx14] = REG[regidx10]; Si highkind == 1 : // SMC(0x83010011, regidx14, regidx10) REG[regidx14] -= 1; Si highkind == 2 : // SMC(0x83010012, regidx14, regidx10) REG[regidx14] += REG[regidx10]; Si highkind == 3 : // SMC(0x83010013, regidx14, regidx10) REG[regidx14] -= REG[regidx10]; Si highkind == 6 : // SMC(0x83010016, regidx14, regidx10) REG[regidx14] ^= REG[regidx10]; Si highkind == 0xb : // Inverse les deux octets d'une valeur de 16 bits r0 = REG[regidx14] REG[regidx14] = (r0 & 0xff) << 8) | (r0 >> 8); Si w1819 == 1 : // Lecture mémoire REG[regidx14] = *(uint32_t*)REG[regidx10]; Si w1819 == 2 : // Écriture mémoire *(uint32_t*)REG[regidx14] = REG[regidx10]; Si w1819 == 3 : Si highkind == 0 : REG[regidx14] = imm14; Si highkind == 2 : // SMC(0x83010022, regidx14, imm14) REG[regidx14] += imm14; Si highkind == 3 : // SMC(0x83010023, regidx14, imm14) REG[regidx14] -= imm14; Si highkind == 4 : REG[regidx14] <<= imm14; Si highkind == 5 : REG[regidx14] >>= imm14; Si highkind == 6 : // SMC(0x83010026, regidx14, imm14) REG[regidx14] ^= imm14; Si highkind == 7 : // SMC(0x83010027, regidx14, imm14) REG[regidx14] &= imm14; Si highkind == 8 : // SMC(0x83010028, regidx14, imm14) goto imm14; Si highkind == 9 : // Code qui mélange du Thumb et de l'ARM32 if (REG[regidx14]) goto imm14; ``` Malgré la difficulté de la compréhension de certaines instructions, ceci correspond à un jeu d'instruction de type RISC raisonnable. Toutefois, son application directe sur les données transmises par /root/safe_02/decrypted_file ne résulte pas en du code convenable. Il est en effet chiffré, et il est nécessaire de trouver où il est déchiffré afin de pouvoir le lire. 4.6. Détour cartographique -------------------------- Pour trouver où les données fournies au "monde sécurisé" sont déchiffrées avant d'être interprétées comme le code d'une machine virtuelle par BL32, je me suis concentré sur les interactions entre les différents composants. Afin de mieux comprendre ces interactions, j'ai réalisé un inventaire de toutes les utilisations de la mémoire physique du système. Dans cette tâche, mes sources d'informations furent les suivantes : * le code source de QEMU 3.1.0 (en particulier hw/arm/virt.c) ; * le code source de TF-A (ARM Trusted Firmware-A, en particulier plat/qemu/) ; * le contenu de /proc/iomem dans le Linux virtualisé (qui correspond à la vision de la mémoire physique vue depuis le "monde normal" en EL1). Voici le résultat de cet inventaire : 00000000..08000000 QEMU VIRT_FLASH 00000000..04000000 "LNRO0015:00" dans /proc/iomem 00000000..00020000 TF-A SEC_ROM_BASE : "Secure ROM" 00000000..000078B1 ROM (BL1, rom.bin) 00000000..00004740 BL1 .text 00004800..00005000 BL1 .vectors 00005000..000077C0 BL1 .rodata 000077C0..000078B1 BL1 .data initial (copié dans .data) 04000000..08000000 TF-A QEMU_FLASH0_BASE ("LNRO0015:01" dans /proc/iomem) 04000000..06237938 Firmware Image Package (FIP, flash.bin) 08000000..09000000 Generic Interrupt Controller (GIC) 08000000..08021000 TF-A DEVICE0_BASE 08000000..08010000 QEMU VIRT_GIC_DIST, TF-A GICD_BASE 08010000..08020000 QEMU VIRT_GIC_CPU, TF-A GICC_BASE 08020000..08021000 QEMU VIRT_GIC_V2M 08030000..08040000 QEMU VIRT_GIC_HYP 08040000..08050000 QEMU VIRT_GIC_VCPU 08080000..080A0000 QEMU VIRT_GIC_ITS 080A0000..09000000 QEMU VIRT_GIC_REDIST 09000000..09070000 Périphériques 09000000..09041000 TF-A DEVICE1_BASE 09000000..09001000 QEMU VIRT_UART, TF-A UART0_BASE ("PLAT_QEMU_BOOT_UART_BASE" aussi dans TF-A) ("ARMH0011:00" dans /proc/iomem, /dev/ttyAMA0 dans Linux) 09010000..09011000 QEMU VIRT_RTC 09020000..09020018 QEMU VIRT_FW_CFG ("fw_cfg_mem" dans /proc/iomem) 09030000..09031000 QEMU VIRT_GPIO ("ARMH0061:00" dans /proc/iomem) 09040000..09041000 QEMU VIRT_SECURE_UART, TF-A UART1_BASE 09050000..09070000 QEMU VIRT_SMMU 0A000000..0A000200 QEMU VIRT_MMIO ("LNRO0005:00" dans /proc/iomem) 0C000000..0E000000 QEMU VIRT_PLATFORM_BUS 0E000000..0F000000 QEMU VIRT_SECURE_MEM, TF-A SEC_SRAM_BASE 0E000000..0E001000 TF-A SHARED_RAM_BASE, mémoire partagée entre les CPU (TF-A PLAT_QEMU_TRUSTED_MAILBOX_BASE) 0E001000..0E060000 TF-A BL_RAM_BASE : Trusted RAM, selon BL1 0E001000..? "EL1 Early Exceptions" dans BL2 0E00B000..0E030000 TF-A BL2_BASE..BL2_LIMIT 0E00B000..0E014000 BL2 read-only 0E00B000..0E00FA68 BL2 .text 0E010000..0E011000 BL2 .vectors 0E011000..0E014000 BL2 .rodata 0E014000..0E01D000 BL2 read-write 0E014000..0E014420 BL2 .data 0E014440..0E016440 BL2 stack (sp est initialisé à 0E016440) 0E016440..0E0167B8 BL2 .bss 0E017000..0E01D000 BL2 xlat_table 0E01D000 (vide) BL2 "coherent mem" 0E030000..0E060000 TF-A BL31_BASE..BL31_LIMIT 0E030000..0E039000 BL31 read-only 0E030000..0E038000 BL31 .text 0E038000..0E039000 BL31 .rodata 0E039000..0E155000 BL31 read-write 0E039000..0E039081 BL31 .data 0E0390C0..0E0490C0 BL31 stacks (8 piles de 8192 octets) 0E0490C0..0E04BCC0 BL31 .bss 0E04C000..0E052000 BL31 xlat_table 0E052000..0E052040 BL31 "coherent mem" 0E053000..0E155000 (BL31) Données copiées depuis le "monde normal", qui dépasse ainsi la zone mémoire parente. Chevauchement avec le chargeur d'amorçage : 0E04E000..0E060000 TF-A BL1_RW_BASE..BL1_RW_LIMIT 0E04E000..0E04E0F1 BL1 .data 0E04E100..0E050100 BL1 stack (sp est initialisé à 0E050100) 0E050100..0E0508D0 BL1 .bss 0E051000..0E057000 BL1 xlat_table 0E057000 (vide) BL1 "coherent mem" (0E100000..0F000000 TF-A SEC_DRAM_BASE dans le code source) 0E200000..0F100000 TF-A SEC_DRAM_BASE réel TF-A BL32_MEM_BASE..BL32_LIMIT 0E200000..0E205000 BL32 read-only 0E200000..0E202e80 BL32 .text 0E203000..0E204000 BL32 tsp_exceptions 0E204000..0E205000 BL32 .rodata 0E205000..0E21C000 BL32 read-write 0E205000..0E205360 BL32 .data 0E205380..0E215380 BL32 stacks (8 piles de 8192 octets) 0E215380..0E215AA0 BL32 .bss 0E216000..0E21C000 BL32 xlat_table 0E21C000 (vide) BL32 "coherent mem" (0E300000..0E700000 TF-A QEMU_OPTEE_PAGEABLE_LOAD_BASE dans le code) 10000000..3EFF0000 QEMU VIRT_PCIE_MMIO ("PCI Bus 0000:00" dans /proc/iomem) 3EFF0000..3F000000 QEMU VIRT_PCIE_PIO 3F000000..40000000 QEMU VIRT_PCIE_ECAM 40000000..80000000 QEMU VIRT_MEM, TF-A NS_DRAM0_BASE (1 Gio de RAM "non sécurisée") 60000000..62220010 TF-A NS_IMAGE_OFFSET (emplacement de BL33) 4000000000..4004000000 QEMU VIRT_GIC_REDIST2 4010000000..4020000000 QEMU VIRT_PCIE_ECAM_HIGH ("PCI ECAM" dans /proc/iomem) 8000000000..10000000000 QEMU VIRT_PCIE_MMIO_HIGH ("PCI Bus 0000:00" dans /proc/iomem) Cette cartographie de la mémoire permet au passage de comprendre ce que font les instructions "highkind == 0xd" et "highkind == 0xe". La première exécute le code suivant : ```c *(uint32_t*)0x09010008 = 0; ``` La zone 09010000..09011000 correspond à une horloge de type "ARM PrimeCell Real Time Clock (PL031)". Le registre 0x008 est documenté comme étant le "RTCLR" (RTC Load Register), qui permet d'écrire une valeur dans l'horloge. Le fait d'y écrire 0 a donc pour effet de réinitialiser l'horloge. En ce qui concerne la seconde instruction : ```c if (*(uint32_t*)0x09010000 > 5) goto imm14; ``` Celle-ci lit le registre 0x000 de l'horloge, documenté comme étant "RTCDR" (RTC Data Register). La valeur lue est donc simplement la valeur de l'horloge. Si celle-ci a dépassé 5, un branchement se produit. La combinaison avec l'instruction précédente permet donc de mesurer des durées, ce qui est utile pour implémenter des protections contre l'analyse dynamique de code. 4.7. Une enclave chiffrée ------------------------- Il est temps de résumer les découvertes des sections précédentes, enrichies par les dernières. La section 4.1 a déterminé que /root/safe_02/decrypted_file fait appel à des SMC pour vérifier l'entrée fournie. Cette vérification utilise un bloc chiffré de 0x00101010 octets, copiée en 0E053000 par BL31. La section 4.4 a permis de trouver les implémentations des SMC et la 4.5 de comprendre que le bloc correspond probablement à des instructions pour une machine virtuelle implémentée dans BL31 et BL32 (BL31 gère les registres et BL32 la tuyauterie de cette machine). Pour trouver comment sont chiffrées ces instructions, la section 4.6 a inventorié les zones mémoires utilisées par chaque composant. Ceci permet de maintenant relire le code d'initialisation des MMU des différents BL afin de trouver où est allouée la zone à l'adresse virtuelle nulle dans BL32. Le point d'entrée de BL32 fait appel à cette fonction, située en 0E20178C : ```c void bl32_plat_arch_setup(void) { // Paramètres : // adresse physique, adresse virtuelle, taille, attributs mmap_add_region(0x0E053000, 0x00413000, 0x1000, MT_RW); iVar2 = fct_0E201650(); uVar1 = (iVar2 != 0) ? 0x0E055000 : 0x0E054000; mmap_add_region(uVar1, 0x00414000, 0x100000, MT_RW); qemu_configure_mmu_el1(/* ... */); enable_mmu_el1(0); } ``` BL32 transpose ainsi la zone 0E053000..0E054000 dans son espace de mémoire virtuelle en 00413000..00414000, et 0E055000..0E155000 ou 0E054000..0E154000 en 00414000..00514000 selon le retour de la fonction `fct_0E201650`. Cela semble très étrange, d'autant plus que cette fonction utilise les fonctionnalités du semihosting pour déterminer avec quels paramètres a été lancé QEMU : ```c int fct_0E201650(void) { int fd = semihosting_file_open("/proc/self/cmdline", 0); if (fd < 1) { unsigned int result = semihosting_system( "(ps -ef |egrep -q 'qemu-system.* -[s] |" "qemu-system.* -[g]db ') && exit 42" ); return ((result >> 8) & 0xff) == 42; } uint8_t buffer[0x400] = {}, *cursor; long bytes_read = 0x400; semihosting_file_read(fd, &bytes_read, buffer); int retval = 0; for (i = 0, cursor = buffer; i < bytes_read; i++) { if (cursor[0] == 0 || cursor[1] == 0) return retval; if ((*(uint32_t*)cursor) & 0x00ffffff = '-s\0\0') retval = 1; if ((*(uint32_t*)cursor) = '-gdb') retval = 1; } return retval; } ``` Cette fonction renvoie 1 si /proc/self/cmdline contient `-s` ou `-gdb`, ou si la sortie de la commande `ps -ef` contient une ligne de commande de QEMU avec ces options. L'option `-gdb` permet de démarrer un serveur GDB permettant de déboguer la machine virtuelle et l'option `-s` est un raccourci pour `-gdb tcp::1234`. La fonction en 0E201650 réalise donc une détection de GDB qu'il est possible de contourner en passant par l'interface de supervision de QEMU (ce que j'ai décrit dans la section 1.1). En temps normal, la fonction en 0E201650 renvoie 0 et la zone 0E053000..0E154000 se retrouve transposée de manière continue dans la mémoire virtuelle de BL32 en 00413000..00514000. Si le débogage est détecté, un décalage se produit et le contenu de 0E054000..0E055000 disparaît, modifiant ainsi les données traitées par BL32 ! En bref, le code chiffré envoyé par /root/safe_02/decrypted_file au BL31 qui est copié en 0E053000..0E155000 est accessible par les adresses 00413000..00514000 pour BL32. Mais cela n'indique toujours pas comment il est déchiffré. Et si la mémoire près de l'adresse nulle n'était pas allouée ? Est-il possible d'en simuler l'accès ? Cela devrait alors passer par les vecteurs d'exception, conçus pour rattraper des erreurs telles que l'accès à une mémoire non allouée (ie. une erreur de segmentation). Les vecteurs d'exceptions du Secure-EL1 sont initialisés au tout début du point d'entrée de BL32. Le code assembleur est le suivant : 0e200000 00 80 01 10 adr x0,0x0E203000 0e200004 00 c0 18 d5 msr vbar_el1,x0 0e200008 df 3f 03 d5 isb Le "Vector Base Address Register for EL1" (`VBAR_EL1`) est une adresse qui définit des fonctions qui traitent les exceptions, selon comment elles arrivent. Voici les fonctions qui sont définies dans BL32 en autre chose que simplement un appel à `plat_panic_handler` (qui termine violemment l'exécution) : +----------------+----------+-------------------------+-----------------------+ | `VBAR_EL1`+... | Adresse | "Exception type" | "Exception set level" | +================+==========+=========================+=======================+ | 0x200 | 0E203200 | "Synchronous Exception" | "Current EL with SPx" | +----------------+----------+-------------------------+-----------------------+ | 0x280 | 0E203280 | "IRQ/vIRQ" | "Current EL with SPx" | +----------------+----------+-------------------------+-----------------------+ | 0x300 | 0E203300 | "FIQ/vFIQ" | "Current EL with SPx" | +----------------+----------+-------------------------+-----------------------+ | 0x600 | 0E203600 | "Synchronous Exception" | "Lower EL with ARM32" | +----------------+----------+-------------------------+-----------------------+ La dernière entrée est utilisée dans l'implémentation de la machine virtuelle pour le code ARM32 qui s'y exécute en Secure-EL0. Celui-ci utilise `svc 0x1338` pour appeler un SMC et `svc 0x1337` pour terminer. Les deux lignes médianes correspondent exactement aux entrées `irq_sp_elx` et `fiq_sp_elx` de bl32/tsp/aarch64/tsp_entrypoint.S dans TF-A. La première ligne en revanche est différente par rapport au TF-A, qui se contente de paniquer dans le cas d'une exception synchrone au niveau d'exécution courant avec SPx. Le code concerné charge la valeur du registre `ESR_EL1` (Exception Syndrome Register, EL1) et en extrait les bits 26 à 31, qui correspondent à l'"Exception Class". Si cette classe est 0x25 (ie. `ESR_EL1_EC_DABT_EL1`), la fonction en 0E202284 est appelée. Sinon, c'est `plat_panic_handler` qui l'est. La classe d'exception 0x25 correspond aux "Data Abort, EL1", qui est une exception générée en particulier quand une erreur de segmentation a lieu. La fonction en 0E202284 exécute les instructions suivantes : ```c x1 = FAR_EL1; // Fault Address Register, EL1 (adresse fautive) x0 = sp; // sp contient ici les registres actuels (x0, x1, etc.) fct_0E200E84(x0, x1); ELR_EL1 += 4; // Exception Link Registers, EL1 restore_caller_regs_and_lr eret ``` Cette fonction appelle celle en 0E200E84 puis revient au code qui a déclenché l'erreur en esquivant l'instruction fautive. La nouvelle fonction a certainement pour rôle de charger les données adaptées. Cette fonction est assez grande et utilise les instructions SM4EKEY et SM4E de l'extension ARMv8.2-SM. Ces instructions implémentent des parties d'un algorithme de chiffrement nommé SM4. La fonction en 0E200E84 permet de chiffrer ou déchiffrer 4 octets situés en 00413000+FAR_EL1 en utilisant les instructions SM4. Comme ce chiffrement utilise des blocs de 16 octets, il est possible que les 4 octets tombent à cheval entre deux blocs, auquel cas la fonction considère ces deux blocs. Une fois que cela est compris, la fonction en 0E200E84 devient plus simple : ```c int fct_0E200E84(uint64_t *regs, uint64_t addr) { uint8_t buffer[32] = {}; if (addr >= 0x00101000) return 1; block_offset = addr & 0xfffffff0 BL32_KEY = {0x2078cc93, 0xb4c15038, 0xd5dc439c, 0x625f824}; FPU_v1 = derive_key_for_decrypt(BL32_KEY, block_offset); // 16 octets FPU_v0 = load_16_bytes(0x00413000 + block_offset); buffer[0,0x10] = sm4e(block=FPU_v0, key=FPU_v1); if ((addr & 0xf) > 0xc) { FPU_v1 = derive_key_for_decrypt(BL32_KEY, block_offset + 0x10); FPU_v0 = load_16_bytes(0x00413000 + block_offset + 0x10); buffer[0x10,0x20] = sm4e(block=FPU_v0, key=FPU_v1); } if (regs->x2 != 1) { // Decrypt address to x0 regs->x0 = *(uint32_t*)(buffer + (addr & 0xf)); return 0; } else { // Encrypt x1 to address *(uint32_t*)(buffer + (addr & 0xf)) = regs->x1; FPU_v0 = derive_key_for_encrypt(BL32_KEY, block_offset); FPU_v2 = load_16_bytes(buffer[0,0x10]); (0x00413000 + block_offset)[0,0x10] = sm4e(block=FPU_v2, key=FPU_v1); flush_dcache_range(0x00413000 + block_offset, 0x10); if ((addr & 0xf) > 0xc) { FPU_v0 = derive_key_for_encrypt(BL32_KEY, block_offset + 0x10); FPU_v2 = load_16_bytes(buffer[0x10,0x20]); (0x00413000 + block_offset)[0x10,0x20] = sm4e(block=FPU_v2, key=FPU_v1); flush_dcache_range(0x00413000 + block_offset + 0x10, 0x10); } return 0; } } ``` Écrit autrement, lorsqu'une instruction déclenche une erreur de segmentation à une adresse comprise en 00000000 et 00101000 : * les données associées dans la zone 00413000..00514000 sont déchiffrées ; * si x2 = 1, la valeur de x1 est écrite dans les données déchiffrées puis ces données sont chiffrées ; * sinon, la valeur déchiffrée (de 32 bits) qui correspond à l'adresse fautive est mise dans x0. Cela est plus contraignant qu'un code qui va chercher à analyser l'instruction fautive afin de l'émuler complètement, comme ce que j'ai par ailleurs publié sur , mais le code est ainsi plus facile à analyser. En reprenant les analyses précédentes et en se concentrant sur les fonctions qui lisent ou écrivent la partie chiffrée, j'obtiens des résultats cohérents. Par exemple, la fonction en 0E200C08 (`tsp_smc_handler`) utilise le code suivant pour lire une instruction chiffrée : 0e200c6c mov x2,#0x0 ; x2 = 0 (pour déchiffrer) 0e200c70 str x0,[x1, #0xa80] 0e200c74 and x0,x0,#0xffffffff ; x0 = adresse à charger 0e200c78 mov x1,#0x0 ; x1 = 0 0e200c7c ldr x0,[x0] ; chargement avec déchiffrement Et quand cette fonction écrit les données passées à /root/safe_02/decrypted_file en 00100020, elle utilise : 0e200cf4 add x0,x3,#0x40, LSL #12 ; x0 = 0x40000 + x3 0e200cf8 mov x2,#0x1 ; x2 = 1 (pour chiffrer) 0e200cfc ldr x4,[sp, #0x48] 0e200d00 add x0,x0,#0x8 ; x0 = 0x40008 + x3 0e200d04 and x1,x4,#0xffffffff ; x1 = x4 & 0xffffffff 0e200d08 lsl x0,x0,#0x2 ; x0 = 0x100020 + 4*x3 0e200d0c str w1,[x0] ; écriture chiffrée Comme le chiffrement est effectué par blocs de 16 octets, je peux simplement déchiffrer l'ensemble du code chiffré afin de l'étudier. C'est ce que font les fonctions Python suivantes : ```py import struct def rol32(x, n): return ((x << n) | (x >> (32 - n))) & 0xffffffff SM4_BOXES_TABLE = [ 0xd6,0x90,0xe9,0xfe,0xcc,0xe1,0x3d,0xb7,0x16,0xb6,0x14,0xc2,0x28,0xfb,0x2c, 0x05,0x2b,0x67,0x9a,0x76,0x2a,0xbe,0x04,0xc3,0xaa,0x44,0x13,0x26,0x49,0x86, 0x06,0x99,0x9c,0x42,0x50,0xf4,0x91,0xef,0x98,0x7a,0x33,0x54,0x0b,0x43,0xed, 0xcf,0xac,0x62,0xe4,0xb3,0x1c,0xa9,0xc9,0x08,0xe8,0x95,0x80,0xdf,0x94,0xfa, 0x75,0x8f,0x3f,0xa6,0x47,0x07,0xa7,0xfc,0xf3,0x73,0x17,0xba,0x83,0x59,0x3c, 0x19,0xe6,0x85,0x4f,0xa8,0x68,0x6b,0x81,0xb2,0x71,0x64,0xda,0x8b,0xf8,0xeb, 0x0f,0x4b,0x70,0x56,0x9d,0x35,0x1e,0x24,0x0e,0x5e,0x63,0x58,0xd1,0xa2,0x25, 0x22,0x7c,0x3b,0x01,0x21,0x78,0x87,0xd4,0x00,0x46,0x57,0x9f,0xd3,0x27,0x52, 0x4c,0x36,0x02,0xe7,0xa0,0xc4,0xc8,0x9e,0xea,0xbf,0x8a,0xd2,0x40,0xc7,0x38, 0xb5,0xa3,0xf7,0xf2,0xce,0xf9,0x61,0x15,0xa1,0xe0,0xae,0x5d,0xa4,0x9b,0x34, 0x1a,0x55,0xad,0x93,0x32,0x30,0xf5,0x8c,0xb1,0xe3,0x1d,0xf6,0xe2,0x2e,0x82, 0x66,0xca,0x60,0xc0,0x29,0x23,0xab,0x0d,0x53,0x4e,0x6f,0xd5,0xdb,0x37,0x45, 0xde,0xfd,0x8e,0x2f,0x03,0xff,0x6a,0x72,0x6d,0x6c,0x5b,0x51,0x8d,0x1b,0xaf, 0x92,0xbb,0xdd,0xbc,0x7f,0x11,0xd9,0x5c,0x41,0x1f,0x10,0x5a,0xd8,0x0a,0xc1, 0x31,0x88,0xa5,0xcd,0x7b,0xbd,0x2d,0x74,0xd0,0x12,0xb8,0xe5,0xb4,0xb0,0x89, 0x69,0x97,0x4a,0x0c,0x96,0x77,0x7e,0x65,0xb9,0xf1,0x09,0xc5,0x6e,0xc6,0x84, 0x18,0xf0,0x7d,0xec,0x3a,0xdc,0x4d,0x20,0x79,0xee,0x5f,0x3e,0xd7,0xcb,0x39, 0x48, ] BL32_KEY = (0x2078cc93, 0xb4c15038, 0xd5dc439c, 0x625f824) DAT_0e204058 = (0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269) def key_derivation_for_decrypt(blk_offset): """Get the derived key for the given block address""" assert blk_offset & 0xf == 0 FPU_v1 = [x ^ blk_offset for x in DAT_0e204058] # Compute FPU_v0 = sm4ekey(FPU_v0=BL32_KEY, FPU_v1) res = [x for x in BL32_KEY] for index in range(4): intval = res[3] ^ res[2] ^ res[1] ^ FPU_v1[index] intval_bytes = struct.unpack('BBBB', struct.pack('IIII', encrypted_block) # FPU_v0 = sm4e(data=FPU_v0, round_key=FPU_v1) res = [x for x in FPU_v0] for index in range(4): round_key = all_round_keys[index] intval = res[3] ^ res[2] ^ res[1] ^ round_key intval_bytes = struct.unpack('BBBB', struct.pack('IIII', *FPU_v0) return decrypted ``` Une fois le code de la machine virtuelle déchiffré, je peux le décoder selon le jeu d'instruction déterminé dans la section 4.5. 4.8. Encore du chiffrement -------------------------- Le programme /root/safe_02/decrypted_file utilise des SMC pour charger ce qui ressemble à une enclave chiffrée dans le BL32 : cette enclave contient du code exécuté dans un environnement confiné (dans une machine virtuelle propre), et l'utilisateur de l'enclave peut l'utiliser sans en connaître son contenu. La section précédente a décrit comment déchiffrer cette enclave (l'algorithme employé étant un dérivé de SM4) et la section 4.5 a décrit le jeu d'instruction utilisé. Le contenu de l'enclave chargée est organisé comme ceci : * 00000000..00001000 : du code (en fait jusqu'en 000001B9, suivi de zéros) * 00001000..00101000 : des données à l'apparence aléatoire * 00100020..00100040 : 32 octets passés en paramètre du programme * 00101000..00101010 : seize zéros Le code est assez bref. En le simplifiant et en considérant que les instructions relatives à l'horloge ne sont présentes que comme protection contre le débogage, il apparaît que le code implémente un algorithme de déchiffrement. Plus précisément, il déchiffre les 32 octets passés en paramètre, compare le résultat avec "pr.a.rfg.cnf.fv.snpvyr@ffgvp.bet" et renvoie un code indiquant le succès en cas d'égalité. Voici une traduction en Python de la fonction de déchiffrement : ```py import struct def decrypt(data): output = b'' for r12 in range(4): r0, r1, r2, r3 = struct.unpack('>HHHH', data[r12*8:r12*8 + 8]) r7 = 7 for r14 in range(0x1f, -1, -1): r4 = (r1 >> 8) & 0xff r6 = MEM[0x1000 + (r7 << 16) + ((r1 & 0xff) << 8) + r4] r7 = (r7 + 9) % 10 r5 = MEM[0x1000 + (r7 << 16) + (r4 << 8) + r6] r7 = (r7 + 9) % 10 r4 = MEM[0x1000 + (r7 << 16) + (r6 << 8) + r5] r7 = (r7 + 9) % 10 r6 = MEM[0x1000 + (r7 << 16) + (r5 << 8) + r4] r7 = (r7 + 9) % 10 new_r0 = (r6 << 8) + r4 new_r1 = r2 new_r2 = r3 if not ((r14 >> 3) & 1): new_r3 = (r14 + 1) ^ r0 ^ r1 else: new_r1 = (r14 + 1) ^ new_r0 ^ new_r1 new_r3 = r0 r0,r1,r2,r3 = new_r0,new_r1,new_r2,new_r3 output += struct.pack('>HHHH', r0, r1, r2, r3) return output ``` Cette fonction semble difficile à inverser au premier abord. Toutefois les données en mémoire (variable MEM) obéissent à une propriété intéressante : Si 0 <= r7 <= 9, 0 <= x <= 255 et 0 <= y <= 255, MEM[0x1000+(r7<<16)+(x<<8)+y] = x ^ MEM[0x1000+(r7<<16)+y] Cela permet d'inverser la fonction. Ensuite, il ne reste plus qu'à chiffrer ce qu'il faut : ```py >>> from binascii import hexlify >>> print(hexlify(encrypt("pr.a.rfg.cnf.fv.snpvyr@ffgvp.bet"))) b'acadaa8b5b55306fb3c6dfc3b2d1c80770084644225febd71a9189aa26ec740e' ``` Ceci est la clé d'emplacement 4 du keystore, permettant de déchiffrer safe_03 : # /root/safe_02/decrypted_file acadaa8b5b55306fb3c6dfc3b2d1c807 70084644225febd71a9189aa26ec740e Win # /root/tools/add_key.py acadaa8b5b55306fb3c6dfc3b2d1c807 70084644225febd71a9189aa26ec740e [+] Key with key_id 00000004 ok [+] Key added into keystore [+] Envoyez le flag SSTIC{acadaa8b5b55306fb3c6dfc3b2d1c807 70084644225febd71a9189aa26ec740e} à l'adresse challenge2019@sstic.org pour valider votre avancée [+] Container /root/safe_03/.encrypted decrypted to /root/safe_03/decrypted_file 5. Les preuves ============== Le fichier /root/safe_03/decrypted_file est une archive au format tar.gz qui contient les données d'applications Android. Dedans, un certain nombre de bases de données SQLite3 contiennent des informations personnelles du propriétaire du téléphone. Par exemple les contacts sont accessibles dans data/com.android.providers.contacts/databases/contacts2.db : $ sqlite3 contacts2.db sqlite> SELECT p.normalized_number, r.display_name FROM raw_contacts AS r LEFT JOIN phone_lookup AS p ON p.raw_contact_id = r._id; +33612345678|Mister X03 +33601020304|Mister X02 +33612345679|SuperPharma +33623232323|Traiteur SSTIC +33642424242|Kevin Leserveur +33689888786|Mister X01 Les derniers fichiers téléchargés, qui sont des images utilisées comme photo de profil, sont décrits dans data/com.android.providers.downloads/databases/downloads.db. Les emplacements d'où ont été téléchargées ces images sont les suivants : * * * * Le fichier le plus intéressant est celui qui contient les conversations SMS du suspect, data/com.google.android.apps.messaging/databases/bugle_db. Pour reconstituer les conversations, une manière qui donne un résultat lisible par un être humain consiste à assembler les contenus des tables `conversations`, `messages`, `participants` et `parts` en un fichier JSON. En voici le résultat. ```json { "conversation-1": { "name": "Mister X03", "participant_normalized_destination": "+33612345678", "participant_count": 1, "last_interactive_event_timestamp": "2019-04-01 09:01:18.109000", "participant_display_destination": "+33 6 12 34 56 78", "messages": { "msg-1": { "self_normalized": "+15555215554", "sender_normalized": "+33612345678", "sender_full_name": "Mister X03", "sent_timestamp": "2019-03-27 13:06:32", "received_timestamp": "2018-05-15 11:05:12.370000", "queue_insert_timestamp": "2018-05-15 11:05:12.542000", "message_protocol": 0, "sms_message_uri": "content://sms/1", "parts": { "1": { "timestamp": "2018-05-15 11:05:12.370000", "text": "Bonjour. Une action d'ampleur est prévue pendant la conférence SSTIC le 13 juin après-midi et dans la soirée. Pouvez-vous profiter du rassemblement d'un grand nombre d'experts pour essayer de les mettre hors d'état de nuire" } } }, "msg-8": { "self_normalized": "+15555215554", "sender_normalized": "+15555215554", "sent_timestamp": "2018-06-01 09:18:17.947000", "received_timestamp": "2018-06-01 09:18:17.947000", "queue_insert_timestamp": "2018-06-01 09:18:17.964000", "message_protocol": 0, "sms_message_uri": "content://sms/5", "retry_start_timestamp": "2018-06-01 09:18:17.947000", "parts": { "5": { "timestamp": "2018-06-01 09:18:17.947000", "text": "OK, l'opération suit son cours, je vous confirmerai les résultats." } } }, "msg-15": { "self_normalized": "+15555215554", "sender_normalized": "+15555215554", "sent_timestamp": "2018-06-13 23:52:30.418000", "received_timestamp": "2018-06-13 23:52:30.418000", "queue_insert_timestamp": "2018-06-13 23:52:30.442000", "message_protocol": 0, "sms_message_uri": "content://sms/9", "retry_start_timestamp": "2018-06-13 23:52:30.418000", "parts": { "11": { "timestamp": "2018-06-13 23:52:30.418000", "text": "Je vous confirme le semi-succès de l'opération \"Ça coule de source\". Je n'ai malheureusement pu avoir accès qu'à la moitié des préparations et je n'ai touché que la moitié du public." } } }, "msg-16": { "self_normalized": "+15555215554", "sender_normalized": "+33612345678", "sender_full_name": "Mister X03", "sent_timestamp": "2019-03-27 13:14:47", "received_timestamp": "2018-06-14 09:22:26.503000", "queue_insert_timestamp": "2018-06-14 09:22:26.537000", "message_protocol": 0, "sms_message_uri": "content://sms/10", "parts": { "12": { "timestamp": "2018-06-14 09:22:26.503000", "text": "OK, félicitations tout de même. À renouveler l'an prochain avec plusieurs infiltrés." } } }, "msg-22": { "self_normalized": "+15555215554", "sender_normalized": "+33612345678", "sender_full_name": "Mister X03", "sent_timestamp": "2019-03-27 13:18:59", "received_timestamp": "2019-03-29 12:40:24.928000", "queue_insert_timestamp": "2019-03-29 12:40:24.978000", "message_protocol": 0, "sms_message_uri": "content://sms/16", "parts": { "18": { "timestamp": "2019-03-29 12:40:24.928000", "text": "Bonjour, Cette année nous allons profiter du social event pour effectuer nos attaques ciblées. Merci de recruter en conséquence pour atteindre le maximum de personnes" } } }, "msg-23": { "self_normalized": "+15555215554", "sender_normalized": "+15555215554", "sent_timestamp": "2019-04-01 09:01:17.892000", "received_timestamp": "2019-04-01 09:01:17.892000", "queue_insert_timestamp": "2019-04-01 09:01:17.916000", "message_protocol": 0, "sms_message_uri": "content://sms/17", "retry_start_timestamp": "2019-04-01 09:01:17.892000", "parts": { "19": { "timestamp": "2019-04-01 09:01:17.892000", "text": "Bien noté, je pense cibler le stand des huîtres pour plus de discrétion et d'efficacité." } } } } }, "conversation-2": { "name": "SuperPharma", "participant_normalized_destination": "+33612345679", "participant_count": 1, "participant_display_destination": "+33 6 12 34 56 79", "messages": { "msg-2": { "self_normalized": "+15555215554", "sender_normalized": "+33612345679", "sender_full_name": "SuperPharma", "sent_timestamp": "2019-03-27 13:08:16", "received_timestamp": "2018-05-25 01:06:29.336000", "queue_insert_timestamp": "2018-05-25 01:06:29.414000", "message_protocol": 0, "sms_message_uri": "content://sms/2", "parts": { "2": { "timestamp": "2018-05-25 01:06:29.336000", "text": "Bonjour. Votre commande pour les produits \"DUPHALAC 1L\" (10 articles) a bien été expédiée. Vous pouvez suivre votre commande à l'adresse suivante : megasuivi.com/abXdSS" } } }, "msg-3": { "self_normalized": "+15555215554", "sender_normalized": "+15555215554", "sent_timestamp": "2018-05-25 01:06:29.337000", "received_timestamp": "2018-05-25 01:06:29.337000", "queue_insert_timestamp": "2018-05-25 01:06:29.588000", "message_protocol": 5, "mms_expiry": "0x7fffffffffffffff", "parts": {} }, "msg-4": { "self_normalized": "+15555215554", "sender_normalized": "+33612345679", "sender_full_name": "SuperPharma", "sent_timestamp": "2019-03-27 13:08:46", "received_timestamp": "2018-05-28 18:06:12.360000", "queue_insert_timestamp": "2018-05-28 18:06:12.399000", "message_protocol": 0, "sms_message_uri": "content://sms/3", "parts": { "3": { "timestamp": "2018-05-28 18:06:12.360000", "text": "Bonjour. Merci d'avoir réceptionné vos produits. Vous pouvez noter votre article à l'adresse superpharma.com/duphalac-1l.html" } } }, "msg-5": { "self_normalized": "+15555215554", "sender_normalized": "+15555215554", "sent_timestamp": "2018-05-28 18:06:12.361000", "received_timestamp": "2018-05-28 18:06:12.361000", "queue_insert_timestamp": "2018-05-28 18:06:12.488000", "message_protocol": 5, "mms_expiry": "0x7fffffffffffffff", "parts": {} } } }, "conversation-3": { "name": "Kevin Leserveur", "participant_normalized_destination": "+33642424242", "participant_count": 1, "last_interactive_event_timestamp": "2018-06-01 11:43:19.513000", "participant_display_destination": "+33 6 42 42 42 42", "messages": { "msg-6": { "self_normalized": "+15555215554", "sender_normalized": "+15555215554", "sent_timestamp": "2018-06-01 11:43:06.303000", "received_timestamp": "2018-06-01 11:43:06.303000", "queue_insert_timestamp": "2018-06-01 11:43:06.339000", "message_protocol": 0, "mms_expiry": "0x7fffffffffffffff", "parts": {} }, "msg-7": { "self_normalized": "+15555215554", "sender_normalized": "+15555215554", "sent_timestamp": "2018-06-01 11:43:19.214000", "received_timestamp": "2018-06-01 11:43:19.214000", "queue_insert_timestamp": "2018-06-01 11:43:19.248000", "message_protocol": 0, "sms_message_uri": "content://sms/4", "retry_start_timestamp": "2018-06-01 11:43:19.214000", "parts": { "4": { "timestamp": "2018-06-01 11:43:19.214000", "text": "Bonjour Kévin. Désolé finalement nous n'avons plus besoin de vous le 13/06/2018, car nous avons déjà trop de personnel. Nous vous recontacterons pour une future mission. \nAgence Interim'expert" } } } } }, "conversation-4": { "name": "Traiteur SSTIC", "participant_normalized_destination": "+33623232323", "participant_count": 1, "last_interactive_event_timestamp": "2018-06-10 12:11:26.745000", "participant_display_destination": "+33 6 23 23 23 23", "messages": { "msg-9": { "self_normalized": "+15555215554", "sender_normalized": "+15555215554", "sent_timestamp": "2018-06-10 08:52:11", "received_timestamp": "2018-06-10 08:52:11", "queue_insert_timestamp": "2018-06-10 08:52:11.027000", "message_protocol": 0, "mms_expiry": "0x7fffffffffffffff", "parts": {} }, "msg-10": { "self_normalized": "+15555215554", "sender_normalized": "+15555215554", "sent_timestamp": "2018-06-10 08:52:24.872000", "received_timestamp": "2018-06-10 08:52:24.872000", "queue_insert_timestamp": "2018-06-10 08:52:24.888000", "message_protocol": 0, "sms_message_uri": "content://sms/6", "retry_start_timestamp": "2018-06-10 08:52:24.872000", "parts": { "6": { "timestamp": "2018-06-10 08:52:24.872000", "text": "Bonjour, l'agence Interim'expert m'a contacté pour remplacer Kevin qui est souffrant pour le service du 13/06/2018 à la Halle Martenot. Pouvez-vous me préciser les horaires." } } }, "msg-13": { "self_normalized": "+15555215554", "sender_normalized": "+33623232323", "sender_full_name": "Traiteur SSTIC", "sent_timestamp": "2019-03-27 13:12:43", "received_timestamp": "2018-06-10 12:11:12.535000", "queue_insert_timestamp": "2018-06-10 12:11:12.573000", "message_protocol": 0, "sms_message_uri": "content://sms/7", "parts": { "9": { "timestamp": "2018-06-10 12:11:12.535000", "text": "Bonjour, merci d'être présent à partir de 9h30 le 13/06/2018. Vous serez affecté au poste des plats chauds." } } }, "msg-14": { "self_normalized": "+15555215554", "sender_normalized": "+15555215554", "sent_timestamp": "2018-06-10 12:11:26.545000", "received_timestamp": "2018-06-10 12:11:26.545000", "queue_insert_timestamp": "2018-06-10 12:11:26.564000", "message_protocol": 0, "sms_message_uri": "content://sms/8", "retry_start_timestamp": "2018-06-10 12:11:26.545000", "parts": { "10": { "timestamp": "2018-06-10 12:11:26.545000", "text": "Bien reçu. Bonne journée." } } } } }, "conversation-5": { "name": "Mister X02", "participant_normalized_destination": "+33601020304", "participant_count": 1, "last_interactive_event_timestamp": "2019-02-02 11:45:23.491000", "participant_display_destination": "+33 6 01 02 03 04", "messages": { "msg-17": { "self_normalized": "+15555215554", "sender_normalized": "+33601020304", "sender_full_name": "Mister X02", "sent_timestamp": "2019-03-27 13:15:29", "received_timestamp": "2019-02-02 11:45:04.469000", "queue_insert_timestamp": "2019-02-02 11:45:04.556000", "message_protocol": 0, "sms_message_uri": "content://sms/11", "parts": { "13": { "timestamp": "2019-02-02 11:45:04.469000", "text": "Bonjour, nous avons identifié un profil idéal pour l'usurpation d'identité dans le cadre du plan visant à focaliser l'attention des pentesteurs. Vous trouverez les identifiants du blog et du compte twitter sur notre outil interne." } } }, "msg-18": { "self_normalized": "+15555215554", "sender_normalized": "+15555215554", "sent_timestamp": "2019-02-02 11:45:23.308000", "received_timestamp": "2019-02-02 11:45:23.308000", "queue_insert_timestamp": "2019-02-02 11:45:23.323000", "message_protocol": 0, "sms_message_uri": "content://sms/12", "retry_start_timestamp": "2019-02-02 11:45:23.308000", "parts": { "14": { "timestamp": "2019-02-02 11:45:23.308000", "text": "Bien reçu, je lance la campagne tout de suite." } } }, "msg-19": { "self_normalized": "+15555215554", "sender_normalized": "+33601020304", "sender_full_name": "Mister X02", "sent_timestamp": "2019-03-27 13:16:37", "received_timestamp": "2019-02-13 16:50:04.515000", "queue_insert_timestamp": "2019-02-13 16:50:04.569000", "message_protocol": 0, "sms_message_uri": "content://sms/13", "parts": { "15": { "timestamp": "2019-02-13 16:50:04.515000", "text": "Bravo. Ce billet \"Pentest et pentesteur\" nous a paru un peu gros lors de la publication, mais cela semble marcher au-delà de nos espérances. Continuez à alimenter la conversation, nous avons déjà constaté une perte d'attention chez la plupart des gens visés." } } } } }, "conversation-6": { "name": "Mister X01", "participant_normalized_destination": "+33689888786", "participant_count": 1, "last_interactive_event_timestamp": "2019-03-15 22:07:20.626000", "participant_display_destination": "+33 6 89 88 87 86", "messages": { "msg-20": { "self_normalized": "+15555215554", "sender_normalized": "+33689888786", "sender_full_name": "Mister X01", "sent_timestamp": "2019-03-27 13:17:24", "received_timestamp": "2019-03-01 09:32:05.449000", "queue_insert_timestamp": "2019-03-01 09:32:05.517000", "message_protocol": 0, "sms_message_uri": "content://sms/14", "parts": { "16": { "timestamp": "2019-03-01 09:32:05.449000", "text": "Bonjour, comme chaque année, l'opération \"challenge SSTIC\" se prépare avec des attaques ciblées courant la première semaine d'avril. Pouvez-vous faire en sorte de focaliser l'attention à ce moment-là." } } }, "msg-21": { "self_normalized": "+15555215554", "sender_normalized": "+15555215554", "sent_timestamp": "2019-03-15 22:07:20.324000", "received_timestamp": "2019-03-15 22:07:20.324000", "queue_insert_timestamp": "2019-03-15 22:07:20.340000", "message_protocol": 0, "sms_message_uri": "content://sms/15", "retry_start_timestamp": "2019-03-15 22:07:20.324000", "parts": { "17": { "timestamp": "2019-03-15 22:07:20.324000", "text": "Mission accomplie, comme d'habitude la perspective d'une tournée de shooter au cactus a suffi à corrompre le CO et à chosir la date de publication du challenge. Pour être sûr que les experts sont toujours occupés, j'ai redirigé l'adresse 9e915a63d3c4d57eb3da968570d69e95@challenge.sstic.org vers votre boîte mail. Tant que vous ne voyez passer aucun mail, la voie est libre..." } } } } } } ``` Ces messages sont des preuves accablantes contre le suspect. Celui-ci est impliqué dans l'affaire de l'intoxication alimentaire qui a frappé les participants du SSTIC en juin 2018, dans des attaques récentes visant à détourner l'attention des chercheurs en sécurité informatique, et dans la préparation d'une compromission du stand des huîtres du prochain social event. Afin de mettre fin à l'opération visant à détourner l'attention des experts, j'ai envoyé un mail à l'adresse mentionnée dans le dernier SMS. Cela a permis à mes collègues et moi-même de se concentrer entièrement sur l'opération "LockedShields 2019", qui fut un franc succès. Toutefois les messages montrent aussi que le suspect ne semble être qu'un exécutant dans ces opérations, à moins qu'il n'ait cherché à brouiller les pistes en forgeant des messages. En effet, un certain nombre de messages reçus indiquent une date d'envoi le 27 mars 2019 aux alentours de 13h, mais une réception bien antérieure, en 2018. De plus, des MMS étaient aussi présents dans les messages, mais ceux-ci ne sont pas disponibles : ces messages sont indiqués comme étant vides et avec un champ `mms_expiry`. Afin de permettre aux enquêteurs d'accéder directement aux messages déchiffrés et de poursuivre leur travail, voici le contenu du keystore qui permet de déverrouiller toutes les couches de chiffrement. $ xxd -a keystore 00000000: 4b45 5900 0000 0000 0100 0000 0001 0000 KEY............. 00000010: d072 dd3f 639c 2d0e 1ff6 9ab9 6578 2f66 .r.?c.-.....ex/f 00000020: 8d7b ffac 766a eeab 5da8 4ae3 73f8 c0dc .{..vj..].J.s... 00000030: aa18 c6bd cbd5 51bb 012f 42af c0f3 fc4a ......Q../B....J 00000040: ad43 8c21 2ff8 a4e5 9705 3cd6 0b54 ac48 .C.!/.....<..T.H 00000050: 4714 fbae 059c 97f8 138c ede9 a800 60d3 G.............`. 00000060: c816 288f f2c7 7d82 081e 586f 73ee 78e0 ..(...}...Xos.x. 00000070: 6f69 d7d5 7596 acf1 959d 6f3e d00d 2cdd oi..u.....o>..,. 00000080: b11d ecf2 5a37 9def 4dce 3b26 4ad7 e819 ....Z7..M.;&J... 00000090: efbe adde 0000 0000 0000 0000 0000 0000 ................ 000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 00000110: 4b45 5900 0000 0000 0200 0000 2000 0000 KEY......... ... 00000120: 5353 5449 437b 6139 3437 6436 3938 3063 SSTIC{a947d6980c 00000130: 6366 3762 3837 6362 3864 3763 3234 367d cf7b87cb8d7c246} 00000140: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 00000220: 4b45 5900 0000 0000 0200 0000 2000 0000 KEY......... ... 00000230: 5fb3 a83d 1fd9 7137 0760 19ad 6e96 c6a3 _..=..q7.`..n... 00000240: 66fb 6b32 618d 162e 00cd ee9b ad42 7a8a f.k2a........Bz. 00000250: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 00000330: 4b45 5900 0000 0000 0200 0000 2000 0000 KEY......... ... 00000340: 5353 5449 437b 4477 3472 665f 564d 5f31 SSTIC{Dw4rf_VM_1 00000350: 735f 636f 306c 5f69 736e 5f74 5f49 747d s_co0l_isn_t_It} 00000360: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 00000440: 4b45 5900 0000 0000 0200 0000 2000 0000 KEY......... ... 00000450: acad aa8b 5b55 306f b3c6 dfc3 b2d1 c807 ....[U0o........ 00000460: 7008 4644 225f ebd7 1a91 89aa 26ec 740e p.FD"_......&.t. 00000470: 0000 0000 0000 0000 0000 0000 0000 0000 ................ * 00000540: 0000 0000 0000 0000 0000 0000 0000 0000 ................ Conclusion ========== Le téléphone de l'individu suspect utilise de multiples couches de chiffrement qui nécessitent des connaissances dans des domaines divers pour y répondre. Au cours de l'analyse que j'ai menée, j'ai beaucoup appris, en particulier sur le fonctionnement de TrustZone et de DWARF. Je remercie ainsi les concepteurs et organisateurs du challenge, qui ont réalisé un travail de très grande qualité. Je remercie aussi mes collègues, en particulier Grumpy qui a passé du temps à partager ses connaissances encyclopédiques des téléphones sécurisés (et m'a permis de gagner du temps sur l'analyse de TrustZone). Il m'a aussi prodigué en une journée suffisamment de connaissances pour pouvoir utiliser Ghidra avec aisance. Je remercie également les personnes qui ont permis de rendre ce merveilleux outil disponible. Il est bien meilleur qu'IDA lorsqu'il s'agit de décoder de l'ARM64 ou de travailler sur plusieurs programmes qui partagent des structures. Plus largement, je remercie toutes les personnes qui ont créé, écrit, développé et maintenu à jour les outils que j'ai utilisés. Le code de QEMU et de TF-A sont étonnamment lisibles et bien documentés, pour des projets avec une telle importance. L'ensemble des programmes que j'ai écrits pour trouver l'adresse électronique finale sera disponible après la clôture du challenge sur .