
Latence x-ray pour matériel non documenté
Un outil exploratoire pour le timing MMIO — mesurez n'importe quelle adresse physique et disséquez le matériel à partir de la latence.

Étonnamment utile pour la rétro-ingénierie matérielle, l'empreinte des hyperviseurs, l'analyse par canaux auxiliaires de l'activité des périphériques, la caractérisation de registres, le calibrage de déclencheurs malveillants et la fabrication d'instructions machine absurdement longues.
Le premier mégaoctet de mémoire physique est un exemple facile pour commencer, même si sa structure est déjà bien connue.

Les limites de latence nous permettent de découper l'espace d'adressage sans nous fier aux dispositions rapportées.
sudo ./mmiotic --start-address 0x0 --end-address 0x100000 --stride 4 --count 20 --quiet-mmap0x9FFFF à ~380 cy, puis le trou vidéo de 128 Ko, qui se divise en deux à 0xB0000. Le changement de variance à 0x20000 peut indiquer un parcours de page différent pour la page nulle.A0000–AFFFF : lent et instable (996 cy, écart-type 327), acheminé vers le Radeon sous tension mais inactif. B0000–BFFFF : rapide et rigide (780 cy, écart-type 3,2, lit ffffffff), revendiqué par personne. Des données d'apparence similaire, mais un écart de variance de 100x.C0000–FFFFF : /proc/iomem indique « System ROM », mais le scan montre une latence DRAM, pas une vitesse de flash — le BIOS est répliqué dans la DRAM.Scannez l'espace de configuration du root complex (00:00.0, 4 Ko) pour détecter les valeurs aberrantes de latence par rapport à un plancher plat d'environ 675 cycles :

La latence de la boîte aux lettres est nettement supérieure aux données environnantes, et les pics dupliqués ainsi que les variances équivalentes permettent de faire correspondre des registres fonctionnellement similaires.
sudo ./mmiotic --start-address 0xf8000000 --end-address 0xf8001000 --stride 4 --count 2000xe4 (1218 cy, valeur 80e3110b) et 0xa4 (1199 cy, valeur deadbeef). e4 est un doorbell documenté ; a4 est non documenté.e4 est public.deadbeef se lit comme une sentinelle de non-initialisation/erreur — même boîte aux lettres, a4 non implémenté ou alimenté par une entrée invalide.Des latences soigneusement sélectionnées peuvent parfois être utilisées dans des scénarios d'exploitation inhabituels (MCHAMMER, smiiiiiiiiiiiiiiii) :

Ici, un Radeon inactif et sans pilote transforme une lecture alignée de 4 octets en un blocage d'environ 100 000 cycles.
sudo ./mmiotic --start-address 0x90e00000 --end-address 0x90e00100 --stride 4 --count 50 — balayez la tête de la BAR GPU (0x90e00000, 256 Ko). 0x90e00008 coûte 110 152 cy ; 0x90e00018, 16 octets plus loin, est de nouveau rapide à 1 523 — par registre, pas par page.sudo ./mmiotic --start-address 0x90e00000 --end-address 0x90e40000 --stride 4 --count 1 --binary — --binary sonde dans l'ordre inverse des bits, de sorte que l'ouverture complète de 256 Ko est uniformément couverte après 1 012 sondes, cartographiant un bloc contigu de 48 Ko où chaque registre est lent.Idéalement, lire une adresse physique serait une opération relativement sûre, mais mmiotic est particulièrement doué pour trouver accidentellement les exceptions :

Par exemple, sur la plateforme ci-dessus, une lecture d'1 octet à 0xdc5003b0 — offset 0x3b0 d'une BAR de registres iGPU Zen 4 sans pilote (Région 5, 0xdc500000, 512 Ko) — réinitialise le système de manière fiable.
sudo ./mmiotic --address 0xdc5003b0 --size 1 --count 1. Aucune sortie ; la machine redémarre.0x3ac et 0x3b4 de part et d'autre lisent 00000000 à environ 3 200 cy et sont inoffensifs.0x3a0–0x3fc trouve 7 dwords empoisonnés entrelacés avec 17 dwords sûrs.sudo busybox devmem 0xdc5003b0 32 et sudo dd if=/dev/mem bs=1 count=1 skip=$((0xdc5003b0))
mettent la machine hors service si vous préférez vous passer de l'intermédiaire.[!WARNING] Certaines plateformes se réinitialiseront lorsque des régions d'adresses spécifiques seront sondées par
mmiotic. Soyez prudent quant à l'endroit où vous l'utilisez.
La base et la taille ECAM sont détectées automatiquement à partir de l'ACPI MCFG (/sys/firmware/acpi/tables/MCFG). Remplacez-les avec -M, --mmio-base / -Z, --mmio-size.
--find-target/--find-longest transforment le scan en recherche :
| Option | Effet |
|---|---|
-F, --find-target <s> | trouve une adresse dont le temps d'accès atteint s secondes, puis augmente la largeur (4b non aligné → 8/16/32/64/512b) pour la pousser plus haut |
-G, --find-longest |
b/d/f.o.l: 00:02:00.00.4 address: 00000000f0100000 min: 312 max: 480 stdev: 2.1 dynamic: 0 value: 8086abcd
-T suffixe les compteurs de cycles avec cy et insère une paire de temps min/max mise à l'échelle (-N les affiche en nanosecondes brutes) :
b/d/f.o.l: 00:02:00.00.4 address: 00000000f0100000 min: 312 cy max: 480 cy ( 104 ns/ 160 ns) stdev: 2.1 dynamic: 0 value: 8086abcd
Compilation :
make
S'exécute en root pour obtenir l'accès à /dev/mem.
Si vous voyez : mmap ... Operation not permitted, essayez de démarrer avec iomem=relaxed pour lever CONFIG_STRICT_DEVMEM :
# /etc/default/grub
GRUB_CMDLINE_LINUX="iomem=relaxed nopat"
Puis sudo update-grub et redémarrez.
mmiotic a été utilisé pour faire avancer diverses recherches internes et externes, parfois de manière inattendue. Quelques exemples d'applications de mmiotic :
MCHAMMER : mmiotic calibre une exception de machine check retardée pour la délivrance de précision des signaux MC# dans des environnements protégés.
smiiiiiiiiiiiiiiii : mmiotic résout l'instruction à haute latence pour briser le rendez-vous SMM.
The Assembly Hall of Shame : mmiotic est largement utilisé pour l'important problème de la déoptimisation des performances.
mmiotic est un effort de recherche de Christopher Domas (@xoreaxeaxeax).
| Option | Noms |
|---|
-b/-d/-f/-r | PCI --bus/--device/--function/--register — décodés par rapport à la base ECAM |
-o, --offset <n> | offset depuis la base MMIO (ignore le décodage B/D/F) |
-A, --address <n> | une adresse physique ; définit implicitement la base de la région |
-a/-z | --start-address/--end-address — une plage physique ; implique --scan |
| Option | Effet |
|---|
-S, --scan | parcourt toutes les adresses de la région cible |
-t, --stride <n> | pas entre les adresses (défaut : 4) |
-n, --limit <n> | s'arrête après ce nombre d'adresses |
--binary | sonde dans l'ordre inverse des bits — après k sondes, la plage a une couverture uniforme à une granularité d'environ range/k (voir l'avertissement ci-dessus) |
-x, --limited | plage de registres limitée (0x00–0xff au lieu de 0x000–0xfff) |
-e, --skip | ignore les fonctions qui lisent 0xffffffff à l'offset 0 (périphériques absents) |
-I, --iomem | scanne chaque région de premier niveau non–System-RAM dans /proc/iomem |
-R, --ioregion <name> | scanne les régions /proc/iomem dont l'étiquette correspond à <name>, à n'importe quelle profondeur |
| même recherche, mais épuise chaque candidat au lieu de s'arrêter au premier résultat |
-B, --fallbacks <n> | candidats conservés pour la phase d'escalade (défaut : 10) |
| Option | Effet |
|---|
-s, --size <n> | octets par accès : 1 / 2 / 4 (défaut) / 8 / 16 (XMM) / 32 (YMM) / 64 (ZMM) / 512 (fxrstor). Les tailles > 4 sont hors spec mais fonctionnent sur le matériel testé ; les largeurs AVX nécessitent AVX / AVX-512F |
-c, --count <n> | échantillons par adresse (défaut : 1) ; le minimum est rapporté |
-L, --lock | chronomètre un RMW verrouillé : lock xadd (tailles 1/2/4/8) ou lock cmpxchg16b (taille 16). Réécrit la valeur |
-g, --gather | chronomètre un gather vectorisé ; --size 16/32/64 choisit XMM/YMM/ZMM (AVX-512) |
-w, --write-read | capture la latence d'écriture en chronométrant une lecture qui doit observer une écriture postée antérieure — voir ci-dessous |
-E, --enter (--enter-2, --enter-3) | génère une rafale de jusqu'à 30 accès à partir d'un seul enter $0, $31 — voir ci-dessous |
-k, --continue | sur un défaut fxrstor, abandonne cet échantillon et continue le scan |
| Option | Effet |
|---|
-C, --min-cycles <n> | n'affiche que les résultats avec min_cycles >= n |
-T, --time | affiche les nanosecondes en plus des cycles (détecte la fréquence TSC) |
-N, --nanosecond | avec --time, affiche les nanosecondes brutes, sans mise à l'échelle d'unité |
-P, --progress | progression du scan en direct sur stderr |
-p, --processor <n> | épingle sur le CPU logique n (défaut : 0) — réduit le bruit du minuteur |
-u, --unbound | n'épingle pas sur un cœur |
--lazy-mmap | mappe des fenêtres de 2 Go à la demande ; requis pour les régions > 2 Go |
--quiet-mmap | supprime le bruit « mmap failed » des régions inaccessibles |
| Champ | Signification |
|---|
b/d/f.o.l | bus/périphérique/fonction, offset de registre, taille d'accès |
offset | offset depuis la base MMIO — remplace b/d/f.o.l lorsque la cible provient de -o/-A/-a+-z |
address | adresse physique complète sur 64 bits |
min/max | accès le plus rapide et le plus lent observé, en cycles |
stdev | écart-type des compteurs de cycles échantillonnés |
dynamic | 1 si la valeur a changé à un moment donné entre les échantillons |
value | valeur lue — pour les tailles supérieures à 8 octets, les 32 premiers octets sous forme de qwords séparés par des espaces |
faults | ajouté uniquement si un accès a provoqué une faute |