在「進程內存管理器中」的一個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; }