/==============================================================\
| |
| 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
.