最も単純なWDMドライバー

この記事では、押されたキーのスキャンコードを表示する簡単なドライバーを作成するプロセスについて説明します。

この記事では、ドライバーを作成するための職場を設定するプロセスについても説明します。

興味があるなら、猫の下でお願いします。





スタンド準備



シンプルなドライバーを作成するために必要なソフトウェアをインストールする


必要なソフトウェア:

  1. Windows DDK(ドライバー開発キット);
  2. VMware WorkstationまたはVirtual Box。
  3. Windows XP
  4. Visual Studio 2005;
  5. DDKWizard;
  6. KmdManager
  7. 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スクリプトもダウンロードします。

ウィザードをインストールしたら、次の手順を実行する必要があります。



ドライバーを実行するマシンはすべて準備ができています。



ドライバーを実行するために必要なソフトウェアをインストールする


次に、記述されたドライバーを実行するマシンを構成します。

次のプログラムが必要になります。



すべて、車はドライバーを走らせる準備ができています。



問題の声明



タスク:押されたキーのスキャンコードとデバッグするそれらの組み合わせを出力するドライバーを作成します。



理論のビット


ドライバーは、デバイスまたはユーザーモードから特定のイベントが発生したときにオペレーティングシステムによって呼び出される一連の関数です。

ドライバーには多くの種類があり、その一部を以下にリストします。



クラスドライバーは、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);
      
      





パラメーターをさらに詳しく分析してみましょう。



次に、デバイスフラグを設定します。

 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); //   KeWaitForSingleObject(&ktTimer, Executive, KernelMode, FALSE, 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); //DbgPrint("After IoAttachDevice"); RtlFreeUnicodeString(&ustrDeviceName); return status; } VOID DriverUnload(IN PDRIVER_OBJECT theDO) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)theDO->DeviceObject->DeviceExtension; IoDetachDevice(pdx->pLowerDO); IoDeleteDevice(theDO->DeviceObject); if (gnRequests != 0) { KTIMER ktTimer; LARGE_INTEGER liTimeout; liTimeout.QuadPart = 1000000; KeInitializeTimer(&ktTimer); while(gnRequests > 0) { KeSetTimer(&ktTimer, liTimeout, NULL); KeWaitForSingleObject(&ktTimer, Executive, KernelMode, FALSE, NULL); } } } NTSTATUS ReadCompletionRoutine(IN PDEVICE_OBJECT pDeviceObject, IN PIRP theIrp, IN PVOID Context) { PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension; PKEYBOARD_INPUT_DATA kidData; if (NT_SUCCESS(theIrp->IoStatus.Status)) { 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); } } if(theIrp->PendingReturned) IoMarkIrpPending(theIrp); __asm{ lock dec gnRequests } return theIrp->IoStatus.Status; } NTSTATUS DispatchRead(IN PDEVICE_OBJECT pDeviceObject, IN PIRP theIrp) { __asm{ lock inc gnRequests } IoCopyCurrentIrpStackLocationToNext(theIrp); IoSetCompletionRoutine(theIrp, ReadCompletionRoutine, pDeviceObject, TRUE, TRUE, TRUE); return IoCallDriver(((PDEVICE_EXTENSION) pDeviceObject->DeviceExtension)->pLowerDO ,theIrp); } extern "C" NTSTATUS DriverEntry(IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING RegistryPath) { NTSTATUS status = {0}; gnRequests = 0; for (int i = 0; i<IRP_MJ_MAXIMUM_FUNCTION; ++i) { theDriverObject->MajorFunction[i] = DispatchThru; } theDriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead; status = InstallFilter(theDriverObject); theDriverObject->DriverUnload = DriverUnload; return status; }
      
      







メイクファイル:

 # # 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



All Articles