文章总结: 本文介绍编写Windows摄像头过滤驱动篡改图像输出的方法。详细解析了配置INF文件挂载驱动、实现内核函数拦截设备栈、处理IRP获取流参数及通过IOCTL修改帧缓冲区的全过程。实验成功将图像像素置零,展示了过滤驱动在底层系统干预与数据劫持中的实战应用。 综合评分: 86 文章分类: 二进制安全,安全开发,实战经验
写一个 Windows 摄像头过滤驱动篡改摄像头输出图像
原创
MyStackTrace
MyStackTrace
2025年11月30日 00:25 北京
Windows 的过滤驱动 (Filter Driver) 是一类非常特殊的 Windows 驱动,这类驱动本身是不能独立工作的,它们需要“附着”到现有驱动栈上才能工作。过滤驱动本身不管理硬件,而是像一个“中间人”或“过滤器”,拦截并处理发往目标设备的请求(IRP)和返回的数据。如果把普通的功能驱动比作公司的各个核心部门,负责核心业务,那么过滤驱动就像是公司内部的审计或者监控部门,它负责监控所有核心业务部门的业务,并对这些业务是否合法合规进行评估和修正。
Windows 过滤驱动是一种极其强大的技术,其应用范围非常广泛,它之所以重要,是因为它能在不修改原有核心驱动和硬件的前提下,为系统增加全新的功能层。Windows 过滤驱动在安全与隐私(杀毒软件,加密软件),功能增强(虚拟设备),系统监控(抓包,性能分析)等领域都有着广泛的应用。本文我们以 Windows 摄像头过滤驱动为例,来介绍一下如何写一个 Windows 过滤驱动,而且我会在这个过滤驱动中去篡改摄像头输出的图像。
不说废话了,下面我们就动手来写一下这个过滤驱动。首先是驱动的 INF 文件,不同于功能驱动的 INF 文件,过滤驱动需要在其 INF 文件中指明它想要“附着”的驱动类型。由于我们要写一个 Windows 摄像头驱动的过滤驱动,所以这个过滤驱动首先也必须是 Windows 摄像头驱动类别 (Class=Camera),其次需要我们通过 AddReg 指明这个过滤驱动要是要附加在摄像头设备的驱动栈上(ca3e7ab9-b4c3-4ae6-8251-579ef933890f),而且这里指定的是 UpperFilters,也就是上层过滤驱动,附加在功能驱动之上,最靠近应用程序。上层过滤驱动是最先收到 IRP、最后收到IRP 完成通知的驱动,非常适合做这种数据监控或者篡改的操作。
;; CameraFilterDriver.inf;
[Version]Signature = "$WINDOWS NT$"Class = CameraClassGuid = {ca3e7ab9-b4c3-4ae6-8251-579ef933890f}DriverPackageType = ClassFilterProvider = %PROVIDER%CatalogFile = CameraFilterDriver.catPnpLockdown = 1
[DestinationDirs]DefaultDestDir = 12
[SourceDisksNames]1 = %DiskId1%,,,"."
[SourceDisksFiles]CameraFilterDriver.sys = 1,,
[DefaultInstall.NTamd64]CopyFiles = @CameraFilterDriver.sysAddReg = CameraFilterDriver.AddReg
[DefaultUninstall.NTamd64]LegacyUninstall = 1DelFiles = CameraFilterDriver.DeleteFileSectionDelReg = CameraFilterDriver.DelReg
[CameraFilterDriver.DeleteFileSection]CameraFilterDriver.sys,,,0x00000001 ; 0x00000001 (DELFLG_IN_USE)
[CameraFilterDriver.AddReg];Add CameraFilterDriver to UpperFiltersHKLM, System\CurrentControlSet\Control\Class\{ca3e7ab9-b4c3-4ae6-8251-579ef933890f}, UpperFilters, 0x00010008, CameraFilterDriver
[CameraFilterDriver.DelReg];Delete CameraFilterDriver from UpperFiltersHKLM, System\CurrentControlSet\Control\Class\{ca3e7ab9-b4c3-4ae6-8251-579ef933890f}, UpperFilters, 0x00018002, CameraFilterDriver
[DefaultInstall.NTamd64.Services]AddService = CameraFilterDriver,,Service.AddService
[DefaultUninstall.NTamd64.Services]LegacyUninstall = 1DelService = CameraFilterDriver,0x00000200
[Service.AddService]DisplayName = %CameraFilterDriver.SvcDesc%ServiceType = 1 ; SERVICE_KERNEL_DRIVERStartType = 3 ; SERVICE_DEMAND_STARTErrorControl = 2 ; SERVICE_ERROR_SEVEREServiceBinary = %12%\CameraFilterDriver.sys
[Strings]PROVIDER = "CameraFilterDriver"StdMfg = "CameraFilterDriver Developers"DiskId1 = "CameraFilterDriver Installation Disk #1"CameraFilterDriver.DeviceDesc = "CameraFilterDriver Filter Driver"CameraFilterDriver.SvcDesc = "CameraFilterDriver
下面介绍过滤驱动代码部分,首先是 DriverEntry 函数。这里的驱动入口函数和普通的 WDM 驱动的写法没有区别,基本上就是提供 Windows 设备驱动的几个重要的派遣函数(Dispatch Function)。我们先用函数 DefaultDispatchFunction 把所有的派遣函数指针填一遍作为默认的派遣函数,然后针对 IRP_MJ_CREATE,IRP_MJ_DEVICE_CONTROL,IRP_MJ_POWER 和 IRP_MJ_PNP 设置不同的派遣函数,对这些 IRP 进行一些特别的处理。我们重点实现的函数有 AddDevice 回调函数,处理 IRP_MJ_CREATE 的派遣函数 DispatchCreate 和处理 IRP_MJ_DEVICE_CONTROL 的派遣函数
DispatchDeviceControl。
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath){ WPP_INIT_TRACING(DriverObject, RegistryPath); DriverObject->DriverExtension->AddDevice = AddDevice; for (int i = 0; i < IRP_MJ_MAXIMUM_FUNCTION + 1; i++) { DriverObject->MajorFunction[i] = DefaultDispatchFunction; } DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreate; DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchDeviceControl; DriverObject->MajorFunction[IRP_MJ_POWER] = DispatchPower; DriverObject->MajorFunction[IRP_MJ_PNP] = DispatchPnp; DriverObject->DriverUnload = DriverUnload; return STATUS_SUCCESS;}
AddDevice 回调函数的作用是为过滤驱动创建过滤设备对象,并且调用 Windows 内核 API 函数 IoAttachDeviceToDeviceStack 将我们自己创建的过滤驱动设备对象(Filter Device Object)附加到目标设备的功能驱动设备对象(Functional Device Object, FDO)所在的设备栈(Device Stack)上,从而使我们的驱动能够拦截发往该目标设备的所有 I/O 请求。这里需要记住的是 NextDeviceObject,这就是过滤驱动所附着的驱动栈的下一层驱动对应的 FDO,后面我们需要把所有的 I/O 请求转发给这个 NextDeviceObject。
NTSTATUS AddDevice(IN PDRIVER_OBJECT DriverObject, IN PDEVICE_OBJECT PhysicalDeviceObject){ PDEVICE_EXTENSION devExt = NULL; PDEVICE_OBJECT devObj = NULL; WCHAR szTmpInfo[512]; ULONG cbTmpInfoSize = 0; NTSTATUS status = STATUS_SUCCESS; Trace(TRACE_INFO, "%!FUNC! Entry"); status = IoCreateDevice(DriverObject, sizeof(DEVICE_EXTENSION), NULL, GetDeviceTypeToUse(PhysicalDeviceObject), 0, FALSE, &devObj); if (NT_SUCCESS(status)) { devExt = devObj->DeviceExtension; devExt->DeviceObject = devObj; devExt->NextDeviceObject = IoAttachDeviceToDeviceStack(devObj, PhysicalDeviceObject); devObj->Flags |= devExt->NextDeviceObject->Flags & (DO_BUFFERED_IO | DO_DIRECT_IO | DO_POWER_PAGABLE); devObj->Flags &= ~DO_DEVICE_INITIALIZING; devObj->DeviceType = devExt->NextDeviceObject->DeviceType; devObj->Characteristics = devExt->NextDeviceObject->Characteristics; IoInitializeRemoveLock(&devExt->RemoveLock, POOL_TAG, 0, 0); if (NT_SUCCESS(IoGetDeviceProperty(PhysicalDeviceObject, DevicePropertyFriendlyName, 512 * sizeof(WCHAR), szTmpInfo, (PULONG)&cbTmpInfoSize))) { Trace(TRACE_INFO, "%!FUNC!, device name: %ws", szTmpInfo); } } Trace(TRACE_INFO, "%!FUNC! Exit"); return status;}
在 DriverEntry 中我们给大部分 IRP 类型都设置了一个默认的派遣函数:DefaultDispatchFunction。对于这些类型的 IRP ,我们的过滤驱动是不关心的,所以仅仅是把这些 IRP 传给给下层驱动。核心函数就两个,第一个是 IoSkipCurrentIrpStackLocation,这个函数的作用是告诉内核:“这个 IRP 不是我关心的,我只是过一下手,什么都不改,也不需要知道它什么时候完成,直接让下一层驱动去处理吧。”。第二个核心函数是 IoCallDriver,这才是真正把这个 IRP 发给下一层驱动去处理的操作,所以这里需要指定下一层驱动的 FDO (NextDeviceObject)。
NTSTATUS DefaultDispatchFunction(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp){ NTSTATUS status = STATUS_SUCCESS; PDEVICE_EXTENSION devExt = DeviceObject->DeviceExtension; PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); Trace(TRACE_INFO, "%!FUNC! Entry, MajorFunction=%u, MinorFunction=%u, " "IoControlCode=%x", irpStack->MajorFunction, irpStack->MinorFunction, irpStack->Parameters.DeviceIoControl.IoControlCode); status = IoAcquireRemoveLock(&devExt->RemoveLock, Irp); if (!NT_SUCCESS(status)) { Irp->IoStatus.Status = status; IoCompleteRequest(Irp, IO_NO_INCREMENT); goto done; } IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(devExt->NextDeviceObject, Irp); IoReleaseRemoveLock(&devExt->RemoveLock, Irp);
done: Trace(TRACE_INFO, "%!FUNC! Exit, status=%x", status); return status;}
当用户态的摄像头应用程序打开摄像头的时候会给摄像头驱动发送 IRP_MJ_CREATE 类型的 IRP,我们的过滤驱动是第一个接收到这个 IRP 的驱动,我们要做的就是从这个 IRP 上获取摄像头应用程序设置的摄像头的参数,比如图像格式,分辨率,像素位宽等参数。由于这个 IRP 中有些内容是需要下层驱动填写的,因此我们需要等到下层驱动完成这个 IRP 的时候才能去获取上面的信息。所以这里我们的做法是调用内核 API 函数 IoSetCompletionRoutine 为这个 IRP 设置一个完成函数 DefaultIrpCompletion,然后通过 IoCopyCurrentIrpStackLocationToNext 和 IoCallDriver 的配合把这个 IRP 转发给下层驱动,接下来过滤驱动就等在一个 KEVENT 上,当下层驱动完成这个 IRP 的时候就会调用我们设置的IRP 完成函数 DefaultIrpCompletion,在这个完成函数中我们会通过 KEVENT 唤醒过滤驱动前面的等待,过滤驱动就知道这个 IRP 完成了,就可以放心的从这个 IRP 上获取我们关心的参数了。注意:这里我们把 IRP 转发给下层驱动用的是 IoCopyCurrentIrpStackLocationToNext 函数,这个函数的作用是告诉内核:“这个IRP我要仔细经手,我需要把我的参数复制给下一层,并且等下一层处理完后,我必须亲自检查一下结果。”,对比 IoSkipCurrentIrpStackLocation 和 IoCopyCurrentIrpStackLocationToNext 这两个函数的行为,大家应该能品出过滤驱动对于自己不关心的 IRP 和关心的 IRP 的处理逻辑的区别。
NTSTATUS DefaultIrpCompletion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID Context){ UNREFERENCED_PARAMETER(DeviceObject); PKEVENT event = (PKEVENT)Context; Trace(TRACE_INFO, "%!FUNC! Entry"); if (Irp->PendingReturned == TRUE) { KeSetEvent(event, IO_NO_INCREMENT, FALSE); } Trace(TRACE_INFO, "%!FUNC! Exit"); return STATUS_MORE_PROCESSING_REQUIRED;}
NTSTATUS DispatchCreate(PDEVICE_OBJECT DeviceObject, PIRP Irp){ NTSTATUS status = STATUS_SUCCESS; PDEVICE_EXTENSION devExt = DeviceObject->DeviceExtension; PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); Trace(TRACE_INFO, "%!FUNC! Entry"); status = IoAcquireRemoveLock(&devExt->RemoveLock, Irp); if (!NT_SUCCESS(status)) { Irp->IoStatus.Status = status; IoCompleteRequest(Irp, IO_NO_INCREMENT); goto done; } KEVENT event; KeInitializeEvent(&event, NotificationEvent, FALSE); IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine(Irp, DefaultIrpCompletion, &event, TRUE, TRUE, TRUE); status = IoCallDriver(devExt->NextDeviceObject, Irp); if (status == STATUS_PENDING) { status = KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); } if (irpStack->FileObject->FileName.Buffer) { Trace(TRACE_INFO, "%!FUNC!, Create %ws", irpStack->FileObject->FileName.Buffer); if (irpStack->FileObject->FileName.Length < wcslen(KSSTRING_Pin) * sizeof(WCHAR) + sizeof(KSPIN_CONNECT) + sizeof(KS_DATAFORMAT_VIDEOINFOHEADER)) { IoReleaseRemoveLock(&devExt->RemoveLock, Irp); goto done; } wchar_t *ksPinNameOffset = wcsstr(irpStack->FileObject->FileName.Buffer, KSSTRING_Pin); if (!ksPinNameOffset) { IoReleaseRemoveLock(&devExt->RemoveLock, Irp); goto done; } PKSPIN_CONNECT ksPinConnect = (PKSPIN_CONNECT)(ksPinNameOffset + wcslen(KSSTRING_Pin) + 1); Trace(TRACE_INFO, "%!FUNC!, Ks PinId: %lu", ksPinConnect->PinId); PKSDATAFORMAT ksDataFormat = (PKSDATAFORMAT)(ksPinConnect + 1); Trace(TRACE_INFO, "%!FUNC!, Ks Data Format: Specifier=%!GUID!, MajorFormat=%!GUID!, SubFormat=%!GUID!", &ksDataFormat->Specifier, &ksDataFormat->MajorFormat, &ksDataFormat->SubFormat); Trace(TRACE_INFO, "%!FUNC!, Ks Data Format: Alignment=%lld, Flags=%x, FormatSize=%lu, SampleSize=%lu", ksDataFormat->Alignment, ksDataFormat->Flags, ksDataFormat->FormatSize, ksDataFormat->SampleSize); const GUID g_KSDATAFORMAT_SPECIFIER_VIDEOINFO = { STATIC_KSDATAFORMAT_SPECIFIER_VIDEOINFO }; const GUID g_KSDATAFORMAT_SPECIFIER_VIDEOINFO2 = { STATIC_KSDATAFORMAT_SPECIFIER_VIDEOINFO2 }; if (IsEqualGUID(&ksDataFormat->Specifier, &g_KSDATAFORMAT_SPECIFIER_VIDEOINFO)) { PKS_DATAFORMAT_VIDEOINFOHEADER videoInfoHeader = (PKS_DATAFORMAT_VIDEOINFOHEADER)(ksPinConnect + 1); if (videoInfoHeader) { Trace(TRACE_INFO, "%!FUNC!, Video Info Header: Width=%d, Height=%d, BitCount=%u, Size=%u, SizeImage=%u", videoInfoHeader->VideoInfoHeader.bmiHeader.biWidth, videoInfoHeader->VideoInfoHeader.bmiHeader.biHeight, videoInfoHeader->VideoInfoHeader.bmiHeader.biBitCount, videoInfoHeader->VideoInfoHeader.bmiHeader.biSize, videoInfoHeader->VideoInfoHeader.bmiHeader.biSizeImage); } } else if (IsEqualGUID(&ksDataFormat->Specifier, &g_KSDATAFORMAT_SPECIFIER_VIDEOINFO2)) { PKS_DATAFORMAT_VIDEOINFOHEADER2 videoInfoHeader = (PKS_DATAFORMAT_VIDEOINFOHEADER2)(ksPinConnect + 1); if (videoInfoHeader) { Trace(TRACE_INFO, "%!FUNC!, Video Info Header2: Width=%d, Height=%d, BitCount=%u, Size=%u, SizeImage=%u", videoInfoHeader->VideoInfoHeader2.bmiHeader.biWidth, videoInfoHeader->VideoInfoHeader2.bmiHeader.biHeight, videoInfoHeader->VideoInfoHeader2.bmiHeader.biBitCount, videoInfoHeader->VideoInfoHeader2.bmiHeader.biSize, videoInfoHeader->VideoInfoHeader2.bmiHeader.biSizeImage); } } else { Trace(TRACE_INFO, "%!FUNC!, Unknown KS data format specifier"); } } IoReleaseRemoveLock(&devExt->RemoveLock, Irp);done: Trace(TRACE_INFO, "%!FUNC! Exit, status=%x", status); return status;}
在现代 Windows 系统中,摄像头驱动基于 AVStream (音视频流)内核驱动模型构建。AVStream 与 Windows 的 Kernel Streaming (内核流)架构紧密集成,高效管理视频数据流。因此这里我们需要首先了解 AVStream 驱动框架,然后才能知道如何从 IRP_MJ_CREATE 类型的 IRP 中提取摄像头相关的参数,这里就不介绍细节了,感兴趣的同学可以结合 MSDN 上对 AVStream 驱动框架的介绍来理解上面从 IRP 中提取摄像头参数的代码。
当摄像头被应用程序打开之后,应用程序会调用摄像头驱动的 DeviceIoControl 给摄像头发送帧缓冲区(Frame Buffer),摄像头驱动会控制摄像头硬件填充这个帧缓冲区,最后再把这个帧缓冲区送回给应用程序。我们的过滤驱动需要做的就是拦截这个 DeviceIoControl,获取并记录每一个帧缓冲区,并且在这些帧缓冲区被下层驱动返上来的时候去篡改其中的图像数据。具体实现就在下面这几个函数中。在众多的 DeviceIoControl 的操作码中,我们需要关注的是 IOCTL_KS_READ_STREAM,这就是负责给摄像头驱动传递帧缓冲区的操作。简单来说我们需要从 IOCTL_KS_READ_STREAM 的 IRP 上获取 KSSTREAM_HEADER,然后从 KSSTREAM_HEADER 上获取帧缓冲区的地址 (streamHeader->Data),并且通过一个 MDL 把这个帧缓冲区给锁定在内核。由于我们需要关注这个 IOCTL_KS_READ_STREAM IRP 的完成,因此为其设置了 IRP 完成函数 HandleKSReadStreamDone,并且给这个 IRP 指定了一个参数,就是那个锁定帧缓冲区的 MDL,通过这个 MDL 我们后面就能找到并访问对应的帧缓冲区。当摄像头硬件填充完帧缓冲区之后,下层驱动就会把这个 IRP 给完成了,这个时候就会调用到 HandleKSReadStreamDone,我们在这个函数中从前面传递的参数,也就是那个锁定帧缓冲区的 MDL 上获取到帧缓冲区的地址,并且把帧缓冲区中的前面 200 行像素给填成 0,如果摄像头应用程序选择的是 YUV 的输出格式,这里会把前面 200 行像素的 Y 分量给清零,在最终的图像上我们会看到前面 200 行都是绿色的。
NTSTATUS HandleKSReadStreamDone(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp, PVOID Context){ PDEVICE_EXTENSION devExt = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension; PMDL mdl = (PMDL)Context; PVOID dataBuffer = NULL; if (Irp->PendingReturned) { IoMarkIrpPending(Irp); } PKSSTREAM_HEADER streamHeader = (PKSSTREAM_HEADER)Irp->AssociatedIrp.SystemBuffer; if (streamHeader == NULL) { Trace(TRACE_ERROR, "%!FUNC!, Stream header is NULL"); goto done; } PKS_FRAME_INFO frameInfo = (PKS_FRAME_INFO)((PBYTE)streamHeader + sizeof(KSSTREAM_HEADER)); if (mdl != NULL) { dataBuffer = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority); if (frameInfo->lSurfacePitch) { // This is just an experiment to override the first 200 lines of the frame memset(dataBuffer, 0, frameInfo->lSurfacePitch * 200); } } Trace(TRACE_INFO, "%!FUNC!, Data=%p, DataUsed=%lu, FrameSize=%lu OptionsFlags=%x, " "SurfacePitch=%ld, MDL=%p, dataBuffer=%p", streamHeader->Data, streamHeader->DataUsed, streamHeader->FrameExtent, streamHeader->OptionsFlags, frameInfo->lSurfacePitch, mdl, dataBuffer); if (mdl != NULL) { MmUnlockPages(mdl); IoFreeMdl(mdl); } IoReleaseRemoveLock(&devExt->RemoveLock, Irp);done: return STATUS_CONTINUE_COMPLETION;}
PMDL GetMdlFromKsStreamHeader(PDEVICE_OBJECT DeviceObject, PIRP Irp){ UNREFERENCED_PARAMETER(DeviceObject); NTSTATUS status = STATUS_SUCCESS; PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); ULONG inBufLength = irpStack->Parameters.DeviceIoControl.InputBufferLength; ULONG outBufLength = irpStack->Parameters.DeviceIoControl.OutputBufferLength; PVOID userBuffer = Irp->UserBuffer; PMDL mdl = NULL;
Trace(TRACE_INFO, "%!FUNC!, userBuffer=%p, inBufLength=%lu, outBufLength=%lu", userBuffer, inBufLength, outBufLength); PKSSTREAM_HEADER streamHeader = (PKSSTREAM_HEADER)userBuffer; if (streamHeader == NULL) { Trace(TRACE_ERROR, "[ERROR] %!FUNC!, Stream header is NULL"); status = STATUS_INVALID_PARAMETER; goto done; } Trace(TRACE_INFO, "%!FUNC!, KSSTREAM_HEADER: Data=%p, FrameExtent=%lu, OptionsFlags=%x", streamHeader->Data, streamHeader->FrameExtent, streamHeader->OptionsFlags); PKS_FRAME_INFO frameInfo = (PKS_FRAME_INFO)(streamHeader + 1); Trace(TRACE_INFO, "%!FUNC!, KS_FRAME_INFO: SurfacePitch=%d", frameInfo->lSurfacePitch); mdl = IoAllocateMdl(streamHeader->Data, streamHeader->FrameExtent, FALSE, TRUE, NULL); if (!mdl) { status = STATUS_INSUFFICIENT_RESOURCES; Trace(TRACE_INFO, "%!FUNC!, Allocate MDL failed"); goto done; } try { MmProbeAndLockPages(mdl, UserMode, IoWriteAccess); } except(EXCEPTION_EXECUTE_HANDLER) { status = GetExceptionCode(); Trace(TRACE_INFO, "%!FUNC!, Exception while locking data buffer 0X%08X in METHOD_NEITHER", status); IoFreeMdl(mdl); mdl = NULL; } Trace(TRACE_INFO, "%!FUNC!, Ks Read Stream MDL=%p", mdl);done: return mdl;}
NTSTATUS DispatchDeviceControl(PDEVICE_OBJECT DeviceObject, PIRP Irp){ NTSTATUS status = STATUS_SUCCESS; PDEVICE_EXTENSION devExt = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension; PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); Trace(TRACE_INFO, "%!FUNC! Entry"); status = IoAcquireRemoveLock(&devExt->RemoveLock, Irp); if (!NT_SUCCESS(status)) { Irp->IoStatus.Status = status; IoCompleteRequest(Irp, IO_NO_INCREMENT); goto done; } switch (irpStack->Parameters.DeviceIoControl.IoControlCode) { case IOCTL_KS_READ_STREAM: { PMDL mdl = GetMdlFromKsStreamHeader(DeviceObject, Irp); IoMarkIrpPending(Irp); IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine(Irp, HandleKSReadStreamDone, (PVOID)mdl, TRUE, TRUE, TRUE); status = IoCallDriver(devExt->NextDeviceObject, Irp); return status; } default: break; } IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(devExt->NextDeviceObject, Irp); IoReleaseRemoveLock(&devExt->RemoveLock, Irp);
done: Trace(TRACE_INFO, "%!FUNC! Exit, status=%x", status); return status;}
剩下的函数就不详细介绍了,代码我贴在下面。
NTSTATUS DispatchPower(PDEVICE_OBJECT DeviceObject, PIRP Irp){ NTSTATUS status = STATUS_SUCCESS; PDEVICE_EXTENSION devExt = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension; Trace(TRACE_INFO, "%!FUNC! Entry"); status = IoAcquireRemoveLock(&devExt->RemoveLock, Irp); if (!NT_SUCCESS(status)) { Irp->IoStatus.Status = status; IoCompleteRequest(Irp, IO_NO_INCREMENT); goto done; } IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(devExt->NextDeviceObject, Irp); IoReleaseRemoveLock(&devExt->RemoveLock, Irp);
done: Trace(TRACE_INFO, "%!FUNC! Exit, status=%x", status); return status;}
NTSTATUS DispatchPnp(PDEVICE_OBJECT DeviceObject, PIRP Irp){ NTSTATUS status = STATUS_SUCCESS; PDEVICE_EXTENSION devExt = DeviceObject->DeviceExtension; PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp); Trace(TRACE_INFO, "%!FUNC!+, MajorFunction=%u, MinorFunction=%u, IoControlCode=%x", irpStack->MajorFunction, irpStack->MinorFunction, irpStack->Parameters.DeviceIoControl.IoControlCode); status = IoAcquireRemoveLock(&devExt->RemoveLock, Irp); if (!NT_SUCCESS(status)) { Irp->IoStatus.Status = status; IoCompleteRequest(Irp, IO_NO_INCREMENT); goto done; } switch (irpStack->MinorFunction) { case IRP_MN_START_DEVICE: { Trace(TRACE_INFO, "%!FUNC!, Start device"); KEVENT event; KeInitializeEvent(&event, NotificationEvent, FALSE); IoCopyCurrentIrpStackLocationToNext(Irp); IoSetCompletionRoutine(Irp, DefaultIrpCompletion, &event, TRUE, TRUE, TRUE); status = IoCallDriver(devExt->NextDeviceObject, Irp); if (status == STATUS_PENDING) { KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); status = Irp->IoStatus.Status; } if (NT_SUCCESS(status)) { if (devExt->NextDeviceObject->Characteristics & FILE_REMOVABLE_MEDIA) { DeviceObject->Characteristics |= FILE_REMOVABLE_MEDIA; } } Irp->IoStatus.Status = status; IoCompleteRequest(Irp, IO_NO_INCREMENT); IoReleaseRemoveLock(&devExt->RemoveLock, Irp); goto done; } case IRP_MN_REMOVE_DEVICE: { Trace(TRACE_INFO, "%!FUNC!, Remove device"); IoReleaseRemoveLockAndWait(&devExt->RemoveLock, Irp); IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(devExt->NextDeviceObject, Irp); IoDetachDevice(devExt->NextDeviceObject); devExt->NextDeviceObject = NULL; IoDeleteDevice(DeviceObject); goto done; } case IRP_MN_QUERY_STOP_DEVICE: { Trace(TRACE_INFO, "%!FUNC!, Query stop device"); status = STATUS_SUCCESS; break; } case IRP_MN_CANCEL_STOP_DEVICE: { Trace(TRACE_INFO, "%!FUNC!, Cancel stop device"); status = STATUS_SUCCESS; break; } case IRP_MN_STOP_DEVICE: { Trace(TRACE_INFO, "%!FUNC!, Stop device"); status = STATUS_SUCCESS; break; } case IRP_MN_QUERY_REMOVE_DEVICE: { Trace(TRACE_INFO, "%!FUNC!, Query remove device"); status = STATUS_SUCCESS; break; } case IRP_MN_SURPRISE_REMOVAL: { Trace(TRACE_INFO, "%!FUNC!, Surprise removal"); status = STATUS_SUCCESS; break; } case IRP_MN_CANCEL_REMOVE_DEVICE: { Trace(TRACE_INFO, "%!FUNC!, Cancel remove device"); status = STATUS_SUCCESS; break; } default: status = Irp->IoStatus.Status; break; } Irp->IoStatus.Status = status; IoSkipCurrentIrpStackLocation(Irp); status = IoCallDriver(devExt->NextDeviceObject, Irp); IoReleaseRemoveLock(&devExt->RemoveLock, Irp);
done: Trace(TRACE_INFO, "%!FUNC!-, status=%x", status); return status;}
void DriverUnload(PDRIVER_OBJECT DriverObject){ WPP_CLEANUP(DriverObject);}
下面是驱动的头文件的内容,主要是定义了 DEVICE_EXTENSION 结构。
#ifndef _DRIVER_H_#define _DRIVER_H_#include <wdm.h>#include <windef.h>#include <ks.h>#include <ksmedia.h>
typedef struct _DEVICE_EXTENSION{ PDEVICE_OBJECT DeviceObject; PDEVICE_OBJECT NextDeviceObject; IO_REMOVE_LOCK RemoveLock;} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
#define POOL_TAG 'FMAC'
#endif // _DRIVER_H_
最后我们来看看把这个驱动安装上的效果,我把这个驱动安装到我的虚拟中的 USB 摄像头上,就是前面使用 QEMU 模拟的那个 USB 摄像头。下面是我们在设备管理器中查看摄像头驱动的情况,这里可以看到除了微软的 USB 摄像头驱动之外,还多了我们的 CameraFilterDriver.sys。
查看摄像头设备的 Device stack,可以看到我们的 CameraFilterDriver 是在最上层的,因为我们添加的是上层过滤驱动(UpperFilters),这里显示的很直观吧。
最后来看看打开摄像头应用程序的效果,可以看到摄像头输出的图像最上面一大片都是绿色的,这就是我们覆盖的那 200 行 0 (Y分量)。
下面是在 Windows Hello 中看到的红外相机的出图效果,由于红外相机出的图是只有 Y 分量的灰度图,全零表示的是黑色,所以这里图像的上方会出现一大片黑色。
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:MyStackTrace MyStackTrace《写一个 Windows 摄像头过滤驱动篡改摄像头输出图像》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论