Rust で Windows API のグローバルフック

本記事は、 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

名前付きパイプによるプロセス間通信

続きを読む

Rust で Win32 API ことはじめ

本記事は、 Rust Advent Calendar 2024 シリーズ3 の 14日目 の記事だ。
ついでに、 Windows Advent Calendar 2024 14日目 の記事としても登録させてもらっている。

Rust で Win32 API を叩くにあたって、最初に知っておきたい情報をまとめておく。

Win32 API を呼ぶ手段

C# の P/Invoke のように一つ一つの API をソースコード上で定義してリンクすれば、呼び出せなくはない。 1

しかしそれは流石に面倒なので、 Microsoft による windows-rs のクレートを使うのが基本となる。

windows-rs とは

windows-rs は、 Microsoft 自身が Windows API のメタデータから生成されたコードを用いて、 Rust から Windows API を呼び出しやすくするクレートを提供するプロジェクトだ。
その仕組みの通り、 Win32 API のほぼ全てに加え、 COM API や WinRT もその多くがカバーしている。

大まかな情報は、以下のページにまとまっていて基本的にはこれを読み進めればよい。

しかしそれではわざわざ記事を書く必要性が薄いので、この記事では Win32 API コードを書くにあたって、最初に知っておきたい情報をまとめておこう。

なお、後述するが windows-rs は COM や WinRT の API もカバーしており、それぞれ呼び出し方にコツがある。
すべてを解説すると分量が多いので、本記事では Win32 API の範囲に限って説明する。


ところで、↑のドキュメントのドメインである kennykerr.ca って誰のやねんって話だが、 Microsoft のエンジニア らしい。
Microsoft 公式マニュアルが個人名のドメインに置かれているってどうなのと言う気もせんでもないが、元々個人の趣味プロジェクトだったのかな…?

また、 2024年12月現在最新版が 0.58.0 と、セマンティックバージョン的にもまだ破壊的変更が入りうるものだ。
最近こそ更新は落ち着いてきているものの、以下の情報が今後変わりうることはご留意いただきたい。

windows クレート と windows-sys クレートの使い分け

続きを読む