
Linux、Windows、macOSにおけるファイル通知サイドチャネル攻撃の研究資料。inotify/FSEventsの情報漏洩、キーストロークタイミング、ウェブサイトフィンガープリンティングを実証。
このリポジトリには、論文「File Notification Attacks: Templating and Exploiting Side-Channel Leakage from the File-Notification Systems on Linux, Windows, and macOS」(CCS '26 採択) の成果物 (まだ査読中!) が含まれています。
デモを掲載したウェブサイトはこちら: https://inoti.fyi/
論文はこちら: https://snee.la/pdf/pubs/file-notification-attacks.pdf または http://inoti.fyi/pubs/file-notification-attacks.pdf
CCS'26 の成果物評価 (まだ進行中!) の一環として、パッチを適用済みのカーネルを備えた Debian 13 + KDE Plasma 6 の VM を提供しました。この VM は論文の評価結果を生成するために使用されたものではなく、その正確な規模を再現することを意図したものでもありません。このリポジトリは、成果物評価者からのレビューやフィードバックを取り入れて更新される可能性があります。成果物評価が完了した時点で、VM を公開する予定です。
私たちは Claude (Opus 4.8) を使用して、コードを適度に文書化され高性能な成果物にラップしました (例えば、Windows のものは少し動作が重かったです)。ただし、それが生成したコード、スクリプト、makefile は以下に基づいています
以下の Linux コードをテストするための手順は、VM 内でコマンドを実行する場合に固有のものです。代わりに Linux/ 内のコードを参照してください。
VM は純粋に利便性のために存在しており、各攻撃をマウントするたびにゼロからの環境セットアップやカーネルのダウングレードを必要としません。VM で実証される基盤となるメカニズムは論文で説明されているものと同一ですが、論文で報告された数値を生成するために VM を使用したわけではないことに注意してください。絶対的なタイミングは、仮想化環境と基盤となるハードウェアにより異なる場合があります。
知見は Debian 13 VM (linux-vm/) 内で再現されます。これは公式の live Debian ISO からインストールされ、KDE Plasma 6 (Wayland) がインストールされ、fsnotify 修正 より前のカーネルを備えています。inotify-tools、Qt6 dev ヘッダー、pkexec がインストールされています。それ以外はアップグレードされておらず、インストール後にカーネルは一切触れられていません。絶対に apt upgrade を実行したり、カーネルをアップグレードしようとしないでください。この VM イメージは意図的に古く、最新ではなく、最新のセキュリティパッチが適用されていません。この VM イメージは迅速な検証とテストのみを目的としています!
VM 内にあるコード、攻撃、デモンストレーションは Linux/ にミラーされています。
注: 各コマンドブロックの先頭に、どのユーザーとしてコマンドを実行するかを記載しています。[host] は VM を実行するホストマシン、[user] は VM 内の攻撃の被害者、[spyuser] はセットアップが必要で、ほとんどの Linux 攻撃で攻撃者となるユーザーです。
ディスクイメージは linux-vm-upload.qcow2 として提供され、zstd で圧縮されています。run.sh (このファイル名を想定しています) を実行する前に linux-vm.qcow2 に名前を変更してください:
# Run As: [host]
cd linux-vm/
mv linux-vm-upload.qcow2 linux-vm.qcow2
./run.sh
zstd 圧縮された qcow2 を読み取るには、qemu-img/qemu-system-x86_64 バージョン 5.2 以降 (2020 年以降) が必要です。qemu-img --version で確認してください。古い QEMU を使っていてイメージを開けない場合は、まずフラットな qcow2 に展開してください:
qemu-img convert -O qcow2 linux-vm-upload.qcow2 linux-vm.qcow2
4 コア、4GB RAM、GUI ディスプレイ。ログイン情報は以下の通りです:
root、パスワード password、user、パスワード password、spyuser、パスワード password (spyuser としてログインしないでください!)提供された VM はすでに脆弱な、パッチ適用済みのカーネル (6.12.43+deb13-amd64) を実行しているため、使用するためにダウングレードは不要です。このイメージ (GUI 付き) を実行するには QEMU が必要です。成果物は VM 内の /home/user/Linux-File-Notification-Attacks にあります。
1 つのコマンドで VM 内を新たにセットアップします: (i) 非特権の spyuser アカウント (非 sudo)、(ii) 成果物ディレクトリの独自のコピー (そうしないと、別の非特権アカウントは user のホームディレクトリに侵入できません)、(iii) 両方のコピーで全てのスクリプトを実行可能にします。
# In the VM, Run As: [user]
sudo bash ~/Linux-File-Notification-Attacks/setup_attacks.sh
以下のすべての攻撃は spyuser (su - spyuser、パスワード password) として実行されますが、auth-ui-redress は例外で、user として実行されます (以下で説明します)。
親ディレクトリが読み取り可能であれば、直接読み取ることができないファイルに対してもファイル操作通知が配信されることを証明します。
# Run As: [spyuser]
# To switch to spyuser, in a new terminal type `su - spyuser`. The password
# is `password`.
cd ~/Linux-File-Notification-Attacks/unreadable-file-bypass
./watch-syslog.sh
実行したままにして、別のターミナルから user としてログ行を生成します:
# Run As: [user]
logger "hello"
この Debian 13 イメージには rsyslog がないため、論文に示されているような /var/log/syslog ファイルはありません。スクリプトは代わりに /var/log/journal/<machine-id>/ の監視にフォールバックします。監視対象の .journal ファイルは root (ユーザー) と systemd-journal (グループ) によって所有されており、どちらも spyuser ではないため、これは同じ考え方です。
inotify の通知遅延を測定します。
# Run As: [user], ensure numpy is installed (or pip3)
sudo apt install python3-numpy
# Run As: [spyuser]
cd ~/Linux-File-Notification-Attacks/temporal-resolution
./run.sh
watcher はテストファイルに inotify ウォッチを開き、受け取ったすべての IN_ACCESS にタイムスタンプを付けます。accessor は同じファイルを 1000 回読み取り、読み取り間にランダムな 5-10ms の遅延を挟み、各読み取り自体にタイムスタンプを付けます。stats.py は 2 つのタイムスタンプ記録を差分し、読み取りが発生してから通知が到着するまでの平均/標準偏差/最小遅延、つまり inotify の時間分解能を出力します。
論文の完全な攻撃ではなく、攻撃の基盤となるフィルタリングの概念実証プリミティブのみです: システム上のどこかで押されたすべてのキーについて、/dev/input を直接読むことなく、タイムスタンプ付きの通知を 1 つ出力します。
# Run As: [spyuser]
cd ~/Linux-File-Notification-Attacks/inter-keystroke-timing
make
./find-keyboard.sh
任意のウィンドウ (おそらく user としての新しいターミナル) で入力してください。各キーストロークは 1 行の KEYPRESS を出力します。抑制ウィンドウ (このデモンストレーションでヒューリスティックに選択したのは 130ms) は、1 回の物理的なキー押下によって生成される複数の IN_ACCESS イベントを統合します。実際には、このウィンドウはハードウェア (例えば、メカニカルキーボード、タイピングスタイル) によって異なることに気付きました。
自動検出が間違ったデバイス (または何も) を選択した場合は、/proc/bus/input/devices を確認し、./build/keystroke-notify /dev/input event4 で直接実行してください。
(pkexec)[https://polkit.pages.freedesktop.org/polkit/] が実行されたことを検出し、さらにその上に偽のウィンドウを描画できることを実証します。セクション 4.4.3 で示したように、この「認証 UI リドレス攻撃」は Wayland を使用する KDE Plasma 5 および 6 で可能です。この攻撃は同一ユーザー攻撃者の脅威モデルで実行します: Wayland ソケットはログインセッションにスコープされており、別の非特権アカウントはそれに描画できません。
標準の KDE インストールでは pkexec はデスクトップによって導入されますが、この live ISO はより軽量なイメージであるため、インストールされていません。したがって、VM イメージを小さく保つ (ように努めながら) 手動でインストールしました。
# Run As: [user]
cd ~/Linux-File-Notification-Attacks/auth-ui-redress
make
./inotify-watcher-with-gui
ターミナルで実行したままにしてください。別のターミナル (ディレクトリは問いません) から、実際の pkexec プロンプトをトリガーします (例: pkexec ls)。watcher は /usr/bin/pkexec へのアクセスを検出し、偽の「Authentication Required」ダイアログ (window-launcher) を本物の上に画面に描画します。偽のダイアログに入力して Authenticate を押すと、入力されたテキストが watcher のターミナルに出力され、その後閉じます。偽のウィンドウは比較しやすいように本物のウィンドウとは意図的に異なっていることに注意してください。
ページの読み込み中にフォントディレクトリを監視すると、どのフォントファイルに触れたかが明らかになります。サイトごとに異なるフォントを読み込むため、ページの読み込み中に出力されるパスの集合はすでにフィンガープリントであり、この最小限のバージョンではタイミングや分類器は不要です。
まず、user として Firefox を開き (画面下部のタスクマネージャードックのアイコンからクリック)、ウェブサイトが開いていないことを確認します。次に、spyuser としてターミナルで:
# Run As: [spyuser]
cd ~/Linux-File-Notification-Attacks/website-fingerprinting-fonts
./compare-fonts.sh
これは font-spy をビルドし、Firefox が使用するフォントディレクトリの監視を開始します。compare-fonts スクリプトは 2 つのウェブサイトを訪問するよう促します。
まず、1 つのウェブサイト (おそらく wikipedia.com) を訪問し、読み込まれるまで数秒待ち、新しいタブを開き、古いタブ (ウェブサイトのあるもの) を閉じ、ターミナルで ENTER を押します。
次に、別のウェブサイト (おそらく reddit.com) で同じことを行います: ウェブサイトを訪問し、読み込まれるまで数秒待ち、新しいタブを開き、古いタブを閉じ、ターミナルで ENTER を押します。
スクリプトはウェブサイトごとにアクセスされたフォントの差分を出力するはずです。論文では、時間情報、つまり フォントがアクセスされた時刻も使用したことに注意してください。単純な概念実証では、この情報を無視し、2 つのフォントアクセス集合の差分を単純に出力するだけです。正確なフォントアクセス集合は実行ごとに異なる場合がありますが、ウェブサイトによって常に一意にアクセスされる特定のフォントファイルがあります。
ウェブサイトは時間とともに変化する可能性があるため、フォントファイルのアクセスは異なる場合があることに注意してください。この成果物の起草時点では、Wikipedia は常に /usr/share/fonts/truetype/liberation/LiberationSans-Bold.ttf にアクセスし、Reddit は常に /usr/share/fonts/truetype/vlgothic/VL-Gothic-Regular.ttf にアクセスすることに気付きました。
Scoped Storage モデルが WhatsApp の共有メディアディレクトリに FileObserver (inotify の Java レベルのラッパー) を登録することを依然として許可している Android バージョンを実行する物理デバイスまたはエミュレータ、およびメッセージ送信者として機能する 2 台目のデバイス/アカウント。提供されたソース (Android/source/) をビルドするか、提供された APK (Android/app-release.apk) を直接インストールします。
Android は、Linux の知見全体で使用されているのと同じ inotify プリミティブを android.os.FileObserver を介して公開しています。セクション 5.4.3 では、非特権でパーミッションのないアプリが WhatsApp のメディアディレクトリにそのようなオブザーバーを登録し、結果として生じる open/close/access イベントのストリームのみから、コンテンツ自体を読み取ることを許可するパーミッションを一切持たずに、プライベートメディアが受信されたことを推測できることを示しています。
アプリを起動し、リフレッシュアクションをトリガーします。オブザーバーサービスがアタッチし、定期的な生存確認エントリを出力し始めます。ログビューの一番下までスクロールし、ObserverService: Still Running エントリが繰り返し表示されるまでリフレッシュを続け、ウォッチがアクティブで安定していることを確認します。
2 つ目のアカウントから、監視対象の会話に画像またはドキュメントを送信し、(まだキャッシュされていない場合は) 監視対象のデバイスでそれをダウンロードします。これによりファイルイベントのログ行がバースト的に生成されます。バースト前の最後の ObserverService: Still Running エントリの直後に、ログは受信したファイルに対応するパスを持つ open/close/access イベントを表示するはずであり、その到着がファイルシステム通知メタデータのみから観察可能であることを示しています。
msys2 でコンパイルできるソースコードを提供しています。それ以外に、exe ファイル (Windows/firefox-fingerprint/monitor.exe) も使用できます。
MSYS2 がインストールされた Windows マシン (または VM)、および MinGW-w64 g++ ツールチェーン (MSYS2 シェルから pacman -S mingw-w64-ucrt-x86_64-gcc を実行)。Firefox がインストールされていること。
この概念実証では ReadDirectoryChangesW を使用して C:\ 全体を再帰的に監視し、パスに http を含むイベントのみを出力します (Firefox がサイトのスキーム/ホストにちなんで命名するオリジンごとのストレージディレクトリ、例: そのキャッシュ/IndexedDB フォルダの下)。一覧表示できるディレクトリを監視するのに管理者権限は必要ありません。このオリジンごとのストレージはページの読み込みごとに書き込まれるため、ブラウザプロセスの外部から監視しても、どのサイトが訪問されたばかりかを明らかにできます。
提供された exe (Windows/firefox-fingerprint/monitor.exe) を使用するか、MSYS2 UCRT64 シェルからコンパイルします:
# Run in an MSYS2 UCRT64 shell
cd Windows/firefox-fingerprint
g++ -municode -static -O2 -o monitor.exe monitor.cpp
gcc ではなく g++ を使用してください。また、monitor.exe は (ダブルクリックではなく) ターミナルから実行してください。そうしないと、出力先のコンソールがアタッチされません。
watcher は、ブラウジングしているアカウントとは別の、2 つ目の非特権ローカルアカウントとして実行されます。まず、設定からそのアカウントを作成します:
attacker) とパスワードを入力し、次へ をクリックします。これにより、ローカルアカウント (Microsoft アカウントに紐付かない、ネットワークサインインなし) がデフォルトで標準 (非管理者) 権限で作成されます。
次に、新しいアカウントが実際に読み取って実行できる場所に monitor.exe を配置します。ビルドしたバイナリを、代わりに公開された誰でも読み取り可能なディレクトリにコピーします:
copy monitor.exe C:\Users\Public\monitor.exe
次に、メイン (被害者) アカウントから、デスクトップを切り替えたりログアウトしたりすることなく、runas を使用して attacker アカウントで起動します:
runas /user:attacker "cmd /k C:\Users\Public\monitor.exe"
プロンプトが表示されたら attacker のパスワードを入力します。cmd /k (monitor.exe を直接実行するのではなく) はコンソールウィンドウを開いたままにして出力を監視できるようにしますが、単に runas /user:attacker C:\Users\Public\monitor.exe でも動作しますが、そのウィンドウはプロセスが終了した瞬間に閉じます。これは純粋に利便性のためです。
monitor.exe のウィンドウを実行したままにして、メインアカウントに戻り、Firefox を開いていくつかの異なるウェブサイト (例: arstechnica.com と reddit.com) を訪問します。出力される各行は、http に一致するストレージパス下のファイル作成/変更/名前変更です。サイトごとに異なるオリジンディレクトリが割り当てられるため、ページの読み込み中に触れられたパスの集合はすでにフィンガープリントです。
簡単にするため、ネイティブの FSEvents API をラップする既存の、最小限で広く使用されている CLI ツールである fswatch を使用します。攻撃者はこのツールなしでも FSEvents API を使用できます。
fswatch をインストールするか、ソースからビルドします:
brew install fswatch
非特権ユーザーとして /Applications を再帰的に監視します:
fswatch -xr /Applications
実行したままにして、Finder またはブラウザから Zoom (またはその他のアプリケーション) をダウンロードしてインストールし、その後アンインストールします (ゴミ箱も空にする必要があるかもしれません)。fswatch は FSEvents が /Applications 下で報告するすべてのパスを、発生するたびに出力します。迅速な評価のために、同じユーザーがディレクトリを監視できます。それ以外の場合は、新しいユーザーを設定し、そのユーザーとして fswatch を使用できます。
MIT License の下でリリースされています。