[PS5] Le writeup de sleirsgoevy sur son hack
Débuté par tralala, oct. 09 2023 19:43
9 réponses à ce sujet
Posté 09 octobre 2023 - 19:43
#1
Le développeur sleirsgoevy vient de mettre en ligne un énorme writeup sur le hack utilisé sur le firmware 4.03, un writeup c'est un résumé de tout ce qu'il faut comprendre pour arriver au même résultat que le code. Pour bien comprendre, le writeup permet si les bons offsets sont trouvés, de porter sur d'autres firmwares les possibilités offertes.
C'est un travail impressionnant, notamment parce que le hacker a dû trouver des décalages de fonctions spécifiques à détourner, bien que le noyau PS5 soit XOM (eXecute Only Memory). Dans un long article, le développeur explique comment il a fait cela et comment cela pourrait être reproduit sur d'autres firmwares.
Il aura fallu plusieurs mois à Sleirsgoevy pour comprendre le fonctionnement du firmware 4.03, il est clair que porter ces possibilités sur d'autres firmwares ne se fera pas en claquant des doigts. Sleirsgoevy ne dispose pas de 20 PS5, et il est probable qu'il n'en dispose que d'une sous firmware 4.03, le portage ne sera possible qu'avec l'appui d'autres développeurs.
Voici les principales catégories de compensations :
- Décalages de données du noyau, ceux-ci peuvent être trouvés à partir de vidages de données qui ne sont pas protégés par XOM
- Décalages de texte du noyau indiqués par les données du noyau
doreti_iret offset (c'est un offset, mais il mérite son propre mot)
- Décalages trouvés à partir d'une seule étape des fonctions du noyau
- Compensations trouvées dans les journaux ps5-kstuff (appelés « parasites » dans la source)
- Décalages des données du noyau
Ceux-ci inclus:
- Décalage IDT (recherchez quelque chose qui ressemble à un pointeur et se trouve au décalage 2 mod 8)
- Décalage TSS (par processeur, tableau)
- Décalage PCPU (par processeur, tableau. Il s'agit de la base GS du noyau)
- sysentvecs (natif et PS4, recherchez les xréfs des chaînes « Native SELF » et « PS4 SELF »)
- crypt_singleton_array (celui-ci n'a pas été trouvé dans le dump)
- Décalages de texte du noyau indiqués par les données du noyau
Cette catégorie contient principalement des gestionnaires d'interruptions, qui peuvent être recherchés dans l'IDT dumpé. Pour ps5-kstuff, deux d'entre eux sont importants : Xinvtlb (nom officiel de FreeBSD, porte également les noms de int244 ou push_pop_all_iret) et Xjustreturn (peut également être trouvé à partir d'une porte IDT).
doreti_iret
Ce décalage est crucial pour établir la primitive de kernel en une seule étape utilisée pour trouver d'autres offsets, et comme il s'agit d'un offset de texte du kernel qui n'est pointé par quoi que ce soit, il doit y avoir un moyen de résoudre ce problème de la poule et de l'œuf.
Il y en a, et voici comment :
Configurez une pile d'interruptions dédiée pour l'exception #GP (int13). Utilisez n'importe quelle primitive malloc du noyau pour lui allouer de la mémoire, ou utilisez un mappage dmem dans le noyau d'une page d'espace utilisateur (cela vous donne également plus de liberté avec l'écrasement). Soit X (x === 0 mod 16) l'adresse de pile écrite dans le TSS.
À partir d'un autre thread, utilisez la primitive d'écriture de 20 octets pour écrire {(uint64_t)0x43, (uint64_t)0x202, (uint32_t)0} à l'adresse X-32.
Dans le thread principal, épinglé au CPU pour lequel vous avez reconfiguré le TSS, utilisez setcontext ou sigreturn pour charger un mcontext_t qui a une adresse non canonique (les 16 premiers bits non 0000 ou ffff) dans son champ mc_rip. Cela provoque le crash du noyau avec #GP sur l'instruction iret (dont on veut connaître l'adresse).
Normalement, le kernel gérerait cela comme un crash et une panique du noyau (ps5-kstuff corrige cela, FreeBSD et PS4 ne sont pas affectés). Cependant, le thread d'arrière-plan qui écrit écrase m_cs et m_eflags (et les 4 octets faibles de m_rsp) dans la trame de trap enregistrée avec des valeurs d'espace utilisateur valides, et fait croire au noyau qu'il s'agit d'un crash dans l'espace utilisateur.
Avant de faire tout ce qui précède, configurez une pile de signaux pour votre thread avec sigaltstack(2) (le rsp « actuel » proviendra du noyau et sera corrompu, donc inutilisable) et enregistrez un gestionnaire de signaux pour SIGBUS. Lorsque le crash écrasé se produit et que le gestionnaire est appelé, récupérez l'adresse de m_rip dans le mcontext transmis.
Décalages trouvés à partir d'une seule étape des fonctions du kernel
Les deux premiers offsets importants sont rdmsr et wrmsr_ret (qui peuvent également être utilisés à la place de wrmsr). Pour trouver rdmsr, effectuez une seule étape sur n'importe quelle entrée du gestionnaire d'interruption et recherchez une instruction qui charge simultanément eax et edx avec des valeurs. Pour trouver wrmsr, il suffit de revenir en un seul pas, de changer la direction du saut (pour faire croire que vous avez un X2APIC), et de noter l'adresse qui fait paniquer la console.
Vous aurez également besoin de l'adresse du « rep movsb ; pop rbp; ret", qui se trouve dans la fonction memcpy(2) et est utilisé par les programmes de trace pour lire/écrire la mémoire du noyau. Pour le trouver, exécutez la trace (sans vider la trame iret, ce que vous ne pouvez pas encore faire) jusqu'à ce que vous voyiez l'effet secondaire spécifique de « rep movsb » ou « rep movsq » (rdi += 1 ou 8, rsi += 1 ou 8, rcx -= 1), puis exécutez à nouveau la trace et utilisez ce gadget pour copier sa propre adresse dans la mémoire de l'espace utilisateur. …Eh bien, c'est comme ça que j'ai fait, il peut être plus facile de mapper la page contenant le cadre de retour dans l'espace utilisateur et de la lire directement.
Une fois que vous les avez mis en place, le suivi des appels système avec la famille de fonctions r0gdb_trace() devrait fonctionner et vous pourrez obtenir des traces du noyau (au moins 1 autre personne est arrivée ici le 4.50). Cela vous permet de trouver un tas d'autres compensations :
malloc(9) — tracez l'allocation IPV6_RTHDR, recherchez un appel avec la taille spécifiée dans rdi. Attention aux arrondis. M_something est le deuxième argument (rsi) de l'appel malloc. Cela est nécessaire pour avoir une primitive d'allocation de taille illimitée, car le malloc basé sur RTHDR est limité à 2 KiB.
printf — tracez dynlib_load_prx d'un fSELF, recherchez le message d'erreur dans rdi. Non nécessaire au moment de l'exécution, mais utile pour trouver d'autres décalages.
Décalage du point d'arrêt mprotect, décalage du point d'arrêt mmap pour le mappage SELF (pour cela, regardez simplement d'où proviennent les codes d'erreur), décalages de contournement des autorisations d'autres appels système.
Cependant, si vous essayez de retrouver un appel système bloquant, le système se bloquera. Cela se produit parce que l'indicateur d'interruption (TF) n'est pas effacé lors des changements de tâches dans le noyau et est divulgué dans un autre processus, où la partie espace utilisateur du mécanisme de traçage n'est pas mappée. Pour éviter cela, vous devez trouver cpu_switch (qui, d'après le PoV du thread appelant, est la primitive de blocage la plus atomique et doit être non tracée). L'idée est la suivante : lancez un appel système bloquant connu (nanosleep est le meilleur car il se réveille tout seul) sous count_instrs, faites une recherche binaire sur le nombre d'instructions jusqu'à ce que vous soyez proche (100-200 instructions) du point de mort . Videz la trace, ouvrez le premier appel de fonction. À la fin, vous verrez un tas de rets sans instructions entre les deux ; cela se produit parce que le pointeur de pile du nouveau thread est chargé, et il arrive (cela m'est arrivé ?) d'être plus haut en mémoire que l'ancien. La dernière instruction avant que cela ne se produise se trouve dans cpu_switch, faites défiler jusqu'au début de la fonction et notez l'adresse.
cpu_switch peut également être trouvé en recherchant une instruction d'appel se produisant sur une pile alignée sur 8 octets (l'alignement normal est de 16 octets). Mais cela n'a pas fonctionné pour moi.
Une fois cette case cochée, vous pouvez désormais tracer des appels système (presque) arbitraires. La plupart des compensations liées à fSELF sont désormais trouvées de manière triviale :
sceSblServiceMailbox — tracez load_prx, recherchez « sx_xlock » transmis en tant que rdi, prenez note de l'adresse de la fonction. Pour vérifier, faites-lui renvoyer une erreur et consultez le journal du noyau.
sceSblServiceMailbox_lr_* (également connu sous le nom de *_call_mailbox) — tracez load_prx, recherchez les appels de sceSblServiceMailbox, prenez note de l'adresse de retour (poussée vers la pile). Pour distinguer les adresses de retour, faites-lui renvoyer -1 et consultez le journal du noyau.
sceSblAuthMgrIsLoadable2 — il s'agit de la fonction qui effectue le deuxième appel de boîte aux lettres pendant load_prx. Je ne me souviens pas si son nom peut être trouvé via l'indice précédent.
sceSblServiceMailbox_lr_decryptMultipleSelfBlocks est spécial, car il n'est normalement pas appelé sur les fichiers SELF vendus au détail et fera planter le noyau en cas de panne. Pour trouver cela, mappez un fSELF suffisamment grand avec un programme de trace qui bloque l'exécution à chaque appel de boîte aux lettres *inconnu* (vous pouvez utiliser trace_mailbox de prosper0gdb). Après le blocage, dans un autre thread, videz le journal de trace, extrayez la dernière valeur RSP enregistrée et lisez l'adresse de retour. Utilisez r0gdb singlestep pour confirmer le message d'erreur.
Un autre ensemble de décalages importants peut être trouvé en effectuant une seule étape avec la fonction cpu_switch (et pmap_activate_sw qu'elle appelle).
Ceux-ci sont:
dr2gpr (lecture des registres de débogage) : pas à pas sur le mauvais chemin d'un condjump (quelque part près du début de la fonction).
gpr2dr (écriture des registres de débogage) : pas à pas sur le mauvais chemin d'un condjump (quelque part près de la fin de la fonction). Notez que le code y est légèrement différent de FreeBSD 11 : il écrit d'abord dr0-dr3, puis effectue un tas d'accès MSR sans rapport (qui manquent sur FreeBSD), et ensuite seulement il écrit finalement dr6-dr7.
mov_rdi_cr3, mov_cr3_rax — pmap_activate_sw en une seule étape avec le thread actuel, prenez le mauvais chemin lors d'un condjump pour lui faire penser qu'il doit changer de table de pages.
Cela devrait être suffisant pour que le déchiffrement fSELF fonctionne pendant l'appel système, pour les décalages de pagination paresseux, voir la section suivante.
Les décalages de boîte aux lettres fPKG, sceSblServiceMailbox_lr_verifySuperBlock et sceSblServiceMailbox_lr_sceSblPfsClearKey_{1,2}, sont plus délicats.
Le syscall mount(2) utilisé pour le montage PFS est trop volumineux pour être entièrement effectué en une seule étape, vous devez donc utiliser des registres de débogage pour interrompre sceSblServiceMailbox et enregistrer les accès. L'appel verifySuperBlock a {uint64_t}rdx == 0x11, les deux appels sceSblPfsClearKey (ceux-ci n'apparaissent que si vous usurpez le premier) ont {uint64_t}rdx == 3.
Compensations trouvées dans les journaux ps5-kstuff (appelés « parasites » dans la source)
Ceux-ci peuvent être regroupés en 2 groupes selon leur importance :
Parasites importants – syscall_before, points de surveillance fSELF, sceSblServiceCryptAsync_deref_singleton. Ceux-ci sont utilisés pour implémenter des fonctionnalités, sans eux, certaines parties de ps5-kstuff ne fonctionneront pas.
Parasites sans importance : ils ne causent aucun dommage s'ils ne sont pas manipulés, mais il est préférable de les manipuler pour éviter d'encombrer les bûches.
ps5-kstuff utilise la technique que j'ai baptisée « empoisonnement du pointeur » pour insérer des hooks dans le kernel sans recourir à des correctifs de texte. En cas d'empoisonnement du pointeur, les 16 premiers bits du pointeur sont remplacés par la constante 0xdeb7 (signifie « DEad PoinTer »), ce qui en fait une adresse non canonique. Lorsque ce pointeur est déréférencé ultérieurement, #GP se produit, qui est accroché par ps5-kstuff, et ce dernier essaie de réparer tous les pointeurs marqués avec 0xdeb7 en pointeurs normaux du noyau, et ces correctifs sont enregistrés dans le journal ps5-kstuff (il devrait normalement il n'y en a pas). Une fois que vous avez l'adresse, vous devez déterminer si elle est importante ou non :
Parasites Syscall – ils apparaissent lorsque vous empoisonnez le pointeur sysents dans le sysvec. Le troisième d'entre eux est syscall_before, les autres ne sont pas importants.
Parasites fSELF — ils apparaissent lorsque vous empoisonnez le pointeur self_header dans self_context. Deux d'entre elles se trouvent à l'intérieur de fonctions appelées au début de decryptSelfBlock (_sceSblAuthMgrLoadSelfBlock) et decryptMultipleSelfBlocks (_sceSblAuthMgrLoadMultipleSelfBlocks), et sont utilisées comme points de surveillance (*_watchpoint_*), d'autres ne sont pas importantes.
Parasites fPKG — un parasite apparaît lorsque vous corrompez les objets de chiffrement à l'intérieur de crypt_singleton_array, à savoir sceSblServiceCryptAsync_deref_singleton.
D’autres parasites peuvent apparaître, ils sont généralement sans importance.
Les parasites importants sont traités dans try_handle_*_trap comme n'importe quel autre piège. Les parasites non importants sont gérés dans handle_*_parasite dans uelf/parasites.h.
Suivi des fichiers de montage
Pour tracer mountpfs, j'ai corrigé le processus shellcore, de sorte que lorsqu'il rencontre un appel système mount(2), il vide ses arguments vers une adresse connue et se bloque. Ensuite, je lis les arguments de mon processus principal en utilisant mdbg_call et j'appelle le même syscall dans le processus principal. Voir « CODE TEMPORAIRE » dans ps5-kstuff/main.c à l'adresse commit 960a62520cd561adb66c2c010a040bc50a659633 pour plus d'informations (vous devrez ajuster les offsets des correctifs shellcore, mais c'est trivial car des dumps de texte sont disponibles).
Derniers mots
Quelqu’un a suggéré qu’un « payload de test » pourrait être développé pour découvrir les compensations. Ce ne serait pas anodin, car cela impliquait beaucoup de devinettes manuelles et d'observation des traces, pour essayer de donner un sens à ce qui se passait. De plus, certaines de ces vérifications impliquent de faire paniquer le système, de sorte qu'il ne peut pas être entièrement automatisé en tant que payload unique. À mon avis, une meilleure approche consiste à développer un script qui s'exécuterait sur PC et sonderait la PS5 sur le réseau – ce script ne se terminera pas en cas de panique, et il pourrait même détecter la panique et demander à l'utilisateur d'agir. Je pense que cela pourrait être le meilleur moyen de réduire le travail manuel impliqué dans la recherche, afin que tous ceux qui possèdent une PS5 puissent contribuer aux compensations en exécutant simplement le script.
Tout est là : github.com/sleirsgoevy
Posté 09 octobre 2023 - 20:09
#2
Et après il y en a qui vont encore oser critiquer des cerveaux pareils...
Respect !!!
Respect !!!
- cyberfred91, Waikiki et vinc3iz aiment ceci
Posté 09 octobre 2023 - 20:11
#3
GG a nos devs.
La vache c'est vraiment technique les hacks.
La vache c'est vraiment technique les hacks.
ôô
Posté 09 octobre 2023 - 21:17
#4
Chapeau bas sleirsgoevy !
Posté 09 octobre 2023 - 21:28
#5
sleirsgoevy est incroyable ! Merci Tralala pour ce long article traduit.
Posté 09 octobre 2023 - 22:08
#6
C'est quand tu lis des articles de ce genre que tu t'aperçois réellement que les mecs sont vraiment balèzes, des putains de génie...
Bravo à lui en tous cas et un grand merci pour tous ce qu' il a fait, fait et fera...
Bravo à lui en tous cas et un grand merci pour tous ce qu' il a fait, fait et fera...
Posté 10 octobre 2023 - 06:08
#7
Merci pour le travail
Modifié par kentder, 10 octobre 2023 - 06:08.
Posté 10 octobre 2023 - 06:28
#8
ça fait plaisir de voir que ça bouge.
Posté 10 octobre 2023 - 08:05
#9
Un monstre bravo à lui
Posté 11 octobre 2023 - 17:20
#10
et oui c'est ce qu'on appelle chercher une aiguille dans une botte de foin, gg a eux pour leur génie et créativité dans les hacks
0 utilisateur(s) li(sen)t ce sujet
0 invité(s) et 0 utilisateur(s) anonyme(s)