我们为什么要学习驱动的通信
试想一下,我们未来写的某个驱动,他要发挥作用,那么处在R0的它必须要R3的程序相互通信,传输字符串,传输结果,传输数据(如果不能正常的做到这一点那么就算你随便读内存了也看不见啊是不是)
理论准备
驱动本身无法进行通信,但是我们可以通过挂载在驱动对象下的设备进行通信,也就是说如果我们想要通信,那就首先要有设备
IoCreateDevice
我们使用IoCreateDevice来根据我们的驱动来创建设备对象,具体的参数如下,我将一一介绍
NTSTATUS
IoCreateDevice(
_In_ PDRIVER_OBJECT DriverObject,//驱动的指针,我们要填入的就是我们的当前设备
_In_ ULONG DeviceExtensionSize,//这个在下面额外讲
_In_opt_ PUNICODE_STRING DeviceName,//给我们设备起的名字
_In_ DEVICE_TYPE DeviceType,//设备的类型,下面介绍
_In_ ULONG DeviceCharacteristics,//设备属性,一般填FILE_DEVICE_SECURE_OPEN
_In_ BOOLEAN Exclusive,//是否可共享,也就是当设备被使用时,其他人是否能一同使用
_Outptr_result_nullonfailure_
_At_(*DeviceObject,
__drv_allocatesMem(Mem)
_When_((((_In_function_class_(DRIVER_INITIALIZE))
||(_In_function_class_(DRIVER_DISPATCH)))),
__drv_aliasesMem))
PDEVICE_OBJECT *DeviceObject//最后是我们要定义一个指针来接住我们申请的设备
);
第二个参数是DeviceExtensionSize,它会指定一个额外的空间来存储结构信息,比如我们创建了某个设备它有一些额外的参数,信息,就可以被存储在这个扩展出来的空间里面
0: kd> dt _DEVICE_OBJECT//打开我们的设备结构体
nt!_DEVICE_OBJECT
+0x000 Type : Int2B
+0x002 Size : Uint2B
+0x004 ReferenceCount : Int4B
+0x008 DriverObject : Ptr32 _DRIVER_OBJECT
+0x00c NextDevice : Ptr32 _DEVICE_OBJECT
+0x010 AttachedDevice : Ptr32 _DEVICE_OBJECT
+0x014 CurrentIrp : Ptr32 _IRP
+0x018 Timer : Ptr32 _IO_TIMER
+0x01c Flags : Uint4B
+0x020 Characteristics : Uint4B
+0x024 Vpb : Ptr32 _VPB
+0x028 DeviceExtension : Ptr32 Void
+0x02c DeviceType : Uint4B
+0x030 StackSize : Char
+0x034 Queue : <unnamed-tag>
+0x05c AlignmentRequirement : Uint4B
+0x060 DeviceQueue : _KDEVICE_QUEUE
+0x074 Dpc : _KDPC
+0x094 ActiveThreadCount : Uint4B
+0x098 SecurityDescriptor : Ptr32 Void
+0x09c DeviceLock : _KEVENT
+0x0ac SectorSize : Uint2B
+0x0ae Spare1 : Uint2B
+0x0b0 DeviceObjectExtension : Ptr32 _DEVOBJ_EXTENSION//在这里
+0x0b4 Reserved : Ptr32 Void
0: kd> dt _DEVOBJ_EXTENSION
nt!_DEVOBJ_EXTENSION
+0x000 Type : Int2B
+0x002 Size : Uint2B
+0x004 DeviceObject : Ptr32 _DEVICE_OBJECT
+0x008 PowerFlags : Uint4B
+0x00c Dope : Ptr32 _DEVICE_OBJECT_POWER_EXTENSION
+0x010 ExtensionFlags : Uint4B
+0x014 DeviceNode : Ptr32 Void
+0x018 AttachedTo : Ptr32 _DEVICE_OBJECT
+0x01c StartIoCount : Int4B
+0x020 StartIoKey : Int4B
+0x024 StartIoFlags : Uint4B
+0x028 Vpb : Ptr32 _VPB
+0x02c DependentList : _LIST_ENTRY
+0x034 ProviderList : _LIST_ENTRY
设备类型有很多,我们一般选FILE_DEVICE_UNKNOWN,因为我们的写驱动申请的设备我们也不知道算什么
#define DEVICE_TYPE ULONG
#define FILE_DEVICE_BEEP 0x00000001
#define FILE_DEVICE_CD_ROM 0x00000002
...................................................
#define FILE_DEVICE_TAPE_FILE_SYSTEM 0x00000020
#define FILE_DEVICE_TRANSPORT 0x00000021
#define FILE_DEVICE_UNKNOWN 0x00000022//一般我们的设备申请这个
#define FILE_DEVICE_VIDEO 0x00000023
#define FILE_DEVICE_VIRTUAL_DISK 0x00000024
#define FILE_DEVICE_WAVE_IN 0x00000025
#define FILE_DEVICE_WAVE_OUT 0x00000026
#define FILE_DEVICE_8042_PORT 0x00000027
.....................................................
#define FILE_DEVICE_STORAGE_REPLICATION 0x00000055
#define FILE_DEVICE_TRUST_ENV 0x00000056
#define FILE_DEVICE_UCM 0x00000057
#define FILE_DEVICE_UCMTCPCI 0x00000058
创建完设备之后,为了方便我们的使用,我们还需要创建一个符号链接
IoCreateSymbolicLink
NTSTATUS
IoCreateSymbolicLink(
_In_ PUNICODE_STRING SymbolicLinkName,
_In_ PUNICODE_STRING DeviceName
);
_DEVICE_OBJECT的FLAG位
这里面我们要去掉一个位,因为有的程序会在通信的时候检查,如果是正在初始化就无法通信
DO_DEVICE_INITIALIZING
- 作用:表示设备对象正在初始化。这个标志位在设备对象创建时自动设置,设备初始化完成后由系统清除。
- 使用场景:驱动程序可以检查这个标志位来判断设备是否已经完成初始化。
还需要加上一个位
DO_BUFFERED_IO
- 作用:表示设备支持缓冲 I/O 操作,即 I/O 请求通过系统缓冲区进行。
- 使用场景:适用于需要简单 I/O 操作的设备,可以减少驱动程序的实现复杂性。
回调函数
typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
.........................................
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload;
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];//这里装我们的回调函数
} DRIVER_OBJECT;
typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT;
这组回调的定义如下,先暂时介绍几个我们马上可能要用到的
#define IRP_MJ_CREATE 0x00//创建或者打开 ZwCreateFile/ZwOpenFile
#define IRP_MJ_CREATE_NAMED_PIPE 0x01//打开命名管道 ZwCreateFile/ZwOpenFile
#define IRP_MJ_CLOSE 0x02//关闭设备CloseHandle
#define IRP_MJ_READ 0x03//读设备
#define IRP_MJ_WRITE 0x04//写设备
#define IRP_MJ_QUERY_INFORMATION 0x05//查询设备信息 ZwQueryInformationFile
#define IRP_MJ_SET_INFORMATION 0x06//设置设备信息 ZwSetInformationFile
...........................................
#define IRP_MJ_DEVICE_CONTROL 0x0e//设备控制
#define IRP_MJ_INTERNAL_DEVICE_CONTROL 0x0f
......................................
#define IRP_MJ_PNP_POWER IRP_MJ_PNP // Obsolete....
#define IRP_MJ_MAXIMUM_FUNCTION 0x1b
IRP
在继续介绍下面的知识之前,还需要知道一个概念,IRP
I/O request packets,简称IRP。即输入输出请求包。它是WINDOWS内核中的一种非常重要的数据结构。上层应用程序与底层驱动程序通信时,应用程序会发出I/O请求。操作系统将相应的I/O请求转换为相应的IRP。不同的IRP会根据类型被分派的不同的派遣历程中进行处理。
也就是我们信息的传输本质上还是依靠IRP进行的,它会被发送给我们的设备,暂时不需要了解IRP的结构特征
说完了这些,我们继续向下,回调函数的结构是PDRIVER_DISPATCH,我们就按照这个给的结构来写
typedef
NTSTATUS
DRIVER_DISPATCH (
_In_ struct _DEVICE_OBJECT *DeviceObject,
_Inout_ struct _IRP *Irp
);
上面的两个参数,一个是我们的设备对象,一个就是我们刚刚介绍的IRP
我们稍微修改一下这个函数
NTSTATUS DeviceCreate (
_In_ struct _DEVICE_OBJECT *DeviceObject,
_Inout_ struct _IRP *Irp
){
DbgPrint("Create MyDevice Success!\r\n");
IoCompleteRequest(Irp,0);//我们要调用这个函数来完成我们的IRP请求
return STATUS_SUCCESS;
}
示例代码
在上面的理论知识吸收完之后,我们就可以给出代码了,我比较懒,就不重复解释代码的作用了
#include<ntifs.h>
#define _DEVICE_NAME L"\\device\\MyDevice"
#define _SYM_NAME L"\\??\\MyDevice"
VOID DriverUnload(PDRIVER_OBJECT pDriver) {
//别忘了要卸载驱动的时候连同设备一起
if (pDriver->DeviceObject) {
UNICODE_STRING UnSYMName = { 0 };
RtlInitUnicodeString(&UnSYMName, _SYM_NAME);
IoDeleteSymbolicLink(&UnSYMName);//删除的顺序和申请是相反的
IoDeleteDevice(pDriver->DeviceObject);
}
}
NTSTATUS DeviceCreate(
_In_ struct _DEVICE_OBJECT *DeviceObject,
_Inout_ struct _IRP *Irp
) {
DbgPrint("Create MyDevice Success!\r\n");
IoCompleteRequest(Irp, 0);//我们要调用这个函数来完成我们的IRP请求
return STATUS_SUCCESS;
}
NTSTATUS DeviceDelete(
_In_ struct _DEVICE_OBJECT *DeviceObject,
_Inout_ struct _IRP *Irp
) {
DbgPrint("Delete MyDevice Success!\r\n");
IoCompleteRequest(Irp, 0);//我们要调用这个函数来完成我们的IRP请求
return STATUS_SUCCESS;
}
NTSTATUS DeviceFunc(
_In_ struct _DEVICE_OBJECT *DeviceObject,
_Inout_ struct _IRP *Irp
) {
DbgPrint("This is MyFunction\r\n");
IoCompleteRequest(Irp, 0);//我们要调用这个函数来完成我们的IRP请求
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pReg) {
UNICODE_STRING UnDeviceName = { 0 };
RtlInitUnicodeString(&UnDeviceName, _DEVICE_NAME);
UNICODE_STRING UnSYMName = { 0 };
RtlInitUnicodeString(&UnSYMName, _SYM_NAME);
PDEVICE_OBJECT pDevice = NULL;
NTSTATUS status = IoCreateDevice(pDriver, 0, &UnDeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDevice);//创建设备
if (!NT_SUCCESS(status)) {
return STATUS_UNSUCCESSFUL;
}
status = IoCreateSymbolicLink(&UnSYMName, &UnDeviceName);//创建符号链接
if (!NT_SUCCESS(status)) {
IoDeleteDevice(&pDevice);
return STATUS_UNSUCCESSFUL;
}
//FLAG位修改
pDevice->Flags &= ~DO_DEVICE_INITIALIZING;
pDevice->Flags |= DO_BUFFERED_IO;
//设置回调
pDriver->MajorFunction[IRP_MJ_CREATE] = DeviceCreate;
pDriver->MajorFunction[IRP_MJ_CLOSE] = DeviceDelete;
pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceFunc;
pDriver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}
在win7上面加载了之后,我们可以通过Windbg来检验到底设备创建成功没有
1: kd> !devobj \Device\MyDevice//win不区分大小写
Device object (86be8ae8) is for://这里告诉我们的设备的地址,就说明创建成功了
MyDeviceUnable to load image MyDriver3.sys, Win32 error 0n2
\Driver\MyDriver3 DriverObject 88b68b50
Current Irp 00000000 RefCount 0 Type 00000022 Flags 00000044
SecurityDescriptor 8da53ae8 DevExt 00000000 DevObjExt 86be8ba0
ExtensionFlags (0x00000800) DOE_DEFAULT_SD_PRESENT
Characteristics (0x00000100) FILE_DEVICE_SECURE_OPEN
Device queue is not busy.
然后,在最后,我们写一个程序来和我们的设备通信
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include<Windows.h>
#define _SYM_NAME "\\\\.\\MyDevice"//这里提一下,两个斜杠是因为转义符,暂且理解为两个斜杠合成一个斜杠就行了
int main()
{
HANDLE hDevice = CreateFileA(_SYM_NAME, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL);
if (!hDevice) {
printf("[-] Open Device Error!!\r\n");
}
else {
system("pause");
ULONG x = 0;
ULONG retLen = 0;//用于接受实际返回的字数
BOOL st = DeviceIoControl(hDevice, 0, &x, 4, &x, 4, &retLen, 0);//这个函数后面会细说
printf("st is %d\r\n", st);
}
return 0;
}
打开DbgView来看回调消息,成功