Sploitus

Exploit for copy-fail-c

kitploit · 2026-08-23

Exploit Code

MARKDOWN171 lines
## 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, от которого они зависят. Это сделано для того, чтобы сохранить тривиальную совместимость лицензий для всех, кто решит включить этот каталог целиком в свои собственные разработки.

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