note:正在看《Windows内核安全与驱动开发》这本书, 自己做一点笔记。以下内容部分来自这本书。
一、 内核模式与 用户模式
内核模式(Kernel Module)简称 内核态 , R0 层 。
用户模式(User Module)j简称用户态, R3层。
一般一个EXE被双击后,系统为其创建一个Process进程,运行在R3层,拥有自己独立的进程空间可以随意使用寄存器,不用担心寄存器是否被其他进程修改。这些都源自系统给虚拟的内存。让每个R3程序都可以独立运行 而不相互影响。
驱动程序运行在内核空间R0层,具体到win32 位操作系统,一个Process进程4GB空间,低2GB是用户空间,是隔离的每个进程都有一个虚拟的空间互不影响,高2GB是系统空间,也就是驱动运行的地方。这个高2GB是被任何Process共享的,这样驱动就可以被每个进程“看到”,进而可以做一些手脚。
那到底驱动是运行在哪里呢?既然4GB被分成高2GB和低2GB,驱动怎么影响整个系统运行的呢?或者说怎么区分是哪个程序?这一点驱动在设计的时候就已经考虑好了。高2GB是系统空间,被所有进程共享,所有驱动实际是运行于任何一个进程中的,简单的说就是驱动中的代码每次执行一定是在某个确定的进程空间中。至于那个进程,是需要根据请求来分辨。
一个函数可以验证一下: HANDLE PsGeCurrentProcessId() 获取当前进程PID, 返回的句柄就是 PID,和我们打开任务管理器中看到的是一样的。
刚开始我以为驱动一直运行在System进程内,这个是错误的。书中也指明了,System进程是系统一个比较特殊的进程,在XP下这个进程ID始终是4,system进程是用来加载驱动的进程,并不是驱动唯一运行的进程,这一点新手容易弄错。
二、 STATUS
驱动开发中每个函数基本上都有一个返回状态,这种设计是为了更具体定位错误原因,增强驱动的稳定性。
STATUS_SUCCESS 成功
STATUS_INVALID_PARAMITER 无效参数(一般参数传入过多会引发这个错误)
STATUS_PENDING 请求未决,这个不能算是一个错误,实质发送的请求尚未完成,一般完成后会提供一个回调或者一个事件,这个常见于异步的文件读写。
STATUS_BUFFER_OVERFLOW 缓冲长度不够
STATUS_BUFFER_TOO_SMALL 缓冲长度不够
三、 驱动对象
window内核采用面向对象的编程方式,但是使用的却是C语言。window内核对象并不等同于C++面向对象,它是采用C语言编写的模拟面向对象。
window内核认为很多东西都是对象,比如:一个驱动、一个设备、一个文件等,我们可以理解“对象”为一个基类。
四、请求
生成请求:主功能号为: IRP_MJ_CREATE
查询请求:主功能号为:IRP_MJ_QUERY_INFORMATION
设置请求:主功能号为:IRP_MJ_SET_INFORMATION
控制请求:主功能号为:IRP_MJ_DEVICE_CONTROL
关闭请求:主功能号为: IRP_MJ_CLOSE
请求指针:IRP的指针,一般写成PIRP或者IRP*
五、代码中断等级
在R3层,用户态程序都是运行在同一个中断等级中的,但是在内核层分为不同的中断等级。
内核中有Passive级和Dispatch级两种,Dispatch中断级比PassIve搞。所以再调用不熟悉的内核APi时候,要注意到官网上查一下函数运行的中断等级。
比如:Callers of IoCreateFile must be running at IRQL = PASSIVE_LEVEL
以下摘自《Windows内核安全与驱动开发》这本书:
调用源 | 一般运行等级 |
DriverEntry,DriverUNload | Passive级 |
各种分发函数 | Passive级 |
完成函数 | Dispatch级 |
各种NDIS回调函数 | Dispatch级 |
六、WDK中特殊代码
#define IN // 输入宏定义
#define OUt // 输出宏定义
指定函数预编译指令
#pragma alloc_text( INIT, DriverEntry)
#pragma alloc_text( PAGE, NdisProtUnload)
#pragma alloc_text( PAGE, NdisProtOpen)
#pragma alloc_text( PAGE, NdisProClose)
#pragma alloc_text 这个宏用来指定某个函数的可执行代码编译出来后在sys文件中的位置。注意:内核文件sys编译完成后也是PE文件,PE文件的代码段(text段)中有不同的节(Section),不同节被加载到内存后系统处理情况不一样。我们只需要记住三个节:
INIT节: 代码初始化完成后就被释放掉。
PAGE节: 位于可以进行分页交换的内存空间,这些空间可以在内存紧张时候被交换到硬盘上。
PAGELK节:如果没有使用上面两种节说明,默认代码位于PAGELK节,加载后位于不可分页交换的内存空间中。
注意:PAGE节中的函数不能再Dispatch级调用,因为这种函数的调用可能诱发缺页中断,而缺页中断处理不能再Dispatch级完成,为此一般都会使用PAGED_CODE()进行测试,如果发现当前中断等级为Dispatch级,则程序会直接报异常。