
Latenz-Röntgen für undokumentierte Hardware
Ein exploratives Werkzeug für MMIO-Timing — miss die Latenz einer beliebigen physischen Adresse und zerlege die Hardware anhand der Latenz.

Unerwartet nützlich für Hardware-Reverse-Engineering, Hypervisor-Fingerprinting, Side-Channeling von Geräteaktivitäten, Registercharakterisierung, Kalibrierung bösartiger Auslöser und das Erzeugen obszön langer Maschinenbefehle.
Das erste Megabyte des physischen Speichers ist ein einfaches Beispiel für den Einstieg, auch wenn seine Struktur bereits gut bekannt ist.

Die Latenzgrenzen erlauben es uns, den Adressraum zu unterteilen, ohne uns auf gemeldete Layouts zu verlassen.
sudo ./mmiotic --start-address 0x0 --end-address 0x100000 --stride 4 --count 20 --quiet-mmap0x9FFFF bei ~380 cy, dann das 128-KB-Video-Loch, das sich bei 0xB0000 in zwei Teile teilt. Die Änderung der Varianz bei 0x20000 könnte auf einen anderen Page Walk für die Null-Seite hindeuten.A0000–AFFFF: langsam und zittrig (996 cy, stdev 327), zum stromversorgten, aber im Leerlauf befindlichen Radeon geleitet. B0000–BFFFF: schnell und starr (780 cy, stdev 3.2, liest ffffffff), von niemandem beansprucht. Ähnlich aussehende Daten, aber eine 100-fache Varianzlücke.C0000–FFFFF: /proc/iomem sagt „System ROM“, aber der Scan zeigt DRAM-Latenz, nicht Flash-Geschwindigkeit — das BIOS wird in den DRAM gespiegelt.Scanne einen Root-Complex-Konfigurationsraum (00:00.0, 4 KB) nach Latenz-Ausreißern gegenüber einem flachen Boden von ~675 Zyklen:

Die Mailbox-Latenz ist deutlich höher als die umgebenden Daten, und die doppelten Spitzen und gleichwertigen Varianzen ermöglichen es uns, funktional ähnliche Register zuzuordnen.
sudo ./mmiotic --start-address 0xf8000000 --end-address 0xf8001000 --stride 4 --count 2000xe4 (1218 cy, Wert 80e3110b) und 0xa4 (1199 cy, Wert deadbeef). e4 ist eine dokumentierte Doorbell; a4 ist undokumentiert.e4 öffentlich ist.deadbeef liest sich wie ein Sentinel für „nicht initialisiert/Fehler“ — gleiche Mailbox, a4 nicht implementiert oder mit ungültiger Eingabe gefüttert.Sorgfältig ausgewählte Latenzen können manchmal in ungewöhnlichen Exploit-Szenarien eingesetzt werden (MCHAMMER, smiiiiiiiiiiiiiiii):

Hier verwandelt ein im Leerlauf befindlicher, treiberloser Radeon einen einzelnen ausgerichteten 4-Byte-Read in einen Stillstand von ~100.000 Zyklen.
sudo ./mmiotic --start-address 0x90e00000 --end-address 0x90e00100 --stride 4 --count 50 — überstreiche den Kopf der GPU-BAR (0x90e00000, 256 KB). 0x90e00008 kostet 110.152 cy; 0x90e00018, 16 Bytes weiter, ist mit 1.523 wieder schnell — pro Register, nicht pro Seite.sudo ./mmiotic --start-address 0x90e00000 --end-address 0x90e40000 --stride 4 --count 1 --binary — --binary testet in Bitumkehr-Reihenfolge, sodass die gesamte 256-KB-Apertur nach 1.012 Sondierungen gleichmäßig abgedeckt ist und ein zusammenhängender 48-KB-Block kartiert wird, in dem jedes Register langsam ist.Idealerweise wäre das Lesen einer physischen Adresse ein relativ sicherer Vorgang, aber mmiotic ist besonders gut darin, versehentlich die Ausnahmen zu finden:

Zum Beispiel setzt auf der obigen Plattform ein 1-Byte-Read von 0xdc5003b0 — Offset 0x3b0 einer treiberlosen Zen-4-iGPU-Register-BAR (Region 5, 0xdc500000, 512 KB) — das System zuverlässig zurück.
sudo ./mmiotic --address 0xdc5003b0 --size 1 --count 1. Keine Ausgabe; die Kiste startet neu.0x3ac und 0x3b4 auf beiden Seiten lesen 00000000 bei ~3.200 cy und sind harmlos.0x3a0–0x3fc findet 7 schädliche Dwords, die mit 17 sicheren verschachtelt sind.sudo busybox devmem 0xdc5003b0 32 und sudo dd if=/dev/mem bs=1 count=1 skip=$((0xdc5003b0)) bringen die Kiste zu Fall, wenn du den Mittelsmann lieber überspringst.[!WARNING] Einige Plattformen setzen zurück, wenn bestimmte Adressbereiche von
mmioticuntersucht werden. Sei vorsichtig, wo du dies einsetzt.
Die ECAM-Basis und -Größe werden automatisch aus ACPI MCFG erkannt (/sys/firmware/acpi/tables/MCFG). Überschreibe sie mit -M, --mmio-base / -Z, --mmio-size.
--find-target/--find-longest verwandeln das Scannen in eine Suche:
| Option | Effekt |
|---|---|
-F, --find-target <s> | finde eine Adresse, deren Zugriffszeit s Sekunden erreicht, und steigere dann die Breite (unausgerichtetes 4b → 8/16/32/64/512b), um sie weiter zu erhöhen |
b/d/f.o.l: 00:02:00.00.4 address: 00000000f0100000 min: 312 max: 480 stdev: 2.1 dynamic: 0 value: 8086abcd
-T versieht die Zykluszahlen mit dem Suffix cy und fügt ein skaliertes Min/Max-Zeitpaar ein (-N gibt diese als rohe Nanosekunden aus):
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
Kompilieren:
make
Wird als Root ausgeführt, um Zugriff auf /dev/mem zu erhalten.
Wenn du Folgendes siehst: mmap ... Operation not permitted, versuche, mit iomem=relaxed zu booten, um CONFIG_STRICT_DEVMEM aufzuheben:
# /etc/default/grub
GRUB_CMDLINE_LINUX="iomem=relaxed nopat"
Führe dann sudo update-grub aus und starte neu.
mmiotic wurde verwendet, um eine Vielzahl interner und externer Forschung voranzutreiben, manchmal auf unerwartete Weise. Einige Beispiele für mmiotic-Anwendungen:
MCHAMMER: mmiotic kalibriert eine verzögerte Machine-Check-Exception für die präzise Zustellung von MC#-Signalen in geschützte Umgebungen.
smiiiiiiiiiiiiiiii:
mmiotic ermittelt die Hochlatenz-Instruktion, um das SMM-Rendezvous zu brechen.
The Assembly Hall of Shame:
mmiotic wird ausgiebig für das wichtige Problem der Performance-Deoptimierung eingesetzt.
mmiotic ist ein Forschungsprojekt von Christopher Domas (@xoreaxeaxeax).
| Option | Namen |
|---|
-b/-d/-f/-r | PCI --bus/--device/--function/--register — gegen die ECAM-Basis dekodiert |
-o, --offset <n> | Offset von der MMIO-Basis (überspringt die B/D/F-Dekodierung) |
-A, --address <n> | eine physische Adresse; setzt die Regionsbasis implizit |
-a/-z | --start-address/--end-address — ein physischer Bereich; impliziert --scan |
| Option | Effekt |
|---|
-S, --scan | alle Adressen in der Zielregion durchlaufen |
-t, --stride <n> | Schritt zwischen Adressen (Standard: 4) |
-n, --limit <n> | nach dieser Anzahl von Adressen anhalten |
--binary | in Bitumkehr-Reihenfolge prüfen — nach k Prüfungen hat der Bereich eine gleichmäßige Abdeckung mit ~range/k-Granularität (siehe Warnung oben) |
-x, --limited | eingeschränkter Registerbereich (0x00–0xff statt 0x000–0xfff) |
-e, --skip | Funktionen überspringen, die bei Offset 0 0xffffffff lesen (nicht vorhandene Geräte) |
-I, --iomem | jeden Top-Level-Bereich in /proc/iomem scannen, der kein System-RAM ist |
-R, --ioregion <name> | /proc/iomem-Bereiche scannen, deren Bezeichnung auf <name> passt, in jeder Tiefe |
-G, --find-longest |
| gleiche Suche, aber jeden Kandidaten ausschöpfen, statt beim ersten Treffer abzubrechen |
-B, --fallbacks <n> | Kandidaten, die in die Eskalationsphase übernommen werden (Standard: 10) |
| Option | Effekt |
|---|
-s, --size <n> | Bytes pro Zugriff: 1 / 2 / 4 (Standard) / 8 / 16 (XMM) / 32 (YMM) / 64 (ZMM) / 512 (fxrstor). Größen > 4 liegen außerhalb der Spezifikation, funktionieren aber auf getesteter Hardware; AVX-Breiten benötigen AVX / AVX-512F |
-c, --count <n> | Stichproben pro Adresse (Standard: 1); das Minimum wird gemeldet |
-L, --lock | messe ein gesperrtes RMW: lock xadd (Größen 1/2/4/8) oder lock cmpxchg16b (Größe 16). Schreibt den Wert zurück |
-g, --gather | messe einen vektorisierten Gather; --size 16/32/64 wählt XMM/YMM/ZMM (AVX-512) |
-w, --write-read | erfasse die Schreib-Latenz, indem ein Read getimed wird, der einen zuvor geposteten Write beobachten muss — siehe unten |
-E, --enter (--enter-2, --enter-3) | treibe einen Burst von bis zu 30 Zugriffen aus einem einzigen enter $0, $31 — siehe unten |
-k, --continue | bei einem fxrstor-Fehler diese Stichprobe verwerfen und mit dem Scannen fortfahren |
| Option | Effekt |
|---|
-C, --min-cycles <n> | nur Ergebnisse mit min_cycles >= n ausgeben |
-T, --time | Nanosekunden neben Zyklen anzeigen (erkennt die TSC-Frequenz) |
-N, --nanosecond | mit --time rohe Nanosekunden ausgeben, ohne Einheiten-Skalierung |
-P, --progress | Live-Scan-Fortschritt auf stderr |
-p, --processor <n> | an logische CPU n binden (Standard: 0) — reduziert Timer-Rauschen |
-u, --unbound | nicht an einen Kern binden |
--lazy-mmap | 2-GB-Fenster bei Bedarf mappen; erforderlich für Bereiche > 2 GB |
--quiet-mmap | „mmap failed“-Rauschen von unzugänglichen Bereichen unterdrücken |
| Feld | Bedeutung |
|---|
b/d/f.o.l | Bus/Gerät/Funktion, Register-Offset, Zugriffsgröße |
offset | Offset von der MMIO-Basis — ersetzt b/d/f.o.l, wenn das Ziel von -o/-A/-a+-z stammt |
address | vollständige 64-Bit-physische Adresse |
min/max | schnellster und langsamster gesehener Zugriff, in Zyklen |
stdev | Standardabweichung der gemessenen Zykluszahlen |
dynamic | 1, wenn sich der Wert zu irgendeinem Zeitpunkt über die Stichproben hinweg geändert hat |
value | gelesener Wert — bei Größen über 8 Byte die ersten 32 Byte als leerzeichengetrennte Qwords |
faults | nur angehängt, wenn ein Zugriff einen Fehler auslöste |