Для подписчиковВесной 2026 года уязвимость Copy Fail привлекла много внимания. Скрипт на Python размером чуть больше 700 байт повышал права пользователя до прав суперпользователя буквально в один клик. В этой статье мы разберем технические подробности бага, напишем демокод целевой программы и пейлоад для нее. Соберем все вместе и проэксплуатируем механизм уязвимости.
Весной 2026 года уязвимость Copy Fail привлекла много внимания. Скрипт на Python размером чуть больше 700 байт повышал права пользователя до прав суперпользователя буквально в один клик. В этой статье мы разберем технические подробности бага, напишем демокод целевой программы и пейлоад для нее. Соберем все вместе и проэксплуатируем механизм уязвимости.
Copy Fail (CVE-2026-31431) — баг в ядре Linux, позволяющий обычному пользователю получить права root с помощью маленького скрипта на Python. Скрипт срабатывает во всех популярных дистрибутивах, вышедших с 2017 года и не имеющих патча.
Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
Эксплоит использует оптимизацию in-place в криптографической подсистеме Linux. Баг получил оценку 7,8 балла по шкале CVSS. Проблему нашли специалисты компании Theori: за час они просканировали код криптографической подсистемы ИИ‑тулзой Xint Code, а затем опубликовали PoC-прототип эксплоита.
Механизм эксплоита такой. Целевой бинарник открывают в режиме read-only, затем запускают расшифровку AEAD через сокеты AF_ALG: шифротекст передают системным вызовом splice() из страничного кеша целевого файла. Из‑за оптимизации in-place шаблон authencesn воспринимает исходные страницы, переданные через splice(), одновременно как ввод с шифротекстом и буфер для открытого текста. После неудачной расшифровки он успевает перезаписать страницу кеша еще до того, как запрос отклонят.
Например, фрагмент кода до эксплуатации:
if (uid != 0) {
// target code
}
После эксплуатации может выглядеть так, будто выполняется:
if (uid == 0) {
// target code
}
Почему для работы эксплоита хватает четырех байт? Все просто: не нужно переписывать весь целевой файл — достаточно заменить несколько инструкций. Некоторые эксплоиты занимают 1–5 байт, условные переходы (jcc) — 2–6 байт, относительные переходы (jmp, call) — 5 байт, а некоторые проверки — буквально несколько байт.
После отравления кеша при вызове /bin/su запускается версия программы из page cache с измененными байтами, и в итоге мы получаем root shell.
Важно: исходный код программы, исполняемый файл и его контрольная сумма не меняются. Все манипуляции происходят в page cache. Инструменты для форензики не заметят вмешательства в работу программы.
Судя по описанию, механизм прост: не требует перебора, специфичного окружения, попыток поймать состояние гонки или переполнение буфера. Поэтому мне пришла в голову мысль реализовать свой PoC-эксплоит. Для этого мне понадобится:
Я решил не усложнять код атакуемой программы, чтобы не создавать себе лишних проблем. Наша задача — показать уязвимость, поэтому будем просто эмулировать проверку доступа.
При запуске передаем в аргументе имя пользователя (строку). Если оно равно admin, идем по одной ветке, иначе — по другой. Результат проверки попадет в консоль, чтобы мы точно видели и понимали, что происходит. Итоговый код:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int check_admin(const char *arg) {
return strcmp(arg, "admin") == 0;
}
int main(int argc, char *argv[]) {
if (argc > 1 && check_admin(argv[1])) {
printf("Access Granted\n");
} else {
printf("Access Denied\n");
}
return 0;
}
Заранее оговорюсь: я вынес проверку в отдельную функцию check_admin, отказавшись от обычного линейного блока if else, чтобы в будущем немного упростить себе жизнь. Дальше ты поймешь почему.
Теперь на минутку представим себя злоумышленниками. Нам удалось где‑то раздобыть исходный код целевой программы, мы проанализировали его и поняли, какой фрагмент нужно поправить, чтобы программа начала выполнять произвольный код. Мы видим функцию check_admin, в которой описана логика проверки доступа. Сейчас там ключ admin жестко зашит и можно даже ничего не эксплуатировать, но это сознательное упрощение кода ради наглядности.
Наша задача — просто подменить возвращаемое значение check_admin. А значит, мы можем заменить всю функцию целиком. С этой мыслью я набросал примитивный код:
int check_admin(const char *arg)
{
return 1;
}
Это почти все, что нужно для атаки. Осталось только скомпилировать, получить шелл‑код и встроить его куда нужно.
Шелл-кодПейлоадПосле компиляции пейлоада можно извлечь машинный код. Для этого воспользуемся утилитой objdump. Для архитектуры x86 наш пейлоад будет выглядеть так:
objdump -d payload.o
Получаем такой вывод:
./payload.o: file format elf64-x86-64 Disassembly of section .text: 0000000000000000 <check_admin>: 0: 48 89 7c 24 f8 mov %rdi,-0x8(%rsp) 5: b8 01 00 00 00 mov $0x1,%eax a: c3 ret
Учти: у тебя вывод может отличаться, но его структура должна остаться такой же, как у нас.
Получаем последовательность байтов. В выводе нас интересуют шестнадцатеричные байты из второго столбца: b8 01 00 00 00 c3.
Извлечение можно автоматизировать с помощью objdump, grep и awk:
objdump -d ./payload.o | grep -oP '\s([0-9a-f]{2}\s)+' | tr -d ' \n' | sed 's/../\x&/g'