この記事では、押されたキーのスキャンコードを表示する簡単なドライバーを作成するプロセスについて説明します。
この記事では、ドライバーを作成するための職場を設定するプロセスについても説明します。
興味があるなら、猫の下でお願いします。
スタンド準備
シンプルなドライバーを作成するために必要なソフトウェアをインストールする
必要なソフトウェア:
- Windows DDK(ドライバー開発キット);
- VMware WorkstationまたはVirtual Box。
- Windows XP
- Visual Studio 2005;
- DDKWizard;
- KmdManager
- DebugView;
2台の仮想マシンを使用し、1台でドライバーを作成し、もう1台で実行します。 また、そうすることにした場合、ドライバーを実行するマシンには、4 GBのハードディスクと256 MBのRAMで十分です。
職場のセットアップ
DDKインストール
インストールは非常に簡単です。 注意する必要があるのは、インストールするコンポーネントを選択するように求められるダイアログだけです。 すべてのドキュメントと例に注意することを強くお勧めします。
Microsoft®Visual Studio 2005をインストールして構成する
Microsoft®Visual Studio 2005のインストールは、DDKのインストールと同じくらい簡単です。 ドライバーの作成のみに使用する場合、インストーラーがインストールするコンポーネントを尋ねるときに、Visual C ++のみを選択します。
次に、Visual Assist Xをインストールできます。このプログラム(アドオン)を使用すると、ドライバーを簡単に作成するためのヒントを簡単に構成できます。
Visual Assist XをVisual Studio 2005にインストールすると、新しいVAssistXメニューが表示されます。 さらにこのメニューで:
Visual Assist X Options -> Projects -> C/C++ Directories -> Platform: Custom, Show Directories for: Stable include files
。
Ins
クリックするか、Windows XPに
%WXPBASE%\inc\ddk\wxp
入力する場合は、表示される行にアイコンと新しいディレクトリを追加します。
DDKWizardをインストールして構成する
Visual Studioでドライバーをコンパイルするには、DDKWizardをインストールする必要があります。
ddkwizard.assarbad.netからダウンロードできます。 このサイトからddkbuild.cmdスクリプトもダウンロードします。
ウィザードをインストールしたら、次の手順を実行する必要があります。
- 次の名前とDDKへのパスに一致する値を持つシステム(推奨)またはユーザー変数を作成します
DDKバージョン
| 変数名
| デフォルトパス
|
---|
Windows XP DDK
| Wxpbase
| C:\ WINDDK \ 2600
|
Windows 2003 Server DDK
| Wnetbase
| C:\ WINDDK \ 3790.1830
|
Windows Vista / Windows 2008 Server WDK
| Wlhbase
| |
Windows 7 / Windows 2008 Server R2 WDK
| W7base
| |
たとえば、Windows XP DDKを使用する場合、DDKへのパスに一致する値を持つWXPBASE変数を作成する必要があります。 インストールパスを変更しなかったため、値はC:\ WINDDK \ 2600になります。
- ダウンロードしたddkbuild.cmdスクリプトを、たとえばDDKのあるフォルダーにコピーします。 このC:\ WINDDK \があります。
- Pathシステム変数の最後にddkbuild.cmdスクリプトへのパスを追加します。
ドライバーを実行するマシンはすべて準備ができています。
ドライバーを実行するために必要なソフトウェアをインストールする
次に、記述されたドライバーを実行するマシンを構成します。
次のプログラムが必要になります。
- DebugView( link )は、ユーザーモードとカーネルモードの両方のデバッグ出力を表示できるユーティリティです。
- KmdManager( link )-ドライバーを動的にロード/アンロードするためのユーティリティ
すべて、車はドライバーを走らせる準備ができています。
問題の声明
タスク:押されたキーのスキャンコードとデバッグするそれらの組み合わせを出力するドライバーを作成します。
理論のビット
ドライバーは、デバイスまたはユーザーモードから特定のイベントが発生したときにオペレーティングシステムによって呼び出される一連の関数です。
ドライバーには多くの種類があり、その一部を以下にリストします。
- クラスドライバー
- ミニドライバー;
- 機能的ドライバー;
- ドライバーのフィルタリング。
クラスドライバーは、Microsoftが作成するドライバーです。 これらは、特定のクラスの(本当に!)デバイス用の汎用ドライバーです。
ミニドライバーは、クラスドライバーを使用してデバイスを制御するドライバーです。
機能ドライバーは、独立して動作し、デバイスに関連するすべてを決定するドライバーです。
フィルタリングドライバーは、送信されるデータを変更することにより、別のドライバーのロジックを監視または変更するために使用されるドライバーです。
ドライバーで考えられるすべての機能を定義する必要はありませんが、
DriverEntry
と
AddDevice
が含まれている必要があります。
IRP
は、ドライバーがデータを交換するために使用する構造です。
そのため、スキャンコードを出力するために(
これは何ですか? )デバッグするには、フィルタードライバーを使用します。
フィルタリングドライバには2つのタイプがあります。
- トップフィルタリングドライバー。
- ボトムフィルタードライバー。
使用するドライバーの種類は、ドライバーがデバイスドライバースタックのどこにあるかによって異なります。 ドライバーが機能ドライバーよりも上にある場合は、上位フィルタードライバーと呼ばれ、下位の場合は下位フィルタードライバーと呼ばれます。
上位と下位のフィルタリングドライバーの違い
すべての要求は、上位のフィルタリングドライバーを通過します。つまり、機能ドライバー、そして場合によってはデバイスに送られる情報を変更および/またはフィルターできます。
トップフィルタリングドライバーの使用例:
IpFilterDirverシステムドライバーのフック関数を設定し、トラフィックを追跡およびフィルター処理するためのフィルターフックドライバー。 このようなドライバーはファイアウォールで使用されます。
ほとんどの要求が機能ドライバーを実行して完了するため、より少ない要求はより低いフィルタードライバーを通過します。
同期の問題
作成するドライバーには、いくつかの「問題」セクションがあります。 ドライバーには、アセンブリインサートを使用するだけで十分です。
__asm { lock dec «, » }
または
__asm { lock inc «, » }
lock
プレフィックスを使用すると、それに続くコマンドを安全に実行できます。 コマンドの実行中は、残りのプロセッサをブロックします。
アクション
まず、ヘッダーファイル「ntddk.h」、「ntddkbd.h」を含める必要があります
extern "C" { #include "ntddk.h" } #include "ntddkbd.h"
DEVICE_EXTENSION
の構造を記述することも必要
DEVICE_EXTENSION
typedef struct _DEVICE_EXTENSION{ PDEVICE_OBJECT pLowerDO; } DEVICE_EXTENSION, *PDEVICE_EXTENSION;
pLowerDO
オブジェクトは、スタック上で下にあるデバイスオブジェクトです。 IRPパケットの送信先を知るために必要です。
ドライバーの操作には、不完全なリクエストの数が格納される変数が必要です。
int gnRequests;
ドライバーのメインエントリポイントである関数から始めましょう。
extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING ustrRegistryPath)
theDriverObject
ドライバーオブジェクトには、初期化する必要があるオペレーティングシステムに必要なすべての関数へのポインターが含まれています。
ustrRegistryPath
このドライバーに関する情報が保存されているレジストリ内のセクションの名前。
まず、変数を宣言して無効にする必要があります。
gnRequests = 0; NTSTATUS status = {0};
次に、上で書いたように、関数ポインタを初期化する必要があります
for (int i = 0; i<IRP_MJ_MAXIMUM_FUNCTION; ++i) { theDriverObject->MajorFunction[i] = DispatchThru; } theDriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead; theDriverObject->DriverUnload = DriverUnload;
DispatchRead
関数は、読み取り要求を処理します。 キーボードのキーが押されるか離されると呼び出されます。
DriverUnload
関数
DriverUnload
、ドライバーが不要になり、メモリからアンロードできる場合、またはユーザーがドライバーをアンロードする場合に呼び出されます。 この関数では、「ストリッピング」を実行する必要があります。 ドライバーによって使用されていたリソースが解放され、すべての不完全な要求が完了します。
DispatchThru
関数はスタブ関数です。 彼女が行うことは、IRPパケットを次のドライバー(スタック内の私たちのドライバー、つまり
pLowerDO
DEVICE_EXTENSION
)に
DEVICE_EXTENSION
です。
次に、関数を呼び出してデバイスを作成し、デバイススタックにインストールします。
status = InstallFilter(theDriverObject);
この機能について以下に説明します。
status
を返します
STATUS_SUCCESS
関数
InstallFilter
と、値
STATUS_SUCCESS
が格納されます。
InstallFilter
関数に渡します。 彼女のプロトタイプは次のとおりです。
NTSTATUS InstallFilter(IN PDRIVER_OBJECT theDO);
この関数は、デバイスオブジェクトを作成して構成し、
\\Device\\KeyboardClass0
介してデバイススタックにプッシュします。
変数を宣言します:
PDEVICE_OBJECT pKeyboardDevice; NTSTATUS status = {0};
pKeyboardDevice
は、作成する必要があるデバイスオブジェクトです。
IoCreateDevice
を呼び出して新しいデバイスを作成します
status = IoCreateDevice(theDO, sizeof(DEVICE_EXTENSION), NULL, FILE_DEVICE_KEYBOARD, 0, FALSE, &pKeyboardDevice);
パラメーターをさらに詳しく分析してみましょう。
- 最初の引数は、InstallFilter関数のパラメーターとして受け取ったドライバーオブジェクトです。 ドライバーと新しいデバイス間の通信を確立するために、IoCreateDeviceに渡されます。
- 3番目のパラメーターはデバイス名です
- 4番目のパラメーターはデバイスのタイプです
- 5番目のパラメーターは、大容量記憶装置に通常設定されるフラグです。
- 6番目のパラメーターは、デバイスマニピュレーターを複数開くことができるかどうかを示します。 FALSEの場合、開くことができるマニピュレータは1つだけです。 それ以外の場合は、任意の数のマニピュレーターを開くことができます。
- 7番目のパラメーターは、作成されたデバイスオブジェクトが保存されるメモリです。
次に、デバイスフラグを設定します。
pKeyboardDevice->Flags = pKeyboardDevice->Flags | (DO_BUFFERED_IO | DO_POWER_PAGABLE); pKeyboardDevice->Flags = pKeyboardDevice->Flags & ~DO_DEVICE_INITIALIZING;
デバイスに設定するフラグは、スタック上にプッシュされるデバイスのフラグと同等である必要があります。
次に、スタックに配置するデバイスの名前の変換を実行する必要があります。
CCHAR cName[40] = "\\Device\\KeyboardClass0"; STRING strName; UNICODE_STRING ustrDeviceName; RtlInitAnsiString(&strName, cName); RtlAnsiStringToUnicodeString(&ustrDeviceName, &strName, TRUE);
IoAttachDevice
関数は、デバイスをスタックに
IoAttachDevice
します。
pdx->pLowerDO
、次の(下の)デバイスのオブジェクトが保存されます。
IoAttachDevice(pKeyboardDevice, &ustrDeviceName, &pdx->pLowerDO);
リソースの解放:
RtlFreeUnicodeString(&ustrDeviceName);
次に、プロトタイプを使用して
DispatchRead
関数を分析します。
NTSTATUS DispatchRead(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp);
この関数は、キーボードのキーを押すか離すと、オペレーティングシステムによって呼び出されます
不完全なリクエストのカウンターを増やします
__asm { lock inc gnRequests }
次のドライバーに要求を渡す前に、ドライバーのスタックポインターを構成する必要があります。
IoCopyCurrentIrpStackLocationToNext
は、現在のドライバーに属するメモリの部分を次のドライバーのメモリ領域にコピーします。
IoCopyCurrentIrpStackLocationToNext(theIrp);
リクエストがスタックを下るとき、必要なデータがまだないため、リクエストが必要なデータでスタックを上るときに呼び出される関数を設定する必要があります。
IoSetCompletionRoutine(theIrp, ReadCompletionRoutine, pDeviceObject, TRUE, TRUE, TRUE)
ここで、
ReadCompletionRoutine
関数です。
IRP
次のドライバーに渡します。
return IoCallDriver(((PDEVICE_EXTENSION) pDeviceObject->DeviceExtension)->pLowerDO ,theIrp);
次に、
IRP
完了するたびに呼び出される関数を分析します。 プロトタイプ:
NTSTATUS ReadCompletionRoutine(IN PDEVICE_OBJECT pDeviceObject, IN PIRP theIrp, IN PVOID Context);
DEVICE_EXTENSION
:
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension;
PKEYBOARD_INPUT_DATA
構造
PKEYBOARD_INPUT_DATA
、押されたキーを記述するために使用されます。
PKEYBOARD_INPUT_DATA kidData;
リクエストが正常に完了したかどうかを確認します
if (NT_SUCCESS(theIrp->IoStatus.Status))
KEYBOARD_INPUT_DATA
構造を取得するには、
IRP
パケットのシステムバッファーにアクセスする必要があります。
kidData = (PKEYBOARD_INPUT_DATA)theIrp->AssociatedIrp.SystemBuffer;
キーの数を調べる
int n = theIrp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA);
そして各キーを印刷します:
for(int i = 0; i<n; ++i) DbgPrint("Code: %x\n", kidData[i].MakeCode);
そして、未処理のリクエストの数を減らすことを忘れないでください
__asm { lock dec gnRequests }
リクエストのステータスを返します
return theIrp->IoStatus.Status;
シャットダウン機能を分析しましょう。 プロトタイプ:
VOID DriverUnload(IN PDRIVER_OBJECT theDO);
DEVICE_EXTENSION
:
PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)theDO->DeviceObject->DeviceExtension;
スタックからデバイスを削除します。
IoDetachDevice(pdx->pLowerDO);
デバイスを削除します。
IoDeleteDevice(theDO->DeviceObject);
保留中の要求があるかどうかを確認します。 このチェックなしでドライバーをアンロードすると、アンロード後にキーを最初に押すとBSODが発生します。
if (gnRequests != 0) { KTIMER ktTimer; LARGE_INTEGER liTimeout; liTimeout.QuadPart = 1000000; KeInitializeTimer(&ktTimer); , while(gnRequests > 0) { KeSetTimer(&ktTimer, liTimeout, NULL);
ドライバーコード:
extern "C" { #include "ntddk.h" } #include "ntddkbd.h" typedef struct _DEVICE_EXTENSION{ PDEVICE_OBJECT pLowerDO; } DEVICE_EXTENSION, *PDEVICE_EXTENSION; int gnRequests; NTSTATUS DispatchThru(PDEVICE_OBJECT theDeviceObject, PIRP theIrp) { IoSkipCurrentIrpStackLocation(theIrp); return IoCallDriver(((PDEVICE_EXTENSION) theDeviceObject->DeviceExtension)->pLowerDO ,theIrp); } NTSTATUS InstallFilter(IN PDRIVER_OBJECT theDO) { PDEVICE_OBJECT pKeyboardDevice; NTSTATUS status = {0}; status = IoCreateDevice(theDO, sizeof(DEVICE_EXTENSION), NULL, FILE_DEVICE_KEYBOARD, 0, FALSE, &pKeyboardDevice); if (!NT_SUCCESS(status)) { DbgPrint("IoCreateDevice error.."); return status; } pKeyboardDevice->Flags = pKeyboardDevice->Flags | (DO_BUFFERED_IO | DO_POWER_PAGABLE); pKeyboardDevice->Flags = pKeyboardDevice->Flags & ~DO_DEVICE_INITIALIZING; RtlZeroMemory(pKeyboardDevice->DeviceExtension, sizeof(DEVICE_EXTENSION)); PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)pKeyboardDevice->DeviceExtension; CCHAR cName[40] = "\\Device\\KeyboardClass0"; STRING strName; UNICODE_STRING ustrDeviceName; RtlInitAnsiString(&strName, cName); RtlAnsiStringToUnicodeString(&ustrDeviceName, &strName, TRUE); IoAttachDevice(pKeyboardDevice, &ustrDeviceName, &pdx->pLowerDO);
メイクファイル:
# # DO NOT EDIT THIS FILE!!! Edit .\sources. if you want to add a new source # file to this component. This file merely indirects to the real make file # that is shared by all the driver components of the Windows NT DDK # !INCLUDE $(NTMAKEENV)\makefile.def
ソース:
TARGETNAME=sysfile TARGETPATH=BIN TARGETTYPE=DRIVER SOURCES = DriverMain.cpp
ドライバーを起動してデバッグ情報を表示する方法
ドライバーを実行するには、KmdManagerユーティリティを使用しました。 デバッグ情報を表示するには、DbgViewユーティリティを使用しました。
PSかなり前に、3年目に記事を書きましたが、今はほとんど何も覚えていません。 しかし、質問があれば、私は答えようとします。
PPSコメント、特に
これに注意してください
UPD: GitHubのプロジェクト:
https :
//github.com/pbespechnyi/simple-wdm-driver