Содержание статьи
Copy Fail (CVE-2026-31431) — баг в ядре Linux, позволяющий обычному пользователю получить права root с помощью маленького скрипта на Python. Скрипт срабатывает во всех популярных дистрибутивах, вышедших с 2017 года и не имеющих патча.
warning
Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
Эксплоит использует оптимизацию 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 байт, а некоторые проверки — буквально несколько байт.
После отравления кеша при вызове / запускается версия программы из 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 , чтобы в будущем немного упростить себе жизнь. Дальше ты поймешь почему.
Пейлоад
Теперь на минутку представим себя злоумышленниками. Нам удалось где‑то раздобыть исходный код целевой программы, мы проанализировали его и поняли, какой фрагмент нужно поправить, чтобы программа начала выполнять произвольный код. Мы видим функцию 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 .
Извлечение можно автоматизировать с помощью objdump, grep и awk:
objdump -d ./payload.o | grep -oP '\s([0-9a-f]{2}\s)+' | tr -d ' \n' | sed 's/../\x&/g'
Продолжение доступно только участникам
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Присоединяйся к сообществу «Xakep.ru»!
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
