本記事は、 Rust Advent Calendar 2024 シリーズ3 の 20日目 の記事だ。
ついでに、 Windows Advent Calendar 2024 20日目 の記事としても登録させてもらっている。
遅刻です。申し訳ない。
今回は、やや実践的な例として、 Rust で Windows API の グローバル フック を使って、各ウィンドウに届くマウスやキーボード・ウィンドウメッセージをダーッとリアルタイムで表示するコードを書いてみよう。
フックDLLでメモリリークやセグメント違反を引き起こすとシステム丸ごと死にかねないし、フックした情報を表示するためにプロセス間通信やマルチスレッドの処理が必要になるなど、割と Rust 向きな例ではなかろうか。
フックとは
Win32 アプリケーションでは、各ウィンドウやコントロールがそれぞれイベントハンドラ(プロシージャ)を持ち、これが届いたメッセージを処理することで様々な機能を振る舞わせる。
「フック」とは、本来のプロシージャに届くはずの処理を先に引っ掛けて(フックして)、処理を間に挟んでから本来のプロシージャに返したり、あるいは処理を奪ってしまう機能だ。
グローバル フック は、同じ PC 内の権限的に許されるあらゆるプロセスのスレッドに対して、フックをねじ込む機能だ。
キーロガーなどのウイルスや、クリップボード監視のソフトなどでよく使われている(と思う)。
グローバルフックした場合、フック対象それぞれのプロセス毎に、フックプロシージャを定義した DLL をロードさせ、そのプロシージャを呼び出させる。
つまり、フックされたプロセス毎に DllMain が呼ばれるし、メモリ空間も独立する。
標準出力も、フックされたプロセスのものが利用されるので、例えばフックプロシージャで println!
しても、フックを登録したプロセスではなくて、フックされたプロセス側の標準出力に書き込まれるわけだ。
graph LR
subgraph caller[呼び出し元 exe]
D0[DLL]
end
subgraph sub1[フック対象プログラム1]
D1[DLL]
end
subgraph sub2[フック対象プログラム2]
D2[DLL]
end
D0 -->|"登録"| D1
D0 -->|"登録"| D2
D1 -->|"println!"| cfor1@{ shape: f-circ }
D2 -->|"println!"| cfor2@{ shape: f-circ }
cfor1 --> sub1
cfor2 --> sub2
このため、フックしたメッセージを登録元プロセスにて表示するためには、スレッド間参照などは使えず、プロセス間通信などを使う必要がある。
今回は、名前付きパイプで登録元プロセスに情報を渡してみる。
graph LR
subgraph caller[呼び出し元 exe]
D0[DLL]
end
subgraph sub1[フック対象プログラム1]
D1[DLL]
end
subgraph sub2[フック対象プログラム2]
D2[DLL]
end
D0 -->|"登録"| D1
D0 -->|"登録"| D2
D1 -->|"名前付きパイプ等"| caller
D2 -->|"名前付きパイプ等"| caller
名前付きパイプによるプロセス間通信
続きを読む →