 Donc le speaker a déjà parlé sur le microcode il y a quelques années et il va nous donner plus de détails sur comment ça fonctionne. Donc Benjamin est spécialisé des attaques et des défenses logiciels et avec Philippe ils vont maintenant abuser le microcode des AMD pour l'amusement et pour la sécurité. Donc merci. Donc comme mentionné on a réussi à reverser le microcode AMD et donc je vais vous expliquer notre voyage qu'est ce qu'on a fait, comment on a fait. Donc j'ai fait ça avec un collègue. En gros on va commencer par un cours très rapide sur les microarchitectures et qu'est ce que c'est que le microcode. Ensuite on va expliquer comment est ce qu'on a reconstruit l'arôme du microcode et ensuite je vais donner des exemples d'applications qu'on a implémentées en utilisant les connaissances qu'on a trouvées la deuxième partie et je vais enfin décrire le frémoire qu'on a employé, comment ça fonctionne, qu'est ce qu'on peut faire avec et bien sûr ces outils et d'autres sont disponibles sur github et libre à vous de continuer notre travail. Donc quand je dis microcode en gros c'est comme le firmware pour un processeur. Donc on peut s'en servir pour fixer des bugs dans le silicium et qu'on veut fixer un peu plus tard dans le design. On peut aussi s'en servir pour du décollage d'instruction, je vais revenir là dessus en détail. C'est aussi utile pour la gestion des exceptions. Par exemple une interruption, une exception élevée. Le microcode peut la modifier, la traiter etc. C'est aussi utilisé pour la gestion de l'énergie et des fonctionnalités plus complexes comme Intel SGX. Mais le plus intéressant pour nous c'est qu'on peut le mettre à jour. Donc on peut faire des mises à jour dans le champ. Donc on peut venir mettre à jour le microcode. Donc l'exécution d'une instruction sur x86 est faite en plusieurs étapes. La première c'est de décoder l'instruction en plusieurs microopérations qui sont ensuite skidulés, ordonnancés et distribués dans les différents pipeline d'exécution, la élue, l'unité de multiplication, de division etc. Donc ce qui nous concerne le plus c'est l'étape de décollage. Donc on a un tampon d'instruction qui va remplir des décodeurs, certains pour des instructions simples, certains pour des instructions plus longues, certains pour les instructions vectorielles. C'est les plus compliqués. Donc en gros le vecteur principal c'est le moteur vecteuriel. Donc tout ça c'est chargé de la rhum du microcode et ça ressemble, c'est géré comme des instructions. Et à côté de cet ROM on a les updates qui sont stockés dans la rhum. Et bien sûr autour de cet ROM et de cet ROM on a des composants qui sont nécessaires. Donc qu'est-ce qu'un match register, c'est un registre de débagage. Ça ressemble à un breakpoint hardware. Si on écrit dans ces registres on peut aller à d'autres offsets dans la rhum et exécuter un propre code. Donc les mises à jour elles sont chargées soit par le BIOS, soit par le système d'exploitation, le noyau. Donc en général on a un header et des triades. Qu'est-ce que c'est qu'une triade ? Donc une triade c'est trois opérations, donc des instructions x86 plus ou moins et un mot de séquence. Ce mot de séquence indique quelle micro-instruction exécuter après celle-ci. Donc on veut par exemple opérer un branchement ou dire qu'on a fini de décoder ou autre. Donc il y a une authentication faible qui protège ses mises à jour. Donc ce qui fait qu'on peut mettre au point nos propres mises à jour, analyser etc. Néanmoins on ne peut avoir qu'une mise à jour chargée à la fois. Donc elle est dans la ram, donc si on reboute la machine, la mise à jour disparaît. Donc on va observer du micro-code et donc on va utiliser un langage de transfert de registres basé sur l'assembleur x86. Donc les différences par rapport à l'assembleur x86, donc on peut avoir trois opérations sur le micro-code. La différence est du x86 ou en général c'est deux. Donc d'abord la destination puis deux opérants de source. De plus il y a des flags qu'on peut définir. Par exemple le flag.c ça veut dire mais aussi à jour le flag de retenue en fonction du résultat de l'instruction. Donc on a une branche, on a la condition sur laquelle la branche est prise. Donc si le carri est pris et à false et ensuite on a l'offset. Donc on a aussi des annotations sur le mot de séquence qui peuvent être implicites. C'est une architecture de l'autre store. Donc on peut utiliser des opérants dans les mémoires comme sous x86 et on doit charger explicitement les choses dans la mort. Donc la ronde du micro-code est vraiment câblée dans le silicium du CPU donc on ne peut pas la modifier. Donc ça c'est une image faite au micro-surface électronique du substrat de base du micro controller à du microprocesseur. Donc on a des régions qui sont divisées en quatre grilles qui sont elles-mêmes divisées encore en quatre blocs. Donc on voit qu'il y a certaines régions qui sont plus ou moins grandes avec un certain nombre de structures. Ici on a la SRAM. Donc le lié c'est que c'est assez proche pour que ce soit rapide et donc la RAM peut interagir avec la RAM. Donc on a commencé avec les images et puis on les a décodés en chaine de bit et puis on est revenu aux opérations à partir de là. Donc on pouvait voir directement que nos triades étaient correctes en analysant aussi la dépendance des données et donc par contre on avait un problème d'ordre des triades. Il a fallu rétro-ingénérer les adresses et donc on a dû faire le lien entre les adresses physiques qu'on a récupérées de la RAM et les adresses logiques. Et une fois qu'on avait fait ça par contre on a pu récupérer et désassembler tout le code. C'était un peu compliqué de retrouver cette correspondance physique logique parce qu'on a trouvé que deux pairs d'updates. Donc une update en général elle va remplacer deux triades de la RAM par des triades de la RAM. Et donc si on relit le microcode on arrive à obtenir les endroits qui ont été modifiés. Et on arrive à en retrouver davantage si on compare la sémantique des triames obtenues dans la RAM et l'exécution basée par adresse. Et du coup on a réussi à implémenter un immulateur de microcode qui nous a permis d'extraire davantage de sémantique. Donc ça fonctionne au niveau des triades et ça détermine l'état de sortie défini en fonction d'un état donné d'entrée. Donc l'état du processeur x86 et des registres des registres du microcode. Donc pour chaque instruction x86 exécutée il peut y avoir différentes modifications de registres x86 et aussi des registres microcodes. Donc ça supporte les opérations arithmétiques connues et on a défini une white list d'opérations no-up qui font rien au totalon. On a trouvé 45 pairs de données supplémentaires ce qui nous a permis de reconstituer la représentation mémoire logique. Donc on a quatre tableaux de blocs qui sont mappés à différents endroits. D'un point de vue de design matériel c'est intéressant de faire comme ça et pas différemment. À présent qu'on arrive à mapper tel ou tel adresse à ce qu'on a obtenu dans la rome du microcode et qu'on connaît certaines adresses de par nos expériences précédentes on peut regarder certaines adresses. Donc décaler à droite et doubler on prend un registre on le décale et on met ça dans un autre registre. Du coup on voit différentes sous opération on a deux opérents de décalage à gauche on voit regmd6 et radmd4 donc ce sera remplacé par les registres utilisés par le registre correspondant x86 par exemple par ex en fonction de l'assembleur x86 qui est exécuté. Donc là ça nous permet déjà d'apprendre encore d'autres choses. Donc on voit qu'on a la source, la source, la source, la destination et cette source indique le nombre de décalage à faire. On ne savait pas ce que c'était avant et on arrive à réaliser que ça sert juste à implémenter du décalage que si on essaie d'écrire dedans ça a des comportements bizarres des fois ça fait cracher le cpu. Donc cette opérante elle détermine le nombre de décalage et si on veut déterminer de combien de décalés on doit lire t41. Donc ça nous a permis de déterminer davantage d'informations. Donc le reste sert à implémenter l'essai mentique de l'instruction x86 et de retourner le résultat. Donc maintenant on va regarder une instruction un peu plus compliquée. Donc là par exemple on a l'instruction rdtsc qui renvoie en fait le compteur, le compteur de programme. Mais divisé dans deux registres différents. Et donc au début on a on a deux opérations de type l'autre donc chargement. Donc ici on connaît pas l'instruction mais on connaît la destination. Ici on sait qu'on arrive en T9 et ici on arrive en T10. Donc on peut suivre les modifications à l'aide de ça. Et donc pour la simplicité on va commencer par T10. Donc ici on voit que c'est un registre qui dénote en fait un registre interne. Et si on joue avec ces bits en fait on voit que ça encode C04. Et par exemple sur le T10 on a un bitmasque de 4. Et si on regarde dans le manuel on voit que ce bit et que C04 c'est le bit qui détermine si on a le droit d'exécuter l'instruction depuis les user modes. Donc si on garde à l'esprit que dans T9 on a une autre valeur chargée depuis un registre interne on peut revenir à ça plus tard. Donc si on suit l'exécution bon il y a du padding c'est un pattern qu'on a déjà vu. Où est-ce que nous amène cette branche ? Cette branche nous amène à une triade de branche conditionnelle. Et si on regarde un peu plus haut donc cette instruction a mis à jour ce flag. Donc cette branche est basée sur le fait de savoir si l'erreur est là ou pas. Bon là on peut voir comment on sort de la routine. Donc on fait un décalage à droite du contenu de T9. Donc on a 20 bits. On veut décaler les 20 bits du timestamp dans ce registre. Donc dans EX on veut les 32 bits les plus bas du timestamp. Mais on n'a pas fini. Regardons l'erreur. Donc si on n'a pas l'accès à l'instruction on peut voir qu'on fait un move immédiate dans un registre interne T19. Et donc 0xd c'est le code de l'interruption de faute de protection générale. Donc ça va être ensuite traité par le gestionnaire d'exception. Et plus tard cette triade nous envoie à cette adresse. Et si on regarde où est-ce qu'elle est utilisée on voit d'autres load immédiates qui vont aussi dans du code x86. Donc on sait comment lever une interruption depuis le microcode. On a juste à écrire dans ce registre là et brancher à cette adresse là. Donc on a appris beaucoup. Maintenant la question c'est comment sont implémentés des instructions. Donc WhiteMSR en gros c'est une instruction qui va aller écrire les données en entrée dans un machine spécifique WebGSTAR. Donc ça sert à implémenter des extensions non-standards spécifiques à la machine. Par exemple déclencher des mises à jour de microcode. Donc on veut donner l'adresse de destination dans RCX. Donc on voit que c'est de décaler de 16 bits et que c'est mis dans T10. Donc on suit les emplois de T10. On voit qu'il est exhauré avec ce bit mask qui correspond à l'espace de nommage du MSR. Du coup en l'occurrence c'est un MSR spécifique à AMD. Donc ça nous met le flag aussi. On a toujours un jump conditionnel en fonction de ce flag à ce qui devrait être le gestionnaire de ce namespace de MSR. On a un deuxième XOR après qui utilise un autre bit mask 6.0.1 qui est le namespace dans lequel la routine de mise à jour du microcode se situe. Donc on arrive dans le gestionnaire de mise à jour. Donc si on continue on continue à faire des trucs sur RCX, on continue à faire des branches et ça continue jusqu'à ce que toutes les données soient dispatchées dans le handler qui va bien. Donc c'est comme ça sur AMD mais ailleurs c'est probablement fait de manière similaire parce que c'est simplement des fonctionnalités similaires. Donc là je vous ai montré comment est-ce qu'on a on a reconstruit la connaissance qu'on a maintenant et maintenant je vais vous montrer les applications. Donc je vais vous montrer la façon dont on a fait, dont on a permis de configurer la précision de RTST. Donc on peut réduire la précision de ça qui peut éviter des attaques par mesure du temps. Et on a écrit aussi un sanitiser d'adresse assisté par microcode. Ensuite on a aussi écrit des instructions. Une randomisation du jeu d'instruction. Et donc on peut dire à chaque fois qu'une instruction est exécutée si c'est pour moi on peut on peut effectuer une action et donc on a aussi ajouté des mises à jour de microcode qui sont authentifiées. On a vu aussi que le microcode avait des... Un environnement similaire à celui d'une enclave sécuritaire. Donc quand on veut écrire on doit le dire de manière explicite via des registres internes. Donc la seule manière d'interferer avec le microcode c'est du microcode en tant que tel. Donc ça peut permettre d'implementer une enclave hardware. Donc pour le sanitiser d'adresse donc c'est de l'instrumentation logicielle pour détecter les axes et mémoirent valides. Donc à chaque instruction on va vérifier si l'adresse est valide, est-ce qu'on a le droit ou pas. Donc il y a des auteurs qui ont proposé une implémentation hardware en rajoutant l'instruction HWASAN qui fait les validations et lève une exception si ça n'a pas. Donc vous avez le microcode, le pseudocode, c'est assez simple. Ça charge l'adresse. Si on a le chadeau, c'est qu'il y a un problème. On compare juste un flag. Donc d'autres avantages sur cette approche, c'est qu'on a une meilleure performance. Comme on a une nouvelle instruction, on peut la recoder et faire des choses qui nous apportent une meilleure performance, avoir du code plus compact et faire des choses qui sont difficiles à faire en logiciel. Donc on a implementé notre variante en remplaçant l'instruction bound qui n'est plus utilisé par les compilateurs sur le x86 moderne. Donc notre nouvelle interface prend comme premier argument un registre avec l'adresse qu'on aimerait accéder, puis la taille de données qu'on aimerait accéder à l'adresse. Donc si la verification fonctionne, ça continue. Et si on rencontre un accès invalid, on peut configurer l'action qui est prise. On peut par exemple lever un page fault ou on peut lever une interruption spécifique ou on peut brancher sur du code x86 qui va faire des validations supplémentaires ou ce que vous voulez écrire des rapports. Le plus important, c'est que ça tient en une instruction et qu'on n'a pas besoin de toucher au registre x86. Il y a des résultats intermédiaires qu'il faut bien stocker quelque part. En général on ferait ça sur x86, mais si c'est déjà utilisé par ailleurs, c'est la merde. On a aussi trouvé qu'on était plus rapide à faire des validations que les validations faites sur x86. Donc malgré le fait que le macrocode ressemble à du software, on arrive à améliorer les performances et l'application est plus rapide parce qu'on a moins d'instruction, donc le cache est moins rempli et ça rend si plus facile le fait de déterminer qu'est-ce qu'il y a du code de test et qu'est-ce qu'il y a du code applicatif réel. Donc la dernière chose, je vais vous montrer les outils qu'on a utilisé pour notre développement que vous pouvez trouver sur Githoba. On s'est assez vite rendu compte qu'on allait devoir tester pas mal de mises à jour parce qu'au début, donc il faut lancer des trucs dans un CPU voir le résultat. A chaque fois, donc on s'est dit qu'on allait faire ça en parallèle. Donc on a développé un moyen de faire ça en parallèle. On a plein de bordes qui sont branchés en série et en GPIO sur une raspy. Donc la raspy peut allumer, éteindre, télécommander ces cartes de mer et on peut s'y connecter depuis n'importe où en ssh et déployer comme on veut. Donc la première version ressemblait à ça. Au début, on ne savait pas trop faire d'électronique, donc on a pris une raspy reply. C'était un peu cher et puis finalement on a amélioré. Donc maintenant on peut utiliser une raspy pour 4 ou 5 bordes. On a besoin que de 3 GPIOs. Si on se connecte à des optocoupleurs pour séparer les niveaux de tension et qu'ensuite on se connecte d'un côté aux pins de risettes et afin de déterminer si l'aborder allumé au éteint, il suffit de brancher une diode d'électronique minissante. Donc comme ça c'est moins cher et c'est compact. Et si vous êtes vraiment contraint, vous pouvez enlever les LEDs parce qu'en gros, vous vous rendez compte assez vite dans quel état est votre système. Du coup on a écrit notre système d'exploitation vraiment minimale. La fonctionnalité principale qu'on voulait c'était un contrôle à 100% sur toutes les instructions qu'elle a été exécutée à part faire d'un certain point parce qu'on veut savoir comment elles sont décodées. Et on sait qu'en jouant avec ça on risque de cracher le CPU et on sait pas ce que font ces instructions. Donc ça écoute sur le porc série en attente de commande. Ça peut appliquer une mise à jour. Donc ces mises à jour sont streamées sur le porc série. On peut aussi streamer du code machine x86 sur une clé usb qu'on n'a pas besoin de reflercher à chaque fois. Ça nous permet aussi de renvoyer les retours d'erreur sur la Raspberry Pi et les retours du test. Donc on est en gros le plus important dans le framework. C'est l'assembleur et le désassembleur verbe de microcode ce qui génère des listings et en utilisant ces outils on peut assez facilement écrire du microcode. On avait aussi besoin d'un assembleur x86 pour écrire du code de test et ça nous a permis de désassembler des mises à jour et d'inspecter les contenus de la ROM et d'utiliser ça ensuite dans notre émulateur pour davantage d'analyse. Ça nous permet aussi de créer nos propres mises à jour que le pilote de mises à jour de Linux peut charger. Donc on peut modifier le driver de base pour charger n'importe quel update et voir ce que ça donne. Donc c'est aussi disponible sur GitHub. Et bien sûr le framework peut contrôler par porc série et GPIO notre OS minimal et on a un joli wrapper pour faire ça à distance sur une Raspberry Pi comme si on était en local. D'où la fin du talk en conclusion la rétro conception de la ROM ouvre plein de possibilités comment fonctionne le microcode comment l'utiliser proprement au lieu de juste inférer en partant d'un jeu de données assez faible basé sur les mises à jour mais il y a encore plein de choses à faire. Donc si ça vous intéresse de bosser là dessus rentrer en contact et on partage nos découvertes avec vous et la totalité du framework de l'OS et des exemples sont disponibles sur un autre GitHub. Donc c'est la fin et on répondra avec plaisir à toutes les questions que vous essayez d'avoir. Merci pour cette présentation quelques questions par rapport à votre sanitiser d'attresses hardware. Donc vous avez l'instrument installation du code source parce que le microcode est responsable de s'occuper de la mémoire qui pourra être occultée. Non, notre implémentation c'est une modification du compilateur pour insérer la nouvelle instruction qui normalement n'existe pas et aussi du loader pour initialiser et mettre à jour ces shadow maps au runtime. Donc en gros il va rajouter entre 10 et 20 instructions avant chaque axe et mémoire. Donc il faut compiler ce composant et l'intégrer. J'ai pas vu, je l'ai peut-être raté mais à quel point est-ce que c'est plus rapide que cette version initiale ? La version hardware ou la version software du sanitiser ? Donc par rapport au sanitiser kassan standard du noyau linux quand ils ont fait un micro benchmark sur microwave. Donc en faisant de l'instrumentation au niveau des accès mémoire c'est pas quelque chose qu'ils ont fait de similaire, ils ont juste un prototype et une instrumentation basique, ils n'ont pas instrumenté un niveau suffisant pour pouvoir faire des comparaisons. Est-ce que vous avez trouvé des implémentations micro-codes bizarres peut-être pas au niveau sécurité mais peut-être quelque chose que vous n'attendiez pas ou des implémentations auxquelles vous ne serez pas attendu ? Donc le problème pour commencer c'est qu'il y a beaucoup de micro-codes, il y a F00000 triade donc il y a beaucoup de codes à couvrir et on a aussi des erreurs de lecture, des fois on a des bitflips donc on peut se dire oui peut-être que le registre c'est pas ce qu'on pensait peut-être que l'adresse est pas bonne et puis de temps en temps on peut avoir de la poussière qui vient faire flipper des bits. Donc on a vraiment regardé uniquement un sous-ensemble, on n'a pas regardé la rôme totale, le micro-code dans sa totalité, c'est pas faisable de regarder la totalité des micro-codes, on n'a pas essayé de trouver des choses rigolotes et on ne pourrait pas dire ce que c'est parce qu'on n'a pas vu de spécification officielle pour du micro-code. Quelle génération de CPU AMD vous avez utilisé ? Sur quoi ça se base ? Donc ça se base toujours sur les mêmes choses que notre taux précédent donc les K8, les K9, donc des CPU qui sont sortis jusqu'à 2013, c'est la dernière année au AMD à faire des trucs de ceci là. Donc sur les nouveaux a priori il y a des cryptographies avec les publics, on n'a pas réussi à la casser, donc les nouveaux pour l'instant il n'y a rien. Je voudrais savoir à quel point les programmes de micro-codes pourraient être complexes, donc quelle est la complexité des nouvelles opérations que vous pourriez implémenter ? Donc le seul facteur limitant c'est la taille de la rame de mise à jour. Donc sur K8 on est limité à 32 triades donc 69 instructions et il y en a certaines qu'on peut enlever du style toujours exécuté la triade suivante, il y a certaines instructions qui peuvent uniquement être dans un slot impair ou des choses comme ça donc c'est vraiment difficile et de ce qu'on en sait on peut charger des valeurs médiates que de 16 bits pas de 32 bits donc ça fait très vite grossir la taille de votre programme si vous essayez de faire quelque chose de compliqué. Par exemple, notre mécanisme de mise à jour authentifié du micro-code c'est le plus compliqué qu'on a écrit et ça remplit presque totalement la rame et on utilise l'algorithme T parce que c'est le seul qu'on a réussi à faire tenir dans la rame. Donc c'est une contrainte sur la taille de la rame et elle est vraiment toute petite. Vous avez dit que le micro-code était utilisé pour le décodage d'instruction et donc ça déploie les instructions et le scheduler, est-ce que vous avez trouvé comment ça fonctionne exactement ? Alors dans les sens, on n'exécute pas vraiment du code dans le moteur du micro-code. De ce qu'on en comprend, le moteur du micro-code c'est juste une recette logicielle qui explique comment décoder une instruction. Donc on n'écrit pas vraiment les instructions, on met juste des trucs dans le pipeline pour faire ce qu'on veut. Donc on peut faire des branches, des branches conditionnelles, on peut faire des boucles mais on ne programme pas, on met des trucs dans le pipeline du CPU et ça fait ce que c'est câblé pour faire. Comment est-ce que vous avez pris la photo du CPU ? Est-ce que vous l'avez ouvert ? Oui, on a travaillé avec Chris, un spécialiste hardware. Il a l'équipement pour délaminer et prendre des photos haute résolution, optique. Il fait aussi des micro-photographies au microscope et le crayon de caballage. C'est très joli, c'est dans son papier de recherche. Est-ce que vous êtes au courant des recherches par Christopher Domas ? Où il a trouvé les instructions pour le micro-code X86 ? Oui, on est au courant. Il y a une carte des instructions du X86 et on peut peut-être comparer ces deux talks parce qu'il a reversé des instructions qui sont emplémentées en micro-code et il y a des composants de la rame du micro-code qui sont probablement pas déclenchés par des instructions X86. Par exemple, l'instruction qui est écrit dans un MSR, par exemple la mise à jour du micro-code, tout ça c'est emplémenté uniquement en micro-code. Merci, merci Benjamin.