Keyboard Filter Driver
皆様、楽しいデバッグライフをお送りしておりますでしょうか!
WDK サポートチームのI沢でございます。
今月も私がお送りしたいと思いますが、これまでのようなツール紹介ではございません!
今回は、キーボードフィルタドライバにつきまして、ご紹介したいと思います。
私はキーボードフィルタドライバと聞くだけで、馬鹿なことをやったな…と思いだすことがあります。
当時、まだ若かった私はキーボードフィルタドライバを実装していたのですが、手軽にいろいろできたので、"Ctrl + Alt + Del" の同時押しを抑制して強制終了できなくしてみたらどうなるだろう…?と対象のキーを抑制したドライバを検証用の PC ではなく作業用の PC にインストールしてしまいました。
オチは想像できるかと思いますが、それはもう見事に Windows にログインできなくなりました!
それを機に、絶対にドライバ開発は検証用の環境で行おう…と心に誓いました。みなさまもお気を付けくださいね!
ちなみにそのときは、別の PC に対象のストレージを接続して、ドライバのバイナリファイルを削除することで復旧いたしました。
■キーボードフィルタドライバについて
キーボードフィルタドライバは、簡単に説明するとキーボードの入力をフィルタリングして、入力を無効にしたり、別のキーに置き換えたり、キー入力を追加したりといったことが可能になります。
フィルタリングはカーネルモードで行われるため、ユーザーモードではフィルタリングされたキー入力結果があたかも実際にそのような入力があったかのように見えます。
実際どのような場面で使用されるかというと、例えばショートカットキーによる強制終了等の特定の処理を抑制したり、キーの連射機能を実現したりと、キー入力に関わることであれば大抵のことが可能です。
ただ、よくお聞きするのがフィルタドライバの実装は難しそう…といったご意見です。しかし、キーボードフィルタについてはその限りではありません。
優秀なサンプルプログラムがございますので、たった 1 つの関数を変更するだけでキー入力のフィルタリングを行うドライバが実装できるのです。
■ KeyboardClassServiceCallback ルーチンについて
さて、早速キーボードフィルタドライバのサンプルプログラムを見ていきましょう。
サンプルプログラムは以下のサイトからダウンロード可能です。ダウンロードした zip ファイルは適当な場所に展開しましょう。
Keyboard Input WDF Filter Driver (Kbfiltr)
<https://code.msdn.microsoft.com/windowshardware/Kbfiltr-WDF-Version-685ff5c4>
展開したら、ソースコードを確認してみましょう。
"<展開したフォルダ>\C++\sys\kbfiltr.c" の 766 行目付近に KbFilter_ServiceCallback という名前の関数が定義されていると思います。
なんと本関数を変更するだけで、基本的なフィルタリング機能は実装できてしまいます。
本関数の型は KeyboardClassServiceCallback ルーチンとして定義されており、キーボードフィルタドライバは Kbdclass ドライバから IOCTL_INTERNAL_KEYBOARD_CONNECT と定義されたコントロールコードが送られてきたときに、この型の関数をコールバック関数として登録するような仕様となっております。
詳しい話は割愛いたしますが、以下の技術資料に詳細が記載されておりますのでご参照ください。
◇KeyboardClassServiceCallback 関数の登録方法について
IOCTL_INTERNAL_KEYBOARD_CONNECT control code
<https://msdn.microsoft.com/en-us/library/windows/hardware/ff541273(v=vs.85).aspx>
◇KeyboardClassServiceCallback ルーチンについて
KeyboardClassServiceCallback Routine
<https://msdn.microsoft.com/en-us/library/windows/hardware/ff542274(v=vs.85).aspx>
◇KBDClass ドライバについて
Kbdclass Driver Reference
<https://msdn.microsoft.com/en-us/library/windows/hardware/ff542278(v=vs.85).aspx>
■ KeyboardClassServiceCallback ルーチンの実装
実際に関数の実装を進めたいと思いますが、まずはそのインターフェースを見てみましょう。
KeyboardClassServiceCallback ( IN PDEVICE_OBJECT DeviceObject, IN PKEYBOARD_INPUT_DATA InputDataStart, IN PKEYBOARD_INPUT_DATA InputDataEnd, IN OUT PULONG InputDataConsumed) InputDataStart - 最初のキー入力データが格納されているポインタ InputDataEnd - 最後のキー入力データポインタの一つ後を指すポインタ InputDataConsumed - 処理したデータ数を格納 |
簡単に説明いたしますと、InputDataStart から InputDataEnd までにキーの入力情報が格納されています。通常はこのデータが上位のドライバにそのまま渡されるのですが、本関数内で変更を加えることでフィルタリングすることができます。なにも手を加えていない状態の実装を見てみましょう。引数で受け取った情報をそのまま上位ドライバに渡しているのがわかるかと思います。
KbFilter_ServiceCallback( IN PDEVICE_OBJECT DeviceObject, IN PKEYBOARD_INPUT_DATA InputDataStart, IN PKEYBOARD_INPUT_DATA InputDataEnd, IN OUT PULONG InputDataConsumed) { PDEVICE_EXTENSION devExt; WDFDEVICE hDevice; hDevice = WdfWdmDeviceGetWdfDeviceHandle(DeviceObject); devExt = FilterGetData(hDevice); (*(PSERVICE_CALLBACK_ROUTINE)(ULONG_PTR) devExt->UpperConnectData.ClassService)( devExt->UpperConnectData.ClassDeviceObject, InputDataStart, InputDataEnd, InputDataConsumed);} |
そこに以下のような実装を加えます。
… devExt = FilterGetData(hDevice); InputData = InputDataStart; while (InputData != InputDataEnd) { if (0 == (InputData->Flags & KEY_BREAK)) { // キーボードの入力信号である Make コードから入力キーを判断する switch (InputData->MakeCode) { case 0x03: // 2 キーが押されたらそれを 3 キーが押されたことにする InputData->MakeCode = 0x04; break; default: break; } } ++InputData; } (*(PSERVICE_CALLBACK_ROUTINE)(ULONG_PTR) devExt->UpperConnectData.ClassService)( … |
これは、InputDataStart から InputDataEnd までに 2 キーの入力がないかを調べて、もしあればそれを 3 キーの入力に変えています。どのキーが押されたかというのは InputData->MakeCode にスキャンコードが格納されますので、それを変更することで実現しています。
Key scan codes
<https://msdn.microsoft.com/en-us/library/aa299374(v=vs.60).aspx>
たったこれだけの実装でキーの変換フィルタを実装できました。次はキー入力を無かったことにしてみましょう。
… devExt = FilterGetData(hDevice); InputData = InputDataStart; while (InputData != InputDataEnd) { if (0 == (InputData->Flags & KEY_BREAK)) { switch (InputData->MakeCode) { case 0x06: // 5 キーが押されていたらその入力を破棄するフラグを立てる bCancel = TRUE; break; default: break; } } ++InputData; } // 破棄フラグが立っていた場合、上位のドライバに渡さずキー入力を消費してしまう if (bCancel) { *InputDataConsumed = InputDataEnd - InputDataStart; return; } (*(PSERVICE_CALLBACK_ROUTINE)(ULONG_PTR)devExt->UpperConnectData.ClassService)( … |
こちらも簡単ですね。5 キーが押されていた場合に、上位ドライバにキー入力を伝えないようにしたため、ユーザーモードプログラムからみると入力がなかったように見えます。最後に、入力が 2 回あったことにしてみましょう。
… devExt = FilterGetData(hDevice); InputData = InputDataStart; while (InputData != InputDataEnd) { if (0 == (InputData->Flags & KEY_BREAK)) { switch (InputData->MakeCode) { case 0x07: // 6 キーが押されていたら もう一回 6 キーが押されていたことに bInsert = TRUE; break; default: break; } } ++InputData; } if (bInsert) { (*(PSERVICE_CALLBACK_ROUTINE)(ULONG_PTR)devExt->UpperConnectData.ClassService)( devExt->UpperConnectData.ClassDeviceObject, InputDataStart, InputDataEnd, InputDataConsumed); } (*(PSERVICE_CALLBACK_ROUTINE)(ULONG_PTR)devExt->UpperConnectData.ClassService)( … |
こちらは先ほどとは逆に、上位ドライバに同じキー入力を 2 回伝えることで、あたかも 2 回押されているかのように見せることができているわけです。
上記のような実装を加えた後に、展開したフォルダに格納されております "description.html" に記載されている内容に従ってビルド、インストールを行っていただければ、すぐにでもキーボードフィルタの威力を実感できるかと思います。2 キーの入力を 3 キーにするだけでも、2 キーをパスワードに使用している人にとっては致命的だったりしますので、くれぐれもインストールするときは慎重に行ってくださいね!なお、上記サンプルコードは、弊社にてその動作を保証するものではございませんので、ご使用の際には、貴社システムに合わせて修正して頂き、十分なテストを実施していただけますようお願いいたします。
今回の記事は以上となります。今回はキーボードフィルタドライバにつきまして、実装ベースでお話をさせていただきましたが、Windows OS としてどのような仕組でフィルタドライバが動作しているかなど、より深い話につきましては、またの機会にご紹介させていただければと思います。
また来月も皆様にお会いできるといいですね!ではまたお会いいたしましょう!