windows内核编程基础-1


windows内核虽然用c实现,但也有面向对象的思想,下面是3个重要概念:

  1. 驱动对象
  2. 设备对象
  3. IRP(Interrupted Request Packet)

3. 驱动对象

驱动程序就是一个.sys模块,驱动对象则是.sys被加载到内核中的实例化出来的对象,用于表示这个驱动模块,并作为参数传给DriverEntry(可看作this指针).

结构体

typedef struct _DRIVER_OBJECT {
    CSHORT Type;
    CSHORT Size;

    PDEVICE_OBJECT DeviceObject;    //设备链
    ULONG Flags;

    PVOID DriverStart;
    ULONG DriverSize;
    PVOID DriverSection;
    PDRIVER_EXTENSION DriverExtension;  //驱动扩展对WDM程序很重要

    UNICODE_STRING DriverName;

    // This is a pointer
    // to the path to the hardware information in the registry
    PUNICODE_STRING HardwareDatabase;
    
    // 快速IO请求函数地址(数组)
    PFAST_IO_DISPATCH FastIoDispatch;

    PDRIVER_INITIALIZE DriverInit;  // 驱动对象初始化函数指针,此函数指针被I/O管理器初始化,指向 DriverEntry 函数
    PDRIVER_STARTIO DriverStartIo;  //派遣函数地址,可以置为NULL
    PDRIVER_UNLOAD DriverUnload;    // 必须赋值,否则卸载时蓝屏
    
    // 为了支持拓展,派遣函数表在最后。
    // 这个数组内的函数指针都可以设置为NULL
    PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];

} DRIVER_OBJECT;

最后一个函数数组,这些函数都是可选的,当用户层或内核层通过设备的符号链接操作设备(打开,读,写,关闭)时,这些函数就会被调用。也就是说,以前所使用的用户层API :CreateFile, GetFileSize , ReadFile, WriteFile, CloseHandle这些API操作的是一个设备对象,在内核中,文件属于一个设备对象.

同一个函数,能够操作不同的对象,这就是内核中通过C语言实现的多态了.

第5节会详细整理这个数组以及传参原理。

4. 设备对象

设备对象一般是由驱动对象构建出来的(非即插即用驱动)。

驱动对象能够保存各种派遣函数,但是,这些派遣函数的一般是由I/O管理器所调用。在调用派遣函数时,I/O 管理器会将附加的信息打包到一个结构体,
并传递给派遣函数。一般这个结构体被称为 IRP 结构.

只有设备对象才能接收到I/O管理器的I/O请求(IRP)

什么是I/O请求?比如,当一个文件的设备对象被打开( CreateFile )之后,对此文件的设备对象的读( ReadFile )和写( WriteFile )就是 I/O 请求。

但设备对象不能独立存在,设备对象能够接收 I/O 请求,但是却没有处理 I/O 请求的派遣函数,处理 I/O 请求的派遣函数保存在驱动对象中。


因此,在一个驱动项目中,要有两种对象存在:

  1. 驱动对象,能够保存处理 I/O 请求的派遣函数.
  2. 设备对象,能够接收到 I/O 请求.

驱动对象无法被用户层代码所访问到,但是设备对象可以(通过符号链接)。

就像程序和窗口一样。

设备对象虽然在内核层,但是创建了DOS下的符号链接之后,在用户层中就可以通过CreateFile来打开设备对象。并能够通过ReadFile , WriteFile , DeviceIOControl , GetFileSize等函数来间接地调用保存在驱动对象中的派遣函数。


4.0 创建和销毁

IoCreateDevice(), IoDeleteDevice()

创建时,命名以/Device开头。

4.1 结构体

typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _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;

4.2 通讯

4.2.0 设备对象的2种通讯方式

用户层提供的缓冲区地址时用户层的,不能再内核态随意使用。

通讯无非就是读写数据,这里3种读写方式:

  1. 缓冲区设备读写:将缓冲区拷贝到内核态,用完再拷会用户态;
  2. 直接读写:将用户态地址重新映射到内存空间;
  3. 其它

前两种其实就是通过pDevice->Flags与下面的值或运算实现:

  • DO_BUFFERED_IO,通过pIrp->AssociatedIrp.SystemBuffer访问缓冲区;
  • DO_DIRECT_IO,通过MmGetSystemAddressForMdlSafe(pIrp->MdlAddress, NormalPagePriority)获取缓冲区。

如果Flags==0,就会通过pIrp->UserBuffer访问用户层地址,一旦进程切换就会地址错误,极度不安全。

4.2.1 读写方式通讯

4.2.2 控制码方式通讯

4.3 符号链接

符号链接就是一个名字,\DosDevices\D:\\是一个盘符,但其实也可以视为一个符号链接名.

设备名称不暴露给用户层,需要创建符号链接,命名/DosDevices/开头,全局则以/DosDevices/Global开头。

4.3.0 作用

能够让用户层的API发出IO请求,并能够在发出IO请求时指定一个设备来处理此IO请求。

当用户层的应用程序发出一个IO请求时,对象管理器通过此符号链接名称来找到对应的设备,对象管理器能够解析符号链接的名称,以确定IO请求的目的地。

符号链接是给设备对象使用的,设备对象默认没有符号链接,为设备对象创建符号链接之后才可以被用户层访问。

4.3.1 分类

  • NT设备名——设备名一般格式为 "\Device\自定义设备名",此格式的名字一般是用于传递函
    数 IoCreateDevice所要求给出的设备名。这个设备名可以在内核下使用,但是用户层程序无法使用
  • DOS设备名——设备名一般格式为:"\DosDevices\自定义设备名",此格式的名字一般用户传递给函数IoCreateSymbolLinkName的参数,后面这个函数的功能是为一个NT设备名创建一个用户层能够使用的符号链接名。

4.3.2 创建与销毁

IoCreateSymbolicLink——为一个NT设备名链接到一个DOS设备名,供用户层程序使用。

IoDeleteSymbolicLink删除一个DOS设备名.

5. IRP和派遣函数

5.0 IRP头部

PDRIVER_OBJECT->MajorFunction数组存储了许多派遣函数,也就是当设备接收到了IO请求之后被调用来处理IO请求的函数。

typedef
NTSTATUS
DRIVER_DISPATCH (
    _In_ struct _DEVICE_OBJECT *DeviceObject,
    _Inout_ struct _IRP *Irp
    );

比如用户层调用CreateFileW(),那么就会打开一个设备对象,PDRIVER_OBJECT->MajorFunction[IRP_MJ_CREATE]就会调用。系统会将CreateFileW() 参数传递给派遣函数,保存在了 IRPIO_STACK_LOCALTION 两个非常大的结构中。

IRP由IRP头部和IRP栈组成。

IRP的头部:

typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _IRP {
    CSHORT Type;
    USHORT Size;

    //
    // Define a pointer to the Memory Descriptor List (MDL) for this I/O
    // request.  This field is only used if the I/O is "direct I/O".
    //

    PMDL MdlAddress;

    ULONG Flags;
    
    union {
        struct _IRP *MasterIrp;
        __volatile LONG IrpCount;
        PVOID SystemBuffer;
    } AssociatedIrp;

    IO_STATUS_BLOCK IoStatus;


    KPROCESSOR_MODE RequestorMode;

    /*
    ...
    */

    PIO_STATUS_BLOCK UserIosb;
    PKEVENT UserEvent;
    union {
        //...
    } Overlay;
    PVOID UserBuffer;
    union {
        struct {
            union {
                //...
                struct {

                    //
                    // The following are available to the driver to use in
                    // whatever manner is desired, while the driver owns the
                    // packet.
                    //

                    PVOID DriverContext[4];

                } ;
            } ;
            //...
            struct {
            //...
                union {
                //...
                struct _IO_STACK_LOCATION *CurrentStackLocation;
                };
            };
            //...
        } Overlay;
    } Tail;
} IRP;

IRP实际就是一个用户保存用户层传递进来的参数. 这些参数有多种 , 而且, 对于不同的IO请求,会有不同的参数, 而无论什么IO请求,多少个参数,都只能通过此结构体来保存, 因此这个结构体比较庞大.

IO管理器创建一个IRP代表一个IO操作,并将IRP传给正确的驱动程序,驱动程序执行该IRP指定操作,再传回给IO管理器,告诉管理器已经完成操作或者传给另一个驱动。

IRP的接收者是设备对象,处理者是驱动对象。

MSDN介绍了不同的IO请求下,参数保存在IRP结构体中哪个字段。

5.1 IO_STACK_LOCATION

上面的大结构体只是IO请求包IRP的头部,后面还有IO_STACK_LOCATION结构体数组,类似重定位表TypeOffset,与IRP同时创建。

数组中的每个堆栈单元都对应一个将处理该 IRP 的驱动程序。IRP的头部有一个当
IO_STACK_LOCATION的数组索引(从1开始),同时也有一个指向该IO_STACK_LOCATION的指针。

通过IoGetCurrentIrpStackLocation()就能过获取到当前设备的IO栈.

typedef struct _IO_STACK_LOCATION {
    UCHAR MajorFunction;
    UCHAR MinorFunction;
    UCHAR Flags;
    UCHAR Control;

    //
    // The following user parameters are based on the service that is being
    // invoked.  Drivers and file systems can determine which set to use based
    // on the above major and minor function codes.
    //

    union {
        struct {/*...*/} Create;
        struct {/*...*/} CreatePipe;
        struct{/*...*/} CreateMailslot;
        //...
    } Parameters;

    //...

    PVOID Context;

} IO_STACK_LOCATION, *PIO_STACK_LOCATION;

里面最重要的是那个联合体,包含不同类型IRP所携带的参数结构体。

当驱动程序准备向次低层驱动程序传递IRP时可以调用 IoCallDriver 例程,它其中的一个工作是递减当前IO_STACK_LOCATION的索引,使之与下一层的驱动程序匹配。但该索引不会设置成0,如果设置成0,系统将会崩溃。就是说,最底层的驱动程序不会调用 IoCallDriver 例程。

5.2 IRQL

Interrupted ReQuest Level.

内核实际就是一个进程ntoskrnl.exe,里面有很多线程和全局变量。为了解决内核线程同步问题,微软提出了IRQL的概念。

3个级别:

  • Dispatch:所有运行在Dispatch级的代码都是会被进行原子操作的,且不能访问分页内存,也就是说操作系统中在一个时间内只能运行一段Dispatch级的代码,且必须将其完全执行完毕后才会发生线程切换
  • APC:比 Dispatch 低的一个级别,可以访问分页内存
  • Passive:最低的优先级,大多数代码所运行的级别
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
编写Windows内核程序,就意味着这个程序可以执行任意指令,可以访问计算机所有的软件、硬件资源。因此,稍有不慎就有可能将系统变得不稳定。Windows的设计者设计了各种驱动模型或者框架,如NT式内核驱动模型、WDM框架和新推出的WDF框架。在这些模型框架下编程,就使内核编程变得简单,同样也降低了内核程序崩溃的机会。其实,Windows驱动程序员和黑客都在写内核程序,唯一不同的是驱动程序员按照微软设计的模型写程序,而黑客可以不按照这些框架写。Windows设计的这些框架,可以将操作系统的原理隐藏起来,只暴露一些接口,驱动程序员只要把这些接口写好就可以了。从这个角度看,驱动开发并不难,尤其是读完本书后,更会觉得不难了。但是想完成一些特殊的功能,如内核级隐藏进程等,Windows的这些框架就没什么用处了,程序员就需要对Windows内核有全面的了解,通过直接修改Windows内核来实现这些目的。往往黑客对这种技术乐此不疲,通过修改Windows内核,你会发现你的程序几乎无所不能。   编写内核程序是一件很痛苦的事情,回想起这些年学习内核程序开发的经历,真是感慨万千。就如同谭文所说:编写内核程序的人从某种程度讲是孤独的。当一个经验并不丰富的小程序员面对庞大复杂的并且不开源的Windows框架时,那是一种怎样的无助感啊!谭文是我比较钦佩的程序员之一,他对技术非常执着,并且精力充沛。内核程序的知识涉及面非常广,不同类别的内核程序差别也特别大,他几乎都有所涉猎。相信读者在读完这本书后,能对Windows内核开发有比较详细的了解,同时也能结合书中的实例写出很优秀的内核程序了 本书从Windows内核编程出发,全面系统地介绍了串口、键盘、磁盘、文件系统、网络等相关的Windows内核模块的编程技术,以及基于这些技术实现的输入密码保护、防毒引擎、文件加密、网络嗅探、网络防火墙等信息安全软件的核心组件的具体编程。主要知识重点包括:Windows串口与键盘过滤驱动Windows虚拟存储设备与存储设备过滤驱动Windows文件系统过滤驱动、文件系统透明加密/解密驱动Windows各类网络驱动(包括TDI过滤驱动及3类NDIS驱动),以及最新的WDF驱动开发模型。有助于读者熟悉Windows内核驱动的体系结构,并精通信息安全类的内核编程技术。本书的大部分代码具有广泛的兼容性,适合从Windows 2000一直到目前最新的Windows 7 Beta版。  本书则基本上介绍的是正统的内核编程技术,是微软在内核编程中给信息安全软件开发者提供的相关接口的大集合,是名门正派的技术,不沾邪气。一个好的内核程序员,“正邪兼修”是有必要的。   本书既适合于有志于成为软件程序员的学生使用,也适合于希望加强自己的技术实力的Windows程序员阅读,同时更适合于从事信息安全行业的Windows软件的开发者作为手头参考。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值