设备挂钩






4.80/5 (13投票s)
设备挂钩
引言
网络嗅探器是文章 https://codeproject.org.cn/Articles/163408/APIHooking 的续篇,主要侧重于设备钩取。在这里,我计划介绍设备栈、设备扩展,并使用 C++(在 Windows 7:64 位系统上)使用 WDM 构建驱动程序。
背景
建议读者阅读 Toby Opferman 关于设备驱动程序的文章,读者需要了解如何构建驱动程序。此外,您还需要 WDK (Windows Driver Kit) 和 Dbgview(请自行搜索)。
我们将编写在“0”环(内核模式)下执行的代码,处理器特权保护机制用于执行高于“0”环的任务。请随时修改代码以使用 DbgPrint 打印 CS 寄存器。请注意,最后两位将设置为 0,表示“0”环代码执行。需要“0”环代码来访问内存中被页面表标记为特权的某些部分。特权页面只能被特权段访问(即,当前特权级别 < 3),用户代码在 CPL=3 下运行(因此无法访问它们)。此外,要访问 IO,CPL <= IOPL。
使用代码
附带的代码是用 C++ 编写的,必须随时参考。
我们将从编写一个设备入口函数开始(类似于 C/C++ 用户模式程序的 'main
'/'winmain
')
extern "C" NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject, PUNICODE_STRING RegistryPath);
// remember we are using C++, remember to provide external linkages for C//
接下来我们需要做的是创建一个设备
NtStatus = IoCreateDevice(pDriverObject, sizeof(PDEVICE_OBJECT), NULL,
FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDeviceObject_main);
//allocate in-device memory: sizeof(PDEVICE_OBJECT))
IoCreateDevice
允许您创建设备并分配四个字节:sizeof(PDEVICE_OBJECT)
。这块内存将属于我们创建的设备的上下文,称为设备扩展。这个与设备扩展相关的内存始终可以通过设备指针访问。它通常用于存储调用 ExAllocatePool
创建的内存的指针,并且非常适合在不同驱动程序之间传递内存。其他驱动程序可以通过调用 IoAttachDevice
(稍后详述)或调用 IoGetDeviceObjectPointer
来访问此指针。
现在我们附加驱动程序。
RtlInitUnicodeString(&usDeviceToFilter, L"\\Device\\tcp");
NtStatus = IoAttachDevice(pDeviceObject_main, &usDeviceToFilter,
(PDEVICE_OBJECT*)&pDeviceObject_main->DeviceExtension);
如前所述,我们使用 IoAttachDevice
,我们将其附加到 device\TCP,因为我们正在钩取 TCP 调用。
当一个驱动程序被钩取时,进行钩取的驱动程序(我们创建的驱动程序,而不是 device\TCP)将接收到发往该设备的所有调用。我们的驱动程序现在位于设备栈的顶部(除非有人钩取了 device\TCP)。驱动程序的责任是将这些请求向下传递给栈中的较低驱动程序。最低的驱动程序通常是您的端口驱动程序(又名,迷你端口)。
请求连同参数通过 IRP(IO 请求包)传递给设备,在分层驱动程序(如我们的,使用设备栈)中。栈中的每个设备都有一个 IRP。因此,我们有一个 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; //(refer to code)
每次 IRP 的当前堆栈位置主函数等于 IRP_MJ_INTERNAL_DEVICE_CONTROL
时,都会调用 IOFunction
。
要处理任何函数(在本例中为 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");
附加代码:SimpleSocket 是供您测试代码的,simple socket 是一个简单的服务器,在使用过滤驱动程序处于活动状态时,使用 Telnet 连接到它,并在 Dbgview 中查看输出。
关注点
我希望读者已经学会了作为网络嗅探器(过滤驱动程序)实现的设备栈。
如前所述,本文仅旨在教授读者设备栈而不是网络过滤器,TDI 的实现改编自互联网上的文章。