
Sortie du journal du noyau après avoir compilé et exécuté example/open1_hook.c
XNUSPY est un module Pongoos qui installe un nouvel appel système, xnuspy_ctl , qui vous permet d'accrocher les fonctions du noyau à partir de l'espace utilisateur. Il prend en charge iOS 13.x, iOS 14.x et iOS 15.x sur Checkra1n 0.12.2 et plus. Les appareils 4K ne sont pas pris en charge.
Ce module sède complètement KTRR / KPP et permet de créer une mémoire RWX à l'intérieur EL1. Ne l'utilisez pas sur votre conducteur quotidien.
Nécessite libusb : brew install libusb
Run make dans le répertoire de niveau supérieur. Il construira le chargeur et le module.
Ajoutez-les avant make .
XNUSPY_DEBUG=1kprintf ).XNUSPY_SERIAL=1IOLog .XNUSPY_LEAKED_PAGE_LIMIT=n64 . Plus d'informations peuvent être trouvées sous les paniques du noyau de débogage.XNUSPY_TRAMP_PAGES=n XNUSPY_DEBUG et XNUSPY_SERIAL ne dépendent pas les uns des autres.
Après avoir tout construit, demandez à Checkra1n démarrez votre appareil sur un shell pongo: /Applications/checkra1n.app/Contents/MacOS/checkra1n -p
Dans le même répertoire, vous avez construit le chargeur et le module, faites loader/loader module/xnuspy . Après cela, Xnuspy fera son truc et en quelques secondes, votre appareil démarrera. loader attendra quelques secondes de plus après la publication xnuspy-getkernelv au cas où seprom devra être exploité.
Parfois, un couple de mes téléphones restait coincé au "démarrage" après les courses KPF de CheckRA1n. Je n'ai pas encore compris ce qui cause cela, mais si cela se produit, réessayez. De plus, si l'appareil est suspendu après bootx , réessayez. Enfin, marquer le code xnuspy_ctl compilé comme exécutable sur mon iPhone X exécutant iOS 13.3.1 est un peu inégal, mais réussit 100% du temps sur mes autres téléphones. Si vous paniquez avec une instruction du noyau, récupérez-vous lorsque vous exécutez votre programme de crochet, réessayez.
XNUSPY PATERA UN enosys SYSTÈME POUR POURRER À xnuspy_ctl_tramp . Il s'agit d'un petit trampoline qui marque le code xnuspy_ctl compilé comme exécutable et se ramifie. Vous pouvez trouver l'implémentation de xnuspy_ctl à module/el1/xnuspy_ctl/xnuspy_ctl.c et des exemples dans l' example de répertoire.
À l'intérieur include/xnuspy/ est xnuspy_ctl.h , un en-tête qui définit les constantes pour xnuspy_ctl . Il est destiné à être inclus dans tous les programmes qui accrochent les fonctions du noyau.
Vous pouvez utiliser sysctlbyname pour déterminer quel appel système a été corrigé:
size_t oldlen = sizeof(long);
long SYS_xnuspy_ctl = 0;
sysctlbyname("kern.xnuspy_ctl_callnum", &SYS_xnuspy_ctl, &oldlen, NULL, 0);
Cet appel système prend quatre arguments, flavor , arg1 , arg2 et arg3 . La saveur peut être XNUSPY_CHECK_IF_PATCHED , XNUSPY_INSTALL_HOOK , XNUSPY_REGISTER_DEATH_CALLBACK , XNUSPY_CALL_HOOKME , XNUSPY_CACHE_READ , XNUSPY_KREAD , XNUSPY_KWRITE , ou XNUSPY_GET_CURRENT_THREAD . La signification des trois arguments suivants dépend de la saveur.
XNUSPY_CHECK_IF_PATCHED Cela existe afin que vous puissiez vérifier si xnuspy_ctl est présent. L'invoquer avec cette saveur le fera revenir 999 . Les valeurs des autres arguments sont ignorées.
XNUSPY_INSTALL_HOOK J'ai conçu cette saveur pour correspondre à l'API de MSHookFunction . arg1 est l'adresse non liée de la fonction du noyau que vous souhaitez accrocher. Si vous fournissez une adresse glissante, vous paniquerez probablement. arg2 est un pointeur vers votre fonction de remplacement compatible ABI. arg3 est un pointeur pour xnuspy_ctl pour copyout l'adresse d'un trampoline qui représente la fonction du noyau d'origine. Cela peut être NULL si vous n'avez pas l'intention d'appeler l'original.
XNUSPY_REGISTER_DEATH_CALLBACKCette saveur vous permet d'enregistrer un "rappel de mort" en option, une fonction que Xnuspy appellera lorsque votre programme de crochet sortira. Il vous donne une chance de nettoyer tout ce que vous avez créé à partir de vos crochets de noyau. Si vous créiez des fils de noyau, vous leur dirais de se terminer dans cette fonction.
Votre rappel n'est pas invoqué de manière asynchrone, donc si vous bloquez, vous empêchez l'exécution du fil de collecte des ordures de Xnuspy.
arg1 est un pointeur vers votre fonction de rappel. Les valeurs des autres arguments sont ignorées.
XNUSPY_CALL_HOOKME hookme est un petit talon d'assemblage que Xnuspy exporte à travers le cache Xnuspy pour vous à accrocher. Invoquer xnuspy_ctl avec cette saveur provoquera l'appel hookme , vous offrant un moyen de gagner facilement l'exécution du code du noyau sans avoir à accrocher une fonction du noyau réelle.
arg1 est un argument qui sera transmis à hookme lorsqu'il sera invoqué. Cela peut être NULL .
XNUSPY_CACHE_READ Cette saveur vous donne un moyen de lire à partir du cache Xnuspy. Il contient beaucoup de choses utiles comme kprintf , current_proc , kernel_thread_start , certaines fonctions Libc et la diapositive du noyau pour ne pas avoir à les trouver vous-même. Pour une liste complète des ID de cache, consultez example/xnuspy_ctl.h .
arg1 est l'un des ID de cache définis dans xnuspy_ctl.h et arg2 est un pointeur pour xnuspy_ctl pour copyout l'adresse ou la valeur de ce que vous avez demandé. Les valeurs des autres arguments sont ignorées.
XNUSPY_KREADCette saveur vous donne un moyen facile de lire la mémoire du noyau de l'espace utilisateur sans TFP0.
arg1 est une adresse virtuelle du noyau, arg2 est l'adresse d'un tampon d'espace utilisateur et arg3 est la taille de ce tampon d'espace utilisateur. Les octets arg3 seront écrits de arg1 à arg2 .
XNUSPY_KWRITECette saveur vous donne un moyen facile d'écrire à la mémoire du noyau à partir de l'espace utilisateur sans TFP0.
arg1 est une adresse virtuelle du noyau, arg2 est l'adresse d'un tampon d'espace utilisateur et arg3 est la taille de ce tampon d'espace utilisateur. Les octets arg3 seront écrits de arg2 à arg1 .
XNUSPY_GET_CURRENT_THREADCette saveur offre à l'espace des utilisateurs l'adresse du noyau du fil d'appel.
arg1 est un pointeur pour xnuspy_ctl pour copyout la valeur de retour de current_thread . Les valeurs des autres arguments sont ignorées.
Pour toutes les saveurs sauf XNUSPY_CHECK_IF_PATCHED , 0 est renvoyé sur succès. Lors de l'erreur, -1 est renvoyé et errno est défini. XNUSPY_CHECK_IF_PATCHED ne renvoie aucune erreur. mach_to_bsd_errno de XNU est utilisé pour convertir un kern_return_t en errno approprié.
XNUSPY_INSTALL_HOOK errno est défini sur ...
EEXIST si:arg1 .ENOMEM si:unified_kalloc a renvoyé NULL .ENOSPC si:xnuspy_tramp gratuites, une structure de données interne à xnuspy. Cela ne devrait se produire que si vous accrochez des centaines de fonctions de noyau en même temps . Si vous avez besoin de plus de crochets de fonction, consultez les limites.ENOTSUP si:ENOENT si:mh_for_addr n'a pas pu déterminer l'en-tête Mach-O correspondant à arg2 dans l'espace d'adressage de l'appelant.EFAULT si:EIO si:mach_make_memory_entry_64 n'a pas renvoyé une entrée de mémoire pour l'intégralité des segments __TEXT et __DATA de l'en-tête Mach-O déterminé. errno dépend également de la valeur de retour de vm_map_wire_external , mach_vm_map_external , mach_make_memory_entry_64 , copyin , copyout , et le cas échéant, la fonction d'initialisation unique.
Si cette saveur renvoie une erreur, la fonction du noyau cible n'était pas accrochée. Si vous avez passé un pointeur non NULL pour arg3 , il peut avoir été initialisé ou non. C'est dangereux à utiliser si c'était le cas.
XNUSPY_REGISTER_DEATH_CALLBACK errno est défini sur ...
ENOENT si:Si cette saveur renvoie une erreur, votre rappel de mort n'a pas été enregistré.
XNUSPY_CALL_HOOKME errno est défini sur ...
ENOTSUP si:hookme est trop éloigné de la mémoire contenant les structures xnuspy_tramp . Ceci est déterminé à l'intérieur de Pongoos, et ne peut se produire que si Xnuspy devait se replier à un code inutilisé déjà à l'intérieur du KernelCache. Dans ce cas, appeler hookme provoquerait presque certainement une panique du noyau, et vous devrez comprendre une autre fonction du noyau. Si cette saveur renvoie une erreur, hookme n'a pas été appelé.
XNUSPY_CACHE_READ errno est défini sur ...
EINVAL si:arg1 ne représente rien dans le cache.arg1 était IO_LOCK , mais le noyau est iOS 14.4.2 ou moins ou iOS 15.x.arg1 était IPC_OBJECT_LOCK , mais le noyau est iOS 15.x.arg1 était IPC_PORT_RELEASE_SEND , mais le noyau est iOS 14,5 ou plus.arg1 était IPC_PORT_RELEASE_SEND_AND_UNLOCK , mais le noyau est iOS 14.4.2 ou moins.arg1 était KALLOC_CANBLOCK , mais le noyau est iOS 14.x ou supérieur.arg1 était KALLOC_EXTERNAL , mais le noyau est iOS 13.x.arg1 était KFREE_ADDR , mais le noyau est iOS 14.x ou supérieur.arg1 était KFREE_EXT , mais le noyau est iOS 13.x.arg1 était PROC_REF , mais le noyau est iOS 14,8 ou moins.arg1 était PROC_REF_LOCKED , mais le noyau est iOS 15.x.arg1 était PROC_RELE , mais le noyau est iOS 14.8 ou moins.arg1 était PROC_RELE_LOCKED , mais le noyau est ios 15.x.arg1 était VM_MAP_UNWIRE , mais le noyau est iOS 15.x.arg1 était VM_MAP_UNWIRE_NESTED , mais le noyau est iOS 14.8 ou moins. errno dépend également de la valeur de retour de copyout et le cas échéant, la valeur de retour de la fonction d'initialisation unique.
Si cette saveur renvoie une erreur, le pointeur que vous avez passé pour arg2 n'a pas été initialisé.
XNUSPY_KREAD et XNUSPY_KWRITE errno est défini sur ...
EFAULT si:arg1 ou arg2 . Si vous avez compilé avec XNUSPY_DEBUG=1 , un message à ce sujet est imprimé dans le journal du noyau.Si cette saveur renvoie une erreur, la mémoire du noyau n'a pas été lue / écrite.
XNUSPY_GET_CURRENT_THREAD Si copyout échoue, errno est défini sur sa valeur de retour.
Lors de l'écriture de fonctions de remplacement, il était facile d'oublier que j'écrivais du code du noyau. Voici quelques éléments à garder à l'esprit lorsque vous écrivez des crochets:
__TEXT de votre programme . Vous paniquerez si, par exemple, vous appelez accidentellement printf au lieu de kprintf . Vous devez réimpliquer la fonction LIBC que vous souhaitez appeler si cette fonction n'est pas déjà disponible via XNUSPY_CACHE_READ . Vous pouvez créer des pointeurs de fonction vers d'autres fonctions du noyau et les appeler, cependant.PAGE_SIZE se développe à vm_page_size , pas une constante. Vous devez désactiver la casserole (sur A10 +, ce que je ne recommande pas non plus) avant de lire cette variable ou vous paniquerez.-fno-stack-protector et -D_FORTIFY_SOURCE=0 Dans certains cas, l'appareil devra lire ___stack_chk_guard en dérégérencer un autre pointeur d'espace utilisateur, qui paniquera sur A10 +.Écrémage https://developer.apple.com/library/archive/documentation/darwin/conceptual/kernelprogramming/style/style.html est également recommandé.
Les bogues sont inévitables lors de la rédaction de code, donc finalement vous allez provoquer une panique du noyau. Une panique ne signifie pas nécessairement qu'il y a un bug avec XNUSPY, donc avant d'ouvrir un problème, assurez-vous que vous paniquez toujours lorsque vous ne faites rien d'autre que d'appeler la fonction d'origine et de renvoyer sa valeur (si nécessaire). Si vous paniquez toujours, c'est probablement un bug Xnuspy (et veuillez ouvrir un problème), mais sinon, il y a quelque chose qui ne va pas avec votre remplacement.
Étant donné que XNUSPY ne redirige pas en fait l'exécution vers les pages EL0, déboguer une panique n'est pas aussi simple. Ouvrez module/el1/xnuspy_ctl/xnuspy_ctl.c , et juste avant le seul appel à kwrite_instr dans xnuspy_install_hook , ajoutez un appel à IOSleep pendant quelques secondes. Ceci est fait pour s'assurer qu'il y a suffisamment de temps avant que l'appareil panique pour que les journaux se propagent. Recompilez XNUSPY avec XNUSPY_DEBUG=1 make -B et chargez à nouveau le module. Après avoir chargé le module, si vous ne l'avez pas déjà fait, compilez klog de klog/ . Téléchargez-le sur votre appareil et faites stdbuf -o0 ./klog | grep shared_mapping_kva . Exécutez à nouveau votre programme de crochet et surveillez une ligne de klog qui ressemble à ceci:
shared_mapping_kva: dist 0x7af4 uaddr 0x104797af4 umh 0x104790000 kmh 0xfffffff00c90c000
Si vous installez plus d'un crochet, il y aura plus d'une occurrence. Dans ce cas, dist et uaddr varieront, mais umh et kmh ne le feront pas. kmh pointe vers le début de la cartographie par le noyau du segment __TEXT de votre programme. Jetez votre programme de crochet dans votre désassembleur préféré et réprimandez-le afin que son en-tête Mach-O soit à l'adresse de kmh . Pour IDA Pro, c'est Edit -> Segments -> Rebase program... avec Image base bouillonnante. Une fois que votre appareil panique et redémarre à nouveau, s'il existe des adresses qui correspondent à la cartographie du noyau de votre remplacement dans le journal de panique, ils correspondront au démontage. S'il n'y en a pas, vous avez probablement une sorte de corruption de mémoire subtile dans votre remplacement.
Xnuspy n'a également aucun moyen de savoir si un thread de noyau exécute toujours (ou exécutera) sur la cartographie du noyau du segment __TEXT de votre programme après que vos crochets soient désinstallés. L'une des choses que Xnuspy fait pour y faire face est de ne pas traiter cette cartographie immédiatement après la mort de votre programme de crochet. Au lieu de cela, il est ajouté à la fin d'une file d'attente. Une fois que le fil de collecte des ordures de Xnuspy remarque une limite définie a été dépassée concernant le nombre de pages de mappages qui sont maintenues dans cette file d'attente, il commencera à traiter de l'avant de la file d'attente et se poursuivra jusqu'à ce que cette limite ne soit plus dépassée. Par défaut, cette limite est de 1 Mo ou 64 pages.
Bien que cela aide énormément, plus les segments __TEXT et __DATA de votre programme de crochet deviennent grands, moins Xnuspy gagne cette course. Si vous paniquez régulièrement et que vous avez un programme de crochet un peu grand, essayez d'augmenter cette limite en ajoutant XNUSPY_LEAKED_PAGE_LIMIT=n avant make . Cela définira cette limite sur n pages plutôt que 64.
XNUSPY se réserve une page de mémoire du noyau statique avant les bottes XNU pour ses structures xnuspy_tramp , vous permettant d'accrocher simultanément autour de 225 fonctions de noyau. Si vous en voulez plus, vous pouvez ajouter XNUSPY_TRAMP_PAGES=n avant make . Cela indiquera à XNUSPY de réserver n pages de mémoire statique pour les structures xnuspy_tramp . Cependant, si Xnuspy doit se replier à un code inutilisé déjà à l'intérieur du KernelCache, cela est ignoré. Lorsque cela se produit est détaillé dans son fonctionnement.
Pour une raison quelconque, les journaux d' os_log_with_args n'apparaissent pas dans le flux sorti de l'outil de ligne de commande oslog . Les journaux de kprintf n'y arrivent pas non plus, mais ils peuvent être vus avec dmesg . Cependant, dmesg n'est pas un flux en direct, j'ai donc écrit klog , un outil qui montre les journaux kprintf en temps réel. Trouvez-le dans klog/ . Je recommande fortement de l'utiliser au lieu de spammer dmesg pour vos messages kprintf .
Si vous vous open: Resource busy après l'exécution klog , exécutez cette commande launchctl unload /System/Library/LaunchDaemons/com.apple.syslogd.plist et réessayez.
Malheureusement, vous ne pourrez pas voir des NSLog si atm_diagnostic_config=0x20000000 est défini dans les bootargs de XNU. klog dépend de cet argument de démarrage présent. Si vous voulez que NSLog revienne, supprimez cet argument de démarrage de pongo_send_command à l'intérieur loader.c .
XNUSPY le gérera pour vous. Une fois qu'un processus sortira, tous les crochets du noyau qui ont été installés par ce processus sont désinstallés dans une seconde environ.
La plupart des cadres d'accrochage des fonctions ont une longueur minimale qui rend une fonction donnée à crochet. XNUSPY n'a cette limite que si vous prévoyez d'appeler la fonction d'origine et que la première instruction de la fonction accrochée n'est pas B . Dans ce cas, la longueur minimale est de huit octets. Sinon, il n'y a pas de longueur minimale.
XNUSPY utilise X16 et X17 pour ses trampolines, donc les fonctions du noyau qui s'attendent à ce que ceux qui persistent à travers les appels de fonction ne peuvent pas être accrochés (il n'y en a pas beaucoup qui l'attendent). Si la fonction que vous souhaitez accrocher commence par BL et que vous avez l'intention d'appeler l'original, vous ne pouvez le faire que si l'exécution de la fonction d'origine ne modifie pas X17 .
xnuspy_ctl effectuera une initialisation ponctuelle la première fois qu'elle est appelée après un nouveau démarrage. C'est la seule partie de Xnuspy qui est dans le cadre de la course car je ne peux pas initialiser statiquement le verrou de lecture / écriture que j'utilise. Après le retour du premier appel, tous les appels futurs sont garantis pour être en file d'attente.
Ceci est simplifié, mais il capture bien l'idée principale. Un crochet de fonction dans XNUSPY est une structure qui réside sur la mémoire du noyau exécutable inscriptible. Dans la plupart des cas, il s'agit de la mémoire renvoyée par alloc_static à l'intérieur de Pongoos. Il peut être résumé à ceci:
struct {
uint64_t replacement;
uint32_t tramp[2];
uint32_t orig[10];
};
Lorsque replacement est l'adresse virtuelle du noyau (élaborée plus tard) de la fonction de remplacement, tramp est un petit trampoline qui redirige l'exécution au replacement , et orig est un trampoline plus grand et plus compliqué qui représente la fonction d'origine.
L'une des premières choses que Xnuspy fait est de déterminer où réside le remplacement EL0 dans l'espace d'adressage des processus d'appel. Cela se fait pour que les fonctions du noyau puissent être accrochées à partir de bibliothèques dynamiques. L'en-tête Mach-O qui correspond à l'adresse de ce remplacement est enregistré.
Après, une cartographie du noyau utilisateur partagée des segments __TEXT et __DATA de cet en-tête (ainsi que tout segment entre ceux, le cas échéant). __TEXT est partagé afin que vous puissiez appeler d'autres fonctions à partir de vos crochets. __DATA est partagé, les modifications des variables globales sont visibles par EL1 et EL0.
Étant donné que ce mappage est une copie individuelle de __TEXT et __DATA , il est facile de déterminer l'adresse de la fonction de remplacement de l'utilisateur. Étant donné l'adresse de l'en- u Mach-O des processus d'appel, l'adresse du début du mappage partagé k et l'adresse de la fonction de remplacement de l'utilisateur r , nous appliquons la formule suivante: replacement = k + (r - u)
Après cela, replacement est l'adresse virtuelle du noyau de la fonction de remplacement de l'utilisateur sur le mappage partagé et est écrite dans la structure du crochet de fonction. XNUSPY ne redirige pas l'exécution à l'adresse EL0 de la fonction de remplacement car c'est extrêmement dangereuse: non seulement cela nous met à la merci du planificateur, mais il ne nous donne aucun contrôle sur le scénario où un processus avec un crochet de noyau décède tandis qu'un thread de noyau est toujours en train d'exécuter sur le remplacement.
Enfin, la cartographie partagée est marquée comme exécutable et une branche immédiate inconditionnelle ( B ) est assemblée. Il dirige l'exécution jusqu'au début de tramp , et c'est ce qui remplace la première instruction de la fonction du noyau désormais silencieuse. Malheureusement, cela nous limite de la ramification à des structures à accrocher à plus de 128 Mo d'une fonction du noyau donné. XNUSPY vérifie ce scénario avant de démarrer et retombe déjà sur du code inutilisé dans le KernelCache pour que les structures de crochet résident à la place si cela trouve que cela pourrait se produire.
Je fais de mon mieux pour m'assurer que les Patchfinders fonctionnent, donc si quelque chose ne fonctionne pas, veuillez ouvrir un problème.