2010年12月21日 星期二

IRP Hook全家福

我們今天一起來彙總看看IRP
HOOK的方法。又是長篇大論,別著急,慢慢看。談到irp攔截,基本上有三種方式,一種是在起點攔截,一種是在半路攔截,一種是在終點攔截。
下面我們會詳細分析這幾種方式哪些是有效的,哪種是無效的。 要理解這幾種攔截,我們需要看看irp地傳送過程。我們看下圖的標準模型。請看大屏幕。

8.JPG

注意這個標準模型中,並不是每種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!KeTickCount+0x1460 (8054c800)]

804ef0fc cc               int      3

804ef0fd 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 = 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_INPUT_DATA)pIrp->AssociatedIrp.SystemBuffer;

     int numKeys = pIrp->IoStatus.Information / sizeof(KEYBOARD_INPUT_DATA);



     for(int i = 0; i < numKeys; i++)

     {

       DbgPrint("ScanCode: %x\n", keys.MakeCode);

      

       if(keys.Flags == KEY_BREAK)

         DbgPrint("%s\n","Key Up");

      

       if(keys.Flags == KEY_MAKE)

         DbgPrint("%s\n","Key Down");

   

      

       KEY_DATA* kData = (KEY_DATA*)ExAllocatePool(NonPagedPool,sizeof(KEY_DATA));

              

   

       kData->KeyData = (char)keys.MakeCode;

       kData->KeyFlags = (char)keys.Flags;



       DbgPrint("Adding IRP to work queue...");

       ExInterlockedInsertTailList(&pKeyboardDeviceExtension->QueueListHead,

       &kData->ListEntry,

       &pKeyboardDeviceExtension->lockQueue);

       KeReleaseSemaphore(&pKeyboardDeviceExtension->semQueue,0,1,FALSE);



     }

   }



  

   if(pIrp->PendingReturned)

     IoMarkIrpPending(pIrp);



    numPendingIrps--;



   return pIrp->IoStatus.Status;

}



在這個demo中要注意的是,由於irp的處理函數的IRQL = DISPATCH_LEVEL,因此,我們申請內存的話,只能申請非分頁內存。在這個IRQL級別,我們不能創建或者保存文件來記錄按鍵信息。

我們只能創建一個系統線程,在系統線程中完成按鍵信息的保存。

後面附上DEMO.


沒有留言:

張貼留言