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

昨日作ったドライバを、使用する側のプログラムを作ってみます(・∀・)
概略としては、Service Control Manager(SCM)でドライバを開始してデバイスをオープン、DeviceIoControl()を呼び出して作成したドライバが意図した応答を返すかどうかを確認するものになります。


ドライバを使う側のアプリケーションは、Visual Studioを使って普通にC++のアプリケーションとして作成します。


まずはincludeや定数について、下記のような準備をしておきます。

#include <winsvc.h>
#include <winioctl.h>

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

#define DRIVER_DOS_NAME _T("\\\\.\\TestDriver")
#define DRIVER_NAME _T("TestDriver")
#define DRIVER_FILE_NAME _T("TestDriver.sys")

winsvc.hはSCMのためのもの、winioctl.hはDeviceIoControl()やマクロのためのものです(・ω・)
IOCTL_TESTDRIVER_HOGEの定義については、ドライバ側と同一です。


次にドライバの開始処理ですが、こんな感じになります(・∀・)
なお、引数はドライバファイル(TestDriver.sys)のパスで、戻り値はオープンしたハンドルになります。

// ドライバ開始
HANDLE StartDriver(LPCTSTR szPath)
{
    SC_HANDLE hManager = ::OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
    SC_HANDLE hService = ::OpenService( hManager, DRIVER_NAME, SERVICE_ALL_ACCESS );

    if( hService == NULL )
    {
        hService = ::CreateService( hManager, DRIVER_NAME, DRIVER_NAME,
                                    SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER,
                                    SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
                                    szPath, NULL, NULL, NULL, NULL, NULL );
        if( hService == NULL )
        {
            ::CloseServiceHandle( hManager );
            return NULL;
        }
    }

    if( ::StartService( hService, 0, NULL ) == FALSE )
    {
        DWORD dwError = ::GetLastError();
        if( dwError != ERROR_SERVICE_ALREADY_RUNNING )
        {
            ::DeleteService( hService );
            ::CloseServiceHandle( hService );
            ::CloseServiceHandle( hManager );
            return NULL;
        }
        
    }

    HANDLE hFile = ::CreateFile( DRIVER_DOS_NAME, GENERIC_READ|GENERIC_WRITE, 
                                 FILE_SHARE_READ|FILE_SHARE_WRITE, 0,
                                 OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0 );
    if( hFile == INVALID_HANDLE_VALUE )
    {
        SERVICE_STATUS status = { 0 };
        ::ControlService( hService, SERVICE_CONTROL_STOP, &status );
        hFile = NULL;
    }    

    ::CloseServiceHandle( hService );
    ::CloseServiceHandle( hManager );

    return hFile;
}

まずはOpenSCManager()でSCMを開き、次にOpenService()でドライバをオープンします。
ドライバが存在しない場合はCreateService()で作成し、StartService()で開始、CreateFile()で作成したドライバのハンドルを返す作りです。


っで、停止処理はこんな感じになります。

// ドライバ停止
BOOL StopDriver(HANDLE hDriver)
{
    ::CloseHandle( hDriver );

    SC_HANDLE hManager = ::OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
    SC_HANDLE hService = ::OpenService( hManager , DRIVER_NAME, SERVICE_ALL_ACCESS );

    SERVICE_STATUS status = { 0 };
    ::ControlService( hService, SERVICE_CONTROL_STOP, &status );

    BOOL bRet = ::DeleteService( hService );

    ::CloseServiceHandle( hService );
    ::CloseServiceHandle( hManager );

    return bRet;
}

開始と同じくSCMをオープンして、ドライバを停止、DeleteService()で削除します。


これでドライバの開始/停止は準備できたので、メインとなるDeviceIoControl()の呼び出しを書いてみます。
こんな感じですね(・∀・)

    // szPathにはTestDriver.sysのパスを指定
    HANDLE hDriver = StartDriver( szPath );

    ::DWORD dwReturn = 0;
    BOOL bSuccess = ::DeviceIoControl( hDriver, IOCTL_TESTDRIVER_HOGE,
                                       NULL, 0, NULL, 0, &dwReturn, NULL );
    assert( dwReturn == 1 );

    StopDriver( hDriver );

DeviceIoControl()の引数dwReturnに、昨日作ったドライバ内で設定した値が返ってきている事を確認できます。
っというわけで、自作ドライバの動作確認ができましたヽ(´▽`)ノ
これでカーネルモードでの動作もやりたい放題ですね!



………っと簡単にはいかないんだな、コレが(#゚Д゚)
(昨日も書いたけど、僕チンの環境でVista x64での話ね)

これを普通に実行すると、StartService()の所でGetLastError() = 577(ERROR_INVALID_IMAGE_HASH)が返ってきちゃうんですよね(´・ω・`)
なぜなら自作ドライバにはデジタル署名が無いから(・ω・)


その事を失念していたヨ(´・ω・`)
まあ、カーネルモードで好き勝手されたらセキュリティ的に問題があるわけで、仕方がないんですが…( ゚д゚)、


っで、じゃあ上記プログラムを試す方法は無いのかよと言うと、1つ方法があったりして。
俗にF8メソッド(?)で、OS起動時にF8を押して、「ドライバ署名の強制を無効にする」を選択してOSを起動すればOK。
上記プログラムの動作確認もできました(´∀`)


…っというわけなんですが、毎回起動時にF8するのもな〜(´д`;)
この辺が「実用には問題あり」な所。*1


まあ、ちゃんとした署名が欲しけりゃGlobalSignとかを使えって話ですが、趣味のデバドラプログラマにとっては敷居が高いわけで(´・ω・`)
もう少し柔軟になってくれないかな〜、とは思ってみたり(・ω・)

*1:まあ、自分はOSの再起動は滅多にしないんですが。1ヶ月間再起動無しとかいう時もあるし。Explore.exeあたりがメモリやリソースを喰ってしまって、たまにログインしなおすとかいう事はあったりするけど(´д`;)