Posts Debugging du bootloader de l'iPhone avec GDB
Post
Cancel

Debugging du bootloader de l'iPhone avec GDB

Il y a quelques mois, je cherchais comment mettre en place un setup pour déboguer l’iBoot, le bootloader iOS, sur un iPhone 8. A mon grand étonnement, personne n’a publié de documentation ou d’article sur comment faire. La seule référence au debugging de l’iBoot était dans la documentation d’IDA Pro pour spécifier que l’iBoot n’est pas supporté.

Ce post a pour objectif de montrer comment mettre en place un setup pour déboguer le bootloader d’iOS sur un iPhone 8.

ipwndfu

Axi0mx a publié checkm8, un exploit bootrom dont j’ai déjà parlé sur ce blog. Implémenté dans un outil nommé ipwndfu, cet exploit permet d’activer le JTAG sur les appareils iOS avec une puce A7-A11.

Grâce aux patches de akayn on peut utiliser ipwndfu pour charger un bootloader non-signé pour les puces A11. Pour ce post j’utilise une version avec ces mêmes patches qui supporte Python 3.

1
2
3
4
5
6
7
8
9
10
11
12
13
➜  ipwndfu git:(main) ✗ ./ipwndfu -p --demote --patch
*** checkm8 exploit by axi0mX ***
Found: CPID:8015 CPRV:11 CPFM:03 SCEP:01 BDID:0A ECID:000XXXXXXXXXXXXX IBFL:3C SRTG:[iBoot-3332.0.0.1.23]
Device is now in pwned DFU Mode.
(1.34 seconds)
Demotion register: 0x207
Attempting to demote device.
Demotion register: 0x206
Success!
Heap repaired.
Bootrom Patched
you can now load unsigned firmware
and debug the next boot stages

l’iPhone est prêt à être attaché à un débuggeur et à recevoir une image non-signée.

iBoot

Avant de parler de la partie debug, on déchiffre et on prépare un iBoot que l’on va charger. Pour cet article, j’ai choisi un bootloader sur la dernière version d’iOS, iOS 15, actuellement en beta.

Avant d’empaqueter l’iBoot dans un conteneur IMG4, j’ai appliqué quelques patches à l’aide de Binary Ninja et d’un plugin que j’ai écrit.

Ces modifications ont pour but d’autoriser l’iBoot à charger des images non-signées et d’activer le kernel debugging pour plus tard. De plus j’ajoute la chaine de caractère -patched au tag de la version de l’iBoot, pour être sûr que c’est bien le bootloader modifié qui est chargé.

Empaquetage de l’iBoot

La SecureROM accepte uniquement les images au format IMG4. Pour convertir le bootloader au format img4, nous allons utiliser tsschecker et img4tool :

1
2
3
$ tsschecker -d iPhone10,2 -e 85888280a402e  -l -s # grab shsh2 ticket
$ img4tool -c iBoot.d20.RELEASE.im4p -t ibot iBoot.d20.RELEASE.bin_patched # pack the payload 
$ img4tool -c iBoot.d20.RELEASE.img4 -p iBoot.d20.RELEASE.im4p -s file.shsh2 # pack IM4P + ticket as IMG4

A la suite de ces trois commandes vous devriez avoir un fichier nommé iBoot.d20.RELEASE.img4. Pour confirmer, vous pouvez inspecter avec un éditeur hexadécimal ou l’utilitaire strings que les trois premières chaines de caractère sont présentes (les autres étant optionnelles).

1
2
3
4
λ ~ » strings iBoot.d20.RELEASE.img4| head -n 3
IMG40
IM4P
ibot

Chargement de l’iBoot

Pour charger le bootloader sur notre iPhone, on utilise irecovery, cet outil pour transférer le bootloader vers l’iPhone via USB. Cet utilitaire fait partie de la bibliothèque libirecovery.

Pour charger le fichier il faut lancer deux fois la commande irecovery -f iBoot.d20.RELEASE.img4. La première commande permet de réinitialiser le transfert USB, la seconde va transférer l’iBoot.

Lorsque le transfert est fini, la SecureROM charge l’iBoot.

Comme on peut le voir ci-dessus, on peut récupérer la sortie série de l’iBoot via un câble DCSD ou bonobo. De plus vous pouvez remarquer que le BUILD_TAG a été modifié (iBoot-7429.12.13-patched) grâce aux patches que j’ai appliqué via Binary Ninja.

Cable Bonobo

Pour déboguer l’iBoot il est nécessaire d’avoir le matériel adéquat. Il faut un câble permettant d’interfacer avec l’iPhone pour faire du Serial Wire Debugging (JTAG alternatif).

Il est possible d’obtenir des câbles internes à Apple, comme le câble Kanzi. Néanmoins ce type de câble est compliqué à obtenir et il nécessite d’utiliser des logiciels internes à Apple.

Pour la suite de ce post je utilise le câble Bonobo conçu par LambdaConcept. Ce câble se connecte sur le port Lightning et permet le débogage du CPU via JTAG/SWD en utilisant OpenOCD.

De plus ce câble permet d’utiliser les fonctions de transfert USB (pour le mode DFU) et d’obtenir la sortie série de l’iPhone pour obtenir une console iBoot par exemple. On a donc un câble tout-en-un qui peut être utilisé avec ipwndfu ou checkra1n.

OpenOCD

Pour utiliser le câble bonobo, on télécharge et on compile le fork d’OpenOCD de LambdaConcept :

1
2
3
4
5
$ git clone https://github.com/lambdaconcept/openocd.git && cd openocd
$ ./bootstrap
$ ./configure --enable-bonobo --disable-werror  # check for "LambdaConcept Bonobo Cable yes"
$ make -j
$ sudo make install

Ensuite on lance OpenOCD en spécifiant le fichier de configuration pour l’iPhone en question, dans notre cas on cible l’iPhone 8 avec comme identifiant de puce (CPID) 8015.

De plus j’ai ajouté le paramètre -c pour qu’il écoute sur 0.0.0.0. Cela permet d’accéder au serveur GDB créé par OpenOCD ainsi qu’au serveur Telnet depuis une machine distante.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
λ ~ » sudo openocd -c "bindto 0.0.0.0" -f t8015.cfg 
[sudo] password for mathieu: 
Open On-Chip Debugger 0.10.0+dev-00949-g0f160f1b (2021-08-28-02:59)
Licensed under GNU GPL v2
For bug reports, read
	http://openocd.org/doc/doxygen/bugs.html
Info : clock speed 10000 kHz
Info : SWD DPIDR 0x4ba02477
Info : Listening on port 3333 for gdb connections
Info : Listening on port 3334 for gdb connections
Info : Listening on port 3335 for gdb connections
Info : Listening on port 3336 for gdb connections
Info : Listening on port 3337 for gdb connections
Info : Listening on port 3338 for gdb connections
Info : Listening on port 3339 for gdb connections
Info : Listening on port 6666 for tcl connections
Info : Listening on port 4444 for telnet connections

On se connecte via Telnet à la console OpenOCD et on commence par lister les cœurs du SoC de l’iPhone avec la commande targets.

Par défaut, c’est le SEP qui est sélectionné. Donc il faut changer de cible pour le sectionner iphone.ecore0. On stop exécution du cœur en question avec la commande halt comme vous le voyez ci-dessus.

Pour confirmer que c’est bien le bon bootloader que l’on va déboguer, on dump la mémoire du CPU:

1
2
> dump_image iboot_partial.bin 0x18001c1e1 0x100
dumped 256 bytes in 0.045041s (5.550 KiB/s)

L’image est sauvegardée dans le répertoire où est exécuté OpenOCD. Avec xxd on peut voir que je télécharge bien l’iBoot que j’ai patché.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
λ ~ » xxd iboot_partial.bin
00000000: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000010: 0000 0000 0000 0000 0000 0000 0000 0069  ...............i
00000020: 426f 6f74 2066 6f72 2064 3230 2c20 436f  Boot for d20, Co
00000030: 7079 7269 6768 7420 3230 3037 2d32 3032  pyright 2007-202
00000040: 312c 2041 7070 6c65 2049 6e63 2e00 0000  1, Apple Inc....
00000050: 0000 0000 0000 0000 0000 0000 0000 0052  ...............R
00000060: 454c 4541 5345 001f 2003 d51f 2003 d500  ELEASE.. ... ...
00000070: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000080: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000090: 0000 0000 0000 0000 0000 0000 0000 0069  ...............i
000000a0: 426f 6f74 2d37 3432 392e 3132 2e31 332d  Boot-7429.12.13-
000000b0: 7061 7463 6865 6400 0000 0000 0000 0000  patched.........
000000c0: 0000 0000 0000 0000 0000 0000 0000 0070  ...............p
000000d0: 6174 6368 6564 2062 7920 6d61 7474 6579  atched by mattey
000000e0: 6575 7800 0000 0000 0000 0000 0000 0000  eux.............
000000f0: 0000 0000 0000 0000 0000 0000 0000 0000  ................

Debugging avec GBD

Passons à la phase de debugging avec GDB.

Pour pouvoir cibler l’architecture ARM64 (aka aarch64 ) on utilise gdb-multiarch.

1
2
3
4
5
6
(gdb) target remote 192.168.1.20:3333
Remote debugging using 192.168.1.20:3333
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
0x000000018001c494 in ?? ()
(gdb) 

On arrive dans la fonction arch_halt de l’iBoot dont on peut lister les instructions :

(gdb) x/4i $pc-12
   0x18001c488:	dsb	sy
   0x18001c48c:	isb
   0x18001c490:	wfi
=> 0x18001c494:	ret

Et voilà ! Nous avons enfin un débogueur fonctionnel pour l’iBoot. Les commandes de GDB habituelles sont accessibles pour parcourir l’exécution de l’iBoot, changer les valeurs des registres, lire sa mémoire, etc…

Pour une meilleure visualisation avec gdb on peut utiliser la commande layout et avoir une vue de l’assembleur et des registres.

Il est aussi possible d’écrire dans la mémoire de l’iBoot ou de changer la valeur des registres. Il est possible, par exemple, de changer la valeur du PC, pour déclencher un iBoot panic :

(gdb) set $pc = 0x414141414141
(gdb) i r $pc
pc             0x414141414141      0x414141414141
(gdb) c
Continuing.

Program stopped.
0x0000414141414140 in ?? ()

Le résultat du panic s’affiche dans la sortie série de l’iPhone grâce au câble Bonobo.


Donc voila ! Il y a un maintenant article pour mettre un place un débogueur pour iBoot avec un iPhone 8 et un câble Bonobo. J’ai utilisé GDB, mais j’aurais aussi pu utiliser IDA PRO.

La suite sera de mettre en place un débogueur pour Binary Ninja avec leur nouveau débogueur en cours de développement, en espérant qu’il supporte le remote debugging.

Pour finir j’espère que ce post vous a plu, n’hésitez pas à me contacter sur Twitter ou même par mail si vous avez des questions.


Liens et sources :

This post is licensed under CC BY 4.0 by the author.