斜渡_桃之夭夭
ATAPI IRP HOOK 实现扇区重定位

本帖最后由 羽化_为蝶 于 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 生成多个设备对象,需要判断目 标到底是哪个;
4IRP的解包以及重组。

通常的驱动读写都是 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 的这种 CDBcdb 的定义见 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扇区。

NTSTATUSNTAPI
Detour_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,而不是
LogicalBlockByte0
pCdb->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号扇区的数据。注意ATAPIIRP输出地
址使用的是MDL描述。
NTSTATUS
stdcall
ATAPIReadIoCompletion(
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现方法易于被检测,所以现在有一些使用磁盘设备对象劫持的方法,劫持的

方法其实很简单,我们来看disk的设备对象,如图1所示。
1

这里面有一个设备扩展DeviceExtension,我们直接dd查看这块内存,如图2所示。
2

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

使用驱动加载工具加载测试驱动,使用WinHex打开物理磁盘,可以看到0号扇区的数据 已经被重定向到1号扇区了,如图3示,虚拟机的1扇区因为测试过《ATAPI现扇区读写》 里面的驱动,所以是全1

饭尐盒
为啥没人回复呢
展开Biu

好厉害。。。为啥没人回复呢。。

[查看全文]
_Nozomi
这个是原创的
展开Biu

这个是原创的?

[查看全文]