Windowsドライバ(っというか、カーネルモードアプリケーション?)を開発してみる……ただし実用には問題あり(1)

にちょっと実験をしていて、ドライバを作りたくなったのでやってみたテスト(・∀・)


ちなみに、最初に言っておくと、僕チンの日常的な作業環境はVista x64(・ω・)
っということで、署名の問題で実用的なモノを作るのは諦めてしまったのだけど、せっかくなのでWindowsドライバ開発のさわりについて書いておきたいと思います。
そっち方面の本職ではないので、おかしな所があったら許してね(´Д`;)。*1


まずは環境構築について。
今回入れたWDK(Windows Driver Kit)は、Microsoft Connectからダウンロードした6001.18002版(6.1.6001.18002.081017-1400_wdksp-WDK18002SP_EN_DVD)。
こいつをインスコすると、スタートメニューに[Windows Driver Kits]が追加されます。
っで、ビルド環境の開き方は、スタートメニューから[Windows Driver Kits]-[WDK 6001.18002]-[Build Environments]と開いていき、僕チンが作りたいのはVista x64用のドライバなので、更に[Windows Vista and Windows Server 2008]を開いて、[Windows Vista and Windows Server 2008 x64 Free Build Environment]を実行します。
そうするとドライバ開発に必要な環境が設定されたコマンドプロンプトが開くので、ソースのあるフォルダに移動して、そこで「build」を実行するとドライバをビルドすることが出来ます(・∀・)


っということで、次は実験で作ってみたドライバのソースコードが以下(・∀・)
ソースとして用意したのは、TestDriver.c、makefile、sourcesの3つのファイル。

!INCLUDE $(NTMAKEENV)\makefile.def
  • sources
TARGETNAME = TestDriver
TARGETTYPE = DRIVER
TARGETPATH = obj

INCLUDES   = %BUILD%\inc
LIBS       = %BUILD%\lib

SOURCES    = TestDriver.c
  • TestDriver.c
#include <ntddk.h>

#define IOCTL_TESTDRIVER_HOGE                \
        CTL_CODE(FILE_DEVICE_UNKNOWN,        \
                 0x802,                      \
                 METHOD_BUFFERED,            \
                 FILE_READ_DATA | FILE_WRITE_DATA)

#define DEVICE_NAME_NT L"\\Device\\TestDriver"
#define DEVICE_NAME_DOS L"\\DosDevices\\TestDriver"

// ドライバーアンロード
void UnLoadDriver(PDRIVER_OBJECT pDriverObject)
{
    UNICODE_STRING usDosDeviceName;
    RtlInitUnicodeString( &usDosDeviceName, DEVICE_NAME_DOS );

    IoDeleteSymbolicLink( &usDosDeviceName );
    IoDeleteDevice( pDriverObject->DeviceObject );
}

// リクエスト処理
NTSTATUS ProcessRequest(PDEVICE_OBJECT pDeviceObject, PIRP pIrp)
{
    NTSTATUS status = STATUS_SUCCESS;
    ULONG_PTR nInformation = 0;

    // スタックロケーション取得
    PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation( pIrp );
    
    switch( pStack->MajorFunction )
    {
    case IRP_MJ_CREATE:
        break;
    case IRP_MJ_CLOSE:
        break;
    case IRP_MJ_DEVICE_CONTROL:
        {
            // DeviceIoControl( ..., IOCTL_TESTDRIVER_HOGE, ... )の処理
            if( pStack->Parameters.DeviceIoControl.IoControlCode == IOCTL_TESTDRIVER_HOGE )
            {
                nInformation = 1;
            }
            else
            {
                status = STATUS_NOT_IMPLEMENTED;
            }
        }
        break;
    default:
        status = STATUS_NOT_IMPLEMENTED;
    }

    pIrp->IoStatus.Status = status;
    pIrp->IoStatus.Information = nInformation;

    // IRP完了通知
    IoCompleteRequest( pIrp, IO_NO_INCREMENT );

    return status;
}

// ドライバーエントリポイント
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath)
{
    NTSTATUS status = STATUS_SUCCESS;
    PDEVICE_OBJECT pDeviceObject = NULL;
    UNICODE_STRING usDriverName;
    UNICODE_STRING usDosDeviceName;
    RtlInitUnicodeString( &usDriverName, DEVICE_NAME_NT );
    RtlInitUnicodeString( &usDosDeviceName, DEVICE_NAME_DOS );

    // デバイス登録
    status = IoCreateDevice( pDriverObject, 0, &usDriverName,
                             FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObject );
    if( !NT_SUCCESS(status) )
    {
        return status;
    }

    status = IoCreateSymbolicLink( &usDosDeviceName, &usDriverName );
    if( !NT_SUCCESS(status) )
    {
        IoDeleteDevice( pDeviceObject );
        return status;
    }

    pDriverObject->MajorFunction[ IRP_MJ_CREATE ]         = ProcessRequest;
    pDriverObject->MajorFunction[ IRP_MJ_CLOSE ]          = ProcessRequest;
    pDriverObject->MajorFunction[ IRP_MJ_DEVICE_CONTROL ] = ProcessRequest;
    pDriverObject->DriverUnload                           = UnLoadDriver;

    return STATUS_SUCCESS;
}

ソースについて、ざっくりと解説してみます(・∀・)


最初のIOCTL_TESTDRIVER_HOGEは、DeviceIoControl()で使うコード。
ユーザモードのアプリからは、DeviceIoControl()を使って、このコードに対応する処理をドライバに依頼します(・ω・)ノ
で、DEVICE_NAME_NTとDEVICE_NAME_DOSは登録するドライバのデバイス名になります。


3つの関数は、DriverEntry()がドライバのエントリポイント、UnLoadDriver()がドライバがアンロードされる時のハンドラ、ProcessRequest()がドライバへのリクエストを処理するハンドラです。


DriverEntry()ではドライバの登録と、コールバック関数の設定を行います。
今回は、オープン、クローズ、I/Oコントロールの全てのハンドラに同一のものを使用して、その中で処理をswitchしています(・ω・)


UnLoadDriver()はドライバがアンロードされる時のハンドラで、登録されたドライバの情報を削除する処理になっています。


っで、ProcessRequest()がメインとなるハンドラの実装です(`・ω・´)
一般的にOSのドライバっていうのは、OSから呼び出されるOpen/Close/Read/Write/IOControlの各ハンドラを実装するものになるわけですが。*2
っで、今回はユーザモードアプリケーションからドライバへの処理依頼をDeviceIoControl()を使うことにして、IRP_MJ_DEVICE_CONTROLに対する処理のみを実装し、Open/Close時の処理はなにもしない作りになっています。
ドライバに対してDeviceIoControl()が呼び出された時に通知されるのはIRP_MJ_DEVICE_CONTROLで、DeviceIoControl()で指定されたコントロールコードに応じて処理を行います。
このソースではIRP.IoStatus.Informationに1を設定しているだけですが、この値はDeviceIoControl()の第7引数として返されます。
ちなみに、ドライバに対してIn/Outのパラメータを使用する場合は、DeviceIoControl()の第3〜6引数(ポインタ及びサイズ*In/Out)で指定します。
また、ドライバ側ではその内容はIRP構造体のメンバとして参照できますが、今回は使用していません。


…っというのが今回作ってみたドライバの概要です(・∀・)
っで、WDKの環境を開き、ソースフォルダの場所へ移動してbuildを実行すると、ソースフォルダの下のobjfre_wlh_amd64/amd64フォルダにTestDriver.sysが作成されますたヽ( ・∀・)ノ


っで、次はこのドライバの使い方についてですが、それは明日。

*1:ドライバモデルとかも良く知らないし(´Д`;)

*2:ここでやりたいのは実デバイスの操作とかでは無いので、割り込みなんかの話は省略(´д`)