1、内核编程的环境
32位系统的进程可访问的内存大小是4G,低位2G是用户空间,高位2G是内核空间。应用程序运行在用户态,进程空间是相互独立的,不必担心进程间访问同一地址空间出错,但是如果如果内核空间获取的信息肯定是一样的。
内核模式也被称为内核态,x86架构下R0层的代码才可以访问内核空间,普通应用程序编译出来都运行在R3层,R3层的代码如果要调用R0层的功能,需要通过系统提供的入口来实现,调用内核API.
内核模块也代表实际的驱动程序,内核空间是所有进程所共享的,系统进程一般是一个名为“system”的进程,PID始终是4。windows一般都用系统进程来加载内核模块,并不是说明内核代码始终运行在System进程里。
2、数据类型
基本数据类型,跟win32应用程序开发用到的基本一样,只是重命名了。
unsigned long | ULONG |
---|---|
unsigned char | UCHAR |
unsigned int | UINT |
void | VOID |
unsigned long * | PULONG |
unsigned char * | PUCHAR |
unsigned int * | PUINT |
void * | PVOID |
绝大部分内核API的返回值类型为NTSTATUS,取值如下:
STATUS_SUCCESS | 成功 |
---|---|
STATUS_INVALID_PARAMETER | 参数错误 |
STATUS_INSUFFICIENT_RESOURCES | 资源不足 |
STATUS_PENDING | 请求未决 |
STATUS_BUFFER_OVERFLOW | 缓冲长度不够 |
STATUS_BUFFER_TOO_SMALL | 缓冲长度不够 |
和win32应用开发的字符串不一样,驱动开发里的字符串使用的是一个结构里
type struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
}UNICODE_STRING *PUNICODE_STRING;
字符串使用的是宽字符,双字节结构。
3、重要的数据结构
驱动对象(DRIVER_OBJECT),代表一个驱动程序,或者说一个内核模块。在Windows启动之后,这些内核对象都在内存中,驱动对象可以通过WinObj工具查看,显示所有的内核对象。
设备对象,设备对象是内核中的重要对象,类似于windows编程中的窗口,窗口时唯一可以接收消息的东西,任何消息都是发给一个窗口的。内核模块中,大部分消息都以请求(IRP)的方式传递。设备对象(DEVICE_OBJECT)是唯一可以接收请求的实体,任何一个请求都是发送给某个设备对象的。
一个驱动程序含有一个驱动对象,一个驱动对象对应有多个设备对象。
4、函数调用
大部分内核API都有前缀,主要的函数以Io-、Ex、Rtl-、Ke、Zw-、Nt-、和Ps-开头。
Ex开头常用的是分配内存,获取互斥体函数。如:ExAllocatePool、ExFreePoll、ExAcquireFastMutex、ExReleaseFastMutex、ExRaiseStatus
Zw开头常用的是文件操作相关函数,如:ZwCreateFile、ZwWriteFile、ZwReadFile、ZwQueryDirectoryFile、ZwDeviceControlFile、ZwCreateKey、ZwQueryValueKey
Rtl开头常用的是字符串操作相关函数,如:RtlInitUnicodeString、RtlCopyUnicodeString、RtlAppendUnicodeToString、RtlStringCbPrintf、RtlCopyMemory、RtlMoveMemory、RtlZeroMemory、RtlCompareMemory、RtlGetVersion
Io开头的是Io管理相关函数。如:IoCreateFile、IoCreateDevice、IoCallDriver、IoCompleteRequest、IoCopyCurrentIrpStackLocationToNext、IoSkipCurrentIrpStackLocationToNext、IoGetCurrentIrpStackLocation
Ps开头常用的是与进程、线程相关的函数,如:PsGetCurrentProcessId