Analyse du contournement de KTRR

Magazine
Marque
MISC
Numéro
102
|
Mois de parution
mars 2019
|
Domaines


Résumé

En mars 2018, le chercheur Luca Todesco (@qwertyoruiop) a publié le code de son exploit qui contourne le mécanisme de protection d'intégrité du noyau iOS sur l'iPhone 7. Ce contournement affecte les versions d'iOS 10 et 10.1. Bien qu'il ne permette pas de modifier directement le code du noyau, il rend possible le changement des valeurs du segment de données constantes, avec les conséquences que nous expliquerons.


Body

 

Introduction

Apple profite souvent de la sortie des nouveaux modèles d'iPhone pour renforcer la sécurité de son système. Avec l'arrivée de l'iPhone 7, un nouveau mécanisme de protection d'intégrité du noyau a été ajouté. Appelé KTRR (vraisemblablement Kernel Text Region Range), il succède à l'ancien mécanisme Watchtower (aussi appelé KPP) présent sur les iPhones 5S jusqu'au 6S sous iOS 9 et supérieur. Le rôle d'un tel mécanisme est d'empêcher la modification de données dans les segments du noyau que l'on souhaite protéger. C'est une mesure préventive, qui permet de restreindre le champ d'action d'un attaquant qui est capable d'aller lire et écrire dans la mémoire du noyau, après l'avoir exploité. Dans le cas d'un jailbreak, après avoir obtenu la capacité d'aller lire et écrire dans la mémoire du noyau, la voie la plus rapide pour parvenir à ses fins est d'aller y modifier code et données. Avec un mécanisme comme KTRR, cela n'est plus possible. Afin de comprendre la manière dont fonctionne l'exploit, nous expliquons dans un premier temps le fonctionnement de KTRR. Nous verrons le déroulement de l'exploit, avant de nous intéresser aux parties marquantes.

1. Prérequis

Pour comprendre les explications qui suivent, il est fortement recommandé au lecteur d'être familier avec l'architecture ARMv8-A [1] et plus particulièrement avec le mécanisme de traduction d'adresses [2]. Les liens vers la documentation correspondante ainsi que le code [3] de l'exploit sont disponibles à la fin de l'article.

2. Principe de fonctionnement de KTRR

Deux nouveaux éléments font leur apparition pour mettre en place le mécanisme. Le premier est une MMIO (Memory Mapped I/O) nommée RoRgn (Read-only Region) qui empêche l'accès en écriture à une zone de la mémoire physique. Les registres de cette MMIO sont situés dans la structure AMCC (Apple's Memory Cache Controller) comme le montre le code du noyau [4] :

//#if defined(KERNEL_INTEGRITY_KTRR)

#define rMCCGEN        (*(volatile uint32_t *) (amcc_base + 0x780))

#define rRORGNBASEADDR (*(volatile uint32_t *) (amcc_base + 0x7e4))

#define rRORGNENDADDR  (*(volatile uint32_t *) (amcc_base + 0x7e8))

#define rRORGNLOCK     (*(volatile uint32_t *) (amcc_base + 0x7ec))

//#endif

Une fois que les adresses de base et de fin sont initialisées, les registres sont verrouillés en plaçant 1 dans rRORGNLOCK. Le second élément apparu est le groupe des registres KTRR : ARM64_REG_KTRR_LOWER_EL1, ARM64_REG_KTRR_UPPER_EL1 et ARM64_REG_KTRR_LOCK_EL1. Ils sont présents pour limiter l'ensemble des adresses exécutables en EL1 (niveau de privilège du noyau). De la même manière que pour RoRgn, en mettant 1 dans le registre ARM64_REG_KTRR_LOCK_EL1, les deux autres registres sont verrouillés. Après le verrouillage des registres RoRgn et KTRR, l'agencement des segments mémoire du noyau est le suivant :

 

Mapping

Segment

Protection

Accès possible

r--/r--

__PRELINK_TEXT

RoRgn + KTRR

r--/r--

r-x/r-x

__PLK_TEXT_EXEC

RoRgn + KTRR

r-x/r-x

r--/r--

__PLK_DATA_CONST

RoRgn + KTRR

r--/r--

r-x/r-x

__TEXT

RoRgn + KTRR

r-x/r-x

rw-/rw-

__DATA_CONST

RoRgn + KTRR

r--/r--

r-x/r-x

__TEXT_EXEC

RoRgn + KTRR

r-x/r-x

rw-/rw-

__LAST

RoRgn

r--/r--

rw-/rw-

__KLD

 

rw-/rw-

rw-/rw-

__DATA

 

rw-/rw-

rw-/rw-

__BOOTDATA

 

rw-/rw-

r--/r--

__LINKEDIT

 

r--/r--

rw-/rw-

__PRELINK_DATA

 

rw-/rw-

rw-/rw-

__PRELINK_INFO

 

rw-/rw-

Il faudrait mettre en gras (ou le style prévus) le texte de la première rangée du tableau (Mapping, Segment, Protection et Accès possible).

Le segment __LAST est particulier : il inclut la section __pinst (vraisemblablement protected ou privileged instructions) qui contient les instructions permettant de changer la valeur de certains registres système, comme TTBR1_EL1 et SCTLR_EL1. Ce segment n'étant pas compris dans l'intervalle défini par les registres KTRR, on ne peut pas y accéder en exécution lorsque la MMU est active (car il faut être dans l’intervalle défini par KTRR pour être exécutable). Ainsi, le noyau définit la valeur de ces registres avant l'activation de la MMU.

3. Description de la vulnérabilité

Lors de la sortie de veille du CPU, une structure accessible en écriture depuis le noyau (elle ne réside pas dans un segment protégé par RoRgn) permet de détourner le flot d'exécution. De plus, l'intervalle défini par les registres KTRR à ce moment-là n'est pas correct, car il inclut __LAST.pinst. Il est alors possible de créer un mapping alternatif pour les segments de données accessibles en lecture seule. L'accès en écriture à ce segment peut faciliter le jailbreak et altérer le fonctionnement du système iOS, chose qu'Apple ne souhaite en aucun cas permettre.

4. Déroulement de l'exploit

Pour mieux saisir le contexte d'exécution de l'exploit ainsi que son fonctionnement, il est possible de se référer à la Figure 1 qui donne une vision plus globale. Il est important de noter qu'un premier exploit visant le noyau est nécessaire (première étape), avant de réaliser celui de KTRR.

 

deroulement

 

Figure 1 : Contexte et séquence de l’exploit

4.1 Phase préparatoire

Avant d'exécuter la charge pour contourner KTRR, plusieurs opérations sont réalisées depuis l'espace utilisateur pour permettre à l'exploit de correctement fonctionner. On peut notamment citer :

  • l'allocation de pages de mémoire pour y placer les gadgets, ainsi que les données utilisées lors de l'exploit ;
  • la préparation de la table de traduction TTBR0_EL1 (modification et ajouts de quelques entrées dans la table de niveau 3) qui sera utilisée par le noyau lors du reset et plus particulièrement, lors de la phase d’exploitation ;
  • la création d'une fausse table de traduction (celle pointée par TTBR1_EL1) visant à remplacer l'originale.

4.2 Lancement de l'exploit

Si l’on s’intéresse au code exécuté lors de la sortie de veille du CPU, on remarque que la fonction common_start utilise la structure _const_boot_args pour convertir l'adresse physique de la fonction arm_init_tramp en adresse virtuelle avant de s'y rendre :

// Load VA of the trampoline

adrp    x0, arm_init_tramp@page

add     x0, x0, arm_init_tramp@pageoff

add     x0, x0, x22 ; x0 += _const_boot_args.BA_VIRT_BASE

sub     x0, x0, x23 ; x0 -= _const_boot_args.BA_PHYS_BASE

// Branch to the trampoline

br      x0

Cette structure à la particularité d’être présente dans un segment accessible en écriture. Dès lors que l’on est en possession d’une primitive d'écriture dans la mémoire du noyau (cf. étape 1 de la Figure 1), il sera possible d’y modifier ses éléments. Si l'on regarde le code ci-dessus, cela signifie que l'on contrôle les registres x22 et x23. Les opérandes du calcul d'obtention de x0 étant connus, la modification du registre x22 ou x23 permettra de rediriger le flot d'exécution du noyau.

C’est exactement de cette manière que le lancement procède :

swritewhere = loadstruct + 8;

swritewhat = (phyzb+gadget0_off)-(G(TTBRMAGIC_BX0)+slide-gVirtBase);

/*...*/

WriteAnywhere64(swritewhere, swritewhat);

Le code ci-dessus est une partie du code exécuté depuis l'espace utilisateur. Après la phase préparatoire, cette écriture permet d'amorcer l'exploit. La fonction WriteAnywhere64 est la primitive d'écriture dans la mémoire du noyau. La variable loadstruct contient l'adresse de la structure _const_boot_args. À son offset 8 se trouve BA_VIRT_BASE (la base virtuelle du noyau).

L'écriture dans loadstruct + 8 avec la fonction WriteAnywhere64 va remplacer _const_boot_args.BA_VIRT_BASE par une valeur contrôlée. Dans notre cas, le registre x0 contiendra phyzb+gadget0_off. La MMU étant active, la traduction s'effectuera sous le contrôle de l'attaquant (car c'est TTBR0_EL1 qui sera utilisé) et dirigera le branchement vers le premier gadget. Il ne reste plus que la sortie de mise en veille (le reset) de l'appareil survient pour que l'exploit se lance.

Il faut cependant faire attention, car il y a un autre endroit où _const_boot_args.BA_VIRT_BASE est utilisé :

    // Set up the exception vectors

    adrp    x0, EXT(ExceptionVectorsBase)@page

    add     x0, x0, EXT(ExceptionVectorsBase)@pageoff

    add     x0, x0, x22 ; x0 += _const_boot_args.BA_VIRT_BASE

    sub     x0, x0, x23 ; x0 -= _const_boot_args.BA_PHYS_BASE

    MSR_VBAR_EL1_X0

La valeur calculée de VBAR_EL1 sera fausse, il faudra donc penser à changer la valeur erronée de VBAR_EL1 par la valeur d'origine.

4.3 Exploitation

Toute la phase d'exploitation se fait en JOP et ROP (Jump-Oriented Programming et Return-Oriented Programming), car au moment où le flot d'exécution est redirigé, KTRR est actif. Ainsi, seul le code du noyau est exécutable. Le point le plus important pour l'exploit est la réalisation d'une copie de la table de traduction d'origine du noyau, et de faire en sorte que la copie qui est modifiable remplace l'originale. On peut ainsi re-mapper en lecture et écriture les segments de données comme __DATA_CONST. Après cela, l'exploit rend la main au noyau iOS pour que le reset reprenne le cours normal de son exécution. Le Figure 2 montre en rouge la partie correspondant à l'exploitation.

 

exploit

 

Figure 2 : Exécution de l’exploit lors du reset

5. Quelques points techniques d'intérêt

5.1 Re-mapping des instructions protégées

L'opération suivante a lieu lors de la phase préparatoire. Même avec le mauvais intervalle des registres KTRR, les instructions du segment __LAST ne sont pas encore exécutables, car elles ne sont accessibles qu'en lecture seule (voir le tableau sur le mapping des segments). Pour invoquer l'instruction capable de changer le registre TTBR1_EL1, il faut donc re-mapper la page en lecture et exécution (rappelons que l'écriture n'est pas possible, car le segment __LAST est inclus dans l'intervalle défini par RoRgn). Cela est réalisé avec la table de traduction de TTBR0_EL1, qui est modifiable. En effet, un branchement vers une adresse physique translatée par TTBR0_EL1 nous amènera à l'instruction souhaitée.

5.2 Détails sur l'exécution de l'exploit

Le premier enchaînement de gadgets à être exécuté va servir à allouer de la place sur la pile, pour y copier une chaîne de ROP avec le gadget suivant :

    LDR             X8, [X8,#0x10] ; memmove + 4 // pour contrôler l'adresse de retour

    ADD             X0, SP, #8

    BLR             X8 ; memmove((SP + 8), chain + 8, ((0x1F * 80) - 8))

Une fois cette chaîne copiée, le flot d'exécution sera redirigé vers celle-ci comme le montre l'épilogue de la fonction memmove :

    LDP             X29, X30, [SP],#0x10

    RET

À partir de là, plusieurs opérations seront effectuées par la chaîne de ROP. Elle commence tout d’abord par restaurer VBAR_EL1, car le noyau s’est basé sur la valeur contenue dans _const_boot_args.BA_VIRT_BASE pour calculer l’adresse virtuelle. Après cela, la fonction __pinst_set_ttbr1 (présente dans le segment _LAST) est appelée pour changer la valeur du registre TTBR1_EL1. Ces deux tâches achevées, il faut s’occuper de rendre la main au noyau. Pour que la reprise puisse se faire normalement, le registre LR doit pointer vers la bonne fonction d’initialisation (arm_init_cpu ou arm_init_idle_cpu). En effet, lors du reset, suivant le type de veille dont on sort, la gestion sera déléguée à l’une des deux fonctions. Pour faire ce choix, la structure cpu_data est utilisée. On y trouve à l'offset 0x130 le CPU_RESET_HANDLER qui indique la fonction devant être lancée par arm_init_tramp une fois l’exploit terminé.

6 Patch

À partir d'iOS 10.2, l'intervalle attribué aux registres KTRR lors du reset CPU est identique à celui du démarrage et donc correct :

// program and lock down KTRR

// subtract one page from rorgn_end to make pinst instructions NX

 

msr     ARM64_REG_KTRR_LOWER_EL1, x17

sub     x19, x19, #(1 << (ARM_PTE_SHIFT-12)), lsl #12

msr     ARM64_REG_KTRR_UPPER_EL1, x19

mov     x17, #1

msr     ARM64_REG_KTRR_LOCK_EL1, x17

De plus, la structure qui permettait de rediriger le flot d'exécution n'est plus accessible en écriture, car elle réside dans le segment __DATA_CONST.

Conclusion

Nous avons vu comment la protection mise en place par KTRR a pu être partiellement contournée. Un tel exploit montre qu'il est devenu assez difficile de contourner les nouveaux mécanismes de protection, cela malgré les erreurs d'implémentation. Nul doute qu'avec les nouvelles sécurités apparues sur les derniers iPhones, les exploits publics se feront de plus en plus rares.

Remerciements

Je souhaite remercier Fred Raynal pour ses relectures et les conseils prodigués. Je remercie également Jean-Baptiste Bédrune, Joffrey Guilbon et Cédric Tessier pour leurs précieux retours, ainsi que Luca Todesco pour avoir publié un exploit des plus intéressants.

Références

[1] Manuel de référence de l’architecture ARMv8-A : https://static.docs.arm.com/ddi0487/da/DDI0487D_a_armv8_arm.pdf

[2] Traduction d’adresses sous ARMv8-A : https://static.docs.arm.com/100940/0100/armv8_a_address%20translation_100940_0100_en.pdf

[3] Code de l’exploit : http://yalu.qwertyoruiop.com/y7.txt

[4] Définition de RoRgn dans le noyau XNU : https://opensource.apple.com/source/xnu/xnu-4570.1.46/pexpert/pexpert/arm64/AMCC.h.auto.html

 

Sur le même sujet

Investigation numérique sous macOS / HFS+

Magazine
Marque
MISC
Numéro
107
|
Mois de parution
janvier 2020
|
Domaines
Résumé

La démocratisation de l’écosystème Apple s'est réalisée à tous les niveaux (informatique de gestion, téléphone portable, wearables...) et cela n'a pas été sans éveiller l'intérêt des développeurs de logiciels malveillants. Les équipes de réponse à incident ont donc dû s'adapter afin de pouvoir apporter leur expertise dans les meilleures conditions.

Le coût de la rétro-ingénierie du silicium

Magazine
Marque
Hackable
Numéro
32
|
Mois de parution
janvier 2020
|
Domaines
Résumé

La rétro-ingénierie matérielle est une pratique qui a longtemps été réservée aux états et aux industriels, et ce, en grande partie à cause des coûts engendrés. Cependant aujourd'hui, chaque personne ayant un bagage technique suffisant peut pratiquer cet art sans avoir à dépenser des sommes astronomiques, on peut alors précisément se poser la question du coût matériel pour différentes configurations. Je vais vous présenter différents ensembles de matériels selon leurs coûts, que nous ferons correspondre à différents niveaux d'expertise en laboratoire. Cet article s'adresse donc autant aux amateurs néophytes, désireux d'acquérir la capacité de pratiquer la rétro-ingénierie matérielle, qu'aux universités voulant lancer un laboratoire dans ce domaine. Nous n'aborderons ici qu'un seul type d'attaque, celle par analyse de puces de silicium. Pour les autres types d'attaques, vous aurez généralement une seule méthode disponible et donc un seul type de matériel ou de machine, ce qui rend la chose trop spécifique pour être abordé ici.

Rançongiciels 101

Magazine
Marque
MISC
Numéro
107
|
Mois de parution
janvier 2020
|
Domaines
Résumé

Qu’ont en commun votre voisin, un fermier du Wisconsin, un centre hospitalier normand, les villes de Baltimore, de Johannesburg ou la Louisiane, la société Prosegur ? Tous ont été les victimes de ce qui en moins de dix ans est devenue une des principales menaces cyber : les rançongiciels.

Par le même auteur

Analyse du contournement de KTRR

Magazine
Marque
MISC
Numéro
102
|
Mois de parution
mars 2019
|
Domaines
Résumé

En mars 2018, le chercheur Luca Todesco (@qwertyoruiop) a publié le code de son exploit qui contourne le mécanisme de protection d'intégrité du noyau iOS sur l'iPhone 7. Ce contournement affecte les versions d'iOS 10 et 10.1. Bien qu'il ne permette pas de modifier directement le code du noyau, il rend possible le changement des valeurs du segment de données constantes, avec les conséquences que nous expliquerons.