Вес­ной 2026 года уяз­вимость Copy Fail прив­лекла мно­го вни­мания. Скрипт на Python раз­мером чуть боль­ше 700 байт повышал пра­ва поль­зовате­ля до прав супер­поль­зовате­ля бук­валь­но в один клик. В этой статье мы раз­берем тех­ничес­кие под­робнос­ти бага, напишем демокод целевой прог­раммы и пей­лоад для нее. Соберем все вмес­те и про­экс­плу­ати­руем механизм уяз­вимос­ти.

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 байт, а некото­рые про­вер­ки — бук­валь­но нес­коль­ко байт.

Пос­ле отравле­ния кеша при вызове /bin/su запус­кает­ся вер­сия прог­раммы из page cache с изме­нен­ными бай­тами, и в ито­ге мы получа­ем root shell.

Важ­но: исходный код прог­раммы, исполня­емый файл и его кон­троль­ная сум­ма не меня­ются. Все манипу­ляции про­исхо­дят в page cache. Инс­тру­мен­ты для форен­зики не заметят вме­шатель­ства в работу прог­раммы.

Су­дя по опи­санию, механизм прост: не тре­бует перебо­ра, спе­цифич­ного окру­жения, попыток пой­мать сос­тояние гон­ки или перепол­нение буфера. Поэто­му мне приш­ла в голову мысль реали­зовать свой PoC-экс­пло­ит. Для это­го мне понадо­бит­ся:

  1. На­писать код целевой прог­раммы, ском­пилиро­вать его в бинар­ный файл и про­верить, что он кор­рек­тно работа­ет.
  2. На­писать код пей­лоада.
  3. Пре­обра­зовать пей­лоад в шелл‑код.
  4. Внед­рить вре­донос­ный код в целевую прог­рамму по опи­сан­ной выше логике экс­плу­ата­ции бага.
 

Жертва

Я решил не усложнять код ата­куемой прог­раммы, что­бы не соз­давать себе лиш­них проб­лем. Наша задача — показать уяз­вимость, поэто­му будем прос­то эму­лиро­вать про­вер­ку дос­тупа.

При запус­ке переда­ем в аргу­мен­те имя поль­зовате­ля (стро­ку). Если оно рав­но 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'

Продолжение доступно только участникам

Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».

Присоединяйся к сообществу «Xakep.ru»!

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии