Dans ce post je vais vous expliquer le concept de Trust Zone ainsi que les niveaux d’exception (Exception Levels) sur les processeurs de type aarch64. Ainsi je parlerai de KPP, de son extraction jusqu’à son analyse puis en expliquant quelques techniques utilisées pour bypass cette nouvelle sécurité d’Apple pour les appareils qui tournent sous iOS et tvOS. Pour finir je vais parler de KTRR la nouvelle sécurité hardware remplaçant KPP
Secure World
Avant de parler de KPP il faut définir ce qu’est le Secure World , la TrustZone et les niveaux d’exception des plateformes ARM.
Exception Levels
Si vous connaissez un peu l’architecture x86 vous connaissez surement les rings qui définissent les niveaux de privilèges imposés par l’architecture du processeur.
Ces mêmes niveaux sont appliqués aux processeurs ARM mais d’une façon un peu différente. Tout d’abord on appelle ça les niveaux d’exception (Exception Levels en anglais, ou EL).
D’après le schéma ci-dessus, voici à quoi correspondent les différents niveaux pour iOS:
- EL0 : Applications et userland.
- EL1 : le kernel et les différents services de l’OS ayant les mêmes privilèges.
- EL2 : pas d’hyperviseur présent pour les appareils mobiles Apple donc EL2 n’est pas utilisé
- EL3 : Secure monitor ou BootROM le premier composant iOS qui démarre.
Contrairement aux rings de l’architecture x86, le niveau le plus privilégié pour aarch64 est EL3. Seul le niveau 3, c’est à dire le Secure Monitor peut accéder au Secure World qui est en fait un espace mémoire protégé.
Trust Zone
La TrustZone est une technologie pour les Systèmes sur Puce (SoC) permettant une approche global au niveau de la sécurité des CPUs. C’est une sécurité hardware implémentée dans les SoC par les fabricants de puces. Le concept est simple: fournir un environnement d’exécution de confiance, en anglais Trusted Execution Environment (TEE), en séparant l’OS du Secure World.
La TrustZone va créer un environnement sécurisé et isolé du reste du système d’exploitation, puis vérifiera l’intégrité de l’OS.
Le système d’exploitation principal tourne en fait dans le normal world. C’est devenu un standard pour le renforcement de la sécurité des téléphones.
KPP
Avec l’arrivée d’iOS 9 en 2015, Apple a introduit une nouvelle fonctionnalité pour veiller à l’intégrité du noyau d’iOS : Watch Tower. Surnommé Kernel Patch Protection
(ou KPP) par la communauté des développeurs de jailbreak iOS.
Watch Tower est une nouvelle protection contre les jailbreaks qui vérifie régulièrement les hashes des pages mémoire de type __TEXT
et __DATA.__const
dans le kernel.
KPP tourne au niveau d’exception 3 (EL3). Tout comme les applications en userland (EL0) ne peuvent pas accéder à la mémoire du kernel (EL1), le kernel ne peut donc pas accéder à la mémoire de KPP.
Kernel Patch Protection est présent uniquement sur les appareils 64 bits car ceux-ci possèdent une Trust Zone. Néanmoins sur les nouveaux appareils tels que l’iPhone 7, 8 et X il n’y a pas KPP.
Extraction de KPP
KPP est un fichier mach-o
, c’est fichier exécutable pour ARM64. Ce fichier se trouve dans le kernelcache qui est au format img4.
A l’aide de joker on va pouvoir décompresser un kernelcache et y extraire KPP. Pour ce post je vais utiliser un kernelcache d’iPhone 5S sous iOS 11.0.
En plus de la décompression du kernelcache, joker
va extraire KPP vers le répertoire /tmp
. Vous devriez avoir deux nouveaux fichiers dans ce répertoire : kpp
et kernel
.
Il ne reste plus qu’à vérifier que kpp est bel et bien un exécutable mach-o
à l’aide de jtool :
Analyse de KPP
A l’aide de jtool
on liste les sections. KPP est chargé par l’iBoot à l’adresse mémoire 0x4100000000
.
1
2
3
4
5
$ jtool -l -v kpp | grep LC_SEGM
LC 00: LC_SEGMENT_64 Mem: 0x4100000000-0x4100006000 File: 0x0-0x6000 r-x/r-x __TEXT
LC 01: LC_SEGMENT_64 Mem: 0x4100006000-0x410000b000 File: Not Mapped rw-/rw- __DATA
LC 02: LC_SEGMENT_64 Mem: 0x410000b000-0x410000b000 File: Not Mapped rw-/rw- __IMAGEEND
LC 03: LC_SEGMENT_64 Mem: 0x410000b000-0x410000b000 File: Not Mapped r--/r-- __LINKEDIT
A son démarrage (_start(boot_args * args)
), KPP va recevoir comme argument la structure boot_args
définie dans pexpert/pexpert/arm64/boot.h
de XNU :
Puis WatchTower va ainsi réécrire son propre en-tête à 0x4100000000
à l’aide d’un trampoline qui va appeler la même fonction _start
mais avec NULL
comme argument. Cela lui permet d’installer deux gestionnaires d’exception :
- sync_handler (+0x400)
- irq_handler (+0x480)
Lorsqu’une exception se produit, le processeur se branche sur la table de vecteurs d’exception et exécute le gestionnaire (ou handler en anglais) correspondant. Avec l’architecture ARMv8, chaque niveau d’exception possède sa propre table de vecteurs
Grâce à la fonction ci-dessous, dès le démarrage du noyau, KPP notifie le moniteur au niveau EL3. C’est à ce moment que KPP va s’initialiser et vérifiera l’intégrité du texte du noyau, des données en lecture seule ainsi que les pages mémoire du kernel.
KPP vérifie l’intégrité des segments de mémoire suivant :
__Text
` __DATA_CONST`
Mais aussi les tables de pages mémoire, ainsi que plusieurs registres systèmes.
La fonction ci-dessous gère les erreurs de KPP. Dans le cas ou vous n’avez pas compilé le noyau vous-même ou que vous n’avez pas mis la main sur un kernel compilé en mode DEBUG
ou DEVELOPMENT
il ne vous sera pas possible de debugger KPP.
Dans tous les cas ces erreurs entraineront un kernel panic d’après la définition préprocesseur.
Avec un kernel de developpement, il vous suffit de l’ouvrir de un désassembleur, on voit que les chaines de caractères correspondent bien.
Ce noyau correspond à celui d’iOS 9.0 et KPP a beaucoup de similarités par rapport à iOS 11.0
KPP bypass
Premièrement il faut savoir que KPP ne stoppe pas les exploits mais les patches appliqués au noyau d’iOS. La raison pour laquelle KPP est si important c’est qu’il empêche d’appliquer les patches pour utiliser Cydia Substrate, le framwork permettant d’ecrire et d’appliquer des extensions pour iOS, aussi appelés tweaks.
Pangu
Le premier jailbreak d’iOS 9.0 a été publié par Pangu. La team Pangu n’a pas concrètement bypass KPP, mais l’a plutôt contourné. En effet ils ont décidé de ne pas patcher __TEXT
mais plutôt certaines structures de données. Il leur a fallu appliquer les patches le plus tôt après le chargement du kernel en mémoire et avant le lancement de KPP.
Ces patches sont considérés comme “Data Only”.
Yalu
Le jailbreak de Luca Todesco, aka qwertyoruiop bypass entièrement KPP.
En effet le code injecté par son exploit remplace l’instruction MSR CPACR_EL1, X0
par un appel vers kppsh1. Ensuite la valeur actuelle du registre système TTBR1_EL1
est sauvegardée vers le registre X1
. Puis charge la valeur originale dans X0
et écrase TTBR1_EL1
. La valeur de CPACR_EL1 est basculée en exécutant l’instruction écrasée et ainsi en invoquant KPP
Pour info Xn
sont des registres de l’ABI iOS, l’équivalent de Rn
pour les architectures x86.
Tout ça à l’aide de la fonction WriteAnywhere32
qui permet d’appeler les fonctions en kernel mode.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
WriteAnywhere32(kppsh+n, 0x580001e1); n+=4; // ldr x1, #60
WriteAnywhere32(kppsh+n, 0x58000140); n+=4; // ldr x0, #40
WriteAnywhere32(kppsh+n, 0xd5182020); n+=4; // msr TTBR1_EL1, x0
WriteAnywhere32(kppsh+n, 0xd2a00600); n+=4; // movz x0, #0x30, lsl #16
WriteAnywhere32(kppsh+n, 0xd5181040); n+=4; // msr CPACR_EL1, x0
WriteAnywhere32(kppsh+n, 0xd5182021); n+=4; // msr TTBR1_EL1, x1
WriteAnywhere32(kppsh+n, 0x10ffffe0); n+=4; // adr x0, #-4
WriteAnywhere32(kppsh+n, isvad ? 0xd5033b9f : 0xd503201f); n+=4; // dsb ish (4k) / nop (16k)
WriteAnywhere32(kppsh+n, isvad ? 0xd508871f : 0xd508873e); n+=4; // tlbi vmalle1 (4k) / tlbi vae1, x30 (16k)
WriteAnywhere32(kppsh+n, 0xd5033fdf); n+=4; // isb
WriteAnywhere32(kppsh+n, 0xd65f03c0); n+=4; // ret
WriteAnywhere64(kppsh+n, ReadAnywhere64(ttbr0_real)); n+=8;
WriteAnywhere64(kppsh+n, physp); n+=8;
WriteAnywhere64(kppsh+n, physp); n+=8;
Cette méthode consiste simplement à appliquer une technique physique de “Copy on Write” : on laisse la page physique originale et non-modifiée pointée par le registre TTBR1_EL1
original. Puis on alloue une nouvelle page physique à modifier et pointée par le registre TTBR1_EL1
actuel.
Après ça le code de KPP qui tourne dans EL3 cherche à vérifier la valeur de TTBR1_EL1
et trouvera la valeur originale qui a été sauvegardée plus tôt.
Les tables de pages mémoire pointées par TTBR1_EL1
sont en fait, celles d’origine utilisées par le noyau lors de son démarrage, et n’ont pas été modifiées.
Cela permet ainsi de cacher de KPP les pages du noyau modifiées.
Apple n’a pas corrigé cette méthode puisqu’en soit ils ne peuvent pas. C’est une faille dans la conception de KPP donc il n’y a pas de manière viable de corriger ça pour si ce n’est mise à part une mise à jour materielle de l’iPhone.
KPP less
Début octobre, xerub à annoncé avoir ajouté une branche sur son fork du jailbreak extra_recipe nommé kppless.
Tout d’abord, contrairement à Yalu, ce n’est pas un bypass, mais juste une méthode pour patcher les zones du kernel non contrôlées par KPP.
De plus Apple peut ajouter des zones de contrôle via une simple mise à jour.
La méthode de fonctionnement de kppless est assez simple :
Lors que l’exploit est exécuté avec succès, celui-ci peut ainsi patcher le kernel avant que KPP ne fasse une quelconque vérification. Grâce à cette méthode, Watch Tower est toujours présent, mais ne détecte rien de mauvais dans le kernel.
Le fait est qu’avec cette branche, xerub monte un nouveau volume ou sont stockés les exécutables pour iOS ainsi qu’un serveur SSH.
Ce volume est monté dans le répertoire /Developer
comme ceci : rv = mount("hfs", "/Developer", MNT_RDONLY, &args);
.
Je vous passe les commandes d’initialisation.
Pour ce qui est de l’utilisation de Cydia et Substrate, il faut que ces technologies soient repensées. Elles ont maintenant 10 ans et il serait temps de les moderniser.
Il reste une dernière méthode pour contourner, que dis-je, pour désactiver KPP : un exploit dans la chaine de démarrage.
En imaginant que vous contrôlez l’iBoot, qui charge lui même le kernel XNU et KPP, vous pouvez l’empêcher de démarrer KPP ou bien même le modifier de façon à ce qu’il ne soit actif ou ne vérifie pas les pages que vous modifiez.
Ceci est possible car l’iBoot tourne au niveau d’exécution 3 (EL3), il a donc les même privilèges que KPP et peux accéder au Secure World dont nous avons parlé plus haut.
Personne n’a encore parlé de cette méthode publiquement puisqu’en fait il y a très peu de chances qu’un exploit pour l’iBoot/LLB ou la BootROM soit publié, ces choses-là ont beaucoup trop de valeur.
Mais je suis sûr que ça a été utilisé en privé.
KTRR
L’iPhone 7 est arrivé avec une nouvelle parade qui, comme KPP, vérifie certaines pages mémoire du kernel mais empêche aussi d’écrire dans certaines région de la mémoire. Longtemps nommée AMCC ou SiDP en fait ces deux fonctionnalités n’ont rien à voir avec l’implémentation sur les nouveaux iPhones.
Cette mitigation remplace KPP dans la vérification de l’intégrité du kernel, celle-ci est maintenant contrôlée par le hardware sur les iPhone 7, 8 et X. Il semble que les capacités de protection de Watch Tower ai été atteintes.
Grâce à la publication du code source du noyau XNU pour iOS on a maintenant le vrai nom de cette mitigation : KTRR (Kernel Text Readonly Region ?)
L’image ci-dessous illustre KTRR tel qu’on l’a découvert à la sortie de l’iPhone 7
KTRR est un mechanisme hardware qui renforce l’intégrité du kernel. Ce mechanisme permet à l’appareil de fournir des régions de mémoire en lecture seule. C’est régions sont programmées en utilisant des registres hardwares spéciaux définis dans le header AMCC.h.
Cette nouvelle technologie verrouille la MMU en définissant 3 registres controleurs au niveau hardware.
Voici la fonction de KTRR ci-dessous. Comme vous le voyez, si compilez le kernel en mode DEVELOPMENT
ou DEBUG
vous pouvez désactiver KTRR en modifiant la variable de type booléen ktrr_disable
.
Toutes les instructions exécutées depuis EL1 vers les zones protégées echoueront.
On sait clairement que KTRR verrouille la MMU, mais la vérification qui est mise en oeuvre par le contrôleur est encore flou.
KTRR Bypass
En théorie, si l’on trouve une faille dans le design de l’implémentation de KTRR, il devient impossible pour Apple de corriger le bug. Mais non.
Les primitives de KTRR viennent du composant hardware et ne sont pas modifiables. Mais il est possible de changer la façon dont elles sont utilisées ou appelées. C’est comme ça qu’Apple a corrigé le bypass de Luca Todesco avec la mise à jour d’iOS 10.2.
Il est tout à fait possible de faire un copy-on-write mais impossible d’injecter du code.
La technique appliquée par Todesco rend les patches “DATA Only”, ce qui veut dire que kloader ne peut pas être compatible pour iPhone 7 (pour l’instant).
Gal Beniamini du Google Project Zero a utilisé KTRR pour déterminer l’adresse physique de base du kernel qui est normalement protégée par KASLR. Il est possible de mapper les registres materiels car ceux-ci ne sont pas affectés par KASLR puisqu’ils sont définis sur des addresses mémoire fixes. On peut donc les utiliser pour déterminer l’addresse du noyau comme ceci :
1
2
3
4
5
6
# Finds the kernel's physical base address by reading the KTRR readonly-region registers.
def find_kernel_base(dart_offset):
rorgn_begin_addr = PE_SOC_BASE_PHYS + RORGN_BEGIN_OFFSET
rorgn_begin = read_host_dword(rorgn_begin_addr, dart_offset)
kernel_base = (G_PHYS_BASE + (rorgn_begin << 14))
return kernel_base
La méthode kppless de xerub qui consiste à patcher le noyau XNU dans des zones non-vérifiées par KPP/KTRR fonctionne. Elle a permis de rendre le jailbreak extra_recipe fonctionnel pour l’iPhone 7 sous iOS 10.2.
KTRR est configurée pour protéger une partie de la mémoire physique, mais il est possible pour Apple de modifier KTRR via une mise à jour pour étendre le champ de vérification et modifier le kernel pour déplacer la mémoire non vérifiée vers une zone vérifiable.
A l’inverse des appareils pré-iPhone 7 un exploit iBoot ne peut pas désactiver KTRR. L’iBoot tourne désormais dans EL1 sur les nouveaux appareils et non plus dans EL3.
Conclusion
Pour conclure, tout ça c’est bien beau mais ces parades n’affectent que les utilisateurs de jailbreaks. En effet en prenant comme exemple le spyware Pegasus, il n’y a pas besoin de patches ni de bypass KPP.
Les malwares comme Pegasus ont uniquement besoin des données qui transitent en userland.
Des dizaines de failles sont dévoilées tous les ans pour les plus récentes versions d’iOS, avec des PoCs de type local privilege escalation mais sans appliquer les patches post-exploitation qui sont utiles uniquement pour les utilisateurs finaux de jailbreak.
Etonnement Apple n’a pas corrigé le KPP bypass de Luca Todesco sous une quelconque version d’iOS 10.
Pour l’instant la seule sécurité qui ai fait ses preuves c’est la sandbox.
Si vous avez besoin d’infos contactez-moi sur twitter: @matteyeux
Github : https://github.com/matteyeux
Sources :
https://elinux.org/Overwrite_detection_for_kernel_text_and_read-only_data
https://developer.arm.com/technologies/trustzone
http://infocenter.arm.com/help/topic/com.arm.doc.prd29-genc-009492c/PRD29-GENC-009492C_trustzone_security_whitepaper.pdf
https://github.com/apple/darwin-xnu
https://xerub.github.io/ios/kpp/2017/04/13/tick-tock.html
https://newosxbook.com
https://www.blackhat.com/docs/us-16/materials/us-16-Wang-Pangu-9-Internals.pdf
https://googleprojectzero.blogspot.fr/2017/10/over-air-vol-2-pt-3-exploiting-wi-fi.html