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

在「進程內存管理器中」的一個Ring0,Ring3層通訊問題,以前也見過這樣的代碼,此次拆分出來詳細總結一下。html

  先經過CreateFile函數獲得設備句柄,CreateFile函數原型:編程

  

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

打開:createFilewindows

關閉:closehandle安全

與普通文件名有所不一樣,設備驅動的「文件名」(常稱爲「設備路徑」)形式固定爲「\\.\DeviceName」(注意寫法爲「\\\\.\\DeviceName」),DeviceName必須與設備驅動程序內定義的設備名稱一致。數據結構

在「進程內存管理器」中:app

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了一個驅動設備名和一個符號連接名spa

驅動設備名是調用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;
}

 

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

相關文章
相關標籤/搜索