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