## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-TGIES-COPY-FAIL-C
# Copy Fail (CVE-2026-31431) - Порт на C
_English (en) ∙ 日本語 (ja) ∙ 简体中文 (zh-cn) ∙ 한국어 (ko) ∙ Русский (ru)_
Кроссплатформенная реализация на C эксплойта Copy Fail для Linux (CVE-2026-31431). Уязвимость была раскрыта 29 апреля 2026 года исследователями Theori / Xint. Полное описание уязвимости, хронологию и процесс обнаружения Theori можно найти в официальном отчете на copy.fail.
Публично выпущенный Proof-of-Concept (PoC) представляет собой Python-скрипт размером 732 байта. Данный порт на C демонстрирует, что тот же самый эксплойт может быть написан на переносимом C, который компилируется под любую архитектуру, поддерживаемую nolibc, без использования специфичных для архитектуры шестнадцатеричных блоков (hex blobs) или встроенного ассемблера в исходном коде самого проекта.
Автор этого порта: Tony Gies some-email@example.com. Обнаружение и исходное раскрытие: Theori / Xint.
## Структура репозитория
root@kitploit:~
copy-fail-c/
├── exploit.c дроппер (вариант с мутацией бинарного файла)
├── exploit-passwd.c дроппер (вариант с подменой UID в /etc/passwd)
├── vulnerable.c неразрушающая проверка уязвимости
├── payload.c сбрасываемая полезная нагрузка (setgid+setuid+execve sh)
├── utils.c, utils.h общий примитив мутации page cache через AF_ALG/splice
├── Makefile оркестрация сборки
├── nolibc/ код, заимствованный из torvalds/linux tools/include/nolibc
└── README.md этот файл
После выполнения `make`:
root@kitploit:~
├── payload крошечный статический ELF, встроенный в дроппер в виде байтов
├── payload.o полезная нагрузка, обернутая в перемещаемый .o с помощью `ld -r -b binary`
├── exploit дроппер, вариант с мутацией бинарного файла
├── exploit-passwd дроппер, вариант с подменой UID в /etc/passwd
└── vulnerable неразрушающая проверка уязвимости
`exploit.c` открывает целевой бинарный файл только для чтения, затем для каждого 4-байтового окна встроенной полезной нагрузки запускает одну фиктивную операцию AEAD-расшифровки через AF_ALG, где зашифрованный ввод поставляется через splice() из страниц кэша (page-cache) целевого файла. Оптимизация "на месте" (in-place) шаблона authencesn рассматривает исходные страницы, переданные через splice, одновременно как ввод шифротекста и место назначения для открытого текста, поэтому обреченная на неудачу расшифровка уже перезаписывает страницу кэша к тому моменту, когда проверка подлинности отклоняет запрос. После 4 * N итераций закэшированный образ целевого файла побайтово заменяется полезной нагрузкой. Выполнение execve() для целевого файла загружает мутировавшие страницы; inode на диске по-прежнему имеет бит setuid root, поэтому ядро предоставляет root-привилегии и запускает полезную нагрузку.
`payload.c` — это обычный переносимый C: `setgid(0); setuid(0); execve("/bin/sh", ...)`. nolibc предоставляет `_start`, механизмы системных вызовов и работу с регистрами, специфичную для каждой архитектуры.
Второй вариант, `exploit-passwd.c`, мутирует четыре байта в кэше страниц файла /etc/passwd вместо образа setuid-бинарника. Ему не нужна встроенная полезная нагрузка, и он работает на системах, где путь с мутацией бинарного файла заблокирован, но его поверхность для успешной эксплуатации (cashout) гораздо уже.
`vulnerable.c` не является эксплойтом. Он создаёт в рабочей директории локальный файл `testfile` со строкой `init`, после чего запускает тот же примитив `patch_chunk()` против собственного кэша страниц этого файла, пытаясь перезаписать байты строкой `vulnerable`. Если прочитанное содержимое совпадает, работающее ядро находится в зоне действия CVE-2026-31431. Inode на диске не модифицируется; `testfile` удаляется при выходе, и мутация кэша страниц исчезает вместе с ним. Запускается без привилегий. Возвращает код выхода 100 при наличии уязвимости, 0 если примитив выполнился, но мутация не сработала, 2 если семейство сокетов AF_ALG или шаблон authencesn недоступны и состояние патча нельзя определить, и 1 для других ошибок выполнения.
## Сборка
По умолчанию (нативно для архитектуры хоста):
root@kitploit:~
make
Кросс-компиляция под aarch64 (или любую другую архитектуру Linux, для которой установлен кросс-тулчейн):
root@kitploit:~
make CC=aarch64-linux-gnu-gcc LD=aarch64-linux-gnu-ld
Архитектуры, поддерживаемые включенной (vendored) версией nolibc (в соответствии с апстримом): x86_64, i386, arm, aarch64, riscv32/64, mips, ppc, s390x, loongarch, m68k, sh, sparc. nolibc осуществляет диспетчеризацию на основе макросов архитектуры компилятора, поэтому достаточно выбрать правильные `CC`/`LD`.
Требования для сборки:
* Компилятор C (`cc`, `gcc` или любой кросс-компилятор)
* Линкер, поддерживающий `ld -r -b binary` (подходят как binutils ld, так и lld)
* Заголовочные файлы ядра UAPI, предоставляющие `linux/if_alg.h` и `<asm/unistd.h>` (Debian/Ubuntu: `linux-libc-dev`; для кросс-компиляции: обычно подтягиваются пакетом кросс-тулчейна)
Наборы заголовков старше Linux 5.6 не содержат `__kernel_old_time_t` и `struct __kernel_old_timespec`, которые использует включённый (vendored) nolibc. `compat.h` (принудительно включаемый в сборку полезной нагрузки) предоставляет их при отсутствии, поэтому сборка проходит и со старым `linux-libc-dev`. На заголовках 5.6 и новее он не даёт никакого эффекта.
Зависимости от внешних библиотек отсутствуют. Полезная нагрузка (payload) собирается автономно (freestanding) с использованием nolibc; дроппер линкуется с системной libc только для использования `fprintf` и `perror`.
## Архитектурные решения
Несколько небольших возможностей тулчейна берут на себя основную задачу по сохранению переносимости исходного кода и небольшого размера полезной нагрузки.
### nolibc
`nolibc/` — это крошечная замена libc от ядра, состоящая только из заголовочных файлов, скопированная из `tools/include/nolibc/` репозитория torvalds/linux. Она предоставляет `_start`, переносимый макрос `syscall()` и встроенные обертки системных вызовов, причем соглашения о регистрах для конкретных архитектур закодированы в `nolibc/arch-*.h`. Сборка полезной нагрузки с флагами `-nostdlib -static -ffreestanding -Inolibc` создает крошечный статический ELF, который обращается к ядру напрямую, не подтягивая код запуска glibc, инициализацию TLS или механизмы stack-canary. Результат, после упаковки и удаления заголовков секций (то и другое описано ниже): ~720 байт на x86_64, ~1.2 КБ на aarch64 по сравнению с ~17 КБ для того же `payload.c`, слинкованного с musl-static, или ~700 КБ с glibc-static.
### `ld -r -b binary` для встраивания
Makefile превращает собранный ELF-файл `payload` в `payload.o` с помощью команды `ld -r -b binary -o payload.o payload`. Линкер выводит входные байты "как есть" в качестве секции данных перемещаемого объектного файла и синтезирует три символа из имени входного файла:
root@kitploit:~
_binary_payload_start адрес первого байта полезной нагрузки
_binary_payload_end адрес байта, следующего за последним байтом полезной нагрузки
_binary_payload_size абсолютный символ, значением которого является размер в байтах
`exploit.c` объявляет первые два символа как `extern const unsigned char[]` и вычисляет размер как `_binary_payload_end - _binary_payload_start`.
### `-Wl,-N` и жесткий `max-page-size`
Полезная нагрузка статически линкуется с опциями `-Wl,-N -Wl,-z,max-page-size=0x10`, что сворачивает секции `.text`/`.rodata`/`.data` в единый сегмент LOAD с выравниванием файла по 16 байтам вместо значения по умолчанию (выравнивание по страницам ядра, 4 КБ на сегмент). Из-за этого `ld` выдает предупреждение об «RWX permissions» (разрешениях на чтение, запись и выполнение), которое носит исключительно информационный характер — защита памяти во время выполнения не имеет значения для данной узконаправленной программы. Без этого флага тот же код линкуется в файл размером ~13 КБ на x86_64 (в основном из-за заполнения нулями между сегментами); с ним размер составляет ~1.3 КБ перед удалением заголовков секций, описанным ниже.
### Удаление заголовков секций
После линковки `objcopy --strip-section-headers` удаляет из полезной нагрузки таблицу заголовков секций и `.shstrtab`. Загрузчик ELF в ядре отображает программу только по её заголовкам программы (program headers), поэтому эти байты никогда не загружаются во время выполнения, а поскольку `payload` встраивается «как есть», они вдобавок увеличивают объём сброса и число итераций `patch_chunk`. Их удаление уменьшает полезную нагрузку на x86_64 с ~1.3 КБ до 720 байт (с 322 четырёхбайтовых итераций до 180). Остальное срезают два флага времени линковки: `-Wl,--build-id=none` убирает заметку build-id, а `-fcf-protection=none` убирает заметку x86 CET там, где её поддерживает компилятор.
Для удаления требуется binutils >= 2.40. При кросс-сборке objcopy для целевой архитектуры передаётся через `OBJCOPY=` (например, `OBJCOPY=aarch64-linux-gnu-objcopy`); если objcopy не может выполнить операцию, сборка выводит примечание и сохраняет корректную, но более крупную полезную нагрузку.
## Варианты эксплойта и их жизнеспособность
В этом репозитории представлены два варианта эксплойта, которые используют один и тот же примитив мутации кэша страниц через AF_ALG/splice, но добиваются выполнения кода от имени root (cashout) по-разному. Профили их надежности неодинаковы, и эта разница имеет значение при анализе реальных моделей угроз.
### Вариант с мутацией бинарного файла (`exploit`)
Мутирует кэш страниц целевого setuid-бинарника, внедряя байты полезной нагрузки, а затем выполняет (exec) этот бинарник. Ядро предоставляет root-привилегии на основе нетронутого бита setuid файла на диске, загружает поврежденный образ из памяти и запускает полезную нагрузку.
Работает везде, где злоумышленник может выполнить `open(target, O_RDONLY)` для любого root-setuid бинарника в системе. В той или иной степени блокируется в средах, где setuid-бинарники скрыты за директориями с ограниченными правами на чтение, а также в системах, спроектированных без использования setuid.
### Вариант с подменой UID в /etc/passwd (`exploit-passwd`)
Мутирует четыре байта в кэше страниц файла /etc/passwd, чтобы установить поле UID текущего пользователя в «0000». Файл /etc/passwd доступен для чтения всем (world-readable) в любой стандартной системе Linux, поэтому _сама мутация_ работает универсально. Однако превращение этого в выполнение кода от имени root зависит от того, будет ли какой-либо процесс со стороны root разрешать имя пользователя через getpwnam/getpwuid и действовать на основе полученного uid без перекрестной проверки. Существует множество таких потребителей; многие из них в целях защиты производят перекрестную проверку с представлением ядра о uid вызывающего процесса или с владельцем файла на диске, что ломает эту цепочку эксплуатации.
#### Матрица жизнеспособности эксплуатации
#### Смена вектора атаки (Pivot) после неудачи `su`
`exploit-passwd` выполняет `su <user>` после мутации как самый простой вариант эксплуатации. Это срабатывает против util-linux `su`, но терпит неудачу против shadow-utils `su` с ошибкой "Cannot determine your user name." (Невозможно определить имя пользователя). В этот момент мутация кэша страниц все еще активна, и возможен переход к любому другому способу эксплуатации (например, использование демона, разрешающего пользователей через getpwnam без перекрестных проверок). Выполните `echo 3 > /proc/sys/vm/drop_caches` от имени root, чтобы очистить поврежденный кэш страниц по завершении тестирования.
## Уязвимые версии ядра
root@kitploit:~
Нижняя граница: torvalds/linux 72548b093ee3 Август 2017 г., v4.14
(Переработка AF_ALG iov_iter,
которая представила примитив
записи на страницу файла через splice
в scatterlist AEAD)
Верхняя граница: torvalds/linux a664bf3d603d Апрель 2026 г., mainline
(Отменяет оптимизацию algif_aead
"на месте" (in-place) 2017 года;
разделяет исходный и целевой scatterlist,
чтобы страницы кэша (page-cache)
больше не могли быть целевым адресом
для записи при криптографической операции)
В промежутке: ядра всех основных дистрибутивов, в которые не был бэкпортирован (backport) патч. Ubuntu, RHEL, SUSE, Amazon Linux и Debian — все они были подтверждены как уязвимые в своих стандартных ядрах для облачных образов на момент раскрытия информации. Бэкпорты на уровне дистрибутивов начали выпускаться примерно 29 апреля 2026 года, параллельно с публичным раскрытием. Чтобы проверить, находится ли целевое ядро в уязвимом диапазоне, убедитесь, присутствует ли коммит `a664bf3d603d` (или его специфичный для дистрибутива бэкпорт) в логах git ядра или в changelog дистрибутива.
## Коммерческая поддержка
По вопросам платного аудита безопасности, заказного портирования или непубличных уведомлений о безопасности обращайтесь к автору через его консалтинговую компанию Crash United, LLC.
Контакты: some-email@example.com · https://crashunited.com
GitHub: @tgies · X/Twitter: @me_irl
## Лицензия и благодарности
Обнаружение и первоначальное раскрытие CVE-2026-31431: Theori / Xint. Публичный отчет: https://copy.fail/.
Данный порт на C: Tony Gies some-email@example.com
`nolibc/`: заимствовано (vendored) из дерева исходного кода ядра Linux; имеет двойное лицензирование LGPL-2.1-or-later ИЛИ MIT (см. `nolibc/nolibc.h` и SPDX-заголовки отдельных файлов).
Исходные коды дроппера и полезной нагрузки в этом репозитории выпущены на тех же условиях двойного лицензирования LGPL-2.1-or-later ИЛИ MIT, что и дерево nolibc, от которого они зависят. Это сделано для того, чтобы сохранить тривиальную совместимость лицензий для всех, кто решит включить этот каталог целиком в свои собственные разработки.
Данный эксплойт и полезная нагрузка публикуются в целях исследования безопасности и создания защитных механизмов обнаружения. Использование против систем, которыми вы не владеете или на тестирование которых у вас нет явного разрешения, является вашей ответственностью, а не проблемой автора.