串口过滤

设备绑定的内核API

进行过滤的最主要方法是对一个设备对象(Device Object)进行绑定
通过编程可以生成一个虚拟的设备对象,并绑定在一个真实的设备上,一旦绑定,则本来操作系统发送给真实设备的请求,会首先发送到这个虚拟设备上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//将设备绑定到自己的虚拟设备上,被绑设备必须有名称
NTSTATUS IoAttachDevice(
_In_ PDEVICE_OBJECT SourceDevice, //自己的虚拟设备
_In_ PUNICODE_STRING TargetDevice, //被绑定的设备名称
_Out_ PDEVICE_OBJECT *AttachedDevice //被绑定的设备指针
);

//根据设备对象指针进行绑定
NTSTATUS IoAttachDeviceToDeviceStackSafe(
_In_ PDEVICE_OBJECT SourceDevice, //自己的虚拟设备
_In_ PDEVICE_OBJECT TargetDevice, //要绑定的设备栈中的设备
_Out_ PDEVICE_OBJECT *AttachedToDeviceObject //返回最终被绑定的设备
);

//常用函数
PDEVICE_OBJECT IoAttachDeviceToDeviceStack(
_In_ PDEVICE_OBJECT SourceDevice, //自己的虚拟设备
_In_ PDEVICE_OBJECT TargetDevice //要绑定的设备栈中的设备
);

进行设备绑定时,总是会绑定位于设备栈顶层的设备

生成过滤设备并绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
//生成过滤设备
NTSTATUS IoCreateDevice(
_In_ PDRIVER_OBJECT DriverObject, //驱动对象指针
_In_ ULONG DeviceExtensionSize, //指定设备扩展的大小
_In_opt_ PUNICODE_STRING DeviceName, //过滤设备一般没有名称(NULL)
_In_ DEVICE_TYPE DeviceType, //设备类型,与被绑定设备一致
_In_ ULONG DeviceCharacteristics, //设置设备对象属性
_In_ BOOLEAN Exclusive, //内核模式使用标志(FALSE)
_Out_ PDEVICE_OBJECT *DeviceObject //保存返回的对象地址
);

//示例,生成设备并绑定
NTSTATUS ToAttachDevice(
PDRIVER_OBJECT driver, //
PDRIVER_OBJECT oldobj, //被绑定的设备(下层设备)
PDRIVER_OBJECT *fltobj, //生成设备的指针
PDRIVER_OBJECT *next)
{
NTSTATUS status;
PDRIVER_OBJECT topdev = NULL;

//生成设备
status = IoCreateDevice(driver,
0,
NULL,
oldobj->DeviceType,
0,
FALSE,
fltobj);
if(status != STATUS_SUCCESS)
{
return status;
}

//根据下层设备设置符号位
if(oldobj->Flags & DO_BUFFERED_IO)
{
(*fltobj)->Flags |= DO_BUFFERED_IO;
}
if(oldobj->Flags & DO_DIRECT_IO)
{
(*fltobj)->Flags |= DO_DIRECT_IO;
}
if(oldobj->Flags & DO_BUFFERED_IO)
{
(*fltobj)->Flags |= DO_BUFFERED_IO;
}
if(oldobj->Characteristics & FILE_DEVICE_SECURE_OPEN)
{
(*fltobj)->Characteristics |= FILE_DEVICE_SECURE_OPEN;
}

// 如果绑定失败了,销毁设备,重新来过
if (topdev == NULL)
{
IoDeleteDevice(*fltobj);
*fltobj = NULL;
status = STATUS_UNSUCCESSFUL;
return status;
}
*next = topdev;

// 设置这个设备已经启动。
(*fltobj)->Flags = (*fltobj)->Flags & ~DO_DEVICE_INITIALIZING;

return STATUS_SUCCESS;
}

从名字获得设备对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//用名字获得设备对象指针,指针保存在参数中,返回状态
NTSTATUS IoGetDeviceObjectPointer(
_In_ PUNICODE_STRING ObjectName, //名称字符串
_In_ ACCESS_MASK DesiredAccess, //访问令牌
_Out_ PFILE_OBJECT *FileObject, //文件指针,调用成功后解引用
_Out_ PDEVICE_OBJECT *DeviceObject //返回的对象指针
);

PDEVICE_OBJECT ccpOpenCom(ULONG id,NTSTATUS *status)
{
UNICODE_STRING name_str;
static WCHAR name[32] = { 0 };
PFILE_OBJECT fileobj = NULL;
PDEVICE_OBJECT devobj = NULL;

// 输入字符串
memset(name,0,sizeof(WCHAR)*32);
RtlStringCchPrintfW(
name,32,
L"\\Device\\Serial%d",id);
RtlInitUnicodeString(&name_str,name);

// 打开设备对象
*status = IoGetDeviceObjectPointer(&name_str, FILE_ALL_ACCESS, &fileobj, &devobj);
if (*status == STATUS_SUCCESS)
ObDereferenceObject(fileobj); //必须解除引用

return devobj;
}
成功打开设备对象后必须调用ObDereferenceObject解除引用

绑定所有串口

串口不会出现插拔的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#define CCP_MAX_COM_ID 32		//假定串口只有32个

// 过滤设备和真实设备
static PDEVICE_OBJECT s_fltobj[CCP_MAX_COM_ID] = { 0 }; //过设指针
static PDEVICE_OBJECT s_nextobj[CCP_MAX_COM_ID] = { 0 }; //实设指针

// 这个函数绑定所有的串口。
void ccpAttachAllComs(PDRIVER_OBJECT driver)
{
ULONG i;
PDEVICE_OBJECT com_ob;
NTSTATUS status;
for(i = 0;i<CCP_MAX_COM_ID;i++)
{
// 获得object引用
com_ob = ccpOpenCom(i,&status);
if(com_ob == NULL)
continue;
// 在这里绑定。并不管绑定是否成功
ccpAttachDevice(driver,com_ob,&s_fltobj[i],&s_nextobj[i]);
}
}

请求的区分

请求中包含有要发送的数据,请求的回答中则含有要接收的数据
  1. 每个驱动程序只有一个驱动对象
  2. 每个驱动程序可以生成若干个设备对象,这些对象从属于一个驱动对象
  3. 若干设备(可以从属于不同的驱动)一次绑定形成一个设备栈,最顶端设备最先收到请求
请求有多种类型,在串口过滤中只需要考虑读请求(接收)和写请求(发出),请求类型通过IRP的主功能号进行区分,IRP的主功能号保存在IRP栈空间(irpsp)中的一个字节
1
2
3
4
//用来获得当前栈空间
PIO_STACK_LOCATION IoGetCurrentIrpStackLocation(
_In_ PIRP Irp
);
过滤设备对象被绑定在设备栈顶端,所以首先接收到IRP请求的时过滤设备对象
1
2
3
4
5
6
7
8
9
10
//将栈空间指针下移,即跳过当前栈空间
VOID IoSkipCurrentIrpStackLocation(
[in, out] PIRP Irp
);

//将IRP请求分发到对应真实设备
NTSTATUS IoCallDriver(
_In_ PDEVICE_OBJECT DeviceObject,
_Inout_ PIRP Irp
);

写请求数据

IRP数据结构中有三个域描述缓冲区

  1. irp->MdlAddress 缓冲方式
  2. irp->UserBuffer 其他方式(最追求效率)
  3. irp->AssociatesIrp.SystemBuffer 直接方式(用于不追请效率)
把一块内存映射到内核空间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
//缓冲区处理
PVOID MmGetSystemAddressForMdlSafe(
_in_ PMDL Mdl,
_in_ MM_PAGE_PRIORITY Priority
);

PBYTE buffer = NULL;
if(irp->MdlAddress != NULL)
{
buffer = (PBYTE)MmGetSystemAddressForMdlSafe
(irp->MdlAddress,
NormalPagePriority);
}
else
{
buffer = (PBYTE)irp->UserBuffer;
}
if(buffer == NULL)
{
buffer = (PBYTE)irp->AssociatesIrp.SystemBuffer;
}

//分发函数
NTSTATUS ccpDispatch(PDEVICE_OBJECT device,PIRP irp)
{
PIO_STACK_LOCATION irpsp =
IoGetCurrentIrpStackLocation(irp); //获取栈空间
NTSTATUS status;
ULONG i,j;

// 判断发送给哪个设备
for(i=0;i<CCP_MAX_COM_ID;i++)
{
if(s_fltobj[i] == device)
{
// 所有电源操作,全部直接放过
if(irpsp->MajorFunction == IRP_MJ_POWER)
{
// 直接发送,然后返回说已经被处理了
PoStartNextPowerIrp(irp);
IoSkipCurrentIrpStackLocation(irp); //跳过当前栈空间
return PoCallDriver(s_nextobj[i],irp); //下发执行
}
// 此外我们只过滤写请求。写请求的话,获得缓冲区以及其长度。
// 然后打印一下。
if(irpsp->MajorFunction == IRP_MJ_WRITE)
{
// 如果是写,先获得长度
ULONG len = irpsp->Parameters.Write.Length;
// 然后获得缓冲区
PUCHAR buf = NULL;
if(irp->MdlAddress != NULL)
{
buf = (PUCHAR)MmGetSystemAddressForMdlSafe
(irp->MdlAddress,
NormalPagePriority);
}
else
{
buf = (PUCHAR)irp->UserBuffer;
}
if(buf == NULL)
{
buf = (PUCHAR)irp->AssociatedIrp.SystemBuffer;
}
// 打印内容
for(j=0;j<len;++j)
{
DbgPrint("comcap: Send Data: %2x\r\n", buf[j]);
}
}
// 这些请求直接下发执行即可。我们并不禁止或者改变它
IoSkipCurrentIrpStackLocation(irp);
return IoCallDriver(s_nextobj[i],irp);
}
}

// 如果根本就不在被绑定的设备中,那是有问题的,直接返回参数错误
irp->IoStatus.Information = 0;
irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
IoCompleteRequest(irp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}

动态卸载

在卸载函数中完成解除绑定

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//将绑定设备解绑
VOID IoDetachDevice(
_Inout_ PDEVICE_OBJECT TargetDevice
);

//删除设备
VOID IoDeleteDevice(
_In_ PDEVICE_OBJECT DeviceObject
);

//指定当前线程等待一定时间
NTSTATUS KeDelayExecutionThread(
_In_ KPROCESSOR_MODE WaitMode,
_In_ BOOLEAN Alertable,
_In_ PLARGE_INTEGER Interval
);

//示例
void ccpUnload(PDRIVER_OBJECT drv)
{
ULONG i;
LARGE_INTEGER interval;

// 首先解除绑定
for(i=0;i<CCP_MAX_COM_ID;i++)
{
if(s_nextobj[i] != NULL)
IoDetachDevice(s_nextobj[i]);
}

// 睡眠5秒,等待所有irp处理结束
interval.QuadPart = (5*1000 * DELAY_ONE_MILLISECOND);
KeDelayExecutionThread(KernelMode,FALSE,&interval);

// 删除这些设备
for(i=0;i<CCP_MAX_COM_ID;i++)
{
if(s_fltobj[i] != NULL)
IoDeleteDevice(s_fltobj[i]);
}
}