Windows 内核函数
因为C语言库是运行的R3应用层上的,而驱动程序是运行的内核模式下的。应用层的函数能调用内核层的函数库,但是内核层的函数由于一些安全性的权限措施,不能调用上层库。所以普通的C语言库是不能在内核模式下运行的,必须使用DDK提供的运行时函数。
1、内核模式下的字符串操作
1.1、ASCII字符串和宽字符串
a)、char型字符串:负责记录ANSI字符集,它指向一个char数组的指针,每个char型变 量的大小为一个字节,字符串是以0标志字符串结束的
Eg: char* str1 = “abc”;
b)、wchar_t型的宽字符串:负责描述unicode字符集的字符串,它指向一个wchar_t数组的指针,wchar_t字符大小为两个字节,字符以0标志字符串结束
Eg: wchar_t * str2 = L”abc”;//使用关键字L,编译器会自动生成所需要的宽字符
在驱动程序开发中,DDK将char和wchar_t类别,替换成CHAR和WCHAR类别。
以上两个例子分别替换成:
CHAR * string = “abc”;
KdPrint((“%s”,string));
WCHAR *string=L”abc”;
KdPrint((“%S”,string));
1.2 、ANSI_STRING字符串与UNICODE_STRING字符串
DDK不鼓励程序员使用C语言的字符串,主要因为:标准C的字符串处理函数容易导致缓冲区溢出等错误,如果程序员不对字符串的长度进行检验,很容易导致这个错误,从而导致整个操作系统的崩溃。
DDK鼓励程序员使用DDK自定义的字符串,这种数据格式定义如下:
Typedef struct _STRING{
USHORT Length;//字符的长度
USHORT MaximumLength;//整个字符串缓冲区的最大长度
PCHAR Buffer;//缓冲区指针
}STRING;
Typedef STRING ANSI_STRING;
Typedef PSTRING PANSI_STRING;
Typedef STRING OEM_STRING;
Typedef PSTRING POEM_STRING;
与ANSI_STRING相对应,DDK将宽字符串封装成UNICODE_STRING数据结构。
Typedef struct _UNICODE_STRING{
USHORT Length;//字符的长度,单位是字节,如果是N个字符,那么Length等于N的2倍
USHORT MaximumLength;//整个字符串缓冲区的最大长度
PCHAR Buffer;//缓冲区指针
}UNICODE_STRING;
关于ANSI)STRING字符串和UNICODE_STRING字符串,KdPrint同样提供了打印log的方法
ANSI_STRING ansiString;
KdPrint((“%Z\n”,&ansiString));//大写的Z
UNICODE_STRING uniString;
KdPrint((“%wz\n”,uniString));//小写的wz
1.3、字符初始化与销毁
ANSI_STRING字符串和UNICODE_STRING字符串使用前需要进行初始化,有两种办法构造该数据结构。
a)、使用DDK提供的相应函数
RtlInitAnsiString(IN OUT PANSI_STRING des,IN PCSZ sour)
该方法将sour的指针指向des,修改一个值,那么另一个值也会发生变化
b)、程序员自己申请内存,并初始化内存,当不用字符串时,需回收字符串占用的内存
ExAllocatePool(size)//用法与C语言的mallocate相似
ExFreePool(UNICODE_STRING);//释放内存
1.4、字符串复制
ANSI_STRING字符串复制函数:
VOID RtlCopyString(IN OUT PSTRING DestStr,IN PSTRING SourStr OPTIONAL);
UNICODE_STRING字符串复制函数:
VOID RtlCopyUnicodeString(IN OUT PSTRING DestStr,IN PSTRING SourStr );
1.5、字符串比较
ANSI_STRING比较函数:
//第三个参数表示是否忽略大小写
LONG RtlCompareString(IN PSTRING str1,IN PSTRING str2,BOOLEAN caseinSen);
UNICODE_STRING 比较函数:
LONG RtlCompareUnicodeString(IN PUNICODE_STRING str1,IN PUNICODE_STRING str2,BOOLEAN caseinSen);
以上两个函数,返回值:如果是0,表示两个字符串相等
小于0,str1小于str2
大于0,str1大于str2
比较是否相等的函数:RtlEqualString与RtlEqualUnicodeString函数,返回非零表示相等,翻译0表示不等
1.6、字符串转化成大写
a)、ANSI_STRING字符串转化成大写:
VOID RtlUpperString(IN OUT PSTRING destStr,IN PSTRING SourStr);
b)、UNICODE_STRING字符串转化成大写:
//第三个参数表示是否为摩的字符串分配内存
NTSTATUS RtlUpcaseUnicodeString(IN OUT PUNICODE_STRING destStr OPTIONAL,IN PCUNICODE_STRING sourStr,IN BOOLEAN allocateDestStr);
1.7、字符串与整型数字相互转换
将UNICODE_STRING字符串转换成整数:
//参数分别表示:需要转换字符串,转化数的进制(2,8,10,16...),需要转换的数字
NTSTATUS RtlUnicodeStringToInteger(IN PUNICODE_STRING String,IN ULONG BASE OPTIONAL,OUT PULONG Value);
将整形转换成UNICODE_STRING字符串
//参数:需要转换的数字,进制,需要转换的字符串
RtlIntegerToUnicodeString(IN ULONG Value,IN ULONG Base OPTIONAL,IN OUT PUNICODE_STRING String);
1.8、ANSI_STRING字符串与UNICODE_STRING字符串相互转换
将UNICODE_STRING字符串转换为ANSI_STRING字符串
NTSTATUS RtlUnicodeStringToAnsiString(IN OUT PANSI_STRING DestStr,IN PUNICODE_STRING SourStr,IN BOOLEAN AllocateDestStr);
将ANSI_STRING字符串转换成UNICODE_STRING字符串
NTSTATUS RtlAnsiStringToUnicodeString(IN PUNICODE_STRING SourStr,IN OUT PANSI_STRING DestStr,IN BOOLEAN AllocateDestStr);
2、内核模式下的文件操作
2.1 文件的创建
对文件的创建或者打开都是通过内核函数ZwCreateFile实现的。这个内核函数返回一个文件句柄,文件的所有操作都是依靠这个句柄进行操作的,在文件操作完毕后,需要关闭这个句柄
NTSTATUS
ZwCreateFile(
__out PHANDLE FileHandle,//返回打开文件的句柄
__in ACCESS_MASK DesiredAccess,//对打开文件操作的描述,读、写或者其他
__in POBJECT_ATTRIBUTES ObjectAttributes,//包含要打开的文件名
__out PIO_STATUS_BLOCK IoStatusBlock,//指向一个IO_STATUS_BLOCK结构,接收操作结果状态
__in_opt PLARGE_INTEGER AllocationSize,//指针,指定文件初始分配时的大小
__in ULONG FileAttributes,//指定新创建文件的属性
__in ULONG ShareAccess,//指定文件的共享方式
__in ULONG CreateDisposition,//指定文件存在或不存在时如何处理
__in ULONG CreateOptions,//指定控制打开操作和句柄使用的附加标志位
__in_opt PVOID EaBuffer,//指向可选的扩展属性区
__in ULONG EaLength //扩展属性区的长度
);
文件路径是通过第三个参数来指定的,另外,文件名必须是符号链接或者是设备名。例如:盘符“c:”就是一个符号链接,这里应该用“\??\c:”代替,“c:\abc.txt”要写成“\??\c:\abc.txt”
其中,“\??\c:”是符号链接,内核会将它转换成设备名“\Device\HarddiskVolume1”。
对应关闭文件句柄ZwClose(hfile);
2.2 文件打开
除了使用ZwCreateFile函数可以打开文件,DDK还提供了一个内核函数ZwOpenFile函数打开文件
NTSTATUS
ZwOpenFile(
OUT PHANDLE FileHandle,//返回打开的文件句柄
IN ACCESS_MASK DesiredAccess,//打开的权限
IN POBJECT_ATTRIBUTES ObjectAttributes,//objectAttribute结构
OUT PIO_STATUS_BLOCK IoStatusBlock,//指向一个结构体指针,指明打开文件的状态
IN ULONG ShareAccess,//共享的权限
IN ULONG OpenOptions//打开选项
);
对应关闭文件句柄ZwClose(hfile);
2.3、获取或修改文件属性
包括获取文件大小,获取或修改文件指针位置、获取或修改文件名、获取或修改文件属性(只读,隐藏属性)、获取或修改文件创建、修改如期等
DDK提供了内核函数ZwSetInformationFile和ZwQueryInformationFile函数来进行获取和修改文件属性
NTSTATUS
ZwSetInformationFile(
IN HANDLE FileHandle,//文件句柄
OUT PIO_STATUS_BLOCK IoStatusBlock,//返回设置状态
IN PVOID FileInformation,//依据FileInformationClass不同而不同,作为输入信息
IN ULONG Length,//FileInformation数据的长度
IN FILE_INFORMATION_CLASS FileInformationClass//描述修改属性的类型
);
NTSTATUS
ZwQueryInformationFile(
IN HANDLE FileHandle,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID FileInformation,
IN ULONG Length,
IN FILE_INFORMATION_CLASS FileInformationClass
);
a)、当FileInformationClass是FileStandardInformation时,输入和输出的数据是FILE_STANDARD_INFORMATION结构体,描述文件的基本信息
typedef struct FILE_STANDARD_INFORMATION {
LARGE_INTEGER AllocationSize;//为文件分配的大小
LARGE_INTEGER EndOfFile;//距离文件结尾还有多少字节
ULONG NumberOfLinks;//有多少给链接文件
BOOLEAN DeletePending;//是否准备删除
BOOLEAN Directory;//是否为目录
} FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION;
b)、当FileInformationClass是FileBasicInformation时,输入和输出的数据是FILE_BASIC_INFORMATION结构体,描述文件的基本信息
typedef struct FILE_BASIC_INFORMATION {
LARGE_INTEGER CreationTime;//文件的创建时间
LARGE_INTEGER LastAccessTime;//最后访问时间
LARGE_INTEGER LastWriteTime;//最后写时间
LARGE_INTEGER ChangeTime;//最后修改时间
ULONG FileAttributes;//文件属性
} FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION;
c)、当FileInformationClass是FileNameInformation时,输入和输出的数据是FILE_NAME_INFORMATION结构体,描述文件名信息。
typedef struct _FILE_NAME_INFORMATION {
ULONG FileNameLength;//文件名长度
WCHAR FileName[1];//文件名
} FILE_NAME_INFORMATION, *PFILE_NAME_INFORMATION;
d)、当FileInformationClass是FilePositionInformation时,输入和输出的数据是FILE_POSITION_INFORMATION结构体,描述文件名信息
typedef struct FILE_POSITION_INFORMATION {
LARGE_INTEGER CurrentByteOffset;//代表当前文件指针位置
} FILE_POSITION_INFORMATION, *PFILE_POSITION_INFORMATION;
2.4、文件的写操作
文件写操作的内核函数,其函数声明如下:
NTSTATUS
ZwWriteFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
IN PVOID Buffer,//从这个缓冲区开始往文件里写
IN ULONG Length,//准备写多少字节
IN PLARGE_INTEGER ByteOffset OPTIONAL,//从文件的多少偏移地址开始写
IN PULONG Key OPTIONAL
);
2.5、文件的读操作
NTSTATUS
ZwReadFile(
IN HANDLE FileHandle,
IN HANDLE Event OPTIONAL,
IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,
IN PVOID ApcContext OPTIONAL,
OUT PIO_STATUS_BLOCK IoStatusBlock,
OUT PVOID Buffer,
IN ULONG Length,
IN PLARGE_INTEGER ByteOffset OPTIONAL,
IN PULONG Key OPTIONAL
);
3、内核模式下的注册表操作
几个注册表相关的概念:
a)、注册表项:注册表中的一个项目,类似目录的概念。每个项中存储多个二元结构,键名—键值。每个项中,可以有若干个子项。
b)、注册表子项:类似于目录中的子目录
c)、键名:通过键名可以寻找相应的键值
d)、键值类别:每个键值存储的时候有不同的类别,可以是整型、字符型串等数据
e)、键值:键名下对应存储的数据
3.1、创建关闭注册表
NTSTATUS
ZwCreateKey(
OUT PHANDLE KeyHandle,//获得的注册表句柄
IN ACCESS_MASK DesiredAccess,//访问权限,一般设置为KEY_ALL_ACCESS
IN POBJECT_ATTRIBUTES ObjectAttributes,//OBJECT_ATTRIBUTE数据结构
IN ULONG TitleIndex,
IN PUNICODE_STRING Class OPTIONAL,
IN ULONG CreateOptions,//创建时的选项
OUT PULONG Disposition OPTIONAL//返回是创建成功还是打开成功
);
3.2、打开注册表
NTSTATUS
ZwOpenKey(
OUT PHANDLE KeyHandle,//返回被打开的句柄
IN ACCESS_MASK DesiredAccess,//打开的权限
IN POBJECT_ATTRIBUTES ObjectAttributes
);
3.3、添加、修改注册表键值
键值的分类
NTSTATUS
ZwSetValueKey(
IN HANDLE KeyHandle,
IN PUNICODE_STRING ValueName,//键名
IN ULONG TitleIndex OPTIONAL,
IN ULONG Type,
IN PVOID Data
IN ULONG DataSize//记录键值数据的大小
);
3.4、查询注册表
NTSTATUS
ZwQueryValueKey(
IN HANDLE KeyHandle,
IN PUNICODE_STRING ValueName,//要查询的键名
IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,//根据keyValueInformation选择查询类别
OUT PVOID KeyValueInformation,
IN ULONG Length,//要查询数据的长度
OUT PULONG ResultLength//实际查询数据的长度
);
一般使用ZwQueryValueKey有以下4个步骤:
a)、用ZwQueryValueKey获取这个数据结构的长度
b)、分配如此长度的内存,用来查询
c)、再次调用ZwQueryValueKey,获取键值
d)、回收内存
3.5、枚举子项
NTSTATUS
ZwQueryKey(//主要是获得某注册表究竟有多少子项
IN HANDLE KeyHandle,
IN KEY_INFORMATION_CLASS KeyInformationClass,
OUT PVOID KeyInformation,
IN ULONG Length,
OUT PULONG ResultLength
);
NTSTATUS
ZwEnumerateKey(//主要针对第几个子项获得该子项的具体信息
IN HANDLE KeyHandle,
IN ULONG Index,
IN KEY_INFORMATION_CLASS KeyInformationClass,
OUT PVOID KeyInformation,
IN ULONG Length,
OUT PULONG ResultLength
);
3.6、枚举子键
NTSTATUS
ZwQueryValueKey(
IN HANDLE KeyHandle,
IN PUNICODE_STRING ValueName,
IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass,
OUT PVOID KeyValueInformation,
IN ULONG Length,
OUT PULONG ResultLength
);
NTSTATUS
ZwEnumerateKey(
IN HANDLE KeyHandle,
IN ULONG Index,
IN KEY_INFORMATION_CLASS KeyInformationClass,
OUT PVOID KeyInformation,
IN ULONG Length,
OUT PULONG ResultLength
);
3.7、删除子项
NTSTATUS
ZwDeleteKey(//该函数只能删除没有子项的项目
IN HANDLE KeyHandle
);
小结:本章介绍了WIndows内核模式下的一些常用内核函数,这些函数在驱动程序的开发中将会经常用到。对这些内核函数的熟练掌握十分必要。本章介绍了字符串相关的一些内核函数。这些函数包括操作标准C语言字符串,也包括操作UNICODE字符串的。另外,还介绍了与文件及注册表相关的内核函数。
以上内容参考自张帆 史彩成等编著的《Windows 驱动开发技术详解》第六章