内核编程环境
概述
- 在Windows下,32为系统中每个进程都有其自身享有的4GB内存空间,每个进程的内存空间都是相互隔离的。其中低2GB是用户空间,高2GB是内核空间。原则上,每个进程的高2GB内核空间内的数据是共享的,即绝大部分都是一样的。
- 而内核空间是受到硬件保护的,比如在x86架构下R0层(Ring0)的代码才可以访问内核空间,普通的应用程序编译出来后都运行在R3层,R3层程序要调用R0层功能时,只能通过系统提供的一个入口(该入口中调用sysenter指令)来实现。
无处不在的内核模块
- 内核是由接口的,微软提供规定的格式,用于让硬件驱动的编程人员能够按照规定的格式编写“驱动程序”。这些驱动程序能加载到内核中,称为内核的一部分,这样内核就有了可扩展性,只要简单的安装驱动程序,就可以适应各种不同的硬件了。
- 把内核模块叫做驱动程序Dirver,也可以按照Linux程序员们的叫法,称之为内核模块(Kernel module)。
- 内核模块位于内核空间,作为R0级代码执行,可以不受任何限制,任意修改内核。
- 内核模块位于内核空间,而内核空间又被所有的进程所共享,因此内核模块实际上位于任何一个进程空间,但是任意一段代码的一次执行,一定是位于某个具体的进程空间中的,至于这个进程是哪个,这取决于当时的运行状况。
- 一个函数
PsGetCurrentprocessId
,能够得到当前进程的进程号,函数返回进程HANDLE句柄,实际上就是一个进程的PID。 - 一个误区:认为所有内核代码都运行在系统进程内:windows中的系统进程是一个名为“System”的进程,是Windows自身生成的一个特殊进程,在XP中这个进程的PID始终为4 。DirverEntry函数被调用时,一般都位于系统进程中,这是因为Windows一般都使用系统进程来加载内核模块,并不是说内核代码始终运行在System进程中。
- 使用微软提供的驱动开发包WDK进行开发。
数据类型
基本数据类型
-
在进行内核编程时,应当遵守WDK的编码习惯,这样可以使得代码在不同的目标平台上编译不会产生不一致的问题。
-
WDK中已经将很多数据类型向SDK那样重新定义过,这样做的好处是,万一有了什么问题,再重新定义一下即可,不至于使得代码产生不可控的问题。
-
以下是一些需要习惯使用的数据类型:
- unsigned long ---->ULONG
- unsigned char ---->UCHAR
- unsigned int ---->UINT
- void ---->VOID
- unsigned long* ---->PULONG
- unsigned char * ---->PUCHAR
- unsigned int * ---->PUINT
- void* ---->PVOID
-
一般我们使用x86和x64平台进行编译,它们的区别除了指针从四个字节变为了8个字节之外,其余几种类型字节的宽度都没有什么变化。
返回状态
- 绝大部分的内核API的返回值都是一个返回状态,就是一个错误码。类型为
NTSTATUS
。如下示例:
NTSTATUS MyFun()
{
NTSTATUS status;
//打开一个文件...
status=ZwCreateFile(..);
//宏NT_SUCESS()用来判断一个返回值是否成功
if(!NT_SUCESS(status))
{
//出错就返回错误码
return status;
}
}
- 返回的错误码完全由编写者说了算,不过错误码有一些约定成俗的含义,如下一些示例:
字符串
- 驱动字符串一般用一个结构来容纳,定义如下:
typedef struct _UNICODE_STRING{
USHORT Length; //字符串的长度,单位是字节数
USHORT MaximumLength; //最大字节数
PWSTR Buffer;
}UNICODE_STRING,*PUNICODE_STRING;
//字符串的字符是宽字符,双字节的
- UNICODE_STRING是可以直接打印的,可以使用如下的写法:
//定义和输出字符串
UNICODE_STRING buff = RTL_CONSTANT_STRING(L"使用字符串结构体的字符串");
DbgPrint("%wZ\n", &buff); //输出使用%wZ
-
UNICODE_STRING结构体的指针(注意是指针,结构体本身不能打印)可以用%wZ来打印。
-
UNICODE_STRING除了可以用于初始化和打印之外,其它普通字符串的操作也可以同样做到。
重要的数据结构
- Windows内核编程中有三个重要的概念:
- 驱动对象
- 设备对象
- IRP
驱动对象
- Windows内核采用了面对对象的编程方式,但使用的却是C语言。所以Windows内核中所谓的内核对象并不是一个C++类对象,而是一种使用C语言对面对对象编程方式的一种模拟。
- 在Windows中很多东西都是“对象”,比如一个驱动、一个设备等,“对象”在这里相当于一个基类。
- 驱动对象代表着当前编写的驱动程序,从面对对象的角度来看就如同Windows编程时的示例句柄INSTANCE代表一个应用程序一样。
- 当驱动一加载,对象管理器便会创建一个驱动对象,并调用DriverEntry将驱动对象传入,然后我们就可以在DriverEntry中为驱动对象结构体的各个字段赋值,为整体的驱动程序定下总的基调。
- 驱动对象的结构如下:
typedef staruct _DRIVER_OBJECT{
//结构的类型和大小
SHORT Type; //0x0
SHORT Size;