CreateFile DeviceIoControl dwIoControlCode——應用程序與驅動程序通信


  在“進程內存管理器中”的一個Ring0,Ring3層通信問題,之前也見過這樣的代碼,這次拆分出來詳細總結一下。

  先通過CreateFile函數得到設備句柄,CreateFile函數原型:

  

HANDLE CreateFile(
    LPCTSTR lpFileName,                         // 文件名/設備路徑 設備的名稱
    DWORD dwDesiredAccess,                      // 訪問方式
    DWORD dwShareMode,                          // 共享方式
    LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 安全描述符指針
    DWORD dwCreationDisposition,                // 創建方式
    DWORD dwFlagsAndAttributes,                 // 文件屬性及標志
    HANDLE hTemplateFile                        // 模板文件的句柄
);

打開:createFile

關閉:closehandle

與普通文件名有所不同,設備驅動的“文件名”(常稱為“設備路徑”)形式固定為“\\.\DeviceName”(注意寫法為“\\\\.\\DeviceName”),DeviceName必須與設備驅動程序內定義的設備名稱一致。

在“進程內存管理器”中:

Ring0層的kProcessMemory.h

#define DEVICE_NAME L"\\Device\\KProcessMemoryDeviceName"
#define LINK_NAME L"\\DosDevices\\KProcessMemoryLinkName"

 

Ring3層的ProcessMemoryManager.cpp

OpenDeviceObject(L"\\\\.\\KProcessMemoryLinkName");

BOOL OpenDeviceObject(LPCTSTR DeviceFullPathData)
{
m_DeviceHandle = CreateFile(DeviceFullPathData,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);

if (m_DeviceHandle == INVALID_HANDLE_VALUE)
{
return FALSE;
}

return TRUE;

}

可以看到在kProcessMemory.h中define了一個驅動設備名和一個符號鏈接名

驅動設備名是調用IoCreateDevice時使用的,IoCreateDevice函數原型:

NTSTATUS IoCreateDevice(
  _In_      PDRIVER_OBJECT DriverObject,
  _In_      ULONG DeviceExtensionSize,
  _In_opt_ PUNICODE_STRING DeviceName,
  _In_      DEVICE_TYPE DeviceType,
  _In_      ULONG DeviceCharacteristics,
  _In_      BOOLEAN Exclusive,
  _Out_     PDEVICE_OBJECT *DeviceObject
);

驅動程序中調用IoCreateDevice函數:

RtlInitUnicodeString(&DeviceName, DEVICE_NAME);
Status = IoCreateDevice(DriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject);

關於在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

winobj和DeviceTree可以用來查看這些信息。

關於驅動設備名和符號鏈接名,可以參考這篇博客:

http://www.cnblogs.com/findumars/p/5636505.html

 

接着回到CreateFile函數上來,它的第二個參數,dwDesireAceess訪問方式,一般設置為0或GENERIC_READ|GENERIC_WRITE共享方式參數設置為FILE_SHARE_READ|FILE_SHARE_WRITE創建方式參數設置為OPEN_EXISTING,其它參數一般設置為0或NULL。

 

Ring3層的CreateFile函數獲取了設備句柄后,將使用DeviceIoControl函數向指定的設備驅動發送一個IO控制碼,驅動程序通過這個控制碼來完成特定的工作。該函數原型如下:

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將進行阻塞調用;否則,應在編程時按異步操作設計
);

  先介紹IO控制碼,驅動程序可以通過CTL_CODE宏來組合定義一個控制碼,並在IRP_MJ_DEVICE_CONTROL的實現中進行控制碼的操作。在驅動層,IoStackLocation->Parameters.DeviceIoControl.IoControlCode表示了這個控制碼。發送不同的控制碼,可以調用設備驅動程序的不同類型的功能。在頭文件winioctl.h中,預定義的標准設備控制碼,都以IOCTL或FSCTL開頭。例如,IOCTL_DISK_GET_DRIVE_GEOMETRY是對物理驅動器取結構參數(介質類型、柱面數、每柱面磁道數、每磁道扇區數等)的控制碼,FSCTL_LOCK_VOLUME是對邏輯驅動器的卷加鎖的控制碼。

 

lpInBuffer

由用戶層發送的緩沖區數據。在“進程內存管理器“程序中,我們是通過進程ID來查詢進程內存,故傳入的是進程ID.在驅動層,依傳輸類型的不同,輸入緩沖區的位置亦不同,見下表。

傳輸類型 位置
METHOD_IN_DIRECT irp->AssociatedIrp.SystemBuffer
METHOD_OUT_DIRECT irp->AssociatedIrp.SystemBuffer
METHOD_BUFFERED irp->AssociatedIrp.SystemBuffer
METHOD_NEITHER irpStack->Parameters.DeviceIoControl.Type3InputBuffer

nInBufferSize

由用戶層發送的緩沖區大小。在驅動層,這個值是IoStackLocation->Parameters.DeviceIoControl.InputBufferLength

 

lpOutBuffer

由用戶層指定,用於接收驅動層返回數據的緩沖區。在驅動層,依傳輸類型的不同,輸出緩沖區的位置亦不同,見下表。

傳輸類型 位置
METHOD_IN_DIRECT irp->MdlAddress
METHOD_OUT_DIRECT irp->MdlAddress
METHOD_BUFFERED irp->AssociatedIrp.SystemBuffer
METHOD_NEITHER irp->UserBuffer

nOutBufferSize

由用戶層指定,用於接收驅動層返回數據的緩沖區大小。在驅動層,這個值是IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength

 

lpBytesReturned

由用戶層指定,用於接收驅動層實際返回數據大小。在驅動層,這個值是irp->IoStatus->Information

 

lpOverlapped

用於異步操作。

 

程序中的派遣函數:

 
         

#define CTL_QUERY_PROCESS_MEMORY \
CTL_CODE(FILE_DEVICE_UNKNOWN,0x830,METHOD_NEITHER,FILE_ANY_ACCESS)

 
         

#define CTL_READ_PROCESS_MEMORY \
CTL_CODE(FILE_DEVICE_UNKNOWN,0x831,METHOD_NEITHER,FILE_ANY_ACCESS)

 
         

#define CTL_WRITE_PROCESS_MEMORY \
CTL_CODE(FILE_DEVICE_UNKNOWN,0x832,METHOD_NEITHER,FILE_ANY_ACCESS)

......

 

RtlInitUnicodeString(&DeviceName, DEVICE_NAME);
Status = IoCreateDevice(DriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &DeviceObject);

......

RtlInitUnicodeString(&LinkName, LINK_NAME);
Status = IoCreateSymbolicLink(&LinkName, &DeviceName);

......

DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceControlDispatch;

NTSTATUS DeviceControlDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{

    NTSTATUS Status = STATUS_SUCCESS;
    ULONG    IoControlCode = 0;
    ULONG    InputLength = 0;
    SIZE_T   OutputLength = 0;
    PVOID    InputData = NULL;
    PVOID    OutputData = NULL;

    PIO_STACK_LOCATION IoStackLocation = IoGetCurrentIrpStackLocation(Irp);
    IoControlCode = IoStackLocation->Parameters.DeviceIoControl.IoControlCode;
    //BufferIO
    //InputData = OutputData = Irp->AssociatedIrp.SystemBuffer;  
    /*
    直接方式DO_DIRECT_IO / 非直接方式(緩沖方式)DO_BUFFERD_IO
        1) 在buffered(AssociatedIrp.SystemBuffer)方式中,I/O管理器先創建一個與用戶模式數據緩沖區大小相等的系統緩沖區。而你的驅動程序將使用這個系統緩沖區工作。
           I/O管理器負責在系統緩沖區和用戶模式緩沖區之間復制數據。
        2) 在direct(MdlAddress)方式中,I/O管理器鎖定了包含用戶模式緩沖區的物理內存頁,並創建一個稱為MDL(內存描述符表)的輔助數據結構來描述鎖定頁。
           因此你的驅動程序將使用MDL工作。
        3) 在neither(UserBuffer)方式中,I/O管理器僅簡單地把用戶模式的虛擬地址傳遞給你。
           而使用用戶模式地址的驅動程序應十分小心。
    */
    //Neither方式提高了通信效率,但是不夠安全,在讀寫之前應使用ProbeForRead和ProbeForWrite函數探測地址是可讀和可寫
    //詳見eDiary筆記中“Driver——DeviceIoControl函數與IoControlCode”
    InputData = IoStackLocation->Parameters.DeviceIoControl.Type3InputBuffer;//得到Ring3的輸入緩沖區地址
    OutputData = Irp->UserBuffer;                                            //得到Ring3的輸出緩沖區地址
    InputLength = IoStackLocation->Parameters.DeviceIoControl.InputBufferLength;
    OutputLength = IoStackLocation->Parameters.DeviceIoControl.OutputBufferLength;

    switch (IoControlCode)   //IO控制碼
    {
    case CTL_QUERY_PROCESS_MEMORY:
    {
        if (!MmIsAddressValid(OutputData))
        {
            Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
            Irp->IoStatus.Information = 0;
            break;
        }
        if (InputLength != sizeof(ULONG) || InputData == NULL)
        {
            Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
            Irp->IoStatus.Information = 0;
            break;
        }
        __try
        {

            ProbeForWrite(OutputData, OutputLength, 1);  //檢測內存是否可寫,這個函數需要在用戶模式下使用,詳見MSDN:
            //If Irp->RequestorMode = KernelMode, the Irp->AssociatedIrp.SystemBuffer and Irp->UserBuffer fields do not contain user-mode addresses,
            //and a call to ProbeForWrite to probe a buffer pointed to by either field will raise an exception.

            Status = RtlQueryVirtualMemory(*(PULONG)InputData, OutputData, OutputLength);
            Irp->IoStatus.Information = 0;
            Irp->IoStatus.Status = Status;

        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            Irp->IoStatus.Information = 0;
            Status = Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
        }
        break;
    }
    case CTL_READ_PROCESS_MEMORY:
    {

        if (!MmIsAddressValid(OutputData) || OutputLength>MAX_LENGTH)
        {
            Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
            Irp->IoStatus.Information = 0;
            break;
        }

        if (InputLength != sizeof(READ_OPERATION) || InputData == NULL)
        {
            Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
            Irp->IoStatus.Information = 0;

            break;
        }
        __try
        {

            ProbeForWrite(OutputData, OutputLength, 1);
            Status = RtlReadVirtualMemory(InputData, OutputData, OutputLength);
            Irp->IoStatus.Information = 0;
            Irp->IoStatus.Status = Status;

        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            Irp->IoStatus.Information = 0;
            Status = Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
        }

        break;
    }
    case CTL_WRITE_PROCESS_MEMORY:
    {

        if (InputLength < sizeof(WRITE_OPERATION) || InputData == NULL)
        {
            Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
            Irp->IoStatus.Information = 0;

            break;
        }
        __try
        {
            Status = RtlWriteVirtualMemory(InputData);
            Irp->IoStatus.Information = 0;
            Irp->IoStatus.Status = Status;

        }
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            Irp->IoStatus.Information = 0;
            Status = Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
        }
        break;
    }
    default:
    {

        Irp->IoStatus.Information = 0;
        Status = Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
        break;
    }
    }


    IoCompleteRequest(Irp, IO_NO_INCREMENT);       //將Irp返回給IO管理器
    return Status;
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM