在本文中,将探讨Windows驱动开发中的核心概念:设备栈和设备扩展,并指导如何使用Windows驱动模型(WDM)和C++在Windows 7 64位系统上构建驱动。
在开始之前,建议读者先了解设备驱动的基础知识,推荐阅读Toby Opferman的相关著作。此外,读者需要掌握如何构建驱动,并需要安装Windows驱动开发工具包(WDK)和Dbgview(可在Google上搜索)。
将编写在环 '0'(内核模式)执行的代码,处理器特权保护提供给执行任务大于环 '0' 的代码。代码可以自由修改以使用DbgPrint打印,CS寄存器。
注意最后两位将被设置为0,表示环 '0' 代码执行。环 '0' 代码需要访问一些标记为特权的内存部分,这些内存只能通过特权段访问(即,当前特权级别 < 3),而用户代码在CPL=3运行(因此无法访问它们)。
附带的代码是用C++编写的,必须始终参考。将从编写设备入口函数开始(类似于C/C++用户模式程序中的 'main' 或 'winmain'):
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING RegistryPath);
接下来,需要创建一个设备:
NtStatus = IoCreateDevice(pDriverObject,
sizeof(PDEVICE_OBJECT), NULL,
FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDeviceObject_main);
IoCreateDevice允许创建一个设备并分配四个字节:sizeof(PDEVICE_OBJECT)。这段内存将属于创建的设备的上下文,并称为设备扩展。这种与设备扩展相关的内存始终可以使用设备指针访问。它通常用于存储通过调用ExAllocatePool创建的内存,并非常适合在不同驱动之间传递内存。其他驱动可以通过调用IoAttachDevice(稍后会有更多介绍)或IoGetDeviceObjectPointer来访问此指针。
RtlInitUnicodeString(&usDeviceToFilter, L"\\Device\\tcp");
NtStatus = IoAttachDevice(pDeviceObject_main, &usDeviceToFilter,
(PDEVICE_OBJECT*)&pDeviceObject_main->DeviceExtension);
如前所述,使用IoAttachDevice,将其附加到设备\TCP,因为正在钩住TCP调用。当驱动被钩住时,执行钩住的驱动(创建的,而不是设备\TCP)将接收对这个设备的调用。
驱动现在位于设备栈的顶部(直到有人钩住设备\TCP)。驱动有责任将这些请求传递到栈中的下一层驱动。最低的驱动通常是端口驱动(也称为,miniports)。
请求和参数通过IRP(IO请求包)传递给设备,在分层驱动(如,使用设备栈)中。因此,每个设备栈都有一个IRP栈。必须注册所有可能的IRP函数,以便每个IRP都有一个函数:
for (uiIndex = 0; uiIndex < IRP_MJ_MAXIMUM_FUNCTION; uiIndex++)
pDriverObject->MajorFunction[uiIndex] = UnSupportedFunction;
UnSupportedFunction除了将IRP传递到更低的驱动之外什么也不做:
NTSTATUS UnSupportedFunction(PDEVICE_OBJECT DeviceObject, PIRP Irp) {
NTSTATUS NtStatus = STATUS_NOT_SUPPORTED;
IoSkipCurrentIrpStackLocation(Irp);
NtStatus = IoCallDriver((PDEVICE_OBJECT)DeviceObject->DeviceExtension, Irp);
return NtStatus;
}
必须跳过当前IRP处理,并将其盲目地传递给栈中的下一个驱动。
要嗅探网络包,只对IRP_MJ_INTERNAL_DEVICE_CONTROL感兴趣。添加以下行以确保调用与IO处理相关的适当函数:
pDriverObject->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = IOFunction;
IOFunction每次IRP的当前堆栈位置主要函数等于IRP_MJ_INTERNAL_DEVICE_CONTROL时都会被调用。要处理任何函数(在情况下是IOFunction),必须获取当前堆栈位置:
PIO_STACK_LOCATION StackIrpPointer= IoGetCurrentIrpStackLocation(Irp);
通过这个可以获取从用户模式代码传递下来的参数(参考代码):
PTDI_REQUEST_KERNEL_CONNECT TDI_connectRequest =
(PTDI_REQUEST_KERNEL_CONNECT)(&StackIrpPointer->Parameters);
PTA_ADDRESS TA_Address_data = ((PTRANSPORT_ADDRESS)(TDI_connectRequest->RequestConnectionInformation->RemoteAddress))->Address;
PTDI_ADDRESS_IP TDI_data = (PTDI_ADDRESS_IP)(TA_Address_data->Address);
{
UINT Address = TDI_data->in_addr;
UINT Port = TDI_data->sin_port;
DbgPrint("address:%x Port: %x",Address,Port);
}
还添加了发送和接收代码。对于发送代码,使用MmGetSystemAddressForMdlSafe,因为使用的是METHOD_IN_DIRECT。对于接收:
PCHAR pWriteDataBuffer = 0;
DbgPrint(IoCopyCurrentIrpStackLocationToNext(Irp);
IoSetCompletionRoutine(Irp,Complete,0,TRUE,TRUE,TRUE);
NtStatus = IoCallDriver((PDEVICE_OBJECT)DeviceObject->DeviceExtension, Irp);
"inside recv function to pull out data");
现在复制当前堆栈位置到下一个,而不是跳过它,因为正在调用IOSetCompletionRoutine。这将设置一个完成例程,当IO完成时会被调用。
驱动的安装方式与服务类似,将使用服务控制管理器进行此操作。
system("sc create Driver1 binPath=\"..//Driver.sys\" type= kernel");
system("sc start Driver1");
MessageBoxA(0,"UnloadFilter","UnloadFilter",0);"sc stop Driver1");
system("sc stop Driver1");
system("sc delete Driver1");