

本帖最后由 羽化_为蝶 于 2013-3-15 00:16 编辑
本文主要描述使用 ATAPI 的 IRP HOOK 实现扇区重定向。ATAPI 层是
Windows 系统中存储系统 IRP 的终结点,ATAPI以下就已经不是使用 IRP 来读写了,所以 ATAPI是拦截磁盘 IRP 操作的最后一站。拦截磁盘实现重定向主要有两种方法,一种就是最普遍的IRP 分发例程 HOOK,另外一种是 DISK 设备扩展中的设备对象劫持。本文主要讨论第一种方 法,对第二种方法也会进行一些简要描述。
IRP 分发例程的 HOOK,只要 熟悉驱动编程的都知道,每个驱动对象中都保存有一组处理 IRP 的分发函数指针,替换这里 的指针即可简单地接管 IRP。对于本文要实现的扇区重定位来说,只需要检测 IRP 中目的 LBA 地址是否包含要被重定向的地址,如果包含则进行一些替换操作即可。为了实现上述目的,需要如下步骤:
1) 获取 ATAPI 驱动对象,这样才能替换驱动对象中的分发函数指针;2) 确定过滤的 IRP 函数是哪个;3) 确定需要过滤的设备对象是哪个,注意,ATAPI 会生成多个设备对象,需要判断目 标到底是哪个;4) IRP的解包以及重组。通常的驱动读写都是 IRP_MJ_READ 和 IRP_MJ_WRITE,但 ATAPI 比较特殊,实现写使用的 是 IRP_INTERNAL_DEVICE_CONTROL , 这个 很像 TDI 层 的驱 动, 也 是 使 用
IRP_INTERNAL_DEVICE_CONTROL 实现数据收发的。知道了这一点可以解决第二个问题,真正的读写操作使用 SRB指令来实现,数据结构为 SCSI_REQUEST_BLOCK(在 srb.h文件中定义)。 关于 SCSI_REQUEST_BLOCK 指令的填写和解析操作,可以参考《ATAPI 实现扇区读写》这篇 文章,所以第四个问题得以解决。获 取 ATAPI 驱动 对象可以使 用 ObReferenceObjectByName 函 数,传 递驱动 的名字 L\\Driver\\ATAPI 进去后可以获取到,从而解决第一个问题。对于第三个问题,虽然ATAPI 驱动会生成多个设备对象,但是真正处理读写操作的设备对象的类型是 FILE_DEVICE_DISK, 这种类型的设备对象就是我们需要的了。明白了以上几点,实现代码就比较容易了,我们来 看实现的代码。
NTSTATUSHookAtapiTest(BOOLEAN Hook){NTSTATUS Status = STATUS_INFO_LENGTH_MISMATCH; PDRIVER_OBJECTlpDriverObj = NULL;// if (IsWin8())// {// Status = OpenDriverObject(L"\\Driver\\storahci",&lpDriverObj);// }// else// {// }Status = OpenDriverObject(L"\\Driver\\ATAPI",&lpDriverObj);if (NT_SUCCESS(Status)){if (Hook){g_Dispatch =(PDRIVER_DISPATCH)InterlockedExchange((LONG*)&lpDriverObj->MajorFunction[IRP_MJ_SCSI],(LONG)Detour_DeviceInternalControl);}else{h);InterlockedExchange((LONG*)&lpDriverObj->MajorFunction[IRP_MJ_SCSI,(LONG)g_Dispatc} ObDereferenceObject(lpDriverObj);return STATUS_SUCCESS ;}return STATUS_UNEXPECTED_IO_ERROR;}从这里我注释的那几行代码可以知道,处理 SRB 的驱动到了 Windows8 下发生了变化, 不再是\\Driver\\ATAPI,使用 DeviceTree 看一下就可以知道,分发函数不处于只读内存,可 以直接修改,不需要关保护或者进行其他同步措施。挂钩的实现异常简单,关键是重定向部分,我们继续往下走。根据《ATAPI 实现扇区读 写》一文,读写磁盘在 WindowsXP 和 Windows7 甚至 Windows8 下都使用了相同的长度是
10 的这种 CDB(cdb 的定义见 wdk 的scsi.h 头文件)。struct _CDB10 {UCHAR OperationCode; UCHAR RelativeAddress :1; UCHAR Reserved1 : 2; UCHAR ForceUnitAccess: 1; UCHAR DisablePageOut: 1;UCHAR LogicalUnitNumber : 3; UCHAR LogicalBlockByte0; UCHAR LogicalBlockByte1; UCHAR LogicalBlockByte2; UCHAR LogicalBlockByte3; UCHAR Reserved2;UCHAR TransferBlocksMsb; UCHAR TransferBlocksLsb;UCHAR Control;}CDB10;写操作的第一个字段是SCSIOP_WRITE,读的时候是SCSIOP_READ,其中红色的部分是我们需要特别关注的。LogicalBlockByte3到LogicalBlockByte0分别是长度是ULONG的LBA块地址 的(扇区号)的低字节到高字节(和Windows内存存放顺序相反),TransferBlocksMsb是要 操作扇区的数目的高字节,TransferBlocksLsb是要操作扇区的数目的低字节,例如把0号扇区
重定向到10号扇区,只需要把LogicalBlockByte3设置到10就可以了。在实际中通常会遇到两种情况,第一种是只读取一个扇区,并且就是我们要重定向的扇区,这种情况很好处理,只需要直接替换扇区号码就可以了;第二种情况是一次读取多个扇
区,其中包含了我们要重定向的扇区。第二种情况的重定向有多种做法,一是直接下发IRP,等IRP完成了,再替换Buffer中的内容;二是拆分成几个IRP下发,等拆分的IRP都完成了,再 完成主IRP就可以了。本文演示第一种方法,比较简单,适合重定位扇区比较少的情况,第 二种方法比较适合商用,可以在重定位扇区比较多的场景使用。代码用于把0号扇区的数据 重定向到1号扇区。
NTSTATUSNTAPIDetour_DeviceInternalControl(PDEVICE_OBJECT lpDevice,PIRP pIrp){PIO_STACK_LOCATION Stack = IoGetCurrentIrpStackLocation(pIrp); PSCSI_REQUEST_BLOCK srb = NULL;NTSTATUS Status = 0;PCDB pCdb =NULL;if (Stack->MajorFunction==IRP_MJ_SCSI){if (lpDevice->DeviceType==FILE_DEVICE_DISK){srb = (PSCSI_REQUEST_BLOCK)Stack->Parameters.Scsi.Srb;if (srb->Function== SRB_FUNCTION_EXECUTE_SCSI&&srb->CdbLength==10){pCdb =(PCDB)srb->Cdb ;if (srb->SrbFlags& SRB_FLAGS_DATA_IN){{KdPrint(("Receiver Read disk request-----%08X\n",pCdb)); PrintSrbBuffer(srb);}ULONG Lba,Len;if (ParseLBAAndLength(pCdb,&Lba,&Len)){if (Lba==0){if (Len==1){//如果只是读取一个扇区,并且就是要重定向的扇区,比较好办,直接替换。前面说了是倒序,所以这里是LogicalBlockByte3,而不是LogicalBlockByte0pCdb->CDB10.LogicalBlockByte3=1;//重定向到1扇区}else{//到这里就是上面的第二种情况,需要实现完成例程 的Hook,在完成例程中实现Buffer替换。注意,ATAPI的很多IRP都是异步的,所以不要 指望在分发函数返回时就去修改buffer,标准的做法是替换完成例程,在完成例程里实现替换。
PCOMPLETE_CONTEXT Context = (PCOMPLETE_CONTEXT)AllocateNonPagedBuffer(sizeof(COMPLETE_CONTEXT));Context->Routine = Stack->CompletionRoutine; Context->Control = Stack->Control;Context->Context = Stack->Context;Stack->CompletionRoutine =ATAPIReadIoCompletion;Stack->Context = Context; Stack->Control =SL_INVOKE_ON_SUCCESS|SL_INVOKE_ON_ERROR|SL_INVOKE_ON_CANCEL;}}}}}}}Status = g_Dispatch(lpDevice,pIrp);return Status ;}完成例程很简单,下面的g_Buffer中保存了1号扇区的数据。注意ATAPI的IRP输出地址使用的是MDL描述。NTSTATUSstdcallATAPIReadIoCompletion(IN PDEVICE_OBJECT DeviceObject, INPIRP Irp,INPVOID Context){NTSTATUS Status =STATUS_SUCCESS; PVOID Buffer = NULL;PCOMPLETE_CONTEXT pContext = (PCOMPLETE_CONTEXT)Context;if (Irp->MdlAddress){Buffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress,NormalPagePriority);if(Buffer){KdPrint(("hit!!!!\n")); RtlCopyMemory(Buffer,g_Buffer,512);}}if (pContext){if (pContext->Routine){Status = pContext->Routine(DeviceObject,Irp,pContext->Context);}FreeBuffer(pContext);}return Status;}IRP HOOK实现方法易于被检测,所以现在有一些使用磁盘设备对象劫持的方法,劫持的
|

看到了吧,设备扩展+8的地址就是ATAPI的设备对象指针,下发IRP的时候会使用这个设 备,所以劫持的原理就是替换DISK设备扩展中的这个底层ATAPI设备的指针为自己驱动的指 针,这样IRP就会被转发到我们的驱动,在驱动中进行一些过滤操作就可以了,过滤原理同上,就不再赘述了。
