内核基础-API命名-字符

流过昼夜 提交于 2019-11-28 18:08:53

环境安装

环境安装需要极其谨慎, 因为稍有不慎, 就需要重装系统.

驱动开发环境依赖WDK(Windows driver kit),也就是微软的驱动开发工具包. 这个工具包里面包含了驱动程序的编译工具 , 头文件, 库等. 缺了这个, 无法编译驱动程序.

WDK有很多的版本, 版本的选择很重要. 一般最好选择最新版的.

除了WDK还需要和WDK同一版本的SDK .

如果所安装的WDK,SDK或者VS错误了, 可以使用卸载工具将它们全部卸载干净再配置. 普通的卸载工具是卸载不干净的, 但以下工具可以卸天卸地卸空气: Total Uninstall(旗舰版) 1566889168640

项目配置

编译出来的驱动其版本必须和加载该驱动的操作系统版本一致, 例如 : 加载驱动的是win7 32位系统, 则必须将驱动项目的目标系统设置为win7 , 且驱动必须是x86体系的.

1566889180511

源码编写

驱动程序可以使用C语言项目, 也可以使用C++项目, 但是, 通常使用C项目, 因为C++的项目有名称粉碎机制, 在定义函数的时候, 需要加入extern "C" . 此外, C++编译器生成的代码不如C语言生成的代码高效, 而内核的代码会被高频繁的调用, 越高效越好.

驱动调试

编写出来的驱动实则属于系统内核的一个插件, Windows的系统内核文件为:ntoskrnl.exe , 所有的内核模块都是加载到这个exe的一个DLL , 因此, 内核驱动(.sys)就像DLL一样, 无法单独运行, 需要加载到主模块中.

调试一个内核驱动, 需要调试整个操作系统, 因此需要建立双机调试环境, 使用windbg来调试.

API使用规则

Windows的API分为用户层API和内核层API, 在用户层中,无法直接调用内核层API, 实际上, 绝大部分的用户层的API在最底层都会通过ntdll.dll切换到内核层,间接调用内核层的API .

在内核层编程中, 不能调用用户层的API , 也就是说, 像是创建文件, 就不能使用CreateFile, 创建进程, 不能使用CreateProcess , 在内核层中会提供内核版本的创建文件,创建进程等等API.

内核API命名规则

window操作系统的内核使用C语言编写, 但是使用了面向对象的思想, 在系统内核中, 它将不同的API分成了不同的组件, 每个组件的API名称等会带有一个组件的名称:

函数前缀
所属的组件或函数说明

Cc
缓存管理器

Cm
配置管理器(即注册表)

Dbg/Kd
调试支持函数

Ex
执行体函数

FsRtl
文件系统驱动程序

Fstub
文件系统引导接口函数

Hal
HAL提供的接口函数

Io
I/O管理器

Ke
内核函数

Lpc
本地过程调用(LPC)函数

Mm
内存管理器

Nt
windwos系统服务

Ob
对象管理器

Perf
日志记录函数

Po
电源管理器

Pp
即插即用管理器

Ps
进程/线程

Raw
RAW文件系统函数

Rtl
内存运行时库函数

Se
安全函数

Vf
驱动程序检验器函数

Wmi
Windows管理器规范

Zw
和Nt前缀同名的一套函数,省去了参数验证的步骤,其它逻辑相同,可以认为以Nt前缀为名称的函数针对用户模式的调用者,以Zw前缀为名称的函数针对内核模式调用者

数据类型, 字符串和内存操作

Windows内核开发中, 采用的是和用户层编程所不一样的数据类型:

数据类型
长度
基本型
类型名称

UINT8
8 bit
unsigned char
无符号字符

UCHAR
8 bit
unsigned char
无符号字符

PUCHAR
32 bit
unsigned char*
无符号字符指针

UINT16
16 bit
unsigned short
无符号短整形

USHORT
16 bit
unsigned short
无符号短整形

PUSHORT
32 bit
unsigned short*
无符号短整形指针

UINT32
32 bit
unsigned int
无符号整形

UINT
32 bit
unsigned int
无符号整形

ULONG
32 bit
unsigned long
无符号长整形

PUNIT
32 bit
unsigned int*
无符号整形指针

UINT64
64 bit
unsigned __int64
无符号64位整形

ULONG64
64 bit
unsigned __int64
无符号64位整形

PULONG64
32 bit
unsigned __int64*
无符号64位整形指针

这些数据类型和用户层的数据类型大同小异.

字符串操作

字符串的操作是截然不同的.

内核中同一采用UNICODE_STRING结构体来存取字符串. 这样做是为了更安全. Windows内核默认使用Unicode编码.

下面是结构体的说明:

typedef struct _UNICODE_STRING {   USHORT Length; // 字符串的长度, 单位是字节数   USHORT MaximumLength; // 最大字节数   PWCH   Buffer;#endif // MIDL_PASS} UNICODE_STRING;

在这个结构体中, length保存的是当前字符串的字符长度(字节数), MaximumLength保存的是字符串缓冲区的最大长度(字节数).

使用这个结构体的时候, 将字符串赋值给Buffer字段, 但不能只给这个字段赋值, 其它字段也必须一起初始化, 下面是操作这个结构体的函数:

函数名
功能

RtlInitUnicodeString
初始化字符串 ,注意,此函数不会分配空间.

RtlFreeUnicodeString
销毁字符串

RtlCopyUnicodeString
拷贝字符串

RtlAppendUnicodeStringToString
追加字符串

RtlCmpareUnicodeString
比较字符串

RtlUnicodeStringToInteger
字符串转数字

RtlIntegerToUnicodeString
数字转字符串

RtlAppendUnicodeStringToString
将UNICODE字符串结构转换为ANSI

Kdprint
输出调试信息

内存操作

  • ExAllocatePool - 内存分配函数

  • ExFreePool - 内存释放函数

内核链表

在内核中, 有很多链表:

  1. 进程内核对象链表

  2. 线程内核对象链表

  3. 驱动对象链表

  4. 模块链表

  5. ...

在Windows内核中, 无论是什么链表, 都是使用如下结构的双向链表:

typedef struct _LIST_ENTRY {   struct _LIST_ENTRY *Flink; // 指向下一个节点,如果没有下一个,则指向链表的头结点(循环链表)   struct _LIST_ENTRY *Blink; // 指向上一个节点.(双向链表)} LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

内核中提供了以下函数来操作链表:

  1. InitializeListHead - 初始化链表

  2. InsertHeadList - 将节点插入到链表头部

  3. InsertTailList - 将节点插入到链表尾部

  4. RemoveTailList - 删除该节点前一个节点.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!