Sur une plateforme de type PC, la configuration des éléments matériels se trouve répartie dans de nombreux registres accessibles par différents moyens. Cet article présente les différents modes de collecte de ces informations, exploitables par la suite pour des vérifications de sécurité.
1. Introduction
Sur une plateforme x86 moderne, la configuration matérielle se trouve dispersée dans de nombreux composants. Cette configuration comprend à la fois des aspects fonctionnels (configuration de l’adresse MAC d’une carte réseau, fréquence d’horloge ou voltage d’un processeur) et des aspects liés à la sécurité (configuration du contrôleur mémoire, verrouillage de la mémoire flash…).
L’écosystème x86 a eu tendance à se complexifier ces dernières années, entraînant parfois des vulnérabilités liées à un manque de communication entre les fabricants de composants, les concepteurs de BIOS et les intégrateurs. La vérification qu’une plateforme est correctement configurée nécessite l’accès à de nombreux espaces de configuration répartis dans différents composants, pas forcément identiques d’une machine à une autre. L’ANSSI s’est intéressée ces dernières années à la façon de valider des configurations matérielles pour vérifier qu’elles sont à l’état de l’art vis-à-vis des attaques publiées.
Dans cet article, nous allons nous intéresser aux méthodes d’accès aux différents registres de configuration présents sur une machine de type x86 récente. Un autre article (Chipsec, un outil pour les tests de conformité des firmwares) présent dans le dossier de ce numéro de MISC, de plus haut niveau, s’intéresse à la mise en cohérence des informations collectées afin de vérifier la bonne configuration de la machine et l’absence de vulnérabilités connues.
2. Notions de registre
Un registre de configuration est un espace physique bien délimité (mémoire d’un contrôleur de périphérique, mémoire du CPU, etc.) stockant des données de configuration spécifiques.
Un registre définit généralement, à travers des valeurs précises, le comportement d’un périphérique ou les droits associés à celui-ci. Certaines valeurs n’ont pas d’importance sur le niveau de sécurité, mais d’autres sont cruciales. À titre d’exemple, il existe une valeur de registre responsable du verrouillage de la mémoire flash SPI stockant le BIOS et empêchant ainsi son altération depuis un système d’exploitation. L’absence de verrouillage de cette mémoire rend par exemple possible l’installation d’une porte dérobée non détectable par les antivirus, car non présente sur le disque dur, mais uniquement dans le BIOS.
Le parcours de la documentation des chipsets Intel montre qu’il existe un nombre très important de registres et valeurs possibles sur une plateforme x86, et qu’ils ont souvent des dépendances entre eux qui ne sont pas forcément simples à identifier : le bit a1 d’un registre Ra peut être nativement positionné correctement, mais si le bit b1 du registre Rb, responsable du verrouillage de Ra, n’est pas correctement verrouillé alors le bit a1 peut finalement être modifié.
Un élément important à préciser est que ces valeurs de registre sont définies généralement via le BIOS au démarrage de la plateforme. Des paramètres de sécurité non conformes nécessitent ainsi généralement une mise à jour du BIOS.
3. Les composants à interroger
Lors du relevé des valeurs des registres, par Chipsec ou manuellement, les composants de la plateforme suivants seront sollicités :
- le CPU : composant exécutant les instructions machine et donc le code provenant du système d’exploitation ou encore du BIOS ;
- le PCH : Platform Controller Hub, aussi appelé chipset, combinant les anciennes puces North Bridge et South Bridge, son rôle est d’interfacer avec le CPU et d’autres périphériques ou contrôleurs avec des protocoles différents (PCI, SPI…) ;
- la mémoire flash SPI : divisée en plusieurs régions, comme des partitions d’un disque, et contenant notamment le firmware BIOS ;
- le contrôleur de la flash SPI : composant, intégré généralement dans le PCH, pour s’interfacer avec la mémoire flash SPI et le PCH ;
- le contrôleur de la RAM : composant, intégré généralement dans le CPU, pour s’interfacer avec la mémoire RAM et d’autres périphériques ou contrôleurs.
4. Moyens d’accès aux registres
4.1 Registres du CPU
Le processeur expose un certain nombre de registres de configuration internes, qui sont accessibles par plusieurs moyens :
- via la réponse à l’instruction cpuid (qui énumère les diverses fonctionnalités exposées par le CPU, comme AES-NI ou encore le bit NX) à travers notamment les registres EAX, EBX, ECX et EDX ;
- via des Model Specific Register (MSR), qui sont des registres spécifiques aux processeurs Intel.
Ces deux moyens sont détaillés dans le Intel 64 and IA-32 Architectures Software Developer’s Manual [2] (volume 1, chapitre 20 pour cpuid et volume 4 pour les MSR).
4.2 Accès via PCI
Pour les composants situés sur le bus PCI Express, les registres de configuration peuvent être situés dans l’espace de configuration PCI (PCI Configuration Space, présent sur tous les périphériques PCI) ou bien dans des espaces spécifiques exposés par le périphérique et découverts automatiquement lors de l’énumération du composant au démarrage de la machine.
L’accès au PCI Configuration Space sur une machine x86 peut se faire de deux façons. La première, plutôt obsolète, est spécifique à cette architecture et utilise la notion de Port Mapped I/O (PMIO), c’est-à-dire l’espace des ports x86. Sans entrer dans les détails, il s’agit d’un espace similaire à un espace mémoire, mais nettement plus limité (64Ko répartis en unités de 1 à 64 bits) et accessible au travers d’instructions x86 spécifiques (in et out typiquement).
Les périphériques PCI sont identifiés par un triplet Bus:Device.Function dans la hiérarchie PCI. Pour accéder à l’espace de configuration d’un périphérique en utilisant PMIO, le processeur utilise les deux ports 0xcf8 (Configuration space address) et 0xcfc (Configuration space data) de la façon suivante :
- le CPU écrit l’adresse (combinant Bus, Device, Function et l’offset du registre) dans 0xcf8 ;
- le CPU lit ou écrit le contenu du registre dans 0xcfc.
En pseudo-code, cela donne :
Ce mode d’accès a été progressivement remplacé avec l’avènement du PCI Express par un mode d’accès nommé Memory Mapped I/O (MMIO), où l’espace de configuration PCI Express (qui fait 4Ko et dont les 256 premiers octets sont communs avec l’espace de configuration PCI) est projeté dans l’espace mémoire du processeur : une zone mémoire est réservée pour les périphériques PCI et tous les accès mémoire à des adresses situées dans cette zone sont dirigés vers le bus PCI. L’adresse de base de cette zone est déterminée au démarrage par le BIOS et configurée dans un registre du chipset nommé PCI Express Register Range Base Address Register (PCIEXBAR). Les accès à l’espace de configuration PCI Express se font ensuite par des accès mémoires standards, là encore en incluant dans l’adresse l’identifiant du périphérique, selon le pseudo-code suivant :
L’adresse mémoire est décodée par le chipset afin de générer une transaction PCI Express dirigée vers le bon périphérique.
Les périphériques PCI Express peuvent aussi exposer de la mémoire interne à destination du processeur, que ce soit pour des données (par exemple une carte réseau) ou de la configuration. Lors de l’énumération du périphérique (au démarrage ou lors de l’insertion à chaud), le BIOS ou le système d’exploitation vérifie au sein du PCI Configuration Space la présence de Base Address Registers (BAR) qui indiquent la présence de mémoire exposée par le périphérique.
Cette mémoire est là encore projetée dans l’espace mémoire du processeur et les accès à ces adresses sont dirigés vers le périphérique en question au lieu de la mémoire centrale.
4.3 Autres accès MMIO
Les accès MMIO ne sont pas spécifiques aux périphériques PCI, mais peuvent se rencontrer sur d’autres types de contrôleurs. Sur des architectures telles que ARM, chaque périphérique exposant de la mémoire (configuration ou espaces d’échange) est habituellement projeté dans l’espace mémoire du processeur, et tous les accès se font en MMIO. La répartition de l’espace d’adressage fait souvent partie des spécifications de la plateforme et est par exemple transmise au système d’exploitation via un mécanisme spécifique comme les device tree.
Sur une plateforme Intel, on peut par exemple accéder à la configuration du contrôleur flash SPI, dans une zone mémoire dont l’adresse est contrôlée par le registre SPIBAR, lui-même situé dans la configuration du chipset, accessible lui en PCI.
5. Extraction manuelle des informations sous Linux
Depuis un système Linux, il est tout à fait possible d’accéder aux différents registres de configuration des périphériques embarqués. Pour chaque type d’accès vus précédemment, il peut exister plusieurs méthodes, nécessitant plus ou moins de privilèges, qui seront détaillés.
5.1 Registres du CPU
5.1.1 CPUID
Comme évoqué précédemment, les informations propres au CPU peuvent être lues via l’instruction assembleur cpuid détaillée dans la documentation Intel de l’architecture x86 [2].
Sous Linux, il existe un module cpuid(4) (la notation (n) indique la section où se trouve la page de manuel correspondante) qui collecte les informations sur les CPU présents sur le système et les expose au travers d’une hiérarchie sous /dev/cpu/*/cpuid. Il existe aussi un utilitaire cpuid(1) pouvant utiliser au choix l’interface noyau ou bien, par défaut, l’instruction cpuid. Cet outil permet notamment une visualisation pratique des informations collectées.
La sémantique de l’instruction cpuid est assez simple : elle prend en entrée une valeur dans le registre EAX et renvoie les informations dans les registres EAX à EDX. Il existe une quinzaine de combinaisons renvoyant chacune une configuration spécifique du CPU. Par exemple, dans la capture précédente, la première ligne correspond à l’appel de cpuid avec EAX positionné à 0. Dans ce cas, le CPU renvoie dans EBX, EDX et ECX la chaîne ASCII correspondant au nom du fabricant du CPU, soit ici : GenuineIntel.
L’outil cpuid(1) interprète automatiquement les valeurs renvoyées pour qu’elles soient clairement compréhensibles (Figure 5).
5.1.2 MSR
Les Model Specific Registers sont, comme leur nom l’indique, des registres spécifiques à différents modèles de processeur Intel. Leur présence est habituellement signalée par une information dans cpuid. Ces registres sont accessibles en lecture et en écriture, à l’aide des instructions privilégiées rdmsr et wrmsr.
Sous Linux, il existe là encore un module noyau msr(4) qui expose une interface vers ces MSR dans la hiérarchie /dev/cpu/*/msr (accessible au super-utilisateur uniquement). De façon similaire à cpuid, il existe un projet nommé msr-tools qui fournit les utilitaires rdmsr(1) et wrmsr(1) qui permettent d’offrir une interface plus simple vers les fichiers exposés par le noyau.
5.2 Listing des périphériques PCI
Au démarrage du système, le noyau Linux identifie tous les périphériques PCI attachés aux bus PCI et stocke diverses informations dans un pseudo système de fichiers sous l’arborescence /sys/bus/pci/devices.
On y trouve diverses métadonnées (vendor et product id, classe du matériel, révision, etc.).
La suite d’outils pciutils se base sur ces informations. Le plus connu est sans doute lspci qui permet de lister les périphériques connus du système :
lspci affiche les informations fournies par les périphériques eux-mêmes, mais aussi les informations topologiques, en particulier les identifiants du périphérique, sous la forme Bus:Device.Function.
À noter que la conversion automatique par la commande lspci du couple VID:DID se base sur le fichier /usr/share/hwdata/pci.ids (ou /usr/share/misc/pci.ids) fourni avec pciutils.
Ainsi, le PCH (dans ce cas 8086/Intel et 8c4c/Q85 Express LPC Controller) est identifié avec l’adresse PCI 00:1f.0 alors que le contrôleur DRAM est identifié avec l’adresse 00:00.0.
5.3 Extraction de registres de périphériques PCI
Extraire les valeurs de registre de configuration d’un périphérique PCI (par exemple, le chipset) nécessite d’identifier préalablement les adresses de ces derniers et donc de consulter la datasheet Intel de celui-ci.
Ainsi, pour accéder au registre BIOS_CNTL (Bios Control Register) il est nécessaire de sélectionner le périphérique PCI correspondant au chipset et de se déplacer à l’offset 0xdc de l’espace de configuration PCI. La commande lspci avec les options -xxx permet d’afficher en hexadécimal l’espace de configuration PCI et donc d’en extraire les registres, tels que BIOS_CNTL qui a en l’occurrence la valeur 0xA2.
Il est aussi possible d’utiliser la commande setpci pour lire directement la valeur du registre correspondant :
Les deux outils (lspci et setpci) reposent sur la bibliothèque pcilib et peuvent utiliser plusieurs méthodes pour récupérer les informations, suivant le système sous-jacent :
- linux-sysfs utilise les informations fournies par le noyau Linux (soit /sys/bus/pci/devices comme évoqué précédemment), qui est dans ce cas-là chargé de la collecte des informations auprès des périphériques eux-mêmes ;
- intel-conf1 utilise les instructions assembleur in et out (accès PMIO).
Les deux méthodes nécessitent des privilèges root pour obtenir des informations complètes, mais sur des systèmes Linux récents (intégrant la fonctionnalité Lockdown) l’accès à l’espace des ports x86 est restreint pour des raisons de sécurité, ce qui empêche l’utilisation de la deuxième méthode.
La valeur du registre connue permet alors, depuis la datasheet, de connaître la valeur de chaque bit et donc des paramètres à vérifier (0x2A vaut 0b00101010) :
- bit 0 : BIOS_WE = 0 ;
- bit 1 : BLE = 1 ;
- bit 5 : SMM_BWP = 1.
Une lecture fine de la documentation et la compréhension de la sémantique des différents paramètres est enfin nécessaire pour vérifier si la configuration est correcte ou non.
5.4 Extraction des registres via des redirections multiples
Comme expliqué précédemment, les registres ne sont pas nécessairement dans l’espace de configuration PCI et peuvent être dans une zone mémoire exposée par le périphérique et projetée dans l’espace mémoire CPU. L’adresse de cette zone mémoire accessible alors en MMIO, est inscrite dans un registre PCI.
Ainsi, un accès PCI seul n’est pas suffisant pour lire les valeurs des registres et il est nécessaire de procéder à plusieurs redirections :
- identifier, au sein du registre PCI, la zone mémoire (l’adresse en mémoire physique) BAR ;
- accéder à la mémoire physique et extraire spécifiquement la zone mémoire BAR ;
- identifier les registres au sein de la zone mémoire extraite et relever les valeurs souhaitées.
Un exemple de ce type d’accès, utilisant notamment l’espace mémoire SPIBAR, spécifique au contrôleur de la flash SPI, est fourni ci-dessous.
5.4.1 Identifier l’adresse mémoire SPIBAR
Si nous souhaitons relever la configuration des registres propres au contrôleur de la flash SPI, il est tout d’abord nécessaire de relever l’adresse mémoire physique RCRB (Root Complex Register Block) stockée au sein de l’espace de configuration PCI du chipset (dans le périphérique PCI 00:1f.0) et précisément dans le registre de 4 octets RCBA (Root Complex Base Address Register) situé à l’offset 0xF0. Les registres de configuration du PCH sont tous projetés à partir de cette adresse, d’où la nécessité de la relever. Le résultat de la commande lspci pour extraire l’espace de configuration PCI indique ainsi la valeur de RCRB (à lire en Little Indian) : FED1 C001.
Tel que cela est spécifié dans la datasheet du chipset, l’espace mémoire dédié aux registres du contrôleur SPI (nommé SPIBAR) débute à l’offset 0x3800 à partir de RCRB, à un offset fixe précisé dans la datasheet (0x3800).
Ainsi, l’adresse finale en mémoire de SPIBAR est : 0x3800+0xFED1C001-1 = 0xFED1F800. La taille de SPIBAR est également indiquée : 0x39FF-0x3800 = 0x1FF.
À noter qu’une alternative consiste à parcourir les plages d’adresses mémoire recensées par le noyau Linux via /proc/iomem et d’identifier celles attribuées au contrôleur SPI :
5.4.2 Extraire les valeurs des registres de SPIBAR via MMIO
Les espaces mémoire projetés en mémoire physique (dont celles de SPIBAR) sont accessibles sous Linux au travers du device /dev/mem. Une copie bit-à-bit permet de copier le contenu de l’espace SPIBAR.
Ainsi, à l’offset 0x04 se trouve le registre de 2 octets HSFS qui a dans le cas présent une valeur de 0xF008 (soit 1111 0000 0000 1000 en binaire).
Il est alors nécessaire d’identifier, depuis la datasheet, les paramètres de configuration associés à chacun des bits du registre (8 variables sur 1 ou 2 bits), de les comprendre et de vérifier leur conformité.
Par exemple, FDOPSS étant le bit 13, sa valeur est donc 1 (1111 0000 0000 1000).
À noter que sur des systèmes actuels l’accès à /dev/mem est en général filtré et il n’est pas possible d’accéder à la totalité de la mémoire physique ainsi.
5.5 Extraire le firmware BIOS
Les paramètres stockés dans les registres sont pour la plupart définis par le BIOS au démarrage de la machine. Il est donc important de s’assurer de la conformité de son code via son extraction depuis la flash SPI. Cette opération permettra à un analyste/auditeur de le comparer à une base de codes sains ou éventuellement de l’analyser pour identifier une malveillance, même si cette opération reste délicate.
Pour extraire le code du BIOS, une première méthode serait d’exploiter la mémoire physique. En effet, au démarrage de la plateforme, le code va être décompressé en mémoire pour être exécuté par le CPU et sera donc accessible. Les moyens existants pour effectuer un dump de la mémoire physique ne manquent pas (localement via /dev/mem ou un driver tel que fmem, ou avec des transferts DMA à l’aide d’outils tels que PCIe Screamer), mais au lancement du noyau Linux (et suite à l’exécution de la fonction UEFI ExitBootService()), une partie de l’espace occupé en mémoire par le firmware peut être libéré.
Une deuxième méthode plus fiable est d’utiliser le contrôleur SPI en programmant ses registres de contrôle pour accéder au contenu de la mémoire flash. Les registres FDATA0 et FDADDR (accès via SPIBAR + 0x08 et 0x10) permettent de lire bloc par bloc la flash SPI via les 15 registres FDATAN (accès via SPIBAR + 0x14 à 0x4C).
Ces méthodes logicielles ont toutefois des limites. En dehors de la nécessité d’avoir des privilèges élevés sur le système (super-utilisateur, voire accès noyau), certaines régions de la flash SPI ne sont de toute façon pas lisibles par le CPU (en particulier la région ME dédiée au Management Engine). Dans ce cas, la seule solution pour récupérer l’intégralité de la flash est de procéder à une extraction physique, par exemple avec un clip de type SOP8 couplé à un Raspberry Pi et un logiciel tel que flashrom pour piloter la flash.
Conclusion
Si nous établissons un bilan global de cette étude, il semble possible, et il est tentant de scripter l’extraction des registres de configuration. Si nous regardons plus en détail, plusieurs difficultés apparaissent :
- le niveau de droits requis dépend de la plateforme ciblée et de son niveau de sécurité, des mécanismes de sécurité système pouvant bloquer l’utilisation de certains outils ou bloquer l’accès à certains périphériques ;
- la présence des registres et leur moyen d’accès peuvent différer selon la plateforme ciblée et il est nécessaire de consulter les spécifications matérielles, qui évoluent dans le temps et sont globalement très complexes ;
- chacune des valeurs des registres relevée doit faire l’objet d’une interprétation en consultant les spécifications ;
- certaines opérations (comme l’extraction de flash SPI) nécessitent d’écrire du bas niveau pour interagir avec des registres MMIO avec plusieurs niveaux d’indirections, opération potentiellement délicate.
La réelle difficulté est donc de développer un outil compatible avec toutes les plateformes. Les analyses sont généralement menées sur des plateformes non maîtrisées et adapter l’outil à chaque vérification devient vite fastidieux. Chipsec, abordé dans l’article du dossier, apporte justement une solution à ces problématiques en mettant à disposition un outil multiplateforme, automatisé, fiable et très simple d’utilisation.
Références
[1] D. Salihun, « Architecture CPU/PCH » : https://resources.infosecinstitute.com/system-address-map-initialization-x86x64-architecture-part-2-pci-express-based-systems/
[2] Intel, « Intel® 64 and IA-32 Architectures Software Developer’s Manuals » : https://software.intel.com/en-us/articles/intel-sdm#combined