Etape 1

Après analyse du fichier rom.bin, on comprend que cette étape consiste à trouver un exposant de déchiffrement RSA qui permettra de déchiffrer une clé AES.

Si l’on saisit un exposant au hasard, on constate que la ROM affiche les caractères "+-" pour les bits à 1 et le caractère "-" pour les bits à 0 de l’exposant saisi, en commençant par les bits de poids faible.

La présence du fichier power_consumption.npz nous met sur la voie de l’attaque à implémenter et la page https://wiki.newae.com/Tutorial_B11_Breaking_RSA décrit un cas ressemblant fortement au nôtre ainsi qu’un exemple de script python permettant d’obtenir une clé RSA à partir de traces similaires.

En prenant ce script en exemple et en tâtonnant pour ajuster les bons paramètres on finit par obtenir le bon exposant de déchiffrement.

Le script python power.py donne l’exposant :

23d87cdf97bb95abe6273c384190c765f552ab86f6de30a8db74435c95e6e3138f54af689812d8f9359cf0f4d453a0c11ec68ce470216c09e74c8947adaf23e902415d61ddf2c0ffe459cbb40f7de42bdb7cd14093100a570e8c29819765e2d8d276f86471b52ac29aa2ce2bb72cd45006279e82bec253ae9675fe45824f6001

On le saisit à l’invite de la ROM, ce qui valide l’étape et nous donne la clé AES suivante une fois déchiffrée :

SSTIC{a947d6980ccf7b87cb8d7c246}

La ROM utilise cette clé AES pour déchiffrer le contenu de flash.bin et booter un Linux.

Déchiffrement des fichiers BL

Les commandes suivantes permettent d’obtenir les différents fichiers présents dans flash.bin :

K=$(echo -n SSTIC{a947d6980ccf7b87cb8d7c246}|xxd -c32 -p)

iv=$(dd if=flash.bin skip=$((0xd8)) count=$((0x10)) ibs=1 status=none|xxd -p)
dd if=flash.bin skip=$((0xe8)) ibs=1 count=$((0x9430)) status=none|openssl enc -d -aes-256-cbc -out bl2.bin -iv "${iv}" -K "${K}" -nopad

iv=$(dd if=flash.bin skip=$((0x9508)) count=$((0x10)) ibs=1 status=none|xxd -p)
dd if=flash.bin skip=$((0x9518)) ibs=1 count=$((0x90a0)) status=none|openssl enc -d -aes-256-cbc -out bl31.bin -iv "${iv}" -K "${K}" -nopad

iv=$(dd if=flash.bin skip=$((0x17918)) count=$((0x10)) ibs=1 status=none|xxd -p)
dd if=flash.bin skip=$((0x17928)) ibs=1 status=none|openssl enc -d -aes-256-cbc -out bl33.bin -iv "${iv}" -K "${K}" -nopad

On peut extraire les fichiers kernel et root.cpio.gz du bl33.bin :

dd if=bl33.bin skip=$((0x220000)) ibs=1 count=$((0xa00000)) status=none of=kernel
dd if=bl33.bin skip=$((0xc20000)) ibs=1 status=none of=root.cpio.gz

Il existe également dans flash.bin un fichier bl32.bin mais il n’est déchiffrable qu’après avoir obtenu le flag de l’étape 3. bl2.bin, bl31.bin et bl32.bin sont des images obtenues à partir de https://github.com/ARM-software/arm-trusted-firmware avec quelques modifications.

bl33.bin est un firmware UEFI obtenu à partir de https://github.com/tianocore/edk2.

Adresses de chargement

Etape 2

On dispose d’un script python get_safe1_key.py et du fichier schematics.png décrivant un schéma logique. Il suffit de compléter le script python avec les données du schéma. On dispose de deux vecteurs de test permettant de valider l’implémentation. Un bruteforce de maximum 16^8 possibilités permet de trouver le flag de l’étape.

Le script python get_safe1_key_part1.py donne les valeurs de départ du bruteforce. Ces valeurs sont réinjectées dans le fichier bf.c qui effectue le bruteforce. Le résultat du bruteforce est à son tour réinjecté dans le script python get_safe1_key_part2.py qui nous donne le flag :

SSTIC{5fb3a83d1fd97137076019ad6e96c6a366fb6b32618d162e00cdee9bad427a8a}

Etape 3

On remarque dans QEMU que le programme decrypted_file met significativement plus de temps à afficher Not good si l’on saisit un flag de 32 caractères, par exemple en comparant les deux lignes de commandes suivantes :

# time ./decrypted_file 00112233445566778899AABBCCDDEEFF
Not good
real	0m 0.33s
user	0m 0.32s
sys	0m 0.00s
# time ./decrypted_file 0
Not good
real	0m 0.03s
user	0m 0.02s
sys	0m 0.00s

En lançant le programme avec qemu-aarch64 et non plus dans qemu-system-aarch64, on peut comparer les traces d’exécution. Au préalable on génère une toolchain aarch64 en utilisant https://github.com/crosstool-ng/crosstool-ng. Par défaut celle-ci sera installée dans ~/x-tools. Cette toolchain a l’avantage d’avoir les symboles de debug.

Les deux commandes suivantes permettent de générer les traces d’exécution :

i="00112233445566778899AABBCCDDEEFF" ; QEMU_LD_PREFIX=~/x-tools/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot qemu-aarch64 -d in_asm -D "qemu_$i" decrypted_file "$i"
i="0" ; QEMU_LD_PREFIX=~/x-tools/aarch64-unknown-linux-gnu/aarch64-unknown-linux-gnu/sysroot qemu-aarch64 -d in_asm -D "qemu_$i" decrypted_file "$i"

Un simple vimdiff entre les deux fichiers qemu_* obtenus met en évidence des adresses qui se trouvent à l’intérieur de la fonction execute_stack_op, définie dans le fichier gcc-8.3.0/libgcc/unwind-dw2.c. Cette fonction permet d’exécuter un programme DWARF.

On arrive dans cette fonction avec les paramètres op_ptr=0x403213 et op_end=0x403216.

L’instruction DWARF en 0x403213 effectue un DW_OP_skip qui fait pointer op_ptr sur 0x400258.

Le script python disassemble.py permet de désassembler le programme en 0x400258. Le code de la fonction execute_stack_op a servi de référence pour implémenter ce désassembleur.

Une fois le code désassemblé obtenu, il a fallu effectuer plusieurs simplifications pour le rendre compréhensible. Le programme obtenu est inversible, il suffit de coder le programme inverse pour retrouver le flag.

L'implémentation du programme et de son inverse se trouvent dans le script python prog_inv.py. L'exécution de ce script donne le flag :

SSTIC{Dw4rf_VM_1s_co0l_isn_t_It}

Déchiffrement du BL32

Une fois ce flag obtenu, on peut déchiffrer le Secure OS, bl32.bin, avec les commandes suivantes :

K=$(echo -n SSTIC{Dw4rf_VM_1s_co0l_isn_t_It}|xxd -c32 -p)

iv=$(dd if=flash.bin skip=$((0x125a8)) count=$((0x10)) ibs=1 status=none|xxd -p)
dd if=flash.bin skip=$((0x125b8)) ibs=1 count=$((0x5370)) status=none|openssl enc -d -aes-256-cbc -out bl32.bin -iv "${iv}" -K "${K}" -nopad

Adresse de chargement

Celui-ci est chargé à l’adresse 0x0e200000.

Etape 4

Notes préliminaires

QEMU

Pour cette étape, QEMU a été patché afin d’afficher le contenu des registres z* et p*. Aussi, l’utilisation des arguments -d in_asm,cpu,nochain -singlestep permet de suivre de manière assez précise les instructions exécutées.

Module noyau

La commande suivante nous donne l’adresse de chargement de sstic.ko :

# cat /proc/modules 
sstic 16384 0 - Live 0xffff000008590000

Ainsi dans gdb on peut faire :

add-symbol-file sstic.ko 0xffff000008590000

Description du programme

Le programme decrypted_file fait d'abord deux appels ioctl :

Puis ensuite une boucle effectuant deux autres ioctls :

On peut penser au chargement d’un programme et à son exécution.

L’analyse de la fonction sstic_ioctl() du fichier sstic.ko nous permet de voir que :

Un suivi pas à pas permet de comprendre ce qui se passe. Nous sommes en présence d’une VM :

Communication entre les fichiers BL

Les deux documents suivants permettent de comprendre les appels SMC :

La valeur passée en X0 est un function identifier. En comparant le contenu des binaires bl31.bin et bl32.bin avec le code de arm-trusted-firmware, on comprend que les services suivants sont modifiés dans bl31.bin :

Function identifiers du OEM Service

Dans bl31.bin, la lecture de FUN_0e031034 nous permet de comprendre les function identifiers suivants du OEM Service :

Function identifier Action
0x83010001 Lecture d’un registre de la VM
0x83010002 Ecriture d’un registre de la VM
0x83010003 Stocke une portion de la clé dans les registres inutilisés suivants : fpexc32_el2, dacr32_el2, ifsr32_el2 et sder32_el3. Le contenu de ces registres AARCH64 est utilisé pour chiffrer/déchiffrer le contenu des registres de la VM
0x83010004 Copie le programme de la VM
0x83010005 Lit une clé dans le keystore
0x83010006 Ajoute une clé au keystore
0x83010011 Décrémente un registre de la VM
0x83010012 Additionne deux registres de la VM
0x83010013 Soustrait deux registres de la VM
0x83010016 Effectue un XOR entre deux registres de la VM
0x83010022 Additionne un registre de la VM et une valeur immédiate
0x83010023 Soustrait une valeur immédiate d’un registre de la VM
0x83010027 Effectue un AND entre un registre de la VM et une valeur immédiate
0x83010028 Effectue un GOTO vers une valeur immédiate

Function identifiers du Test Secure-EL1 Payload Dispatcher

Dans bl32.bin, la lecture de FUN_0e200c08 nous permet de comprendre les function identifiers suivants du Test Secure-EL1 Payload Dispatcher :

Function identifier Action
0xf2005001 Lit l’instruction courante de 3 octets du programme de la VM
0xf2005002 Exécute l’instruction courante de 3 octets du programme de la VM
0xf2005003 Stocke 4 octets de la clé chiffrés avec SM4. Cet SMC est effectué 8 fois par sstic.ko pour les 32 octets de la clé

Programme de la VM

Le programme sm4.c déchiffre le programme de la VM afin de pouvoir le désassembler.

Traduction des opcodes

C’est la fonction FUN_0e2005a4 qui traduit les opcodes. La grosse subtilité est que FUN_0e202610 et FUN_0e2025a4 permettent de changer l’état d’exécution et exécutent du code AARCH32. Ce code AARCH32 fait des appels SVC dont le handler se trouve en 0xe203600 dans bl32.bin. La différence entre les fonctions FUN_0e202610 et FUN_0e2025a4 est que FUN_0e202610 switche en plus en instructions THUMB si le registre x5 passé en paramètre est nul.

La fonction FUN_0e202610 est utilisée avec les instructions 32 bits en 0xe205000 pour effectuer l’instruction “jmp 0x%x si r_i = 0”.

Code désassemblé

On peut alors écrire un désassembleur du programme de la VM, c'est le script python disassemble.py.

Anti-debug

Clock anti-debug

On remarque dans le code désassemblé deux instructions surprenantes :

*(0x9010000+8) = 0

et

jmp 0x%x si (*(0x9010000) & 0xffffffff) > 5

En faisant un grep de “9010000” dans le code de qemu on tombe sur le résultat suivant :

./hw/arm/virt.c:    [VIRT_RTC] =                { 0x09010000, 0x00001000 },

On a affaire à une RTC (Real-time clock) et la deuxième instruction teste si le temps écoulé est supérieur à 5 secondes, c’est donc un anti-debug.

QEMU anti-debug

Il y a une autre fonction anti-debug au début du BL32 qui teste si QEMU est lancé avec les arguments de debugging, c’est la fonction FUN_0e201650. Si cette fonction détecte un debugger, le mapping du programme de la VM est modifié. Il suffit de patcher la valeur de retour de cette fonction pour désactiver l’anti-debug.

Inversion du programme de la VM

On réécrit l’algorithme en python, avant de l’inverser. L'implémentation du programme et de son inverse se trouvent dans le script python prog_inv.py. L'exécution de ce script donne le flag :

SSTIC{acadaa8b5b55306fb3c6dfc3b2d1c80770084644225febd71a9189aa26ec740e}

Adresse mail de validation

Le fichier decrypted_file obtenu avec le dernier flag est un fichier .tar.gz. Une fois décompressé, un simple grep dans le dossier data obtenu permet de faire apparaître l'adresse de validation du challenge :

9e915a63d3c4d57eb3da968570d69e95@challenge.sstic.org