Files
qemu-ios/contrib/ipod-emu/HANDOFF.md
T

13 KiB
Raw Blame History

Passation — simulateur iPod Touch 2G / iPhone OS 2.2.1

État au 2026-09-30. Environnement : conteneur Arch Linux, utilisateur dev avec sudo, sans CAP_NET_ADMIN, sans /dev/ppp ni TUN.

Objectif

On fournit un simulateur d'iPod Touch 2G sous iPhone OS 2.2.1 qui sert surtout à un agent IA pour tester les applications iOS 2.x d'AlloVoisins. Les apps sont compilées en armv6 avec /opt/cctools et /opt/theos/sdks/iPhoneOS2.2.1.sdk. L'agent doit pouvoir faire les choses suivantes, sans intervention humaine :

  • installer une app ;
  • la lancer ;
  • lire ses logs ;
  • toucher l'écran et prendre des captures ;
  • utiliser le réseau.

On veut le comportement le plus fidèle possible à un vrai appareil. La consigne de l'utilisateur est de travailler en autonomie et de se créer ses propres outils, voire une skill.

Ce qui marche

  • Boot 2.2.1 (5H11a), SpringBoard prêt en environ 25 s.
  • 2.1.1 (5F138) marche aussi, avec l'ancienne image de devos50.
  • Écran : capture 320×480 par QMP. Touch, boutons et VNC sur 127.0.0.1:5900.
  • Réseau / Internet : PPP sur l'UART1 émulée, relié à ipod-netd côté hôte (libslirp, en userland).
    • iPod en 10.0.2.15, passerelle 10.0.2.2, DNS 10.0.2.3.
    • Le DNS et HTTP marchent depuis l'iPod, et Safari affiche example.com.
  • Agent sur l'iPod (emuagent, v10), joignable depuis l'hôte sur 127.0.0.1:7788 via ipod-agent. Commandes :
    • launch (bundle id, avec --log pour stdout/stderr et --url), apps, front ;
    • exec, ps, kill, respring ;
    • put, get, ls, rm, mkdir ;
    • fetch, net, routes, sc (SCDynamicStore) ;
    • settime, sync.
  • Persistance NAND : programmation des pages et métadonnées, effacement des blocs de 128 pages et détection des pages vierges fonctionnent avec le format VSVFL de production. La base 5H11a a été migrée ; nand-write=on est inscrit dans ses options machine. Les nouveaux ipod-build-fw / ipod-mkimage utilisent gen-v1/mknand.
  • Apps : ipod-mkimage MonApp.app|.ipa|.deb|dossier/ construit une image dérivée. Les apps signées avec ldid passent grâce au boot-arg amfi_allow_any_signature.
  • Heure : start -W / wait envoie settime dès que l'agent répond, avant la fin du démarrage de SpringBoard. Ce réglage précoce évite d'initialiser les timers de l'interface en 1970 avec des préférences datées dans le futur.
  • Veille : agent v10, propriété SleepDisabled=true sur IOPMrootDomain (acceptée). Les assertions publiques IOPMAssertionCreateWithName sont non supportées sur ce firmware. Le SoC ne sait toujours pas reprendre une veille.
  • Réglages de l'image : langue fr, région fr_FR, fuseau Europe/Paris. Verrouillage auto désactivé, pas d'alerte « Edit Home Screen ».
  • Reconstruction complète depuis l'IPSW Apple : ipod-build-fw 5H11a.

Limites connues, et suite logique par priorité

  1. Installation / lancement dans une session conservée : les apps copiées dans /Applications sont maintenant persistées, mais la reconstruction du catalogue lors d'un respring reste peu fiable. push-app synchronise puis redémarre l'émulateur sans -f et sans reconstruire l'image. Le lancement après ce redémarrage répond encore parfois device locked ; un écran de code a été observé dans l'image de test. Ne pas annoncer ce workflow comme entièrement validé. ipod-mkimage reste disponible pour intégrer l'app avant le premier démarrage.
    • La persistance elle-même est validée : témoin texte et binaire de 256 Kio identiques après redémarrage ; fichier aléatoire de 8 Mio conservé, SHA-1 calculé dans l'iPod identique à l'hôte : 4d322e06f3433b874bc77d8b0298d97346210eb5.
    • Le téléchargement de 8 Mio par get a été tronqué. Le client vérifie désormais la longueur reçue et signale ce cas. Utiliser un calcul d'empreinte dans l'iPod pour distinguer le transport de l'intégrité NAND.
    • Ancienne base sauvegardée : images/5H11a/nand-readonly-backup, rootfs-v8-backup.img, machine-opts-readonly-backup. Session d'origine : run/nand-before-persistence et fw-before-persistence. Session de test avec app et fichier de 8 Mio : run/nand-persistence-verified (firmware persistence-test-v10).
  2. Horloge RTC du PMU non émulée : le D1759 passe par un accès indirect, index en 0x61 et donnée en 0x62. Le modèle PCF50633 existant est faux pour le 2G. Contournement en place : settime.
  3. Réglages > Wi-Fi affiche « Pas de Wi-Fi », parce que la puce BCM4325 n'est pas émulée. Le réseau passe par une interface ppp0, que l'app peut voir comme un VPN ou une connexion transitoire. Émuler le WiFi pour de vrai (SDIO + firmware FullMAC) serait un gros chantier ; le pont slirp est réutilisable.
  4. Pas encore écrits :
    • un script ipod-test-app (construit l'image, boote, lance l'app avec --log, fait une capture, affiche les logs) ;
    • une skill Claude/Codex qui documente ce workflow ;
    • la mise à jour de ~/ipod-emu/README.md (réseau, agent, heure, français).
  5. Mise à jour de l'agent à chaud (put puis kill emuagent) : elle coupe PPP pendant la renégociation. Préférer reconstruire l'image.

Démarrer

ipod-emu start -f -W            # 2.2.1 (défaut), NAND vierge, attend SpringBoard puis règle l'heure
ipod-agent ping                 # l'agent répond quand PPP est monté (quelques secondes)
ipod-agent launch com.apple.mobilesafari --url http://example.com/
ipod-qmp screenshot /tmp/s.png  # tap X Y | swipe | home | hmp "info registers"
ipod-emu stop

Ne jamais toucher l'écran avant la fin de ipod-emu wait ou start -W : le contrôleur tactile fait planter QEMU s'il est touché pendant son démarrage.

Logs :

  • ~/ipod-emu/run/serial.log : console kernel et syslog, pppd, emuagent ;
  • ~/ipod-emu/run/qemu.log ;
  • ~/ipod-emu/run/netd.log : avec IPOD_NETD_FWD=-v, trace de chaque paquet IP.

Debug : IPOD_QEMU_EXTRA="-d unimp,guest_errors -D ~/ipod-emu/run/int.log" ; IPOD_QEMU=~/ipod-emu/bin/qemu-gdb pour avoir une backtrace en cas de crash.

Carte des fichiers

Où Quoi
~/qemu-ios Fork Mahtan/devos50 (branche ipod_touch_2g). Sources et outils sauvegardés dans le dépôt (outils sous contrib/ipod-emu). Build : cd build && make -j4
/opt/ios-legacy/bin/ Copies installées des outils (réinstaller avec sudo install -m 755 ~/ipod-emu/bin/X /opt/ios-legacy/bin/)
~/ipod-emu/bin/ipod-emu Launcher : QEMU (serial0 = fichier, serial1 = run/uart1.sock), ipod-netd, wait, settime
~/ipod-emu/bin/ipod-qmp Écran, touch, boutons, HMP
~/ipod-emu/bin/ipod-agent Client de l'agent (protocole : ligne à champs séparés par TAB, réponse OK <n> [info]\n + n octets)
~/ipod-emu/bin/ipod-mkimage Image dérivée avec apps / .deb / overlay (re-signe avec ldid en gardant les entitlements)
~/ipod-emu/bin/ipod-build-fw IPSW → NOR + rootfs (mkbase-rootfs) + NAND (gen-v1/mknand, VSVFL de production, signature 2 et écritures activées)
~/ipod-emu/bin/mkbase-rootfs LaunchDaemons réduits, fstab rw, Lockdown (assets/), prefs, fr/Paris, intègre agent/overlay
~/ipod-emu/agent/ emuagent.c + Makefile (make → overlay/), entitlements.plist (com.apple.springboard.launchapplications etc., indispensables pour SBSLaunchApplication)
~/ipod-emu/netd/ipod-netd.c Serveur PPP (HDLC, LCP, IPCP) ↔ libslirp. gcc -O2 -o ipod-netd ipod-netd.c $(pkg-config --cflags --libs slirp) -Wno-deprecated-declarations
~/ipod-emu/bin/armdis.py, dtdump.py Désassembleur capstone (base, début, fin, arm) et dump du DeviceTree. Kexts du kernelcache : base de fichier 0xc0029000
~/ipod-emu/fw-5H11a/dec/ kernelcache, iBoot, LLB, DeviceTree déchiffrés (clés via api.ipsw.me)
~/ipod-emu/xpwn/ Outil hfsplus (patché : grow alloue ensuite dans l'espace neuf)

Découvertes clés (modifs QEMU)

  • AES (ipod_touch_aes.c) :
    • clé GID choisie d'après le contenu du KBAG (ipod_touch_gid_keys.h) ;
    • le dernier bloc incomplet d'une IMG3 est laissé en clair ;
    • la vérification de longueur LZSS d'iBoot est patchée, trouvée par signature ;
    • dans le DeviceTree déchiffré, les propriétés DMA de uart1 sont renommées : le pilote reste en PIO.
  • SHA1 (ipod_touch_sha1.c) : c'est une fonction de compression brute.
    • CONFIG 0x2 = premier bloc, 0x8 puis 0xa = suite ;
    • état H0–H4 lisible et écrivable, mots byte-swappés ;
    • bit 0x4 = mode asynchrone, avec IRQ 40 (activée par 0x0c, acquittée par 0x08, levée par un timer de 20 µs).
  • UART (ipod_touch_uart.c, nouveau modèle, remplace l'Exynos) :
    • IRQ activées dans UCON (0x2000 TX, 0x1000/0x0880 RX) ;
    • flags en attente dans UTRSTAT (0x08/0x10/0x20), effacés en écrivant 1 ;
    • UFSTAT : un FIFO plein vaut 0 + le bit 0x100 ;
    • FIFO limité à 8 octets.
  • NAND / FMSS (ipod_touch_fmss.c) :
    • La programmation CSGENRC=0xa02 utilise une liste de paires en D10, terminée par un mot zéro. D08=8 est la taille d'une paire, pas le numéro de page ; D18 est une ancienne valeur de lecture.
    • Premier mot : masque CE dans les bits 0–3, commande 80h/81h dans les bits 16–23 et flags dans les autres bits. Deuxième mot : page physique. D20 pointe deux adresses de données (2048 octets chacune) par page ; D1C contient les métadonnées de 12 octets. Toutes les paires doivent être enregistrées.
    • Effacement reconnu dans la liste du séquenceur C04 par les écritures immédiates NAND 60h/D0h ; D0C pointe la page de début de bloc, D10 un index CE, D18=1. Supprimer les 128 fichiers de pages du bloc.
    • Une page sans fichier est vierge (FF). C30=0x20000000 pour une lecture entièrement vierge fait retourner 0xe00002e5 par le pilote ; sans ce statut, _FTLRestore ignore les blocs de logs et perd les écritures.
    • Les autres micro-commandes restent émulées par les chemins de haut niveau existants ; pas d'interpréteur complet.
    • .ipod-nand-format=vsvfl-production-v1 dans la NAND : le launcher refuse nand-write=on sur une ancienne image.
  • Écran MIPI DSI : CLKCTRL[31] contrôle STATUS.TxReadyHsClk ; le bit doit aussi pouvoir retomber à zéro. Le modèle précédent le renvoyait toujours actif, bloquant le pilote lors de l'arrêt de l'écran.
  • Machine :
    • stubs RAM MPVD (0x39600000) et 0x38100000 ;
    • la NAND 2.2.1 exige la signature 0x43313132 ;
    • options legacy-nor et nand-write.
  • Réseau iOS :
    • pppd est lancé avec serviceid emuppp ;
    • l'agent publie Setup:/Network/Service/emuppp et Setup:/Network/Global/IPv4 ServiceOrder, sans quoi IPMonitor n'installe ni route ni DNS ;
    • mDNSResponder est réactivé (nécessaire au DNS) ;
    • tous les descripteurs passés à pppd doivent être FD_CLOEXEC ;
    • SpringBoard refuse SBSLaunchApplication sans l'entitlement launchapplications.

Pièges

  • pkill -f 'ipod-netd -s' tue aussi le shell qui lance la commande : utiliser pkill -x ipod-netd.
  • ipod-build-fw réécrit images/5H11a/. Refaire make dans agent/ avant, si l'agent a changé.
  • -f efface la session persistée. Ne pas l'utiliser pour vérifier la conservation d'un fichier.
  • QEMU et netd sont lancés avec setsid pour survivre à la fin du terminal appelant. Le launcher suit netd par PID.
  • Un .deb 2.x qui dépend d'un jailbreak (bash, etc.) ne marchera pas : aucun shell dans l'image.

Reprise du 30 septembre 2026 — après déplacement des homes

  • Projets retrouvés sous /srv/homes/iphoneos2/{ipod-emu,qemu-ios}.
  • Bibliothèques QEMU/libslirp et ImageMagick réinstallées dans le conteneur courant ; lien BootROM 5H11a corrigé en relatif ; commandes accessibles par wrappers dans /home/dev/.local/bin. Ces installations locales ne modifient pas l'image Docker.
  • Lanceur adapté aux chemins déplacés, sélection automatique des keymaps via -L, contrôle du démarrage QEMU et du relais, nettoyage du relais même après arrêt QEMU. ipod-qmp prend en compte IPOD_EMU_HOME.
  • Test sur une copie de NAND dans ipod-emu/work/reprise-20260930 : boot de l'image de base en 28 s, Safari et HTTP validés. push-app HelloEmu.app copie les deux fichiers, redémarre sans réinitialiser la NAND ; HelloEmu apparaît ensuite dans le catalogue apps. Le lancement répond toujours device locked, écran noir. Le programme de diagnostic unlock retourne 0 sans débloquer le lancement. Ne pas considérer le lancement après installation comme validé.
  • La copie de la session initiale est conservée dans work/reprise-20260930/run-session-initiale ; la session d'origine run/nand n'a pas été modifiée. Les captures et préférences de diagnostic sont dans le dossier de reprise. Les sessions de test ont été arrêtées.
  • Un ancien relais survivant à QEMU occupait 7788 : supprimé avant de refaire le test. L'ancien code continuait malgré l'échec de redirection ; le nouveau échoue.
  • Vérifications : syntaxe Bash/Python, compilation du relais avec -Wall, échec QEMU volontaire détecté immédiatement, port 7788 occupé détecté et QEMU arrêté, stop après échec. Aucun changement des modèles matériels dans cette reprise.