Utilisation de Intel Processor Trace pour tracer le code du mode de gestion du système


Cet article est consacré à tester la possibilité d'utiliser la technologie Intel Processor Trace (Intel PT) pour enregistrer des pistes en mode System Management Mode (SMM). Ce travail a été réalisé dans le cadre de Summer Of Hack 2019. Publié par @sysenter_eip .


La plupart des outils utilisés sont écrits par d'autres personnes (en particulier @d_olex , @aionescu ). Le résultat est juste une combinaison des outils disponibles afin d'obtenir le chemin d'exécution du code en mode SMM pour une carte mère spécifique . Cependant, le matériel peut être intéressant pour ceux qui veulent répéter cela pour leur plate-forme ou qui sont simplement intéressés par le travail de SMM.


Mode de gestion du système


SMM est un mode spécial et privilégié du processeur d'architecture x86, qui est disponible pendant que le système d'exploitation est en cours d'exécution, mais qui lui est complètement invisible. Il est conçu pour une interaction de bas niveau avec le matériel, la gestion de l'alimentation, l'émulation de l'héritage de l'appareil, la transition vers le mode veille (S3), l'accès au TPM et plus encore. Il fonctionne complètement isolé du système d'exploitation. Pendant la durée de l'exécution de SMM, le système d'exploitation s'arrête complètement. Le code de programme qui s'exécute dans ce mode est stocké dans la mémoire SPI-Flash de la carte mère et fait partie du micrologiciel du BIOS UEFI.


Le passage en mode SMM s'effectue à l'aide d'interruptions SMI spéciales (System Management Interrupt). L'une des options de cette interruption est disponible pour une utilisation dans l'anneau zéro (c'est-à-dire à partir du noyau du système d'exploitation) - Interruption SMI au niveau de l'application (Software SMI). De plus, nous nous concentrerons sur ces interruptions.


En raison de ses privilèges élevés, SMM présente un intérêt particulier pour la recherche en sécurité. Le compromis de SMM entraîne de graves violations de l'intégrité et de la confidentialité de l'ensemble du système, et dans la plupart des cas, il vous permet d'injecter du code malveillant qui ne peut pas être supprimé et ne peut pas être détecté par le système d'exploitation dans le micrologiciel du BIOS UEFI.


Intel Processor Trace


L'un des écueils du processus de débogage pour diverses applications très chargées est les frais généraux - les coûts des outils de débogage. Ils peuvent être réduits avec une solution matérielle.


La cinquième génération de processeurs d'Intel (Broadwell) a présenté au monde des technologies telles que Intel Processor Trace. En quoi est-il utile? Intel PT vous permet d'obtenir le flux complet d'exécution (Control Flow) de l'application déboguée avec un minimum de surcharge (<5%). Dans le même temps, il prend en charge le multithreading et peut aider à corriger des erreurs telles que la «condition de concurrence critique» en raison des horodatages lors de l'enregistrement de la trace d'application. Sans aucun doute, la technologie Intel PT offre de grandes opportunités pour écrire des outils de recherche de vulnérabilité dans les applications.


Aujourd'hui, cette technologie est utilisée dans divers outils de traçage, de débogage et d'évaluation de la couverture de code - à la fois dans les applications de niveau utilisateur et noyau. Des exemples d'outils sont disponibles sur le site Web d' Intel . Une option AFL fuzzer qui tire parti d'Intel PT est disponible dans le référentiel PTfuzzer . Parmi les projets récents, faites attention à l' iptanalyzer .


Cependant, nous n'avons vu aucun travail sur l'utilisation d'Intel PT en mode SMM. Puisque rien ne nous empêche d'utiliser Intel PT dans ce contexte, nous avons décidé de savoir s'il était possible de tracer le code du mode de gestion du système avec lui.


Préparation au travail


Il résulte du manuel du développeur Intel qu'il est impossible d'activer le traçage Intel PT dans SMM de l'extérieur en utilisant des moyens réguliers. S'il était actif au moment où le SMI a été déclenché, le processeur le désactivera avant de transférer le contrôle au point d'entrée du gestionnaire SMI. La seule méthode d'activation est l'inclusion volontaire du gestionnaire SMI par le code lui-même.


Même si le processeur ne fournit pas initialement une telle opportunité, nous pouvons l'intercepter et activer manuellement Intel PT. Cependant, vous devez en quelque sorte déterminer que le système est prêt à enregistrer la trace (l'adresse du tampon de sortie est définie) et également désactiver le suivi à la fin de l'exécution du processeur (exécution de l'instruction RSM). Sinon, le processeur arrêtera tout le système.


Tout d'abord, vous devez accéder à SMRAM (la zone de RAM dans laquelle se trouve le code exécuté en mode SMM). Étant donné que cette région RAM est protégée, nous ne pouvons pas y accéder à partir du système d'exploitation (même cela ne peut pas être fait avec DMA). Il existe plusieurs scénarios:


  1. exploiter une vulnérabilité connue dans SMM et en tirer la primitive R / W. Cela peut être une erreur logicielle (une vulnérabilité dans le processeur SMI lui-même; en règle générale, dans SMM, il y a suffisamment de code qui a été ajouté par l'OEM, donc les vulnérabilités ne sont pas rares), ainsi qu'une configuration de plate-forme vulnérable (déverrouillage / déplacement de SMRAM);
  2. pour patcher l'image UEFI de telle manière que nous ayons une interface pour lire et écrire dans des adresses arbitraires - une porte dérobée. Pour implémenter cette option, vous devez trouver une carte mère sur laquelle Intel Boot Guard est désactivé ou il existe des vulnérabilités qui peuvent la contourner.

Intégrez votre code dans le firmware


Malgré le fait que des vulnérabilités SMM dans le code de différents fabricants se trouvent de temps en temps , il vaudrait mieux que nous ne nous appuyions pas sur elles. Il est plus intéressant pour nous de retracer le code sur un nouveau firmware et, en conséquence, d'essayer de trouver des vulnérabilités dans ceux-ci. Nous avions déjà la carte mère GIGABYTE GA-Q270M-D3H avec Intel Boot Guard désactivée, donc tout ce que nous avions à faire était d'ajouter une porte dérobée à SMM.



Figure 1. Banc d'essai


Il existe déjà un cadre pour «infecter» SMM et travailler avec une porte dérobée . Il se compose de trois composants: le pilote UEFI en C, l '"infecteur" et le script client en Python. Pour son fonctionnement, vous devez extraire un pilote DXE arbitraire (vous pouvez le faire en utilisant UEFITool ) et le traiter avec un infecteur. Le module d'origine a été remplacé par «amélioré» et le micrologiciel a été téléchargé dans la mémoire SPI (pour faciliter le flashage, le lecteur flash SPI a été retiré de la carte).



Figure 2. Puce de mémoire SPI-Flash


Le système a démarré avec succès et nous avons maintenant un accès complet à SMRAM depuis Python (un exemple d'utilisation est fourni avec la porte dérobée). Étant donné que le script client pour la porte dérobée est basé sur CHIPSEC , vous devez lui donner accès au mode noyau (nous avons utilisé le pilote RWEverything; il sera pratique pour quelqu'un d'utiliser son propre pilote CHIPSEC avec la vérification de signature désactivée dans le système).


Vous pouvez vérifier la porte dérobée en demandant un vidage SMRAM.


$ python SmmBackdoor.py -d


Après avoir exécuté cette commande, le fichier SMRAM_dump_cb000000_cb7fffff.bin sera créé contenant l'état SMRAM actuel. Les valeurs cb000000 et cb7fffff sont, respectivement, les adresses physiques du début et de la fin de SMRAM.


Travailler avec dump SMRAM


Le vidage SMRAM peut être chargé dans un désassembleur ou transmis pour analyse au script smram_parse.py , qui extraira de nombreuses informations utiles pour nous. Le plus important pour nous sera l'adresse des points d'entrée SMI. Ce sont les adresses des fonctions vers lesquelles le contrôle sera transféré lorsque SMI sera déclenché. Chaque CPU a son propre point d'entrée.



Figure 3. La sortie du script smram_parse


Regardons leur code. Étant donné que SMM commence son exécution en mode réel 16 bits (les 4 premiers Go de RAM se reflètent dans l'espace virtuel), la première chose que le code fait est de passer en mode 64 bits. Dans le même temps, l'intégralité de la SMRAM est disponible avec des droits d'écriture et d'exécution, car un seul segment a été créé (existe-t-il des fournisseurs qui le font différemment?).


Nous ne voudrions pas écrire du code 16 bits ou préparer tout le nécessaire pour passer en mode 64 bits par nous-mêmes, nous placerons donc notre intercepteur juste avant d'appeler la fonction de gestionnaire SMI (cette fonction détermine quel module SMM l'exécution doit être transférée en fonction de quel service a été appelé ou quel événement s'est produit).



Figure 4. Emplacement pour le crochet


La façon la plus simple de prendre le contrôle est de remplacer l'adresse du répartiteur par la nôtre. Tous les points d'entrée ont le même code, donc le patch doit être répété pour chacun.


Remarque: Concernant l'emplacement du code d'intercepteur. Puisque la structure de SMRAM n'est pas complètement connue de nous, nous avons choisi un morceau aléatoire de mémoire nulle près d'un des points d'entrée, où nous avons placé le code d'intercepteur. La meilleure option serait d'ajouter votre module SMM au firmware, que l'UEFI placerait légalement dans SMRAM, afin de ne pas craindre que quelque chose d'important soit écrasé par notre code.


Implémentation d'un intercepteur SMI Manager


Désignons ce que nous allons faire exactement à l'intérieur de notre intercepteur. Nous devons d'abord déterminer si Intel PT a été activé avant de passer à SMM. Il est connu de la documentation d'Intel que chaque processeur a sa propre base SMBASE (MSR 0x9E) et son propre espace pour stocker l'état du processeur (SMM Save State area) au moment de la transition vers SMM.



Figure 5. Disposition SMBASE


Nous déterminons le statut d'Intel PT


Dans le Save State SMM, la valeur du registre MSR IA32_RTIT_CTL, qui est responsable de la gestion du suivi Intel PT, doit être enregistrée. Malheureusement, Intel Manual n'indique pas où le processeur enregistre l'état du bit IA32_RTIT_CTL.TraceEn au moment de la transition vers SMM (si le traçage est activé, zéro bit). Cependant, nous pouvons le déterminer nous-mêmes en vidant deux fois le Save State SMM: avec et sans traçage activé.


Nous avons utilisé l'outil WinIPT pour activer le traçage sur le processus d'interpréteur Python (pid 1337 ), allouant 2 ^ 12 (4096) octets au tampon de trace, puis exécutant le script SmmBackdoor.py à l'intérieur de l'interpréteur (l'argument 0 est un indicateur, pour nous, ils ne le sont pas important, car dans SMM, vous devez toujours forcer vos paramètres de trace).


$ ipttool.exe --start 1337 12 0


En comparant les instantanés SMRAM, nous avons déterminé l'emplacement du registre IA32_RTIT_CTL dans la structure d'état de sauvegarde SMM. Il est stocké à l'offset SMBASE + 0xFE3C. L'état du bit IA32_RTIT_CTL.TraceEn est la condition principale pour la réactivation d'Intel PT dans SMM. Le champ à ce décalage est marqué comme réservé dans le manuel du développeur Intel.



Figure 6. Marquage que les champs sont réservés


Écriture du shellcode


Nous ne voulions pas configurer Intel PT dans SMM par nous-mêmes, car cela compliquerait notre shellcode (par exemple, étant dans SMM, il serait difficile d'allouer un gros morceau de RAM afin qu'il ne soit pas utilisé par le système d'exploitation lui-même). Par conséquent, nous avons décidé d'utiliser le traceur déjà configuré et de simplement le "sauter" dans SMM, d'autant plus qu'il a déjà pour fonction d'enregistrer la trace dans un fichier.


Étant donné que nous avons utilisé WinIPT à cette fin, qui à l'époque ne prenait pas en charge le traçage du code du noyau (CPL == 0), il était évident que même lorsque la trace était incluse dans SMM, rien n'apparaissait dans le journal, car le code SMM était exécuté à CPL = 0 . Nous devons modifier certains filtres afin que le traceur puisse fonctionner tout au long du temps passé dans SMM. Nous listons tout ce qui doit être vérifié et installé:


  1. Le traçage avec CPL = 0 doit être activé.
  2. Le traçage pour CPL> 0 doit être activé (facultatif).
  3. Les plages IP valides pour l'enregistrement des événements doivent être désactivées.
  4. IA32_RTIT_STATUS.PacketByteCnt doit être réinitialisé.
  5. Le filtrage CR3 doit être désactivé.

Il faut dire quelques mots sur PacketByteCnt. Ce compteur détermine à quel moment vous devez insérer des paquets de synchronisation (une séquence de plusieurs commandes PSB) dans la trace. Nous devons réinitialiser ce compteur, sinon, pendant le traitement de la trace, le moment d'entrer dans le SMM sera manqué et la trace commencera à partir d'un endroit aléatoire lorsque le PSB est généré naturellement.


Voici le shellcode que nous avons utilisé:


  sub rsp, 0x18 ; this will align stack at 16 byte boundary (in case SMM ; code uses align dependent instructions) mov qword ptr ss:[rsp+0x10], rcx ; need to save rcx for SMI_Dispatcher mov ecx, 0x9E ; MSR_IA32_SMBASE rdmsr test byte ptr ds:[rax+0xFE3C], 0x1 ; Save State area contains saved ; IA32_RTIT_CTL.TraceEn je short @NoTrace call @Trace_Enable mov rcx, qword ptr ss:[rsp+0x10] ; SMI_Dispatcher is __fastcall ; (first argument in rcx) mov eax, 0xCB7DDAA4 ; original SMI_Dispatcher !!!!!!!!!!!!!!!!!!!!! call rax call @Trace_Disable add rsp, 0x18 ret @NoTrace: mov rcx, qword ptr ss:[rsp+0x10] ; SMI_Dispatcher is __fastcall mov eax, 0xCB7DDAA4 ; original SMI_Dispatcher !!!!!!!!!!!!!!!!!!!!! call rax add rsp, 0x18 ret @Trace_Disable: mov ecx, 0x570 ; IA32_RTIT_CTL rdmsr mov rax, qword ptr ss:[rsp+0x10] ; restore IA32_RTIT_STATUS wrmsr mov ecx, 0x571 ; IA32_RTIT_STATUS rdmsr mov rax, qword ptr ss:[rsp+0x8] ; restore IA32_RTIT_CTL wrmsr ret @Trace_Enable: mov ecx, 0x571 ; IA32_RTIT_STATUS rdmsr mov qword ptr ss:[rsp+0x8], rax ; save IA32_RTIT_STATUS and edx, 0xFFFF0000 ; IA32_RTIT_STATUS.PacketByteCnt = 0 wrmsr mov ecx, 0x570 ; IA32_RTIT_CTL rdmsr mov qword ptr ss:[rsp+0x10], rax ; save IA32_RTIT_CTL and eax, 0xFFFFFFBF ; IA32_RTIT_CTL.CR3Filter = 0 or eax, 0x5 ; IA32_RTIT_CTL.OS = 1; IA32_RTIT_CTL.User = 1; and edx, 0xFFFF0000 ; IA32_RTIT_CTL.ADDRx_CFG = 0 wrmsr ret 

Ce code doit être placé dans SMRAM, et la transition vers le gestionnaire SMI doit être corrigée pour accéder à notre code. Tout cela se fait à l'aide de SmmBackdoor.


Travailler avec la piste


L'intercepteur du gestionnaire SMI nous a permis d'écrire la première trace de code à partir de SMM. La commande suivante peut demander à WinIPT d'enregistrer la trace dans un fichier:


$ ipttool.exe --trace 1337 trace_file_name


Désactiver le traçage sur un processus:


$ ipttool.exe --stop 1337


Vous pouvez essayer de désassembler la trace à l'aide de l'utilitaire dumppt de libipt .


$ ptdump.exe --no-pad ./examples/trace_smm_handler_33 > ./examples/trace_smm_handler_33_pt_dump.txt


Exemple de sortie:



Figure 7. Le premier chemin d'instruction SMM


Nous pouvons voir certaines adresses, mais il est extrêmement difficile d'utiliser ces informations, car elles sont de très bas niveau.


Pour obtenir un aspect plus lisible, il existe un utilitaire ptxed (de libipt) qui convertit la trace en journal des instructions assembleur exécutées. Bien sûr, nous devrons fournir à l'utilitaire un vidage de mémoire SMRAM, car le journal IPT ne contient pas d'informations sur les valeurs des cellules de mémoire ou les instructions qui ont été exécutées; il contient uniquement des informations sur les changements survenus dans le flux de contrôle.


$ ptxed.exe --pt tracesmm_12 --raw SMRAM_dump_cb000000_cb7fffff.bin:0xcb000000 > tracesmm_12_ptasm



Figure 8. Liste des assembleurs correspondant au journal IPT


Cela semble déjà beaucoup mieux, mais si le code contient une boucle, la sortie sera obstruée avec les mêmes instructions.


Définir la couverture du code à l'aide de la trace


Pour obtenir la visualisation de la couverture, nous avons choisi le plugin Lighthouse pour IDA Pro, qui utilise le format drcov.


Aucun outil prêt à l'emploi n'a été trouvé, nous avons donc modifié ptxed afin qu'il génère également un fichier de couverture dans le processus. Le ptxed patché est disponible dans le référentiel . Jetez un œil à l'historique des validations pour déterminer exactement ce qui a été ajouté.


Une fois ptxed terminé, le fichier SMRAM_dump_cb000000_cb7fffff.bin.log apparaît, qui contiendra les informations de couverture au format drcov.


Remarque: il y a un léger problème avec la synchronisation du désassembleur sur le premier PSB. Pour une raison pas tout à fait claire, si le PSB est généré avant PGE (le compteur est remis à zéro avant que la trace ne soit à nouveau activée), alors ptxed ne peut pas être synchronisé sur celui-ci. Pour contourner ce problème, nous avons créé un petit correctif. Il n'est pas clair si c'est un problème pour ptxed lui-même, ou si nous faisons quelque chose de mal en réinitialisant IA32_RTIT_STATUS.PacketByteCnt.



Figure 9. Un patch qui vous permet d'utiliser le PSB situé juste en face du PGE


Les fichiers de couverture générés peuvent être téléchargés dans IDA Pro et obtenir une belle mise en évidence, ainsi que des statistiques sur le pourcentage de couverture pour chaque fonction.



Figure 10. Plugin IDA Pro Lighthouse avec informations sur la couverture du code


Remarque: Le plugin Lighthouse fonctionne un peu étrangement sur des bases de données incomplètement analysées (le code exécutable n'est pas étiqueté, les fonctions n'ont pas été créées). Nous avons tracé ce "problème" à la fonction get_instructions_slice dans le fichier \ lighthouse \ metadata.py, où il renvoie 0 instructions même pour l'adresse où la fonction a été créée manuellement. Le plugin semble utiliser le cache et ignorer le nouveau code spécifique. Cela peut être contourné en appelant Reanalyze sur le programme et en rouvrant IDB. Ce n'est qu'après cela que le plugin pourra voir le nouveau code et commencer à le considérer. Étant donné que ce problème est très gênant dans le cas d'un vidage SMRAM (qui au premier démarrage se compose presque entièrement de code non défini), nous avons apporté une petite modification au code Lighthouse afin que nous puissions définir manuellement le nouveau code plus rapidement.



Figure 11. Message de journal ajouté pour aider à identifier le nouveau code


Prise en charge Linux


Étant donné que tous nos tests ont été effectués sur Windows 10 x64 (nous avions besoin d'ipt.sys, qui est apparu dans Windows October Creators Update 2018), disons quelques mots sur la possibilité de l'implémenter sous Linux.


  • Il existe un module de performance du noyau Linux qui peut effectuer des actions WinIPT (ipt.sys) similaires, y compris la possibilité de tracer du code en mode noyau.
  • Étant donné que l'interface SMM de porte dérobée est basée sur le framework CHIPSEC multiplateforme, notre correctif fonctionnera sur un système Linux sans aucune modification.

Conclusion


Nous avons réussi à obtenir une trace de code exécutée dans SMM à l'aide de la technologie Intel Processor Trace. Un résultat similaire pourrait être obtenu à l'aide d'équipements et de logiciels coûteux qui ne sont pas vendus à tout le monde. Il nous suffisait d'avoir en main une carte mère et un programmeur SPI. La vitesse de retrait de la piste est vraiment impressionnante, et il n'y a rien à redire sur la précision du résultat.


Nous espérons que cet article aidera les autres à tirer parti de la technologie Intel PT pour enquêter et rechercher les vulnérabilités dans le code SMM. L'adaptation de notre travail à d'autres cartes mères ne devrait pas poser de problèmes (n'oubliez pas Intel Boot Guard). L'essentiel est de bien comprendre comment cela fonctionne. La partie la plus difficile est de déterminer comment intercepter le répartiteur SMI et écrire un shellcode pour l'intercepteur. Dans notre version, des adresses "câblées" étaient utilisées, vous devez donc transférer soigneusement le shellcode vers un autre système.


Tous les outils et scripts utilisés sont disponibles dans le référentiel sur GitHub .

Source: https://habr.com/ru/post/fr481692/


All Articles