應用程序與驅動程序通訊 DeviceIoControl

以前寫過一篇關於經過DeviceIoControl函數來使應用程序與驅動程序通訊的博客,此次再經過這個完整的代碼來簡要疏通總結一下。編程

   這種通訊方式,就是驅動程序和應用程序自定義一種IO控制碼,而後調用DeviceIoControl函數IO管理器產生一個MajorFunction 爲IRP_MJ_DEVICE_CONTROL(DeviceIoControl函數會產生此IRP),MinorFunction 爲本身定義的控制碼的IRP,系統就調用相應的處理IRP_MJ_DEVICE_CONTROL的派遣函數,你在派遣函數中判斷MinorFunction ,是自定義的控制碼你就進行相應的處理windows

 

  一.先談一下這個定義IO控制碼 ,其實能夠看做是一種通訊協議。安全

       看看CTL_CODE原型:app

  #define CTL_CODE( DeviceType, Function, Method, Access ) ( \
  ((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method) \
  )異步

   能夠看到,這個宏四個參數,天然是一個32位分紅了4部分,高16位存儲設備類型,14~15位訪問權限,2~13位操做功能,最後0,1兩位就是肯定緩衝區是如何與I/O和文件系統數據緩衝區進行數據傳遞方式,最多見的就是METHOD_BUFFERED。函數

 

       自定義CTL_CODE:spa

  #define IOCTL_Device_Function CTL_CODE(DeviceType, Function, Method, Access).net

  IOCTL_Device_Function:生成的IRP的MinorFunction設計

  DeviceType:設備對象的類型。設備類型可參考:http://blog.csdn.net/liyun123gx/article/details/380589653d

  Function :自定義的IO控制碼。本身定義時取0x800到0xFFF,由於0x0到0x7FF是微軟保留的。

  Method :數據的操做模式。

              METHOD_BUFFERED:緩衝區模式

              METHOD_IN_DIRECT:直接寫模式

              METHOD_OUT_DIRECT:直接讀模式

              METHOD_NEITHER :Neither模式

Access:訪問權限,可取值有:

            FILE_ANY_ACCESS:代表用戶擁有全部的權限

            FILE_READ_DATA:代表權限爲只讀

            FILE_WRITE_DATA:代表權限爲可寫

            也能夠 FILE_WRITE_DATA | FILE_READ_DATA:代表權限爲可讀可寫,但還沒達到FILE_ANY_ACCESS的權限。

 

  

  

       繼續介紹這個緩衝區數據傳遞方式Method:

  Method表示Ring3/Ring0的通訊中的內存訪問方式,有四種方式:
  #define METHOD_BUFFERED                0  
  #define METHOD_IN_DIRECT               1  
  #define METHOD_OUT_DIRECT              2  
  #define METHOD_NEITHER                  3  


       (1)若是使用METHOD_BUFFERED,表示系統將用戶的輸入輸出都通過pIrp->AssociatedIrp.SystemBuffer緩衝,所以這種方式的通訊比較安全

  METHOD_BUFFERED方式至關於對Ring3的輸入輸出都進行了緩衝

       METHOD_BUFFERED方式(借圖):

       

 


  (2)若是使用METHOD_IN_DIRECTMETHOD_OUT_DIRECT方式,表示系統會將輸入緩衝在pIrp->AssociatedIrp.SystemBuffer中,並將輸出緩衝區鎖定,而後在內核模式下從新映射一段地址,這樣也是比較安全的。

  METHOD_IN_DIRECT和METHOD_OUT_DIRECT可稱爲"直接方式",是指系統依然對Ring3的輸入緩衝區進行緩衝,可是對Ring3的輸出緩衝區並無緩衝,而是在內核中進行了鎖定。這樣Ring3輸出緩衝區在驅動程序完成I/O請求以前,都是沒法訪問的,從必定程度上保障了安全性。如圖21.1.14所示。
這兩種方式,對於Ring3的輸入緩衝區和METHOD_BUFFERED方式是一致的。對於Ring3的輸出緩衝區,首先由系統鎖定,並使用pIrp->MdlAddress來描述這段內存,驅動程序須要使用MmGetSystemAddressForMdlSafe函數將這段內存映射到內核內存地址(OutputBuffer),而後能夠直接寫入OutputBuffer地址,最終在驅動派遣例程返回後,由系統解除這段內存的鎖定。
 
  METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的內存訪問
  8METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式的區別,僅在於打開設備的權限上,當以只讀權限打開設備時,METHOD_IN_DIRECT方式的IoControl將會成功,而METHOD_OUT_DIRECT方式將會失敗。若是以讀寫權限打開設備,兩種方式都會成功。

  METHOD_IN_DIRECT和METHOD_OUT_DIRECT方式(借圖)

       

 


  (3)若是使用METHOD_NEITHER方式,"其餘方式",雖然通訊的效率提升了,可是不夠安全。驅動的派遣函數中輸入緩衝區能夠經過I/O堆棧(IO_STACK_LOCATION)的stack->Parameters.DeviceIo Control.Type3InputBuffer獲得。輸出緩衝區能夠經過pIrp->UserBuffer獲得。因爲驅動中的派遣函數不能保證傳遞進來的用戶輸入和輸出地址,所以最好不要直接去讀寫這些地址的緩衝區。應該在讀寫前使用ProbeForRead和ProbeForWrite函數探測地址是否可讀和可寫。

  METHOD_ NEITHER方式是不進行緩衝的,在驅動中能夠直接使用Ring3的輸入輸出內存地址

  驅動程序能夠經過pIrpStack->Parameters.DeviceIoControl.Type3InputBuffer獲得Ring3的輸入緩衝區地址(其中pIrpStack是IoGetCurrentIrpStackLocation(pIrp)的返回);經過pIrp-> UserBuffer獲得Ring3的輸出緩衝區地址。
  因爲METHOD_NEITHER方式並不安全,所以最好對Type3InputBuffer讀取以前使用ProbeForRead函數進行探測,對UserBuffer寫入以前使用ProbeForWrite函數進行探測,當沒有發生異常時,再進行讀取和寫入操做。

  METHOD_NEITHER方式(借圖)

  


  二 .定義驅動設備名,符號連接名
       定義好了IO控制碼CTL_CODE,第二步驅動程序還要準備驅動設備名和符號連接名。     

    關於在Ring0層中要設置驅動設備名的同時還要設置符號連接名的緣由,是由於只有符號連接名才能夠被用戶模式下的應用程序識別

    windows下的設備是以"\Device\[設備名]」形式命名的。例如磁盤分區的c盤,d盤的設備名稱就是"\Device\HarddiskVolume1」,"\Device\HarddiskVolume2」, 固然也能夠不指定設備名稱。                               若是IoCreateDevice中沒有指定設備名稱,那麼I/O管理器會自動分配一個數字做爲設備的名稱。例如"\Device\00000001"。\Device\[設備名],不容易記憶,一般符號連接能夠理解爲設備的別名,更重要的是設備名,只能被內核模式下的其餘驅動所識別,而別名能夠被用戶模式下的應用程序識別,例如c盤,就是名爲"c:"的符號連接,其真正的設備對象是"\Device\HarddiskVolume1」,因此在寫驅動時候,通常咱們建立符號連接,即便驅動中沒有用到,這也算是一個好的習慣吧。

    驅動中符號連接名是這樣寫的
    L"\\??\\HelloDDK" --->\??\HelloDDK

    或者
    L"\\DosDevices\\HelloDDK"--->\DosDevices\HelloDDK


    在應用程序中,符號連接名:
    L"\\\\.\\HelloDDK"-->\\.\HelloDDK

    DosDevices的符號連接名就是??, 因此"\\DosDevices\\XXXX"其實就是\\??\\XXXX

              

1
2
3
4
#define DEVICE_OBJECT_NAME  L"\\Device\\BufferedIODeviceObjectName"
//設備與設備之間通訊
#define DEVICE_LINK_NAME    L"\\DosDevices\\BufferedIODevcieLinkName"
//設備與Ring3之間通訊

  三.將符號連接名與設備對象名稱關聯 ,等待IO控制碼

    驅動程序要作的最後一步,先用IoCreateDevice函數建立設備對象,再用IoCreateSymbolicLink符號連接名與設備對象名稱關聯 ,大功告成,等待IO控制碼。

    

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
     //建立設備對象名稱
RtlInitUnicodeString(&DeviceObjectName,DEVICE_OBJECT_NAME);
//建立設備對象
Status = IoCreateDevice(DriverObject,NULL,
     &DeviceObjectName,
     FILE_DEVICE_UNKNOWN,
     0, FALSE,
     &DeviceObject);
if (!NT_SUCCESS(Status))
{
     return Status;
}
 
//建立設備鏈接名稱
RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);
//將設備鏈接名稱與設備名稱關聯
Status = IoCreateSymbolicLink(&DeviceLinkName,&DeviceObjectName);
 
if (!NT_SUCCESS(Status))
{
     IoDeleteDevice(DeviceObject);
     return Status;
}       

  

  四.應用程序獲取設備句柄,發送IO控制碼。

    驅動程序鋪墊打理好以後,應用程序就能夠由符號連接名經過CreateFile函數獲取到設備句柄DeviceHandle,再用本場的主角,DeviceIoControl經過這個DeviceHandle發送控制碼了。

    先看看這兩個函數:

   

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
BOOL WINAPI DeviceIoControl(
   _In_         HANDLE hDevice,       //CreateFile函數打開的設備句柄
   _In_         DWORD dwIoControlCode, //自定義的控制碼
   _In_opt_     LPVOID lpInBuffer,    //輸入緩衝區
   _In_         DWORD nInBufferSize,  //輸入緩衝區的大小
   _Out_opt_    LPVOID lpOutBuffer,   //輸出緩衝區
   _In_         DWORD nOutBufferSize, //輸出緩衝區的大小
   _Out_opt_    LPDWORD lpBytesReturned, //實際返回的字節數,對應驅動程序中pIrp->IoStatus.Information。
   _Inout_opt_  LPOVERLAPPED lpOverlapped //重疊操做結構指針。同步設爲NULL,DeviceIoControl將進行阻塞調用;不然,應在編程時按異步操做設計
);
 
 
 
 
 
 
 
HANDLE CreateFile(
   LPCTSTR lpFileName,                         //打開的文件名
   DWORD dwDesiredAccess,                    //訪問權限
   DWORD dwShareMode,                      //共享模式
   LPSECURITY_ATTRIBUTES lpSecurityAttributes,   //安全屬性
   DWORD dwCreationDisposition,               //文件存在與不存在時的文件建立模式
   DWORD dwFlagsAndAttributes,                //文件屬性設定(隱藏、只讀、壓縮、指定爲系統文件等)
   HANDLE hTemplateFile                       //文件副本句柄
);

  

  最後總結一下DeviceIoControl的通訊流程:

    1.驅動程序和應用程序自定義IO控制碼 (CTL_CODE宏 四個參數,32位,4部分,存儲設備類型,訪問權限,操做功能,緩衝區數據傳遞方式(四種))

    2.驅動程序定義驅動設備名,符號連接名, 將符號連接名與設備對象名稱關聯 ,等待IO控制碼(IoCreateDevice,IoCreateSymbolicLink)

    3.應用程序由符號連接名經過CreateFile函數獲取到設備句柄DeviceHandle,再用本場的主角,DeviceIoControl經過這個設備句柄發送控制碼給派遣函數

 

 

 

 

  源代碼:

  BufferedIO.h

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#pragma once
#include <ntifs.h>
 
 
#define CTL_SYS \
     CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_BUFFERED,FILE_ANY_ACCESS)
 
 
#define DEVICE_OBJECT_NAME  L"\\Device\\BufferedIODeviceObjectName"
//設備與設備之間通訊
#define DEVICE_LINK_NAME    L"\\DosDevices\\BufferedIODevcieLinkName"
//設備與Ring3之間通訊
VOID DriverUnload(PDRIVER_OBJECT DriverObject);
NTSTATUS PassThroughDispatch(PDEVICE_OBJECT  DeviceObject, PIRP Irp);
NTSTATUS ControlThroughDispatch(PDEVICE_OBJECT  DeviceObject, PIRP Irp);

  

BufferedIO.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
#include "BufferedIO.h"
 
 
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegisterPath)
{
     NTSTATUS Status = STATUS_SUCCESS;
     PDEVICE_OBJECT  DeviceObject = NULL;
     UNICODE_STRING  DeviceObjectName;
     UNICODE_STRING  DeviceLinkName;
     ULONG           i;
     //   棧
     //   堆
     //   全局(global Static Const)
     DriverObject->DriverUnload = DriverUnload;
 
     //建立設備對象名稱
     RtlInitUnicodeString(&DeviceObjectName,DEVICE_OBJECT_NAME);
 
     //建立設備對象
     Status = IoCreateDevice(DriverObject,NULL,
         &DeviceObjectName,
         FILE_DEVICE_UNKNOWN,
         0, FALSE,
         &DeviceObject);
     if (!NT_SUCCESS(Status))
     {
         return Status;
     }
     //建立設備鏈接名稱
     RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);
 
     //將設備鏈接名稱與設備名稱關聯
     Status = IoCreateSymbolicLink(&DeviceLinkName,&DeviceObjectName);
 
     if (!NT_SUCCESS(Status))
     {
         IoDeleteDevice(DeviceObject);
         return Status;
     }
     //設計符合咱們代碼的派遣歷程
     for (i=0;i<IRP_MJ_MAXIMUM_FUNCTION;i++)
     {
         DriverObject->MajorFunction[i] = PassThroughDispatch;   //函數指針
     }
     DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ControlThroughDispatch;
     return Status;
}
//派遣歷程
NTSTATUS PassThroughDispatch(PDEVICE_OBJECT  DeviceObject,PIRP Irp)
{
     Irp->IoStatus.Status = STATUS_SUCCESS;     //LastError()
     Irp->IoStatus.Information = 0;             //ReturnLength
     IoCompleteRequest(Irp, IO_NO_INCREMENT);   //將Irp返回給Io管理器
     return STATUS_SUCCESS;
}
NTSTATUS ControlThroughDispatch(PDEVICE_OBJECT  DeviceObject, PIRP Irp)
{
     NTSTATUS Status;
     ULONG_PTR Informaiton = 0;
     PVOID InputData = NULL;
     ULONG InputDataLength = 0;
     PVOID OutputData = NULL;
     ULONG OutputDataLength = 0;
     ULONG IoControlCode = 0;
     PIO_STACK_LOCATION  IoStackLocation = IoGetCurrentIrpStackLocation(Irp);  //Irp堆棧  
     IoControlCode = IoStackLocation->Parameters.DeviceIoControl.IoControlCode;
     InputData  = Irp->AssociatedIrp.SystemBuffer;
     OutputData = Irp->AssociatedIrp.SystemBuffer;
     InputDataLength  = IoStackLocation->Parameters.DeviceIoControl.InputBufferLength;
     OutputDataLength = IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength;
     switch (IoControlCode)
     {
     case CTL_SYS:
     {
         if (InputData != NULL&&InputDataLength > 0)
         {
             DbgPrint( "%s\r\n" , InputData);
         }
         if (OutputData != NULL&&OutputDataLength >= strlen ( "Ring0->Ring3" ) + 1)
         {
             memcpy (OutputData, "Ring0->Ring3" , strlen ( "Ring0->Ring3" ) + 1);
             Status = STATUS_SUCCESS;
             Informaiton = strlen ( "Ring0->Ring3" ) + 1;
         }
         else
         {
             Status = STATUS_INSUFFICIENT_RESOURCES;   //內存不夠
             Informaiton = 0;
         }
         break ;
     }
     default :
         break ;
     }
     Irp->IoStatus.Status = Status;             //Ring3 GetLastError();
     Irp->IoStatus.Information = Informaiton;
     IoCompleteRequest(Irp, IO_NO_INCREMENT);  //將Irp返回給Io管理器
     return Status;                            //Ring3 DeviceIoControl()返回值
}
VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
     UNICODE_STRING  DeviceLinkName;
     PDEVICE_OBJECT  v1 = NULL;
     PDEVICE_OBJECT  DeleteDeviceObject = NULL;
     
     RtlInitUnicodeString(&DeviceLinkName, DEVICE_LINK_NAME);
     IoDeleteSymbolicLink(&DeviceLinkName);
 
     DeleteDeviceObject = DriverObject->DeviceObject;
     while (DeleteDeviceObject != NULL)
     {
         v1 = DeleteDeviceObject->NextDevice;
         IoDeleteDevice(DeleteDeviceObject);
         DeleteDeviceObject = v1;
     }
}

  

IO.cpp

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 緩衝區IO.cpp : 定義控制檯應用程序的入口點。
//
 
#include "stdafx.h"
#include <windows.h>
#define DEVICE_LINK_NAME    L"\\\\.\\BufferedIODevcieLinkName"
 
 
#define CTL_SYS \
     CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_BUFFERED,FILE_ANY_ACCESS)
int main()
{
     HANDLE DeviceHandle = CreateFile(DEVICE_LINK_NAME,
         GENERIC_READ | GENERIC_WRITE,
         FILE_SHARE_READ | FILE_SHARE_WRITE,
         NULL,
         OPEN_EXISTING,
         FILE_ATTRIBUTE_NORMAL,
         NULL);
     if (DeviceHandle==INVALID_HANDLE_VALUE)
     {
         return 0;
     }
     char BufferData = NULL;
     DWORD ReturnLength = 0;
     BOOL IsOk = DeviceIoControl(DeviceHandle, CTL_SYS,
         "Ring3->Ring0" ,
         strlen ( "Ring3->Ring0" )+1,
         ( LPVOID )BufferData,
         0,
         &ReturnLength,
         NULL);
     if (IsOk == FALSE)
     {
         int LastError = GetLastError();
 
         if (LastError == ERROR_NO_SYSTEM_RESOURCES)
         {
             char BufferData[MAX_PATH] = { 0 };
             IsOk = DeviceIoControl(DeviceHandle, CTL_SYS,
                 "Ring3->Ring0" ,
                 strlen ( "Ring3->Ring0" ) + 1,
                 ( LPVOID )BufferData,
                 MAX_PATH,
                 &ReturnLength,
                 NULL);
 
             if (IsOk == TRUE)
             {
                 printf ( "%s\r\n" , BufferData);
             }
         }
     }
     if (DeviceHandle != NULL)
     {
         CloseHandle(DeviceHandle);
         DeviceHandle = NULL;
     }
     printf ( "Input AnyKey To Exit\r\n" );
 
     getchar ();
     return 0;
}
相關文章
相關標籤/搜索