程式扎記: [ Windows DDP ] 驅動呼叫驅動 : 以檔案控制程式碼形式呼叫其他驅動程式

標籤

2011年12月6日 星期二

[ Windows DDP ] 驅動呼叫驅動 : 以檔案控制程式碼形式呼叫其他驅動程式


前言 :
這裡將介紹以檔案控制程式碼的方式呼叫其他驅動程式. 這種方式類似於在應用程式中呼叫驅動程式, 使用方法簡單, 不需要程式師對 Windows 底層瞭解過的的門檻.

準備一個標準驅動程式 :
在介紹驅動程式呼叫其他驅動程式之前, 首先準備一個標準驅動程式, 作為 "目標" 驅動程式. 本章後面介紹的所有驅動程式, 都以不同方法呼叫這個 "目標" 驅動程式.
這個 "目標" 驅動程式新建一個模擬裝置, 模擬裝置支援非同步讀取操作. 事先規定每次對裝置讀取需耗時 3s, 因為這樣可以很好地演示非同步讀取操作. 對於這樣的驅動程式, 應該設置一個計時器, 計時器的間隔設置為 3s. 另外在 IRP_MJ_READ 的派遣函式中不結束 IRP 請求, 而是將 IRP 請求 Pending, 並且再派遣函式退出前開啟計時器. 這樣 3s 後就會進入計時器的回呼函式中, 並在回乎函式中結束 IRP 請求.
我們把這個 "目標" 驅動程式命名為 DriverA, 呼叫 DriverA 的驅動程式被命名為 DriverB. 以下列出的是 DriverA 的部分程式碼 :
- Driver.cpp : IRP_MJ_READ 的派遣函式
  1. NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,  
  2.                                  IN PIRP pIrp)   
  3. {  
  4.     KdPrint(("DriverA:Enter A HelloDDKRead\n"));  
  5.   
  6.     PDEVICE_EXTENSION pDevExt = (PDEVICE_EXTENSION)  
  7.             pDevObj->DeviceExtension;  
  8.   
  9.     //將IRP設置為掛起  
  10.     IoMarkIrpPending(pIrp);  
  11.   
  12.     //將掛起的IRP記錄下來  
  13.     pDevExt->currentPendingIRP = pIrp;  
  14.   
  15.     //定義3秒後將IRP_MJ_READ的IRP完成  
  16.     ULONG ulMicroSecond = 3000000;  
  17.   
  18.     //將32位元整數轉化成64位元整數  
  19.     LARGE_INTEGER timeout = RtlConvertLongToLargeInteger(-10*ulMicroSecond);  
  20.       
  21.     KeSetTimer(  
  22.         &pDevExt->pollingTimer,  
  23.         timeout,  
  24.         &pDevExt->pollingDPC );  
  25.   
  26.     KdPrint(("DriverA:Leave A HelloDDKRead\n"));  
  27.   
  28.     //返回pending狀態  
  29.     return STATUS_PENDING;  
  30. }  

- Driver.cpp : 超時 DPC 常式
  1. VOID OnTimerDpc( IN PKDPC pDpc,  
  2.                       IN PVOID pContext,  
  3.                       IN PVOID SysArg1,  
  4.                       IN PVOID SysArg2 )   
  5. {  
  6.     PDEVICE_OBJECT pDevObj = (PDEVICE_OBJECT)pContext;  
  7.     PDEVICE_EXTENSION pdx = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;  
  8.   
  9.     PIRP currentPendingIRP = pdx->currentPendingIRP;  
  10.   
  11.     KdPrint(("DriverA:complete the Driver A IRP_MJ_READ irp!\n"));  
  12.   
  13.     //設置完成狀態為STATUS_CANCELLED  
  14.     currentPendingIRP->IoStatus.Status = STATUS_SUCCESS;  
  15.     currentPendingIRP->IoStatus.Information = 0// bytes xfered  
  16.     IoCompleteRequest( currentPendingIRP, IO_NO_INCREMENT );  
  17. }  

以上就是需要準備的 "標準" 驅動程式 DriverA. 接下來將介紹幾種不同方法, 使另外的驅動程式 (DriverB) 可以呼叫 DriverA 的讀取功能. 為了演示 DriverB 呼叫 DriverA, 並使讀者可以看清楚 log 資訊來自哪個驅動程式, 這裡讓 DriverA 在輸出 log 資訊前加上 "DriverA:" 這個Prefix, 而讓 DriverB 在輸出 log 資訊之前加上 "DriverB:" 的 Prefix.
在前面 (計時器) 的例子中, 都是讓應用程式做為驅動程式的 "客戶端". 而這裡 DriverA 的用戶端不再是應用程式, 而變成了另外的驅動程式 (DriverB). 其呼叫關係如下所示 :


獲得裝置控制碼 :
前言準備了 DriverA, 接著要介紹如何編寫 DriverB 並呼叫 DriverA. 下面回憶一下應用程式是如何呼叫驅動程式吧. 應該先用 CreateFile 函式打開裝置, 然後使用 ReadFile 函式讀取裝置, 最後用 CloseHandle 函式關閉裝置.
在驅動程式中, 打開裝置使用 ZwCreateFile 內核函式, 它會返回裝置控制碼. 這裡要討論一下如何用 ZwCreateFile 內核函式打開 "同步" 裝置和 "非同步" 裝置. 其函式宣告如下 :
- Syntax :
  1. NTSTATUS ZwCreateFile(  
  2.   __out     PHANDLE FileHandle,  
  3.   __in      ACCESS_MASK DesiredAccess,  
  4.   __in      POBJECT_ATTRIBUTES ObjectAttributes,  
  5.   __out     PIO_STATUS_BLOCK IoStatusBlock,  
  6.   __in_opt  PLARGE_INTEGER AllocationSize,  
  7.   __in      ULONG FileAttributes,  
  8.   __in      ULONG ShareAccess,  
  9.   __in      ULONG CreateDisposition,  
  10.   __in      ULONG CreateOptions,  
  11.   __in_opt  PVOID EaBuffer,  
  12.   __in      ULONG EaLength  
  13. );  

ZwCreateFile 內核函式的大多數參數已經詳細介紹過, 這裡只介紹打開 "同步" 裝置和 "非同步" 裝置的區別. 如果打開 "同步" 裝置, 第二個參數 DesiredAccess 需要設置為 SYNCHRONIZE, 並且倒數第三個參數 CreateOptions 需要指定為 FILE_SYNCHRONOUS_IO_NONALERT 或者 FILE_SYNCHRONOUS_IO_ALERT. 範例代碼如下 :
  1. // 同步打開裝置  
  2. // 設定了 FILE_SYNCHRONOUS_IO_NONALERT 或者 FILE_SYNCHRONOUS_IO_ALERT 為同步打開裝置  
  3. ntStatus = ZwCreateFile(&hDevice,  
  4.                                         FILE_READ_ATTRIBUTES|SYNCHRONIZE,  
  5.                                         &objectAttributes,  
  6.                                         &status_block,  
  7.                                         NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ,  
  8.                                         FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);  
如果打開了 "非同步" 裝置, 第二個參數 DesiredAccess 不能設置為 SYNCHRONIZE, 並且倒數第三個參數 CreateOptions 不能指定 FILE_SYNCHRONOUS_IO_NONALERT 或 FILE_SYNCHRONOUS_IO_ALERT.

同步呼叫 :
在應用程式中, ReadFile 函式既可以用來讀取檔案, 也可以用來讀取裝置. 同樣在驅動程式中, ZwReadFile 內核函式既可以用來讀取檔案, 也可以讀取裝置. 如果用 ZwReadFile 內核函式同步讀取裝置, 操作對應的 IRP_MJ_READ 請求被結束後, 函式才會返回, 否則函式會一直等待 IRP_MJ_READ 請求被結束. 而這裡介紹的 DriverB 是利用同步讀取方式呼叫 DriverA, 如用 ZwReadFile 內核函式同步讀取 DriverA 的裝置, 它的內部操作過程如下 :
1. 在 DriverB 中用 ZwReadFile 內核函式讀取 DriverA 裝置物件. ZwReadFile 內核函式內部會新建 IRP_MJ_READ 類型的 IRP, 然後將這個 IRP 當作參數傳遞給 DriverA 的派遣函式.
2. DriverA 的派遣函式沒有結束 IRP 請求, 而是將 IRP 請求 Pending.
3. ZwReadFile 函式會一直等待 IRP 中的一個事件, 此時當前執行緒進入睡眠狀態.
4. 3s 後, 觸發 DriverA 的計時器常式, 這時候 IRP 請求被結束, IRP 中的相關事件也被設置.
5. 由於相關事件被設置, 剛才休眠的執行緒恢復執行, ZwReadFile 內核函式退出.

明白上述過程後, 使用 ZwReadFile 內核函式進行同步讀取就變的簡單了. 下面程式碼演示了如何使用 ZwReadFile 內核函式讀取 DriverA 的裝置. 另外 DriverB 還需要應用程式的用戶端, 因此 DriverB 的派遣函式還需要結束來自應用程式的 IRP 請求, 底下是相關範例代碼 :
- Driver.cpp of DriverB : READ IRP 派遣函式
  1. NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,  
  2.                                  IN PIRP pIrp)   
  3. {  
  4.     KdPrint(("DriverB:Enter B HelloDDKRead\n"));  
  5.     NTSTATUS ntStatus = STATUS_SUCCESS;  
  6.   
  7.     UNICODE_STRING DeviceName;  
  8.     RtlInitUnicodeString( &DeviceName, L"\\Device\\MyDDKDeviceA" );  
  9.   
  10.     //初始化objectAttributes  
  11.     OBJECT_ATTRIBUTES objectAttributes;  
  12.     InitializeObjectAttributes(&objectAttributes,   
  13.                             &DeviceName,  
  14.                             OBJ_CASE_INSENSITIVE,   
  15.                             NULL,   
  16.                             NULL );  
  17.   
  18.     HANDLE hDevice;  
  19.     IO_STATUS_BLOCK status_block;  
  20.     //同步打開裝置  
  21.     //設定了FILE_SYNCHRONOUS_IO_NONALERT或者FILE_SYNCHRONOUS_IO_ALERT為同步打開裝置  
  22.     ntStatus = ZwCreateFile(&hDevice,  
  23.         FILE_READ_ATTRIBUTES|SYNCHRONIZE,  
  24.         &objectAttributes,  
  25.         &status_block,  
  26.         NULL,FILE_ATTRIBUTE_NORMAL,FILE_SHARE_READ,  
  27.         FILE_OPEN_IF,FILE_SYNCHRONOUS_IO_NONALERT,NULL,0);  
  28.   
  29.     if (NT_SUCCESS(ntStatus))  
  30.     {  
  31.         ZwReadFile(hDevice,NULL,NULL,NULL,&status_block,NULL,0,NULL,NULL);  
  32.     }  
  33.       
  34.     ZwClose(hDevice);  
  35.   
  36.     // 完成IRP  
  37.     pIrp->IoStatus.Status = ntStatus;  
  38.     pIrp->IoStatus.Information = 0;  // bytes xfered  
  39.     IoCompleteRequest( pIrp, IO_NO_INCREMENT );  
  40.     KdPrint(("DriverB:Leave B HelloDDKRead\n"));  
  41.     return ntStatus;  
  42. }  

底下是執行結果 :


非同步呼叫方法一 :
非同步讀取主要是指 ZwReadFile 內核函式在沒有等待 DriverA 真正結束 IRP 請求時就已經退出. 如果 ZwReadFile 內核函式非同步讀取 DriverA 裝置時, 它的內部操作過程如下 :
1. ZwReadFile 內核函式新建 IRP_MJ_READ 類型的 IRP, 然後將這個 IRP 傳遞給 DriverA 派遣函式
2. DriverA 派遣函式沒有結束 IRP 請求, 而是 "Pending" 該 IRP.
3. ZwReadFile 內核函式發現 DriverA 將 IRP_MJ_READ "Pending", 於是它直接返回, 返回值是 STATUS_PENDING, 這代表讀取操作正在進行中.

ZwReadFile 內核函式退出後, 無法得知 "Pending" 的 IRP 何時被結束. 因此在呼叫 ZwReadFile 前可以為IRP 設置一個完成常式. 當 IRP 結束時就觸發這個完成常式. 在本例中將一個事件的指標傳遞給該完成常式, 在完成常式中觸發該事件. 一旦這個事件被觸發, 就可以知道 IRP_MJ_READ 請求已經結束. 下面程式碼演示了如何用 ZwReadFile 內核函式進行非同步讀取操作 :
- Driver.cpp of DriverB : Read IRP 派遣函式
  1. NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,  
  2.                                  IN PIRP pIrp)   
  3. {  
  4.     KdPrint(("DriverB:Enter B HelloDDKRead\n"));  
  5.     NTSTATUS ntStatus = STATUS_SUCCESS;  
  6.   
  7.     UNICODE_STRING DeviceName;  
  8.     RtlInitUnicodeString( &DeviceName, L"\\Device\\MyDDKDeviceA" );  
  9.   
  10.     //初始化objectAttributes  
  11.     OBJECT_ATTRIBUTES objectAttributes;  
  12.     InitializeObjectAttributes(&objectAttributes,   
  13.                             &DeviceName,  
  14.                             OBJ_CASE_INSENSITIVE,   
  15.                             NULL,   
  16.                             NULL );  
  17.   
  18.     HANDLE hDevice;  
  19.     IO_STATUS_BLOCK status_block;  
  20.     //非同步打開裝置  
  21.     //沒有設定了FILE_SYNCHRONOUS_IO_NONALERT和FILE_SYNCHRONOUS_IO_ALERT為非同步打開裝置  
  22.     ntStatus = ZwCreateFile(&hDevice,  
  23.         FILE_READ_ATTRIBUTES,//沒有設SYNCHRONIZE  
  24.         &objectAttributes,  
  25.         &status_block,  
  26.         NULL,FILE_ATTRIBUTE_NORMAL,FILE_SHARE_READ,  
  27.         FILE_OPEN_IF,0,NULL,0);  
  28.   
  29.     KEVENT event;  
  30.     //初始化事件,用於非同步讀  
  31.     KeInitializeEvent(&event,SynchronizationEvent,FALSE);  
  32.   
  33.     LARGE_INTEGER offset = RtlConvertLongToLargeInteger(0);  
  34.     if (NT_SUCCESS(ntStatus))  
  35.     {  
  36.         ntStatus = ZwReadFile(hDevice,NULL,CompleteDriverA_Read,&event,&status_block,NULL,0,&offset,NULL);  
  37.     }  
  38.   
  39.     if (ntStatus==STATUS_PENDING)  
  40.     {  
  41.         KdPrint(("DriverB:ZwReadFile return STATUS_PENDING!\n"));  
  42.         KdPrint(("DriverB:Waiting..."));  
  43.         KeWaitForSingleObject(&event,Executive,KernelMode,FALSE,NULL);  
  44.     }  
  45.     ZwClose(hDevice);  
  46.   
  47.     ntStatus = STATUS_SUCCESS;  
  48.     // 完成IRP  
  49.     pIrp->IoStatus.Status = ntStatus;  
  50.     pIrp->IoStatus.Information = 0;  // bytes xfered  
  51.     IoCompleteRequest( pIrp, IO_NO_INCREMENT );  
  52.     KdPrint(("DriverB:Leave B HelloDDKRead\n"));  
  53.     return ntStatus;  
  54. }  

以下是完成常式的程式碼. 完成常式主要是用一個事件通知 IRP 請求被結束.
- Driver.cpp of DriverB : 完成常式 CompleteDriverA_Read
  1. VOID CompleteDriverA_Read(PVOID context,PIO_STATUS_BLOCK pStatus_block,ULONG)  
  2. {  
  3.     KdPrint(("DriverB:The Driver A Read completed now!\n"));  
  4.     KeSetEvent((PKEVENT)context,IO_NO_INCREMENT,FALSE);  
  5. }  

底下是執行結果 :


非同步呼叫方法二 :
非同步方法一在非同步讀取時, 將一個事件的控制碼傳遞給 ZwReadFile 內核函式, 這個事件可以用來通知讀取操作何時完成. 而非同步方法二不用將事件控制碼傳遞給 ZwReadFile 函式, 而是透過檔案物件判斷是否讀取操作完畢. 每打開一個裝置, 都會伴隨一個關聯的檔案物件 (FILE_OBJECT). 利用內核函式 ObReferenceObjectByHandle 可以獲取和裝置相關的檔案物件指標.當 IRP_MJ_READ 請求被結束後, 檔案物件的子欄位 Event 會被設置, 因此用檔案物件的 Event 子欄位可以當做同步處理使用. 以下是相關的部分程式碼 :
- Driver.cpp of DriverB : Read IRP 的派遣函式
  1. NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,  
  2.                                  IN PIRP pIrp)   
  3. {  
  4.     KdPrint(("DriverB:Enter B HelloDDKRead\n"));  
  5.     NTSTATUS ntStatus = STATUS_SUCCESS;  
  6.   
  7.     UNICODE_STRING DeviceName;  
  8.     RtlInitUnicodeString( &DeviceName, L"\\Device\\MyDDKDeviceA" );  
  9.   
  10.     //初始化objectAttributes  
  11.     OBJECT_ATTRIBUTES objectAttributes;  
  12.     InitializeObjectAttributes(&objectAttributes,   
  13.                             &DeviceName,  
  14.                             OBJ_CASE_INSENSITIVE,   
  15.                             NULL,   
  16.                             NULL );  
  17.   
  18.     HANDLE hDevice;  
  19.     IO_STATUS_BLOCK status_block;  
  20.       
  21.     //非同步打開裝置  
  22.     ntStatus = ZwCreateFile(&hDevice,  
  23.         FILE_READ_ATTRIBUTES,//沒有設SYNCHRONIZE  
  24.         &objectAttributes,  
  25.         &status_block,  
  26.         NULL,FILE_ATTRIBUTE_NORMAL,FILE_SHARE_READ,  
  27.         FILE_OPEN_IF,0,NULL,0);  
  28.   
  29.     LARGE_INTEGER offset = RtlConvertLongToLargeInteger(0);  
  30.     if (NT_SUCCESS(ntStatus))  
  31.     {  
  32.         ntStatus = ZwReadFile(hDevice,NULL,NULL,NULL,&status_block,NULL,0,&offset,NULL);  
  33.     }  
  34.   
  35.     if (ntStatus==STATUS_PENDING)  
  36.     {  
  37.         KdPrint(("DriverB:ZwReadFile return STATUS_PENDING!\n"));  
  38.   
  39.         PFILE_OBJECT FileObject;  
  40.         ntStatus = ObReferenceObjectByHandle(hDevice, EVENT_MODIFY_STATE, *ExEventObjectType,  
  41.                         KernelMode, (PVOID*) &FileObject, NULL);  
  42.         if (NT_SUCCESS(ntStatus))  
  43.         {  
  44.             KdPrint(("DriverB:Waiting..."));  
  45.             KeWaitForSingleObject(&FileObject->Event,Executive,KernelMode,FALSE,NULL);  
  46.             KdPrint(("DriverB:Driver A Read IRP completed now!\n"));  
  47.             ObDereferenceObject(FileObject);  
  48.         }  
  49.     }  
  50.     ZwClose(hDevice);  
  51.   
  52.     ntStatus = STATUS_SUCCESS;  
  53.     // 完成IRP  
  54.     pIrp->IoStatus.Status = ntStatus;  
  55.     pIrp->IoStatus.Information = 0;  // bytes xfered  
  56.     IoCompleteRequest( pIrp, IO_NO_INCREMENT );  
  57.     KdPrint(("DriverB:Leave B HelloDDKRead\n"));  
  58.     return ntStatus;  
  59. }  


透過符號連結打開裝置 :
前面介紹的例子都是透過裝置名打開裝置. 但是很多情況, 使用者是不容易知道具體的裝置名, 而只知道符號連結. 例如 "C:" 代表第一個硬碟分割區. 而 "C:" 就是一個符號連結, 他指向一個磁碟分割裝置. 尤其在 WDK 驅動程式中, 透過符號連結打開裝置是常用的方法.
利用 ZwOpenSymbolicLinkObject 內核函式先得到符號連結的控制碼, 然後使用 ZwQuerySymbolicLinkObject 內核函式找到裝置名. 透過裝置名就可以方便的打開裝置了. 底下示相關的範例代碼 :
- Driver.cpp of DriverB : Read IRP 派遣函式
  1. NTSTATUS HelloDDKRead(IN PDEVICE_OBJECT pDevObj,  
  2.                                  IN PIRP pIrp)   
  3. {  
  4.     KdPrint(("DriverB:Enter B HelloDDKRead\n"));  
  5.     NTSTATUS ntStatus = STATUS_SUCCESS;  
  6.   
  7.     UNICODE_STRING DeviceSymbolicLinkName;  
  8.     RtlInitUnicodeString( &DeviceSymbolicLinkName, L"\\??\\HelloDDKA" );  
  9.   
  10.     //初始化objectAttributes  
  11.     OBJECT_ATTRIBUTES objectAttributes;  
  12.     InitializeObjectAttributes(&objectAttributes,   
  13.                             &DeviceSymbolicLinkName,  
  14.                             OBJ_CASE_INSENSITIVE|OBJ_KERNEL_HANDLE,   
  15.                             NULL,   
  16.                             NULL );  
  17.   
  18.     HANDLE hSymbolic;  
  19.     //設定了FILE_SYNCHRONOUS_IO_NONALERT或者FILE_SYNCHRONOUS_IO_ALERT為同步打開裝置  
  20.     ntStatus = ZwOpenSymbolicLinkObject(&hSymbolic,FILE_ALL_ACCESS,&objectAttributes);  
  21. #define UNICODE_SIZE 50  
  22.     UNICODE_STRING LinkTarget;  
  23.     LinkTarget.Buffer = (PWSTR)ExAllocatePool(PagedPool,UNICODE_SIZE);  
  24.     LinkTarget.Length = 0;  
  25.     LinkTarget.MaximumLength = UNICODE_SIZE;  
  26.   
  27.     ULONG unicode_length;  
  28.     ntStatus = ZwQuerySymbolicLinkObject(hSymbolic,&LinkTarget,&unicode_length);  
  29.   
  30.     KdPrint(("DriverB:The device name is %wZ\n",&LinkTarget));  
  31.   
  32.     InitializeObjectAttributes(&objectAttributes,   
  33.                             &LinkTarget,  
  34.                             OBJ_CASE_INSENSITIVE,   
  35.                             NULL,   
  36.                             NULL );  
  37.       
  38.     HANDLE hDevice;  
  39.     IO_STATUS_BLOCK status_block;  
  40.     //設定了FILE_SYNCHRONOUS_IO_NONALERT或者FILE_SYNCHRONOUS_IO_ALERT為同步打開裝置  
  41.     ntStatus = ZwCreateFile(&hDevice,  
  42.         FILE_READ_ATTRIBUTES|SYNCHRONIZE,  
  43.         &objectAttributes,  
  44.         &status_block,  
  45.         NULL,FILE_ATTRIBUTE_NORMAL,FILE_SHARE_READ,  
  46.         FILE_OPEN_IF,FILE_SYNCHRONOUS_IO_NONALERT,NULL,0);  
  47.   
  48.     if (NT_SUCCESS(ntStatus))  
  49.     {  
  50.         ZwReadFile(hDevice,NULL,NULL,NULL,&status_block,NULL,0,NULL,NULL);  
  51.     }  
  52.       
  53.     ZwClose(hDevice);  
  54.     ZwClose(hSymbolic);  
  55.     ExFreePool(LinkTarget.Buffer);  
  56.   
  57.     ntStatus = STATUS_SUCCESS;  
  58.     // 完成IRP  
  59.     pIrp->IoStatus.Status = ntStatus;  
  60.     pIrp->IoStatus.Information = 0;  // bytes xfered  
  61.     IoCompleteRequest( pIrp, IO_NO_INCREMENT );  
  62.     KdPrint(("DriverB:Leave B HelloDDKRead\n"));  
  63.     return ntStatus;  
  64. }  

底下是執行結果 :
This message was edited 22 times. Last update was at 08/02/2011 17:44:20

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!