Windows磁盘驱动基础教程

Windows磁盘驱动基础教程


创建时间:2010-01-19
文章类别:磁盘驱动
文章大小:15928 Bytes

本文讲述Windows磁盘驱动的主要结构功能与编写方法基础。本文描述的内容仅限于软件层面,并不与具体的硬件相关。 

1.磁盘驱动基础

    不少人把文件系统驱动和磁盘驱动混为一谈。实际上文件系统驱动应该与磁盘驱动是两类不同的驱动程序。文件系统仅仅考虑数据在存储设备上的保存格式(而不考虑具体是什么存储设备),而磁盘是存储设备的一种。

    在存储设备驱动(storage driver)中,与实际的硬件设备打交道的驱动称为微端口miniport)驱动,而更上层的驱动称为类驱动(class driver)。这里说的磁盘驱动(disk driver)是一个类驱动。类驱动具体功能通过下层的微端口驱动实现。而自己则抽象出一类设备(如磁盘),供文件系统驱动调用。这样,文件系统就不用自己去和硬件细节打交道了,在它看来所有的磁盘都是一个磁盘设备。这就是类驱动存在的意义

    如果认为微端口驱动都是硬件驱动的话,则类驱动是硬件驱动之上的软件驱动

    要学习磁盘驱动的编写,可以参考WINDDK/src/storage/class/disk,这是一个SCSI磁盘类驱动(SCSI disk class driver)的例子。但是这个例子的实现细节过于烦琐。为此我在网上找到一个文件虚拟磁盘(FileDisk的例子,用这个来说明磁盘驱动的编写方法。你可以在网上下载它。

    磁盘驱动找到物理媒质的时候,生成磁盘设备对象。但是要注意磁盘设备对象与卷(volume)设备对象(volume device driver)的区别。一个磁盘设备对象对应一个物理的磁盘。而卷,则是文件系统驱动找到磁盘设备之后,挂载形成的一种新设备。这种设备可以进行很多操作,比如生成文件,删除文件,修改文件等,这些是与文件系统相关的操作。而磁盘设备对象则没有这些操作。因为磁盘并不知道有文件系统,所以仅仅能进行读,写,获取一些磁盘信息等操作。比文件系统操作简单得多。

    每当你生成一个磁盘驱动对象,系统中就出现一个新的磁盘。是否出现在windows的我的电脑之中,还需要涉及到一些其他信息的返回。但是你确实可以随时生成新的磁盘,无论具体的硬件是否存在。在内核代码中,你可以使用IoCreateDevice来生成一个磁盘设备。

    XP版本的ifs下的例子disk使用IoCreateDisk来生成一个磁盘。似乎这个函数的使用更加简便。而FileDisk的例子,使用IoCreateDevice,可能这个例子开发比较早,或者是为了兼容2000ddk.

    驱动的入口是DriverEntry,你可以首先编写这个函数。你可以在这里生成磁盘设备对象.设备对象都是有名字的。首先你必须确定它们的名字。FileDiskDriverEntry中生成了一连串设备,这些设备的名字如下:

    "//Device//FileDisk0"

    "//Device//FileDisk1"

    "//Device//FileDisk2"

    "//Device//FileDisk3"

    "//Device//FileDisk4"

    ... 依次类推。

    需要多少个磁盘设备对象和你的需要有关,这个数字事先已经保存在注册表中。对你的简单代码来说,只要:

NTSTATUS

DriverEntry (

  IN PDRIVER_OBJECT   DriverObject,

  IN PUNICODE_STRING RegistryPath

  )

{

    PDEVICE_OBJECT device_object;

    UNICODE_STRING device_name;

    RtlInitUnicodeString(&device_name,L"//Device//FileDisk");

    

    status = IoCreateDevice(

        DriverObject,

        0,     //sizeof(DEVICE_EXTENSION),这里是设备扩展的大小空间

        &device_name,

        FILE_DEVICE_DISK, //krpr的驱动中VML模块也是通过该参数创建卷设备

                            //文件系统会识别这个卷,并往该卷发消息

        0,

        FALSE,

        &device_object);

    return status;

}     

    

    建议打开ddk帮助看看IoCreateDevice的参数说明。FILE_DEVICE_DISKddk中定义的一种设备类型。现在磁盘设备对象已经生成了,只要加载这个驱动,系统将知道增加一个磁盘。不过还有以下的一些问题:

    (1)当设备目录"//Device"不存在的时候,你的创建会失败。所以应该先创建这个目录,使用ZwCreateDirectoryObject即可。(我的疑问:这种情况会出现么?)

    (2)你确实创建了一个磁盘设备对象。但是你没有为你的驱动指定分发例程。当windows对这个磁盘有所请求(比如获取磁盘的信息,读写这个磁盘等),你的分发例程会被调用。此时没有写分发例程,因此windows也无法得到这个磁盘的信息,因而它不会起作用。

    (3)你必然要在磁盘设备对象上保留一些私人信息,因此不能把设备扩展大小设置为0。你应该定义设备扩展的数据结构。当然这要看你的需要了。

    (4)你还需要设置一些设备标志。

        *                       *                       *

分发例程和设备扩展

    应该给你的驱动指定分发例程。这样你的磁盘设备生成之后,windows会发给你请求,来读取你的磁盘的信息。而这些请求(irp)就会发到你的分发例程中。

    分发例程是一组用来处理不同请求的函数。

NTSTATUS

DriverEntry (

  IN PDRIVER_OBJECT   DriverObject,

  IN PUNICODE_STRING RegistryPath

  )

{

    ...     // 前面生成设备对象...

    // 设置分发例程。请注意仅仅需要5个,比文件系统少多了。

    DriverObject->MajorFunction[IRP_MJ_CREATE]       = FileDiskCreateClose;

    DriverObject->MajorFunction[IRP_MJ_CLOSE]       = FileDiskCreateClose;

    DriverObject->MajorFunction[IRP_MJ_READ]       = FileDiskReadWrite;

    DriverObject->MajorFunction[IRP_MJ_WRITE]       = FileDiskReadWrite;

    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = FileDiskDeviceControl;

    // 卸载例程。

    DriverObject->DriverUnload = FileDiskUnload;

    return status;

}

    下面的任务你要自己编写FileDiskCreateClose, FileDiskReadWrite, FileDiskDeviceControl这三个函数。

    请注意所有的分发例程的结构都是如此:

NTSTATUS

DispatchFunction(

    IN PDEVICE_OBJECT   DeviceObject,

    IN PIRP         Irp

  );

    DeviceObject是接受请求的设备对象指针,应该是由你的驱动生成的,所以才会发到你的驱动的分发例程。Irp是请求包指针。里边含有请求相关的信息。最后返回执行的结果(成功或者错误代码)。

    FileDisk看来,分发例程比文件系统驱动要简单得多DriverObject->DriverUnload是一个特殊的例程,在windows卸载你的驱动的时候被调用。你可以在其中删除设备,关闭打开的文件等等。

    FileDisk定义了一个设备扩展。这个设备扩展被记录在设备对象中,你随时可以通过DeviceObject->DeviceExtension得到它。它的内容如下:

typedef struct _DEVICE_EXTENSION {

  BOOLEAN               media_in_device; //有无指定文件与之映射

  HANDLE               file_handle; //映射文件的句柄

  FILE_STANDARD_INFORMATION   file_information; //映射文件的信息

  BOOLEAN               read_only; //是否只读

  PSECURITY_CLIENT_CONTEXT   security_client_context;

  LIST_ENTRY             list_head; //IRP的处理链表

  KSPIN_LOCK             list_lock; //为了保证链表读写同步的锁

  KEVENT               request_event; //是否有要处理的IRP事件到达

  PVOID               thread_pointer; //线程的指针

  BOOLEAN               terminate_thread; //是否终止处理线程

} DEVICE_EXTENSION, *PDEVICE_EXTENSION;

    

    media_in_device是指这个设备是否已经指定了一个文件作为存储媒质。这是一个用文件来虚拟磁盘的驱动。那么一个磁盘应该对应一个实际存在的文件。读写这个磁盘的请求最终转变为对文件的读写。如果一个磁盘设备对象还没有指定文件,那么这个内容是FALSE.

    file_handle是文件句柄。也就是这个虚拟磁盘所对应的文件。

    file_information是这个文件的一些信息。

    read_only是否只读。

    security_client_context 访问文件的时候需要使用的一个线程客户安全性上下文。

    list_head是一个链表头。一部分irpwindows发来的请求包)被放入这个链表中。我们为每个磁盘对象开启一个系统线程(处理线程),专门用来处理这些请求。

    list_lock是为了保证链表读写同步的锁。

    request_event是一个事件。当链表中没有请求的时候,处理请求的系统线程并不做任何事情,而只等待这个事件。当有请求到来,我们把请求放入链表,然后设置这个事件。处理线程就会开始处理这些请求。

    thread_pointer是线程的指针,用来最后等待这个线程的结束。

    terminate_thread是一个标志。如果设置为TRUE,处理线程执行的时候检测到这个,就会把自己终止掉。

        *                       *                       *

3.分发例程-打开与关闭,读与写

    这里需要CreateClose是为了通信。让应用程序可以打开这些磁盘设备来设置一些信息。

    打开关闭非常简单,都是对irp无条件返回成功:

NTSTATUS

FileDiskCreateClose (

  IN PDEVICE_OBJECT   DeviceObject,

  IN PIRP         Irp

  )

{

  PAGED_CODE(); //当前的函数是否能够存在于分页内存中

  //If the IRQL > APC_LEVEL, PAGED_CODE() causes the system to ASSERT.

  Irp->IoStatus.Status = STATUS_SUCCESS;

  Irp->IoStatus.Information = FILE_OPENED;

  IoCompleteRequest(Irp, IO_NO_INCREMENT); //完成IRP请求

  return STATUS_SUCCESS;

}

    PAGED_CODE()是一个调试用的宏。如果一个函数被定义在可分页交换的段中,那么它执行时的中断级别必须满足一定的要求。PAGED_CODE()用来检测是否符合。如果不行,调试版本中这里会出现失败。这个宏仅仅在调试版本下有效

    例子disk的读写非常复杂。而FileDisk的读写是对文件的读写。所以相对简单一些。基本的过程如下:

    1.得到设备扩展。

    2.检查是否已经打开了文件。如果没有,直接返回失败。

    3.对于长度为0的读写,直接返回成功。

    4.把请求加入设备扩展中的请求队列中设置事件让处理线程运行处理

    5.返回等待。

    你固然可以在读写例程中直接读写文件。但是这不符合惯例。读写文件需要消耗的时间比较长,应该让系统尽快得到答复以便可以干其他的事情。此外这个函数很容易重入,我们希望把读写请求的完成序列化,为此我们并不在这里直接读写文件。而是把请求放入队列中。为每个磁盘设备对象生成一个系统线程,来依次处理这些请求。

NTSTATUS

FileDiskReadWrite (

  IN PDEVICE_OBJECT   DeviceObject,

  IN PIRP         Irp

  )

{

    PDEVICE_EXTENSION   device_extension;

    PIO_STACK_LOCATION io_stack;

    // 得到设备扩展

    device_extension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

    ...

              

}

    上面得到了设备扩展,那么我检查这个磁盘是否已经有物理媒质(也就是一个文件用来模拟磁盘空间)。

    // 如果没有打开过文件,就返回失败

    if (!device_extension->media_in_device)

    {

        Irp->IoStatus.Status = STATUS_NO_MEDIA_IN_DEVICE;

        Irp->IoStatus.Information = 0;

        IoCompleteRequest(Irp, IO_NO_INCREMENT);

        return STATUS_NO_MEDIA_IN_DEVICE;

    }         

    想得到要读多长的时候要得到Irp的当前栈空间:

    io_stack = IoGetCurrentIrpStackLocation(Irp);

    // 0长的时候立刻成功

    if (io_stack->Parameters.Read.Length == 0)

    {

        Irp->IoStatus.Status = STATUS_SUCCESS;

        Irp->IoStatus.Information = 0;

        IoCompleteRequest(Irp, IO_NO_INCREMENT);

        return STATUS_SUCCESS;

    }

        

    然后就可以把Irp放到队列里去等待完成了:

    // 标志pending

    IoMarkIrpPending(Irp);

    // 把请求写入链表

    ExInterlockedInsertTailList(

        &device_extension->list_head, //链表头

        &Irp->Tail.Overlay.ListEntry, //要插入的节点

        &device_extension->list_lock //SPLOCK

    );

    // 设置事件。让线程循环运行。

    KeSetEvent(

        &device_extension->request_event,

        (KPRIORITY) 0,

        FALSE

        );

    return STATUS_PENDING;

    到此读写例程完成。至于真正的读写完成在处理线程中,请阅读后面关于处理线程的章节。

        *                       *                       *

5.分发例程-磁盘固有的设备控制

    

    设备控制(Device Control)是除了读,写之外最重要的操作之一。对磁盘来说,windows通过发送设备控制请求来询问此磁盘的一些信息。收到设备控制请求之后,应该首先得到控制功能号,然后根据不同的控制功能号进行不同的处理.这些功能号有些是windows固有的,有些是由你自己定义的。

NTSTATUS

FileDiskDeviceControl (

  IN PDEVICE_OBJECT   DeviceObject,

  IN PIRP         Irp

  )

{

    PDEVICE_EXTENSION   device_extension;

    PIO_STACK_LOCATION io_stack;

    NTSTATUS         status;

    // 得到设备扩展

    device_extension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

    // 得到当前设备栈

    io_stack = IoGetCurrentIrpStackLocation(Irp);

    // 判断如果是还没有加载物理媒质就返回失败。但是

    // IOCTL_FILE_DISK_OPEN_FILE是自定义的功能号,专

    // 用来加载物理媒质的,所以排除在外

    if (!device_extension->media_in_device &&

        io_stack->Parameters.DeviceIoControl.IoControlCode !=

        IOCTL_FILE_DISK_OPEN_FILE)

    {

        Irp->IoStatus.Status = STATUS_NO_MEDIA_IN_DEVICE;

        Irp->IoStatus.Information = 0;

        IoCompleteRequest(Irp, IO_NO_INCREMENT);

        return STATUS_NO_MEDIA_IN_DEVICE;

    }

    // 根据不同的功能号处理...

    switch (io_stack->Parameters.DeviceIoControl.IoControlCode)

    {     

    ...

    }

    

    ...

}

    有非常多的固有设备控制功能码要处理。这些请求都有参数,从输入空间得到参数后,把返回结果写到输出空间。无论什么请求,其输入和输出空间都是Irp->AssociatedIrp.SystemBuffer,但是每种请求的参数和返回都有自己的格式,需要一一阅读文档才行。

    举例说明,下面的一些功能号检查磁盘的有效性。现在一律返回有效,这个请求简单,不带参数。

    case IOCTL_DISK_CHECK_VERIFY:

    case IOCTL_CDROM_CHECK_VERIFY:

    case IOCTL_STORAGE_CHECK_VERIFY:

    case IOCTL_STORAGE_CHECK_VERIFY2:

    {

        status = STATUS_SUCCESS;

        Irp->IoStatus.Information = 0;

        break;

    }     

    

    下面这个请求获得磁盘的物理属性:

    case IOCTL_DISK_GET_DRIVE_GEOMETRY:

    case IOCTL_CDROM_GET_DRIVE_GEOMETRY:

    {

        PDISK_GEOMETRY disk_geometry;

        ULONGLONG     length;

        if (io_stack->Parameters.DeviceIoControl.OutputBufferLength IoStatus.Information = 0;

        break;

        }

        disk_geometry = (PDISK_GEOMETRY) Irp->AssociatedIrp.SystemBuffer;

        length = device_extension->file_information.AllocationSize.QuadPart;

        disk_geometry->Cylinders.QuadPart = length / MM_MAXIMUM_DISK_IO_SIZE;

        disk_geometry->MediaType = FixedMedia;

        disk_geometry->TracksPerCylinder = MM_MAXIMUM_DISK_IO_SIZE / PAGE_SIZE;

        disk_geometry->SectorsPerTrack = PAGE_SIZE / SECTOR_SIZE;

        disk_geometry->BytesPerSector = SECTOR_SIZE;

        status = STATUS_SUCCESS;

        Irp->IoStatus.Information = sizeof(DISK_GEOMETRY);

        break;

    }

    请注意你要把结果写入到一个DISK_GEOMETRY结构中并把这个结构返回到Irp->AssociatedIrp.SystemBuffer中。这个结构的定义如下:

    typedef struct _DISK_GEOMETRY {

        LARGE_INTEGER Cylinders;     // 磁柱个数

        MEDIA_TYPE MediaType;         // 媒质类型

        ULONG TracksPerCylinder;     // 每个磁柱上的磁道数

        ULONG SectorsPerTrack;         // 每个磁道上的扇区数

        ULONG BytesPerSector;         // 每个扇区上的字节数

    } DISK_GEOMETRY, *PDISK_GEOMETRY;

    

    以上这个结构说明来自ddk文档。你必须"如实"的返回这些数据。

    此外比较重要的是获取分区信息,获取分区信息有两个功能号,IOCTL_DISK_GET_PARTITION_INFOIOCTL_DISK_GET_PARTITION_INFO_EX,其处理过程是类似的,主要是返回结果的结构不同,下面只举出一个例子:

    case IOCTL_DISK_GET_PARTITION_INFO_EX:

    {

        PPARTITION_INFORMATION_EX   partition_information_ex;

        ULONGLONG             length;

        if (io_stack->Parameters.DeviceIoControl.OutputBufferLength IoStatus.Information = 0;

              break;

        }

        partition_information_ex = (PPARTITION_INFORMATION_EX) Irp->AssociatedIrp.SystemBuffer;

        length = device_extension->file_information.AllocationSize.QuadPart;

        partition_information_ex->PartitionStyle = PARTITION_STYLE_MBR;

        partition_information_ex->StartingOffset.QuadPart = SECTOR_SIZE;

        partition_information_ex->PartitionLength.QuadPart = length - SECTOR_SIZE;

        partition_information_ex->PartitionNumber = 0;

        partition_information_ex->RewritePartition = FALSE;

        partition_information_ex->Mbr.PartitionType = 0;

        partition_information_ex->Mbr.BootIndicator = FALSE;

        partition_information_ex->Mbr.RecognizedPartition = FALSE;

        partition_information_ex->Mbr.HiddenSectors = 1;

        status = STATUS_SUCCESS;

        Irp->IoStatus.Information = sizeof(PARTITION_INFORMATION_EX);

        break;

    }

    还有其他的一些功能号。你可以查看FileDisk的代码来了解他们。

    做为文件虚拟磁盘,这些数据都是虚拟的,所以根据需要返回就行了。无需动用下层设备。而Disk的例子则不同。这些请求都要发到下层设备来获得结果

        *                       *                       *

6.每个设备的处理线程

    前面说到为每一个磁盘设备对象生成了一个系统线程,用来处理Irp。系统线程的生成用以下的调用:

    

    // 生成一个系统线程

    status = PsCreateSystemThread(

        &thread_handle,

        (ACCESS_MASK) 0L,

        NULL,

        NULL,

        NULL,

        FileDiskThread,

        device_object

        );     

    device_object是我所生成的磁盘设备对象。作为线程上下文传入。便于我们在线程中得到设备对象指针,然后得到设备扩展。

    稍后我们要把线程对象的指针保留下来存到设备扩展中。这使用ObReferenceObjectByHandle来实现。

    status = ObReferenceObjectByHandle(

        thread_handle,

        THREAD_ALL_ACCESS,

        NULL,

        KernelMode,

        &device_extension->thread_pointer,

        NULL

        );

    此后我们关注这个线程的运作。线程的启动函数是FileDiskThread.这个FileDisk实现了以下的功能:

    1.获得设备扩展。

    2.设置线程优先级。

    3.进入死循环。首先等待事件的发生(device_extension->request_event),避免空循环消耗资源。

    4.检查终止标志(device_extension->terminate_thread)。如果外部要求终止,就使用PsTerminateSystemThread(STATUS_SUCCESS)终止它。

    5.检查请求链表(device_extension->list_head),若有请求,则完成他们。

    读写请求的处理非常简单,如下:

    switch (io_stack->MajorFunction)

    {

    case IRP_MJ_READ:

        // 对于读,我直接读文件即可

        ZwReadFile(

              device_extension->file_handle,

              NULL,

              NULL,

              NULL,

              &irp->IoStatus,

              MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority),

              io_stack->Parameters.Read.Length,

              &io_stack->Parameters.Read.ByteOffset,

              NULL

              );

        break;

    case IRP_MJ_WRITE:

        // 写也是与之类似的。

        if ((io_stack->Parameters.Write.ByteOffset.QuadPart +

              io_stack->Parameters.Write.Length) >

              device_extension->file_information.AllocationSize.QuadPart)

        {

              irp->IoStatus.Status = STATUS_INVALID_PARAMETER;

              irp->IoStatus.Information = 0;

        }

        ZwWriteFile(

              device_extension->file_handle,

              NULL,

              NULL,

              NULL,

              &irp->IoStatus,

              MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority),

              io_stack->Parameters.Write.Length,

              &io_stack->Parameters.Write.ByteOffset,

              NULL

              );

        break;

    };     

    最后都用下面的代码完成这些请求:

    IoCompleteRequest(

          irp,

          (CCHAR) (NT_SUCCESS(irp->IoStatus.Status) ?

          IO_DISK_INCREMENT : IO_NO_INCREMENT)

          );

    这个线程最后在Unload中终止。Unload例程中调用了FileDiskDeleteDevice,这个函数的主要用途是删除生成的磁盘对象,并终止其处理线程。下面的代码终止处理线程并等待成功终止:

    // 得到设备扩展

    device_extension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

    // 设置线程终止标志

    device_extension->terminate_thread = TRUE;

    // 设置启动事件

    KeSetEvent(

        &device_extension->request_event,

        (KPRIORITY) 0,

        FALSE

        );

    // 等待线程的结束

    KeWaitForSingleObject(

        device_extension->thread_pointer,

        Executive,

        KernelMode,

        FALSE,

        NULL

        );

    ObDereferenceObject(device_extension->thread_pointer);

    使用IoDeleteDevice()最终将磁盘设备对象删除。

    上面曾经提起的,每个虚拟磁盘设备对象打开一个真实的文件作为物理媒质。打开和关闭文件对象也是在处理线程中进行的。FileDisk的作者自定义了两个设备控制功能号,IOCTL_FILE_DISK_CLOSE_FILEIOCTL_FILE_DISK_OPEN_FILE,很容易让人误解这是在硬盘上生成文件。但是开头我们已经说过生成文件是文件系统驱动所处理的任务,磁盘驱动是不会接受生成文件这样的请求的。这是两个自定义的功能号。

    当收到打开文件的请求时,本驱动根据传入的路径使用ZwCreateFile()打开文件。关闭则反之。然后保留文件句柄在设备扩展中(device_extension->file_handle),然后以后处理读写请求就读写这个文件了。以此来实现用文件来虚拟磁盘空间。这部分操作和磁盘驱动无关,这里也不详细叙述了。

        *                       *                       *

    

7.总结

    ifs下的例子disk是一个磁盘类驱动。而FileDisk虽然是一个磁盘驱动,但是并不是一个类驱动。类驱动应该能得到PDO(发现总线上的物理设备),然后生成功能设备(FDO)去绑定它,并处理给这些设备的请求。存储类设备有一个复杂的框架,其基础代码在E:/WINDDK/2600/src/storage/class/下面。类设备有专用的分发函数,并把大部分请求转发给下层设备。

    而FileDisk从上层到下层已经全部包办,因此其构造要简单得多。但是这也使我们更清楚的了解磁盘驱动的本质,在合适的时候生成合适的磁盘设备对象,并处理系统发来的请求。

    FileDisk被作为虚拟磁盘的框架广泛应用。可以作为网络磁盘(不同于用文件系统驱动实现的网络卷),简单的存储设备驱动等的开发基础。(全文完)

转自: http://blog.csdn.net/yunfei90/archive/2007/02/05/1502941.aspx

;--------------------------------------------------------------------------------------------------------------------

腾讯科技讯 319日,由中国最大的互联网综合服务提供商腾讯发起和组织的互联网安全峰会进入第二天。包括微软、盛大、新浪等互联网界各大巨头的技术专家,学者和专业人士参与了此次的交流。此次峰会是今年以来首场由中国互联网各顶尖企业共同参与的大型网络安全专业盛会。

来自奇虎的反木马专家郑文彬,在现场发表演讲。以下为文字实录:

郑文彬:大家好!我今天给大家介绍这几个方面:背景、还原系统技术原理概览、流行还原系统穿透技术介绍、通用还原系统保护技术、演示&GuardField、还原系统保护之未来趋势。最近一段时间,有机器狗这类病毒工具对还原系统攻击,使用还原系统环境的用户一般都不会安装其他的防护软件,一旦还原软件被穿透的话,会带来比较大的安全威胁。

还原系统技术原理:基本原理是磁盘设备过滤驱动。比较常用方法是自己会建一个磁盘卷设备,在 harddiskX进行文件过滤。过滤驱动如何做到还原?首先还原系统会在磁盘上分配一块预留的区域,应用程序以为他已经写到真实磁盘,实际上被分配到一 块内容区域里,真实磁盘根本就没有被写入。

下面介绍一下还原软件怎么更新过滤。首先是一个普通的Windows程序,会调用 Win32API,从用户模式到内存模式,这些函数调用Windows内核,把文件请求发到文件系统上,根据磁盘卷分区格式不同来创建。文件系统设备会将 上层发来的文件读写请求转化磁盘读写请求,在harddisk volume之前会有还原系统过滤驱动。再往下会根据硬盘接口不同而有不同。如果IDE结构硬盘,会发布到电源系统。api最终会调用函数读写端口。如果 是USB设备,会发送到usb stor

刚才说了还原系统的一些基本原理,知道原理之后对如何穿透还原也就很简单了。既然还原系统都 在磁盘上过滤驱动,只要我们解除过滤驱动与真实磁盘之间的关系绕过过滤关系的话,就等于直接穿透了还原。第一种方法:DR0设备过滤设备链摘链。这种方 法其实就是摘除一个harddiskDR0上的过滤设备。指明设备上会有哪些过滤设备,第一代机器狗病毒将这个域给清零,导致还原系统设备被清除,所有请 求就不通过还原系统直接到达过滤磁盘设备。对于没有防备的还原系统就被成功攻破了。国内大部分还原系统都没有办法对抗这种技术。但是这种技术也是有一些缺 陷的,只能摘除在DR0上的物理设备。文件请求先到达磁盘卷,磁盘卷上的过滤设备摘除的话对系统有影响。所以第一代机器狗病毒使用了自己解析文件系统方式 进行感染,这是它的缺陷。

第二种方法:会自己创建虚拟磁盘设备,作为磁盘卷挂载到文件系统上,对虚拟磁盘读写影射到真 实磁盘,将请求下发到下层设备。相对第一代机器狗来说,这种方法不需要对磁盘系统摘除,可以通过文件对虚拟磁盘操作,操作结果是和对真实磁盘操作是一样 的,可以成功穿透还原。在这里还用一种方式就是他没有直接发送磁盘读写请求,发送SCSI-REQUEST-BLOCK下发到下层磁盘设备。

还有一种方法,这是方法不使用驱动程序,直接在用户模式穿透还原系统。磁盘系统提供一套 passthrough指令,不向磁盘发送直接请求,就可以获取磁盘信息甚至直接读写磁盘扇区。IDE/SCSI/ATA Pass Through指令穿透还原RING3下使用Devicelocontrel函数发送请求。大多数还原系统对此过滤不严或根本未过滤,导致在RING3 下即可达成攻击。

其他一些方法,比如说直接操作端口驱动,比如USB,更底层的磁盘操作:端口驱动、直接IO 等等,缺点是难度大,通用较麻烦。另外的方法是可以摘除其他一些过滤设备,Attach到还原系统上,先于磁盘系统获得磁盘的请求,可以做一个绕过动作。 可以在磁盘卷设备保存指针上所手脚。方法很多,不再一一解释了。主要是两类,第一类是新的磁盘技术或者磁盘卷绕过或者穿透的一些技巧。

通用还原系统的保护技术,GuardField。还原系统脆弱的原因是什么呢?刚才也说过了 他是通过磁盘设备上的过滤驱动,也就是说他跟磁盘设备没有紧密联系,只要被攻击者使用、摘除或者绕过方法就可以把磁盘请求发送到真实磁盘上。穿透基本原理:必须使读写请求不经过还原系统物理驱动,而是到了下层的物理磁盘设备。这里就有一个穿透思路,一个磁盘请求是从上层逐层发布到下层,我们只要监控发送路径,进行对比操作,就可以作为一个还原穿透的角色。

这是我们上周发布的360GuardField文件,给大家演示一下效果。这是一台XP虚拟 机,安装了冰点的还原系统,这个还原系统正常会被第一代机器狗穿透。这是我们第一代机器(图)。这个系统已经安装了还原保护。可以看到还原攻击已经被拦截 了,这个时候看到攻击者已经没有了。然后使用微软工具查看一下,可以看到攻击者攻击是失败的。这是第二代机器狗样本,它可以直接对文件操作,不需要感染。 他可以在启动目录上拷贝一个文件,因为他是穿透还原去拷贝文件,所以重启后就无法被还原了。这里会有第三种攻击方法,passthrouh,它可以破坏磁 盘数据。我们用Winhex看一下。攻击演示就做到这里。可以看到我们在对待第一、二、三代攻击都成功做到保护。

现在说一下GuardField原理,首先在启动时收集、挂钩还原系统的磁盘过滤驱动,监视 磁盘IRP发送。之后我们挂钩底层磁盘设备,监视磁盘IRP达到,如果数据结构里没有磁盘IRP,我们就认为磁盘IRP没有经过还原系统到达下层。我们首 先对系统做一个检查、修复,检查之前保存的磁盘过滤设备链有没有被摘除,如果摘除了,我们恢复。然后会将这个信息发送到Ring3服务进程。我刚才说的第 一种攻击手段,它以后读写都不会再成功了。后面请求的时候都会被还原系统拦截到。第二代虚拟磁盘方式,每次读写都穿透还原,所以每次穿透还原都会拦截掉。

我们挂钩还原系统在磁盘卷上的过滤驱动,进行收集IRP操作,下面还原系统在磁盘上的过滤驱动,然后在磁盘设备下层也会挂钩,GuardField分析IRP

IRP监视回收,他不一定会发送到下层。可能在磁盘卷或者过滤设备上被取消或者直接完成掉, 没有往下传送。数据始终保持在数据结构里没有被清除。Lofreelrp是用于IRP取消或者完成来释放的。他jmp ds_Plofreelrp。每次当IRP回收的时候,我们从数据结构里可以得到一个监视。

对抗passthrough,因为我们挂钩下层磁盘设备,TM都会使用passthrough这些指令,他是通过这些指令获取磁盘信息。分析passthrough请求包意图。拦截恶意攻击者的passthrough指令。

同时还结合传统反病毒技术,为什么还会使用传统反病毒技术呢?主要原因是Ringo攻击者同 我们处在同一水平,除非阻止其进入RING0,不可能完全对其进行防御。GuardField使用方法,通过Mmloadsystemlmage函数。如 果是一个已知的驱动的话,我们会阻止,最大可能乐观防止攻击者攻击。

还原系统未来趋势。我们现在有GuardField的保护,恶意攻击者肯定会开发出一些新的 更新,对抗GuardField。他们可能会使用哪些手段,猜测主要有两方面:第一,更底层或者更新的磁盘读写技术,绕过磁盘IRP分析,直接写入磁盘。 第二,针对GuardField本身的工具,对GuardField进行破坏、脱钩。我们发布之后,大概不到两天时间就有新的驱动出来,对我们 GuardField脱钩。

如何防御:更底层的磁盘读写监视。他们开发起来难度比较大,短期内没有办法形成比较大的规 模。GuardField这套系统如果有一定时间可以进行修改的话,还是可以用现有系统兼容,对磁盘底盘操作进行监视。我们知道atapi.sys IRP还是存在的,对这一层做hook

针对第二种方法脱钩,可以适量的自我保护、恢复。就我个人来看,针对性攻击不足为惧。如果攻 击者对防御者产生一些针对性攻击,等于攻击者容易落入一个被动捱打的局面。如果已经到脱钩了,说明攻击者已经比较穷了。还原系统在软件方面的对抗应该是没 有止境的。有什么问题大家可以问。

以下是现场问答部分:

问:我给郑文彬补充一些数据,根据权威部门统计,盗号70%是来自网吧。而网吧几乎100%安装了还原设备。刚才郑文彬也介绍了还原设备的攻防。其实还原系统是很重要的一块。

问:我想请问一下您刚才介绍的还原系统软件原理、实现方法,能不能介绍还原卡硬件的原理?

郑文彬:对于还原卡来说,一般有两种。现在市面上的还原卡都不是真正的硬件上的还原卡,他们 使用技术就是PCI设备,可以在OS启动之前获得控制权,在OS无整个过程中监控磁盘读写,但是实质上是通过磁盘驱动监视。应该国外有一些硬盘磁盘设备监 控磁盘IO端口,在IDE接口上做一些保护。但是这种硬件还原产品至今我没有看到。主要还是采用软件方面保护。

问:你的意思是你看到还原卡本质还是软件还原设备?

郑文彬:用PCI就可以比磁盘的OS启动的更早。

问:那就是说PCI还原卡是忽悠人的?

郑文彬:它的好处就是可以更早的监视。像一些纯软件的还原卡,如果在DOS下攻击的话,应该是没有办法的。

主持人:感谢文彬给我们带来的精彩演讲。下面还是茶歇时间

;===================================================================

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值