La team derrière le jailbreak Electra vient de publier la version initiale permettant d’installer Cydia, pour ainsi appliquer des tweaks et thèmes. Ce post n’est pas un tutoriel pour installer et utiliser cet outil, mais une analyse du jailbreak à proprement parler.

Electra

Electra était initialement un jailbreak pour les versions de iOS 11 et de iOS 11.1.x. Cette première version utilisait l’exploit async_wake de Ian Beer pour appeler la fonction task_for_pid(0).

Electra permet d’installer Cydia, un serveur SSH et d’injecter du code dans des applications et processus à l’aide de Substitute.


Contrairement à la version actuelle, Electra pour iOS 11.1.2 est disponible sur Github sous licence open source. A l’aide du code source de la première version de Electra et d’un désassembleur on va analyser cet outil.

Electra est un jailbreak que l’on appelle KPPLess. En effet, pour supporter tous les appareils sous iOS 11.x il n’est pas possible d’utiliser le contournement de KPP utilisé dans Yalu et DoubleHelix car les appareils récents comme l’iPhone 7, 8 et X ont une nouvelle protection hardware que j’ai détaillé ici

Exploitation

Les deux exploits utilisés permettent la même chose : appeler la fonction task_for_pid() avec le PID 0, qui correspond au noyau. Par défaut ceci n’est bien évidemment pas possible. Ces exploits vont ainsi permettre de lire et d’écrire dans la mémoire du noyau.

multi_path

Le premier exploit MPTCP a été détaillé pour la première fois par Eloi Vanderbeken. Le bug exploité est un Heap overflow, dû à la mauvaise vérification de limites dans le protocole MPTCP.

Le protocole MPTCP (Multi Path TCP) permet d’utiliser une même connexion TCP au travers de plusieurs interfaces réseau. Dans le cas de l’iPhone, il est utilisé par Siri pour communiquer en même temps par les données en cellulaires et Wifi.

Le protocole a été rendu accessible aux développeurs avec iOS 11 et nécessite un compte dev pour utiliser les sockets MPTCP et déclencher l’exploit.

Le premier exploit disponible a été implémenté par John Åkerblom, puis un second exploit a été publié par Ian Beer. Les exploits sont similaires, mise à part que celui de Ian Beer fait un overflow de 3 octets, alors que celui John Åkerblom n’overflow que 2 octets et les remplace par d’autres objets différents.

Ian a détaillé plus précisément sont exploit ici.

empty_list

Empty_list a été publié plus tard par Ian Beer. Le bug en question est qu’il manque une simple vérification. En effet si la taille de la mémoire allouée du buffer est inférieure à la taille minimum requise, le programme va passer les différentes vérifications et continuer son chemin. Cela va déclencher un Heap overflow.
Voici le PoC de Ian Beer en une vingtaine de lignes :

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/attr.h>

int main() {
  int fd = open("/", O_RDONLY);
  if (fd == -1) {
	perror("unable to open fs root\n");
	return 0;
  }

  struct attrlist al = {0};

  al.bitmapcount = ATTR_BIT_MAP_COUNT;
  al.volattr = 0xfff;
  al.commonattr = ATTR_CMN_RETURNED_ATTRS;

  size_t attrBufSize = 16;
  void* attrBuf = malloc(attrBufSize);
  int options = 0;

  int err = fgetattrlist(fd, &al, attrBuf, attrBufSize, options);
  printf("err: %d\n", err);
  return 0;
}

L’exploit lui nous permet d’exécuter le task port pour le processus 0, qui correspond au noyau. On peut ainsi lire et écrire dans la mémoire du noyau à l’aide des fonctions implémentées dans kmem.h.

Je ne vais pas m’attarder sur l’exploit puisqu’il a déjà été documenté et compréhensible si vous avez un peu d’expérience avec XNU et iOS.

Le seul problème avec cet exploit est qu’il pas stable, et pour l’exécuter correctement il faut au moins 5 ou 6 essais. D’après Ian Beer il a 30% de réussite contrairement à multi_path qui est à 80-90%.

Post exploitation

UID : 0

La première étape classique dans une escalade de privilèges est d’être root. Et contrairement à MacOS, ce n’est que le début. En effet être root sur un iPhone ou iPad est très limité.

D’après le code suivant, voici comment on obtient les autorisations du noyau pour avoir l’UID 0 et empêchant la fonction setuid(0) de crash à l’aide de la macro KCALL qui permet d’appeler des fonctions depuis le noyau.

// Properly copy the kernel's credentials so setuid(0) doesn't crash
uint64_t kern_ucred = 0;
KCALL(find_copyout(), kern_proc+0x100, &kern_ucred, sizeof(kern_ucred), 0, 0, 0, 0);
	
uint64_t self_ucred = 0;
KCALL(find_copyout(), our_proc+0x100, &self_ucred, sizeof(self_ucred), 0, 0, 0, 0);

KCALL(find_bcopy(), kern_ucred + 0x78, self_ucred + 0x78, sizeof(uint64_t), 0, 0, 0, 0);
KCALL(find_bzero(), self_ucred + 0x18, 12, 0, 0, 0, 0, 0);
	
setuid(0);

Puis à l’aide de setuid() on configure le processus avec le PID 0, pour être root.

AMFID

Amfid est le démon qui interface avec la kext AMFI qui s’occupe de gérer tout ce qui est relatif aux signatures de code et l’autorisation d’éxecution de code signé sur iOS. En me basant sur le code et les commentaires, voici comment fonction le contournement :

  • Calculer les CD Hashes pour les ressources qui sont ciblées
  • Créer un trust chain (chain de confiance) personnalisée
  • Insérer la trust chain dans la trust chain déjà existante en stockant uniquement les 20 premiers octets de chaque hash

RootFS R/W

Avec iOS 11.3, Apple a implémenté une nouvelle mitigation dans la kext APFS d’iOS, qui empêche de remonter la racine (/) en R/W. C’est un problème car cela empêche donc d’installer Cydia ou tout autre fichier système.

Min Spark et Xiaolong Bai ont publié un premier writeup sur comment contourner le principal problème, mais sans persistance. C’est à dire qu’il faut réinstaller tous les composants après un redémarrage.

Electra a ensuite été publié avec un bypass d’APFS fonctionnel et persistent considéré comme 0-day (bien que ça pas rapport avec la sécurité d’iOS). Ce jailbreak embarque deux fonctions permettant de remonter les fichiers systèmes en écriture :

  • remountRootAsRW_old() : 11.2.x
  • remountRootAsRW() : 11.3.x

Dans notre cas on va s’occuper de la partie 11.3.x.

D’après le pseudocode généré par IDA Pro, voici comment fonctionne cette fonction : L’outil effectue une vérification pour voir si le dossier /var/rootfsmnt existe, puis le supprime si la condition est vraie. Il créé ensuite le répertoire qui est vérifié plus tôt. Et (si je comprends bien) défini les identifications du noyau temporairement pour monter les rootfs.

if ((unsigned int)file_exists("/var/rootfsmnt"))
	rmdir("/var/rootfsmnt")

mkdir("/var/rootfsmnt", 0x1EDu);
chown("/var/rootfsmnt", 0, 0);
printf("Temporarily setting kern ucred\n");
wk64(v12 + (unsigned int)offsetof_p_ucred, v11);

Ensuite on la fonction tente de monter la partition /dev/disk0s1s1 dans /var/rootfsmnt. Dès que celle-ci est montée l’app renomme “/var/rootfsmnt” en orig-fs.
Le disque monté dans /var/rootmntfs est démonté et on restaure les indentifications par défaut.

if ((unsigned int)mountDevAsRWAtPath("/dev/disk0s1s1", "/var/rootfsmnt") ){
	printf("Error mounting root at %s\n", "/var/rootfsmnt");
}
else {
	printf("Disabling the APFS snapshot mitigations\n");
	snapshot = find_system_snapshot();
	if (snapshot && !do_rename("/var/rootfsmnt", snapshot, "orig-fs")){
		status = 0;
		unmount("/var/rootfsmnt", 0);
		rmdir("/var/rootfsmnt");
	}
}
printf("Restoring our ucred\n");
wk64(v12 + (unsigned int)offsetof_p_ucred, v10);
vnode_put(disk0s1s1_vnode);

Si tout s’est bien passé, l’appareil va redémarrer.

Ensuite une vérification est effectuée pour savoir si les rootfs sont bien montées en R/W avec un simple test en créant un fichier à la racine.

Installation de Cydia

Depuis sa sortie, l’installation de Cydia n’a pas changée. Pour fonctionner Cydia a besoin de plusieurs composants comme APT ou dpkg. Pour cela, Saurik propose le gestionnaire de paquets et ses dépendances dans une archive nommée bootstrap.tar. Pour l’installer c’est tout simple, il suffit d’extraire les composants de l’archive dans /.

Le code ci-dessus montre Electra qui configure le fichier /etc/hosts et certains repos pour Cydia.

Ensuite le fichier .cydia_no_stash est créé à la racine pour empêcher Cydia de déplacer les répertoires dans la partition système vers la partition mobile. Ce n’est plus utile depuis que les deux partitions partagent le même stockage.

jailbreakd

Electra étant un jailbreak KPPless, il n’est pas capable de patcher directement le kernel. C’est pour cela que jailbreakd est utilisé. jailbreakd est un démon qui d’après la doc et le code va appliquer les patches de setuid pour les processus, les plateformes et le transfert de certaines fonctionnalités.

On peut interfacer avec ce démon à l’aide d’une bibliothèque dynamique libjailbreak.dylib. Ce démon et la lib sont principalement utilisés par Cydia pour patcher setuid(0).

Detection de jailbreak

Electra est aussi muni d’un système de détéction de jailbreak tiers. En effet, les exploits étant open source, tout le monde peut developper des outils basé sur ces exploits. Pour empecher toute forme de disfonctionnement, Electra va tenter de supprimer les potentiels jailbreak qui modifient les fichiers systèmes. La fonction removePotentialJailbreak() est tout simple : elle supprime des fichiers et repertoires créée par ces alternatives.

int removePotentialJailbreak()
{
	rmdir("/jb");
	rmdir("/Applications/Filza.app");
	unlink("/etc/dropbear");
	rmdir("/var/LIB");
	rmdir("/var/ulb");
	rmdir("/var/bin");
	rmdir("/var/sbin");
	unlink("/var/profile");
	unlink("/var/motd");
	unlink("/var/dropbear");
	rmdir("/var/containers/Bundle/tweaksupport");
	rmdir("/var/containers/Bundle/iosbinpack64");
	rmdir("/var/containers/Bundle/dylibs");
	unlink("/var/log/testbin.log");
	unlink("/var/log/jailbreakd-stdout.log");
	return unlink("/var/log/jailbreakd-stderr.log");
}

En plus de cette fonction, les devs ont implémenté deux autres fonctions pour détecter et supprimer LiberiOS, le jailbreak de Jonathan Levin. Non pas par concurence, mais plutôt pour éviter tout conflit lors de l’installation de certains fichiers comme les executables installé dans /usr/local/bin/.


On a fait le tour du jailbreak Electra pour iOS 11.3.1. La team a décidé de ne pas publier les versions avec symboles histoire d’obfusquer un minimum leur code. Mais diaphora est là pour m’aider dans mon périple héhé.

EDIT : Seule la version VFS est deployée sans symboles de debug etc…

Bref j’ai surement fais des erreurs, et il me reste encore pleins de choses à comprendre, donc si vous avez des retours ou des questions à poser n’hésitez pas !

Si vous avez besoin d’infos contactez-moi sur twitter: @matteyeux

Github : https://github.com/matteyeux


Sources :

https://github.com/coolstar/electra

http://newosxbook.com/QiLin/qilin.pdf

https://github.com/Siguza/v0rtex

https://bugs.chromium.org/p/project-zero/issues/detail?id=1564

https://github.com/potmdehex/multipath_kfree

https://bugs.chromium.org/p/project-zero/issues/detail?id=1558

https://www.rump.beer/2018/slides/ios_48h.pdf

https://github.com/externalist/exploit_playground/blob/master/empty_list/empty_list/empty_list/sploit.c