从今天开始为了不高频占用论坛资源,所有《Windows内核编程》编程系列的更新都会在本帖进行更新说明,不再重复发布新帖,大家如有需要可收藏本贴,我已经把所有《Windows内核编程》系列视频整理为合集,通过上方链接跳转过去即可查看合集。
2023.09.24 14:51:34 更新至1集 1.环境搭建以及第一个内核程序
2023.09.25 23:04:38 更新至2集 2.双击调试环境搭建
2023.09.26 22:55:04 更新至2集(附加) 3.网络调试
2023.09.26 22:44:25 更新至3集 3.服务加载驱动
2023.09.27 23:34:26 更新至4集 4.上下文环境、中断级、常见异常
2023.09.29 00:36:54 更新至5集 5.字符串
2023.09.30 15:15:23 更新至6集 6.链表
2023.10.01 21:55:41 更新至7集 7.自旋锁与队列自旋锁
2023.10.02 17:09:16 更新至8集 8.旁视列表
2023.10.03 20:17:11 更新至9集 9.对象与句柄
2023.10.04 19:40:33 更新至10集 10.注册表操作
2023.10.5 14:40 更新至11集 11.文件操作
2023.10.6 13:00 更新至12集 12.线程、通知、同步
2023.10.6 23:55 更新至13集 13.通信
以下是所有讲义
1.大纲
第1篇 基础篇
第1章 内核编程环境 002
1.1 下载开发编译环境 002
1.1.1 编译环境介绍 002
1.1.2 下载Visual Studio与WDK 004
1.2 编写第一个C文件 006
1.2.1 通过Visual Studio新建工程 006
1.2.2 内核入口函数 007
1.2.3 编写入口函数体 008
1.3 编译第一个驱动 010
1.3.1 通过Visual Studio编译 010
1.3.2 通过WDK直接编译 011
第2章 内核驱动运行与调试 013
2.1 驱动的运行 013
2.2 服务的基本操作 015
2.2.1 打开服务管理器 015
2.2.2 服务的注册 016
2.2.3 服务的启动与停止 018
2.2.4 服务的删除 019
2.2.5 服务的例子 020
2.2.6 服务小结 022
2.3 驱动的调试 022
2.3.1 基于VS+WDK环境调试 022
2.3.2 基于Windbg调试 026
第3章 内核编程基础 029
3.1 上下文环境 029
3.2 中断请求级别 031
3.3 驱动异常 033
3.4 字符串操作 034
3.5 链表 036
3.5.1 头节点初始化 038
3.5.2 节点插入 038
3.5.3 链表遍历 039
3.5.4 节点移除 040
3.6 自旋锁 040
3.6.1 使用自旋锁 040
3.6.2 在双向链表中使用自旋锁 041
3.6.3 使用队列自旋锁提高性能 042
3.7 内存分配 043
3.7.1 常规内存分配 043
3.7.2 旁视列表 045
3.8 对象与句柄 049
3.9 注册表 054
3.9.1 注册表的打开与关闭 054
3.9.2 注册表的修改 056
3.9.3 注册表的读取 057
3.10 文件操作 060
3.10.1 文件的打开与关闭 060
3.10.2 文件的读写 063
3.11 线程与事件 066
3.11.1 使用系统线程 066
3.11.2 使用同步事件 067
第4章 应用与内核通信 070
4.1 内核方面的编程 071
4.1.1 生成控制设备 071
4.1.2 控制设备的名字和符号链接 073
4.1.3 控制设备的删除 074
4.1.4 分发函数 074
4.1.5 请求的处理 076
4.2 应用方面的编程 077
4.2.1 基本的功能需求 077
4.2.2 在应用程序中打开与关闭设备 077
4.2.3 设备控制请求 078
4.2.4 内核中的对应处理 080
4.2.5 结合测试的效果 082
第5章 64位和32位内核开发差异 083
5.1 64位系统新增机制 083
5.1.1 WOW64子系统 083
5.1.2 PatchGuard技术 086
5.1.3 64位驱动的编译、安装与运行 086
5.2 编程差异 087
5.2.1 汇编嵌入变化 087
5.2.2 预处理与条件编译 088
5.2.3 数据结构调整 088
第6章 内核编程技巧 090
6.1 初始化赋值问题 090
6.2 有效性判断 091
6.3 一次性申请 092
6.4 独立性与最小化原则 095
6.5 嵌套陷阱 097
6.6 稳定性处理 098
6.6.1 事前处理 098
6.6.2 事中处理 100
6.6.3 事后处理 104
2.内核编程环境
1.搭建2019
Visual Studio 较旧的下载 - 2019、2017、2015 和以前的版本 (microsoft.com)
2.安装wdk
下载 Windows 驱动程序工具包 (WDK) - Windows drivers | Microsoft Learn
3.内核模块与服务的关系
-
内核模块(Kernel Module):
- 内核模块是操作系统内核的一部分,它是操作系统核心的一部分,负责管理硬件资源、进程管理、内存管理、文件系统等核心功能。
- 内核模块是操作系统的一部分,通常由操作系统的制造商或者内核开发者编写和维护。
- 内核模块运行在特权级别,具有最高的系统权限,可以直接访问硬件资源,执行特权指令。
-
服务(Service):
- 服务是用户模式(User Mode)下运行的应用程序,它们不直接操作内核,而是运行在操作系统之上,通过操作系统提供的API(应用程序编程接口)与内核通信。
- 服务通常是为了执行一些特定的任务或功能而设计的,可以在后台运行,不需要用户交互界面。
- 服务可以由操作系统自身提供,也可以由第三方应用程序创建和管理。
服务通常依赖于内核模块来提供底层的操作系统功能。例如,文件系统服务可以依赖于内核中的文件系统模块来进行文件操作。服务可以通过内核提供的API来与内核模块交互,请求执行特定的操作,如访问文件、网络通信等。
总结起来,内核模块是操作系统内核的一部分,负责核心功能和硬件资源管理,而服务是运行在用户模式下的应用程序,依赖于内核模块来实现各种功能。内核模块和服务之间有密切的合作关系,以确保操作系统的正常运行和提供各种服务功能。
命令实现
创建服务
SC create 服务名 binPath= "驱动文件绝对路径" type= kernel start= demand
启动服务
sc start HELLO
代码实现
#include <windows.h>
#include <stdio.h>
int main() {
SC_HANDLE schSCManager, schService;
BOOL bRet;
// 打开服务控制管理器
schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (schSCManager == NULL) {
printf("OpenSCManager failed (%d)\n", GetLastError());
return 1;
}
// 创建服务
schService = CreateService(
schSCManager,
"MyKernelModuleLoaderService", // 服务名称
"My Kernel Module Loader Service", // 服务显示名称
SERVICE_ALL_ACCESS,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
"d:\\1.sys", // 内核模块路径
NULL,
NULL,
NULL,
NULL,
NULL
);
if (schService == NULL) {
printf("CreateService failed (%d)\n", GetLastError());
CloseServiceHandle(schSCManager);
return 1;
}
// 启动服务
bRet = StartService(schService, 0, NULL);
if (!bRet) {
printf("StartService failed (%d)\n", GetLastError());
} else {
printf("Service started successfully.\n");
}
CloseServiceHandle(schService);
CloseServiceHandle(schSCManager);
return 0;
}
4.上下文环境、中断请求、驱动异常
上下文环境
Windows内核模块中的上下文环境是指在执行内核模块代码时,包含有关当前执行状态和环境的信息的数据结构。这些上下文环境允许内核模块与操作系统交互,访问系统资源,执行特定的任务,并保持操作系统的稳定性和安全性。以下是一些与Windows内核模块中的上下文环境相关的关键方面:
-
线程上下文(Thread Context):线程上下文包含有关线程的信息,如线程ID、CPU寄存器状态、调度信息等。内核模块可以通过线程上下文与正在运行的线程交互。
-
中断上下文(Interrupt Context):当硬件触发中断时,操作系统会保存当前执行的上下文并切换到中断上下文。内核模块可以编写中断处理程序来处理硬件中断,并在中断上下文中执行特定的操作。
-
DPC上下文(Deferred Procedure Call Context):DPC是一种用于延迟执行的机制,通常用于处理高优先级的任务。内核模块可以注册DPC例程,在DPC上下文中执行需要延迟的操作。
-
IRQL(Interrupt Request Level):IRQL是一个表示当前中断处理程序运行在何种优先级的值。内核模块需要了解当前的IRQL,以确保在适当的上下文中执行操作。
-
内核模式和用户模式:Windows操作系统有两种模式,即内核模式和用户模式。内核模块运行在内核模式,可以执行特权操作和访问受限资源。上下文环境中的模式信息指示了当前代码运行在哪种模式下。
上下文环境在Windows内核模块编程中非常重要,因为它们确定了内核模块可以访问和执行的操作范围,并确保了系统的稳定性。
中断级请求
Windows内核的中断请求级别(Interrupt Request Level,简称IRQL)是一个表示当前执行上下文中的中断优先级的值。它用于管理和控制系统中断的处理,确保高优先级的中断不会被低优先级的中断打断,从而保持系统的稳定性和可靠性。
在Windows中,IRQL通常有以下几个级别,按照从低到高的顺序:
- PASSIVE_LEVEL:这是最低的IRQL级别,表示没有中断或抢占操作。在这个级别下,内核模块可以执行大多数操作,包括访问用户模式进程的数据。可访问分页、非分页。
- APC_LEVEL:表示异步过程调用(Asynchronous Procedure Call,简称APC)的级别。在这个级别下,内核模块可以执行与APC相关的操作。通常用于在内核模块中请求异步执行某些任务。可访问分页、非分页。
- DISPATCH_LEVEL:表示调度级别,比APC_LEVEL高,用于内核的调度和执行线程切换操作。在这个级别下,内核模块可以执行一些与线程调度相关的操作,但不能执行可能引发调度的操作。可访问非分页。
- DIRQL(Device Interrupt Request Level):每个硬件设备都可以有自己的DIRQL级别,表示硬件中断的优先级。DIRQL级别通常用于硬件中断处理程序中,它们比DISPATCH_LEVEL更高,用于处理硬件中断。
- HIGH_LEVEL:这是最高的IRQL级别,表示非常紧急的系统操作,通常不由驱动程序或内核模块使用。在这个级别下,几乎没有操作可以执行,因为系统已经处于非常紧急的状态。
不同的IRQL级别用于确保系统中的中断处理和调度操作按照优先级顺序进行,从而避免竞态条件和资源冲突。内核模块需要根据当前的IRQL级别来决定可以执行哪些操作,以保持系统的稳定性和可靠性。提高IRQL级别会限制可执行的操作,降低IRQL级别则允许执行更多操作,但要注意避免不合适的级别切换,以避免系统崩溃或不稳定。
DISPATCH_LEVEL不能访问分页内存的原因:
DISPATCH_LEVEL 的特性:DISPATCH_LEVEL
表示调度级别,它比 APC_LEVEL
高,用于内核的调度和执行线程切换操作。在 DISPATCH_LEVEL
下,系统已经禁用了大部分的软中断,但仍然可以执行一些高优先级的内核代码。但是,由于高优先级的中断可能随时发生,而且不可预测,因此需要确保在 DISPATCH_LEVEL
下不会导致页面故障或其他不可预测的情况。
由于以上特性,DISPATCH_LEVEL
不能访问分页内存的原因在于,访问分页内存可能会涉及到页面故障(Page Faults),而页面故障需要访问分页文件或磁盘,这是一个可能会引发新的页面故障的操作。在高优先级的 DISPATCH_LEVEL
中,这种不可控制的操作会引发系统不稳定性,因此被禁止。
因此,内核开发人员在编写代码时必须小心谨慎,确保在 DISPATCH_LEVEL
下只执行那些不会引发页面故障或不可控制的操作,以保持系统的稳定性和可靠性。非分页内存通常用于存储需要在高优先级中断处理程序中访问的数据,以确保这些数据始终可用且不会引发页面故障。
获取当前线程中断级
DbgPrintEx(77, 0, "interrupt level :%d\n", KeGetCurrentIrql());
驱动异常
-
内存访问错误:驱动程序可能会访问无效的内存地址,读取或写入不属于它的内存区域,导致内存访问冲突。这可能会导致系统蓝屏。
-
资源冲突:如果多个驱动程序尝试同时访问或控制相同的硬件资源(如IRQ、DMA通道或IO端口),则可能会导致资源冲突,使系统不稳定。
-
未初始化的变量:使用未初始化的变量或未正确初始化的数据结构可能导致未定义的行为,这可能会在驱动程序中引发异常。
-
内核模式堆栈溢出:如果驱动程序在内核模式下使用的堆栈空间耗尽,可能会导致堆栈溢出,这通常会导致蓝屏异常。
-
死锁:驱动程序中的错误同步操作可能导致死锁,其中多个线程互相等待资源,从而使系统无法继续运行。
-
过度使用或滥用系统调用:过多地使用或滥用Windows系统调用(例如内核模式API)可能会导致系统不稳定,特别是如果调用不正确或不合适。
-
错误的中断处理:如果驱动程序未正确处理硬件中断或中断服务例程存在问题,那么这可能会导致蓝屏异常。
-
不合适的硬件操作:驱动程序可能会执行不合适的硬件操作,如过度超频或不正确的硬件设置,导致系统不稳定。
-
资源泄漏:未释放或管理好资源(如内存或句柄)可能导致资源泄漏,最终导致系统资源不足,从而引发异常。
-
不正确的错误处理:不正确或不充分的错误处理代码可能会导致系统无法适当地应对错误情况。
5.字符串操作
在 Windows 驱动程序开发中,UNICODE_STRING
是一个重要的数据结构,用于表示 Unicode 字符串。这个数据结构通常在驱动程序中用于处理字符串,特别是在与用户空间应用程序通信或与操作系统内核进行交互时。UNICODE_STRING
结构包含以下两个主要字段:
-
Length
:这是 Unicode 字符串的长度,以字节为单位。它表示字符串的实际长度,不包括结尾的空字符(\0
)。
-
MaximumLength
:这是 Unicode 字符串的最大长度,以字节为单位。它表示分配给字符串的缓冲区的最大容量,包括结尾的空字符。通常,MaximumLength
的值大于或等于 Length
的值。
UNICODE_STRING
结构的定义如下:
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
其中 PWSTR
是一个指向 Unicode 字符串缓冲区的指针。这个缓冲区包含实际的字符串内容,以双字节字符(16 位)编码表示。
在 Windows 驱动程序开发中,经常会使用 UNICODE_STRING
结构来传递、存储和操作 Unicode 字符串。这对于处理文件路径、注册表键名、设备名称等情况非常有用,因为 Windows 操作系统广泛使用 Unicode 字符串来表示文本数据。
初始化和使用 UNICODE_STRING
结构:
UNICODE_STRING myUnicodeString;
WCHAR buffer[] = L"Hello, Unicode!"; // Unicode字符串
RtlInitUnicodeString(&myUnicodeString, buffer);
6.链表
简介
在 Windows 驱动开发模型(WDM,Windows Driver Model)中,LIST_ENTRY
是一个非常重要的数据结构,用于实现双向链表(又称为双链表)。LIST_ENTRY
的定义如下:
typedef struct _LIST_ENTRY {
struct _LIST_ENTRY *Flink;
struct _LIST_ENTRY *Blink;
} LIST_ENTRY, *PLIST_ENTRY;
这个数据结构包含两个指针成员:Flink
(forward)和 Blink
(backward),它们分别指向链表中的下一个元素和前一个元素。Flink
表示链表中的下一个节点,而 Blink
表示链表中的前一个节点。
LIST_ENTRY
的主要作用是在 Windows 驱动程序中管理数据结构的链接和遍历。它允许你将多个元素组织成一个双向链表,这些元素可以是任何数据结构,例如设备对象、IRP(I/O 请求包)等。通过使用 LIST_ENTRY
,你可以轻松地在链表中添加、删除和遍历元素,而不需要手动维护指针关系。
操作
-
初始化链表:要创建一个新的双向链表,你需要初始化一个 LIST_ENTRY
结构体,通常称为链表头。初始化后,链表头的 Flink
和 Blink
指针都指向自身,表示链表为空。
LIST_ENTRY myList;
InitializeListHead(&myList);
-
添加元素到链表尾部:要将元素添加到双向链表的尾部,你可以使用 InsertTailList
函数。
PLIST_ENTRY newEntry; // 指向要添加的元素的 LIST_ENTRY 结构体
InsertTailList(&myList, newEntry);
-
添加元素到链表头部:要将元素添加到双向链表的头部,你可以使用 InsertHeadList
函数。
PLIST_ENTRY newEntry; // 指向要添加的元素的 LIST_ENTRY 结构体
InsertHeadList(&myList, newEntry);
-
移除元素:要从链表中移除一个元素,可以使用 RemoveEntryList
函数。
PLIST_ENTRY entryToRemove; // 指向要移除的元素的 LIST_ENTRY 结构体
RemoveEntryList(entryToRemove);
-
遍历链表:你可以使用 Flink
和 Blink
指针来遍历链表。以下是从头到尾遍历链表的示例:
PLIST_ENTRY currentEntry = myList.Flink;
while (currentEntry != &myList) {
// 处理当前节点的数据
// ...
// 移动到下一个节点
currentEntry = currentEntry->Flink;
}
如果你想要从尾部到头部遍历链表,可以将 currentEntry
初始化为 myList.Blink
并使用 currentEntry->Blink
来移动到前一个节点。
-
检查链表是否为空:你可以使用 IsListEmpty
函数来检查链表是否为空。
CONTAINING_RECORD
CONTAINING_RECORD
是一个在Windows内核开发中非常常用的宏,它的作用是根据一个包含成员指针获取包含该成员的结构体的指针。这个宏的目的是帮助开发人员在链表或其他数据结构中进行数据访问,特别是在使用LIST_ENTRY
等链表管理技术时。
具体来说,CONTAINING_RECORD
宏接受三个参数:
Pointer
:包含某个成员的指针。
Type
:包含结构的类型。
Field
:包含成员的名称。
宏的作用是根据 Pointer
的地址计算出包含结构体的起始地址,并返回该结构体的指针。这样,您就可以通过该指针来访问结构体中的其他成员。
#include <ntddk.h>
typedef struct _MyDataStructure {
int data;
LIST_ENTRY ListEntry; // 用于链接到链表的节点
} MyDataStructure;
// 示例函数,假设 entry 是一个包含成员 ListEntry 的指针
VOID AccessDataInMyDataStructure(PVOID entry) {
MyDataStructure* dataStruct = CONTAINING_RECORD(entry, MyDataStructure, ListEntry);
// 现在您可以访问 dataStruct 中的成员
KdPrint(("Data in MyDataStructure: %d\n", dataStruct->data));
}
在上面的示例中,CONTAINING_RECORD
宏帮助我们从 entry
指针中计算出包含结构体 MyDataStructure
的指针,以便我们可以访问其中的 data
成员。
7.自旋锁、队列自旋锁
什么是自旋锁
自旋锁是一种用于多线程编程的同步机制,其主要作用是保护临界区(也称为关键部分或共享资源),以确保在同一时刻只有一个线程可以访问共享资源。以下是自旋锁的主要作用:
-
临界区的互斥访问:自旋锁用于实现互斥访问,确保在任何时刻只有一个线程可以进入临界区执行相关操作。这是防止多个线程同时访问共享资源,避免数据竞争和不一致性的关键手段。
-
避免竞态条件:自旋锁的使用可以防止竞态条件的发生。竞态条件是指多个线程尝试同时修改共享资源,从而导致不确定的结果。自旋锁可以确保只有一个线程能够修改资源,从而避免竞态条件。
-
提供线程间的协调:自旋锁也可以用于线程之间的协调,例如等待某个条件变为真。一个线程可能会尝试获取自旋锁,但如果它发现条件不满足,它可以继续自旋等待,直到条件满足为止。这种情况下,自旋锁用作等待机制的一部分。
-
减少上下文切换开销:与其他同步机制(如互斥锁)相比,自旋锁具有较低的上下文切换开销。因为自旋锁在尝试获取锁时会一直自旋等待,而不会将线程置于休眠状态,从而避免了线程切换的开销。
自旋锁、队列自旋锁的区别
在Windows驱动程序模块(Windows Driver Model,WDM)编程中,队列自旋锁和自旋锁是用于同步多线程访问共享资源的两种不同机制。它们之间的主要区别在于它们的应用场景和性能权衡。
-
自旋锁(Spin Lock):
- 自旋锁是一种轻量级的同步机制,用于保护临界区,确保在同一时刻只有一个线程可以访问共享资源。
- 当一个线程尝试获取自旋锁时,如果锁已经被其他线程占用,它不会被阻塞,而是会一直在一个循环中自旋等待锁的释放。
- 自旋锁适用于临界区的保护时间非常短,且竞争情况不频繁的情况,因为它会持续消耗CPU时间。
- 自旋锁的优点是减少了上下文切换的开销,但缺点是它可能会浪费CPU资源,特别是在高竞争的情况下。
-
队列自旋锁(Queued Spin Lock):
- 队列自旋锁是一种改进的自旋锁,它被设计用来降低自旋锁的竞争和资源浪费。
- 当多个线程尝试获取队列自旋锁时,它们会形成一个队列,而不是简单地自旋等待。这意味着只有队列中的第一个线程会真正自旋等待,其他线程会在队列中等待。
- 队列自旋锁的实现通常更复杂,但在高度竞争的情况下,它可以提供更好的性能,因为它减少了自旋等待的线程数。
总的来说,选择自旋锁还是队列自旋锁取决于你的具体应用场景。如果你知道临界区的保护时间很短且竞争不频繁,可以使用自旋锁来降低上下文切换的开销。但如果竞争频繁或临界区保护时间较长,队列自旋锁可能是更好的选择,因为它可以减少资源浪费和竞争情况的影响。
8.普通内存管理与旁视列表内存管理(内存分配)
ExAllocateFromNPagedLookasideList
和 ExAllocatePoolWithTag
是 Windows 内核编程中用于分配非分页池内存的不同函数,下面是它们的区别和特点:
-
用途和分配方式:
ExAllocateFromNPagedLookasideList
:这个函数用于从非分页池的查找表(Lookaside List)中分配内存块。查找表是一种预先分配的内存池,通常由驱动程序或内核组件在初始化阶段创建,内存块的大小在创建查找表时指定。该函数用于高效地分配同一大小的内存块。
ExAllocatePoolWithTag
:这个函数用于直接从非分页池中分配内存块,而不涉及查找表。你可以在调用时指定要分配的内存块的大小和标签,这使得它更灵活,但可能更慢。
-
内存分配效率:
ExAllocateFromNPagedLookasideList
的效率通常比 ExAllocatePoolWithTag
更高。因为查找表提前分配了一组内存块,并在分配请求时快速返回这些内存块,减少了内存分配的开销。这对于需要频繁分配相同大小的内存块的情况非常有用,例如驱动程序的数据结构或缓冲区。
ExAllocatePoolWithTag
更灵活,但可能需要更多的内存分配和释放操作,因此在频繁分配不同大小的内存块时可能效率较低。
-
内存块的大小:
ExAllocateFromNPagedLookasideList
分配的内存块大小在创建查找表时预先指定,通常是相对较小的大小,例如字节或几个字节。
ExAllocatePoolWithTag
允许你指定分配的内存块的大小,可以根据需要分配不同大小的内存块。
-
内存块的标记:
- 两者都允许你指定一个标记(Tag)以标识分配的内存块,这有助于在调试和分析内存问题时跟踪内存分配的来源。
ExAllocateFromNPagedLookasideList
和 ExAllocatePoolWithTag
都用于分配非分页池内存,但它们的主要区别在于内存分配的方式和效率。前者适用于频繁分配相同大小的内存块的情况,而后者更灵活,适用于需要不同大小内存块的情况。
9.对象与句柄
什么是对象与句柄?
Windows操作系统把一切都作为对象(OBJECT)来管理,进程是对象,线程是对象,本书第1章提到的驱动也是对象,对象可以有名字,也可以没有名字。如果根据是否有名字来划分的话,对象可以分为命名对象与匿名对象,如在应用层创建一个EVENT或MUTEX时,就可以根据需求来决定创建的是命名对象还是匿名对象。
-
对象(Object):对象是Windows内核中表示各种资源的抽象概念。这些资源可以是文件、进程、线程、事件、互斥体、文件映射、窗口等等。每个对象都有其自己的属性和状态,以及一组操作,例如打开、关闭、读取、写入等等。
-
句柄(Handle):句柄是对对象的引用或标识符。它是一个整数值,用于唯一标识一个特定的对象实例。句柄是在应用程序和Windows内核之间进行通信的一种方式。应用程序通过句柄来访问和操作内核对象。
-
句柄表(Handle Table):Windows内核维护了一个全局的句柄表,用于跟踪和管理所有打开的句柄。每个进程都有自己的句柄表,用于存储其特定对象的句柄。
-
句柄的用途:应用程序可以通过句柄来执行与对象相关的操作,例如读取文件、等待事件、访问窗口等等。句柄还可以用于在不同的进程之间共享对同一对象的访问权。
-
句柄的管理:Windows内核负责管理句柄的分配和释放。当应用程序打开一个对象时,内核会分配一个句柄,并在句柄表中记录该句柄与对象之间的关联。当应用程序不再需要句柄时,它应该关闭句柄,以便内核可以回收资源。
句柄是一种重要的机制,用于在Windows操作系统中管理和访问各种资源对象。通过句柄,应用程序可以与内核中的对象进行通信和操作,而内核负责管理句柄的生命周期和安全性。这种机制有助于确保多个应用程序可以安全地共享和访问系统资源。
例子
ZwCreateFile
是一个用于创建文件对象的函数。它通常在Windows内核模式驱动程序中使用,以打开或创建文件、设备或其他对象。ZwCreateFile
函数的参数如下:
NTSTATUS ZwCreateFile(
PHANDLE FileHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,
PLARGE_INTEGER AllocationSize,
ULONG FileAttributes,
ULONG ShareAccess,
ULONG CreateDisposition,
ULONG CreateOptions,
PVOID EaBuffer,
ULONG EaLength
);
以下是每个参数的详细解释:
-
FileHandle
(输出参数):一个指向文件句柄的指针。函数成功时,将返回一个唯一的句柄,该句柄用于后续文件操作。
-
DesiredAccess
:一个用于指定对文件的期望访问权限的访问掩码。它确定了您可以在文件上执行的操作,如读取、写入、删除等。常见的取值包括FILE_GENERIC_READ
和FILE_GENERIC_WRITE
。
-
ObjectAttributes
:一个指向OBJECT_ATTRIBUTES
结构的指针,该结构包含了有关文件对象的属性,如文件名、对象名空间、安全描述符等。这个参数用于指定文件的位置和属性。
-
IoStatusBlock
(输出参数):一个指向IO_STATUS_BLOCK
结构的指针,用于接收操作的结果信息,如操作状态、实际传输的字节数等。
-
AllocationSize
:一个指向LARGE_INTEGER
结构的指针,用于指定文件的分配大小。通常情况下,如果不需要指定分配大小,可以将其设置为NULL。
-
FileAttributes
:一个指定文件属性的掩码,如只读、隐藏等。
-
ShareAccess
:一个用于指定与其他进程共享文件时的共享模式的掩码。常见的共享模式包括FILE_SHARE_READ
和FILE_SHARE_WRITE
。
-
CreateDisposition
:一个用于指定文件的创建或打开方式的标志。它确定了在文件已存在或不存在时应该采取的操作,如创建、打开、截断等。
-
CreateOptions
:一个用于指定其他文件创建选项的标志,如同步/异步操作、缓冲/非缓冲等。
-
EaBuffer
:一个指向扩展属性(Extended Attributes)信息的缓冲区的指针,通常用于存储与文件相关的额外元数据。如果不使用扩展属性,可以将其设置为NULL。
-
EaLength
:指定扩展属性信息的长度。
ZwCreateFile
函数用于在内核模式中创建或打开文件对象,它的行为类似于用户模式下的CreateFile
函数。
实验代码
#include <ntifs.h>
#include <stdlib.h>
VOID UnloadDriver(PDRIVER_OBJECT pDriver)
{
DbgPrint("卸载成功\n");
}
NTSTATUS DriverEntry(
IN OUT PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
DriverObject->DriverUnload = UnloadDriver;
UNICODE_STRING filePath;
RtlInitUnicodeString(&filePath, L"\\??\\C:\\1.txt"); // 文件路径
OBJECT_ATTRIBUTES fileAttributes;
InitializeObjectAttributes(&fileAttributes, &filePath, OBJ_CASE_INSENSITIVE, NULL, NULL);
HANDLE fileHandle=0;
IO_STATUS_BLOCK ioStatusBlock;
NTSTATUS status;
// 打开文件
status = ZwCreateFile(&fileHandle, GENERIC_READ, &fileAttributes, &ioStatusBlock, NULL,
FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN,
FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
if (!NT_SUCCESS(status)) {
DbgPrintEx(77, 0, "[DB]Failed to open file: 0x%X\n", fileHandle);
CHAR buffer[512];
ULONG bytesRead;
status = ZwReadFile(fileHandle, NULL, NULL, NULL, &ioStatusBlock, buffer, sizeof(buffer), NULL, NULL);
if (NT_SUCCESS(status)) {
DbgPrintEx(77, 0, "File contents:\n%s\n", buffer);
}
else {
DbgPrintEx(77, 0, "Failed to read file: 0x%X\n", status);
}
}
else
{
DbgPrintEx(77, 0, "[DB]Open successfully: 0x%X\n", fileHandle);
CHAR buffer[512];
ULONG bytesRead;
status = ZwReadFile(fileHandle, NULL, NULL, NULL, &ioStatusBlock, buffer, sizeof(buffer), NULL, NULL);
if (NT_SUCCESS(status)) {
DbgPrintEx(77,0,"File contents:\n%s\n", buffer);
}
else {
DbgPrintEx(77,0,"Failed to read file: 0x%X\n", status);
}
ZwClose(fileHandle);
}
return STATUS_SUCCESS;
}
10.注册表操作
什么是注册表
注册表(Registry)是Windows操作系统中用于存储配置信息和系统设置的关键数据库。在注册表中,有两个主要的概念:键(Keys)和值(Values)。
键(Keys):
注册表中的键类似于文件夹,它们用于组织和存储相关设置和信息。每个键可以包含其他子键或值。键通常按照层次结构排列,类似于文件系统的文件夹结构。在注册表中,键使用路径来标识,路径是由反斜杠(\)分隔的字符串。
例子:
假设我们有一个名为"MyApp"的应用程序,该应用程序在注册表中有自己的设置。那么,我们可以创建一个名为"HKEY_LOCAL_MACHINE\Software\MyApp"的键来存储这个应用程序的相关设置。在这个例子中,"HKEY_LOCAL_MACHINE"是注册表的顶级键之一,"Software"是它的子键,然后是"MyApp"子键。
值(Values):
注册表中的值用于存储实际的配置数据,如字符串、数字或二进制数据。值存储在键下,并与键关联,以提供有关应用程序、系统设置或用户首选项的具体信息。
例子:
在上面的"MyApp"示例中,我们可以在"MyApp"键下创建不同的值来存储不同的配置信息。例如,我们可以创建一个名为"Version"的字符串值,用于存储应用程序的版本号。这个值可以如下所示:
- 键路径:"HKEY_LOCAL_MACHINE\Software\MyApp"
- 值名称:"Version"
- 值数据:"3"
这表示"MyApp"应用程序的版本号为"3"。当应用程序需要访问版本号时,它可以查询注册表中的该值。
注册表的键用于组织和分类信息,而值用于存储实际的配置数据。这些键和值的组合允许操作系统和应用程序轻松访问和管理系统设置和配置信息。
读、写、删例子:
#include <ntifs.h>
// 定义要创建的注册表键路径
#define REGISTRY_PATH L"\\Registry\\Machine\\Software\\MyApp"
// 定义要创建的值的名称和数据
#define VALUE_NAME L"version"
#define VALUE_DATA 12
VOID UnloadDriver()
{
DbgPrintEx(77, 0, "卸载成功\n");
}
NTSTATUS DriverEntry(
IN OUT PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath
)
{
DriverObject->DriverUnload = UnloadDriver;
NTSTATUS status;
UNICODE_STRING registryPath;
OBJECT_ATTRIBUTES objectAttributes;
HANDLE registryKey;
UNICODE_STRING valueName;
ULONG valueData;
PKEY_VALUE_PARTIAL_INFORMATION pKeyinfo = ExAllocatePoolWithTag(NonPagedPool, 0x10, 'info');
ULONG queryLen;
ULONG relqueryLen;
// 初始化注册表路径
RtlInitUnicodeString(®istryPath, REGISTRY_PATH);
// 初始化对象属性
InitializeObjectAttributes(&objectAttributes, ®istryPath, OBJ_CASE_INSENSITIVE , NULL, NULL);
// 创建或打开注册表键
status = ZwCreateKey(®istryKey,KEY_ALL_ACCESS, &objectAttributes, 0, NULL, REG_OPTION_NON_VOLATILE, NULL);
if (!NT_SUCCESS(status)) {
return status;
}
/*
写注册表项
*/
// 初始化值名称
RtlInitUnicodeString(&valueName, VALUE_NAME);
// 设置整数值数据
valueData = VALUE_DATA;
// 写入整数值到注册表
status = ZwSetValueKey(registryKey, &valueName, 0, REG_DWORD, &valueData, sizeof(valueData));
if (NT_SUCCESS(status))
{
DbgPrintEx(77, 0, "set successfully!\n");
}
/*
读注册表项
*/
// 初次查询获取储存所需的缓冲区大小
status = ZwQueryValueKey(registryKey,
&valueName,
KeyValuePartialInformation,
pKeyinfo,
sizeof(KEY_VALUE_PARTIAL_INFORMATION),
&queryLen);
DbgPrintEx(77, 0, "[statu1]:%d\n", status);
DbgPrintEx(77, 0, "[query1]%d\n", *(PUCHAR)(pKeyinfo->Data));
// 释放防止内存泄露
ExFreePoolWithTag(pKeyinfo, 'info');
// 申请符合所需大小的内存块
PKEY_VALUE_PARTIAL_INFORMATION pKeyinfo2 = ExAllocatePoolWithTag(NonPagedPool, queryLen, 'info');
if (pKeyinfo)
{
if (status == STATUS_BUFFER_TOO_SMALL || status == STATUS_BUFFER_OVERFLOW)
{
relqueryLen = queryLen;
status = ZwQueryValueKey(registryKey,
&valueName,
KeyValuePartialInformation,
pKeyinfo2,
relqueryLen,
&queryLen);
DbgPrintEx(77, 0, "[statu2]:%d\n", status);
if (NT_SUCCESS(status))
{
DbgPrintEx(77, 0, "[query2]%d\n", *(PUCHAR)(pKeyinfo->Data));
}
else
{
DbgPrintEx(77, 0, "Wrong!");
}
}
}
/*
删注册表项
*/
status = ZwDeleteKey(registryKey);
if (NT_SUCCESS(status))
{
DbgPrintEx(77, 0, "Had deleted!\n");
}
// 关闭注册表键
status = ZwClose(registryKey);
if (NT_SUCCESS(status))
{
DbgPrintEx(77, 0, "Had closed!\n");
}
return STATUS_SUCCESS;
}
11.文件读写例子:
#include <ntddk.h>
#include <ntstrsafe.h>
#define FILE_PATH L"\\??\\C:\\1.txt"
UNICODE_STRING filePath;
OBJECT_ATTRIBUTES fileAttributes;
HANDLE fileHandle;
NTSTATUS OpenFile() {
RtlInitUnicodeString(&filePath, FILE_PATH);
InitializeObjectAttributes(&fileAttributes, &filePath, OBJ_CASE_INSENSITIVE, NULL, NULL);
IO_STATUS_BLOCK ioStatus;
NTSTATUS status;
status = ZwCreateFile(&fileHandle,
GENERIC_WRITE | GENERIC_READ,
&fileAttributes,
&ioStatus,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_OPEN_IF,
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0);
return status;
}
NTSTATUS WriteToFile() {
NTSTATUS status;
UNICODE_STRING textToWrite;
RtlInitUnicodeString(&textToWrite, L"aaaaa");
IO_STATUS_BLOCK ioStatus;
status = ZwWriteFile(fileHandle,
NULL,
NULL,
NULL,
&ioStatus,
textToWrite.Buffer,
textToWrite.Length,
NULL,
NULL);
return status;
}
NTSTATUS ReadFromFile() {
NTSTATUS status;
LARGE_INTEGER byteOffset;
byteOffset.QuadPart = 0;
ULONG bytesRead;
IO_STATUS_BLOCK ioStatus;
CHAR buffer[1024]; // 用于存储读取的内容
status = ZwReadFile(fileHandle,
NULL,
NULL,
NULL,
&ioStatus,
buffer,
sizeof(buffer),
&byteOffset,
NULL);
if (NT_SUCCESS(status)) {
KdPrint(("File Content: %s\n", buffer));
}
return status;
}
VOID CloseFile() {
ZwClose(fileHandle);
}
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
NTSTATUS status;
DriverObject->DriverUnload = UnloadDriver;
status = OpenFile();
if (!NT_SUCCESS(status)) {
KdPrint(("Failed to open file: %08X\n", status));
return status;
}
status = WriteToFile();
if (!NT_SUCCESS(status)) {
KdPrint(("Failed to write to file: %08X\n", status));
CloseFile();
return status;
}
status = ReadFromFile();
if (!NT_SUCCESS(status)) {
KdPrint(("Failed to read from file: %08X\n", status));
}
CloseFile();
return status;
}
VOID UnloadDriver(_In_ PDRIVER_OBJECT DriverObject) {
UNREFERENCED_PARAMETER(DriverObject);
KdPrint(("Driver unloaded\n"));
}
12.线程、通知 、同步
什么是通知事件,什么是同步事件
-
通知事件(Notification Event):
- 通知事件用于线程之间的通信,通常用于一对多的通知机制。
- 当一个线程设置通知事件时,它将通知等待该事件的所有线程,以表明某个事件已经发生或某个条件已经满足。
- 通知事件通过
KeSetEvent
函数来设置。一旦事件被设置,所有等待该事件的线程都可以继续执行。
- 通知事件通常用于驱动程序内部的协调和通信,例如,一个线程可以设置通知事件,告知其他线程某个设备状态已经改变。
-
同步事件(Synchronization Event):
- 同步事件用于线程之间的同步,通常用于一对一的同步机制。
- 同步事件通常用于协调两个线程的操作,以确保它们按照特定的顺序执行或以控制对共享资源的访问。
- 同步事件通过
KeSetEvent
函数来设置,但与通知事件不同,通常不会用于通知其他线程。它用于线程之间的隐式同步。
- 例如,您可以使用同步事件来实现互斥锁,其中一个线程等待获取同步事件(锁),而其他线程必须等待前一个线程释放锁才能继续执行。
同步与通知的区别:
NotificationEvent
用于一对多的通信,而SynchronizationEvent
用于一对一的同步。
-
NotificationEvent
通常用于一方通知多方某个事件的发生。例如,在驱动程序中,一个线程可以设置通知事件,然后多个线程等待该事件的发生。当事件被设置时,所有等待线程都可以继续执行。
-
SynchronizationEvent
通常用于同步两个线程之间的操作,确保它们以协调的方式执行。例如,在多线程编程中,您可以使用同步事件来实现互斥锁,确保只有一个线程能够访问共享资源。每个线程都会等待同一个同步事件,但只有一个线程能够成功获取该事件,从而执行关键部分的代码。
实验代码例子:
#include <ntddk.h>
KEVENT event;
HANDLE t1, t2 , t3;
VOID thread1()
{
INT i = 0;
do
{
DbgPrintEx(77, 0, "[db]thread1,%d次\n",i);
i++;
} while (i<100);
KeSetEvent(&event, 0, TRUE);
}
VOID thread2()
{
DbgPrintEx(77, 0, "[db]线程2等待中\n");
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
INT i = 0;
do
{
DbgPrintEx(77, 0, "[db]thread2,%d次\n", i);
i++;
} while (i < 100);
KeResetEvent(&event);
}
VOID thread3()
{
DbgPrintEx(77, 0, "[db]线程3等待中\n");
KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL);
INT i = 0;
do
{
DbgPrintEx(77, 0, "[db]thread3,%d次\n", i);
i++;
} while (i < 100);
}
VOID UnloadDriver(_In_ PDRIVER_OBJECT DriverObject) {
UNREFERENCED_PARAMETER(DriverObject);
DbgPrintEx(77, 0, "[db]Driver unloaded\n");
if (MmIsAddressValid(&event))
{
DbgPrintEx(77, 0, "[db]event:%p\n", &event);
}
}
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
{
NTSTATUS status;
DriverObject->DriverUnload = UnloadDriver;
KeInitializeEvent(&event, SynchronizationEvent, TRUE);
status = PsCreateSystemThread(&t1, THREAD_ALL_ACCESS, 0, NULL, NULL, thread1, NULL);
status = PsCreateSystemThread(&t2, THREAD_ALL_ACCESS, 0, NULL, NULL, thread2, NULL);
status = PsCreateSystemThread(&t3, THREAD_ALL_ACCESS, 0, NULL, NULL, thread3, NULL);
return STATUS_SUCCESS;
}
13.通信
通信
应用层和驱动交互数据
IRP
IRP(I/O Request Packet)是Windows内核中的一个重要数据结构,用于表示和管理输入/输出请求。IRP包含了有关特定I/O请求的信息,以便操作系统可以正确地分派和处理这些请求。下面是IRP结构体的详细解释:
typedef struct _IRP {
CSHORT Type;
USHORT Size;
PMDL MdlAddress;
ULONG Flags;
union {
struct {
PVOID SystemBuffer;
union {
struct {
USHORT Type3InputBufferLength;
USHORT DataLength;
ULONG UserBuffer;
} DeviceIoControl;
struct {
ULONG Parameters;
PVOID DataBuffer;
USHORT DataSize;
USHORT Status;
} DeviceReadWrite;
struct {
PVOID OutputBuffer;
ULONG InputBuffer;
ULONG FsControlCode;
PVOID SpecialOutputBuffer;
ULONG Status;
BOOLEAN DeferredProcedure;
BOOLEAN AsynchronousParameters;
} FileSystemControl;
struct {
PVOID SecurityContext;
PETHREAD Thread;
PFILE_OBJECT FileObject;
ULONG RequestorMode;
PVOID ImpersonationInfo;
IO_STATUS_BLOCK IoStatus;
KPROCESSOR_MODE RequestorMode;
BOOLEAN PendingReturned;
BOOLEAN Cancel;
KIRQL CancelIrql;
CCHAR ApcEnvironment;
UCHAR AllocationFlags;
} Create;
} AssociatedIrp;
PDEVICE_OBJECT DeviceObject;
PIO_STACK_LOCATION CurrentStackLocation;
ULONG NumberParameters;
PIO_STACK_LOCATION Tail;
PVOID Reserved0;
PVOID Reserved1;
} Overlay;
KAPC Apc;
PVOID CompletionKey;
} Tail;
} IRP, *PIRP;
以下是IRP结构体的各个字段和子结构的解释:
-
Type
:IRP的类型。通常为IRP_MJ_XXX(例如IRP_MJ_READ,IRP_MJ_WRITE),表示I/O操作的类型。
-
Size
:IRP结构体的大小,以字节为单位。
-
MdlAddress
:描述内存分页的MDL(Memory Descriptor List)结构的地址。MDL用于描述与IRP关联的数据缓冲区。
-
Flags
:用于指定与IRP相关的标志,例如同步或异步处理。
-
AssociatedIrp
:一个联合结构,根据I/O请求类型的不同,可能包含其他字段。
- 对于设备IO控制,它包含有关缓冲区和控制代码的信息。
- 对于文件系统控制,它包含有关文件系统操作的信息。
- 对于创建操作,它包含有关创建文件的信息。
-
DeviceObject
:指向处理请求的设备对象的指针。
-
CurrentStackLocation
:指向IRP栈位置的指针,该位置包含有关当前操作的信息。
-
NumberParameters
:IRP中包含的参数数量,用于特定操作的参数。
-
Tail
:IRP的尾部字段,包括一些保留字段和与异步处理相关的字段。
-
Apc
:用于异步处理的内核异步过程调用(APC)。
-
CompletionKey
:用于异步操作完成时识别请求的关键值。
应用层代码
#include <stdio.h>
#include <windows.h>
#define _SYB_NAME L"\\\\.\\sysmblicname"
#define CTL_TALK CTL_CODE(FILE_DEVICE_UNKNOWN,0x9000,METHOD_BUFFERED,FILE_ANY_ACCESS)
int main()
{
HANDLE hDevice = CreateFile(_SYB_NAME,
FILE_ALL_ACCESS,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
BOOL flag;
if (!hDevice)
{
printf("%x", GetLastError());
}
else
{
char inbuf[260] = { 0 };
char outbuf[260] = { 0 };
memcpy(inbuf,"demo",strlen("demo"));
DWORD retlen = 0;
printf("创建成功\n");
if (DeviceIoControl(hDevice, CTL_TALK, inbuf, strlen(inbuf), outbuf, sizeof(inbuf), &retlen, NULL))
{
printf("通信成功\n");
}
else
{
printf("错误码:%d\n", GetLastError());
}
}
getchar();
}
驱动层
#include <ntifs.h>
#define _DEVICE_NAME L"\\device\\mydevice"
#define _SYB_NAME L"\\??\\sysmblicname"
#define CTL_TALK CTL_CODE(FILE_DEVICE_UNKNOWN,0x9000,METHOD_BUFFERED,FILE_ANY_ACCESS)
NTSTATUS DispatchControl(PDEVICE_OBJECT pDevice, PIRP pIrp)
{
PVOID pBuff = pIrp->AssociatedIrp.SystemBuffer;
PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp);
ULONG CtlCode = pStack->Parameters.DeviceIoControl.IoControlCode;
ULONG uLen = { 0 };
uLen = strlen(pBuff);
switch (CtlCode)
{
case CTL_TALK:
{
DbgPrintEx(77, 0, "长度:%d", uLen);
DbgPrintEx(77, 0, "接收到的数据为:%s", pBuff);
}
default:
break;
}
pIrp->IoStatus.Information = 0;
pIrp->IoStatus.Status = STATUS_SUCCESS;
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS DisPatchCreate(PDEVICE_OBJECT pDevice, PIRP pIrp)
{
DbgPrintEx(77, 0, "创建派遣通信成功\n");
IoCompleteRequest(pIrp, 0);
return STATUS_SUCCESS;
}
VOID UnloadDriver(PDRIVER_OBJECT pDriver)
{
DbgPrintEx(77, 0, "卸载成功\n");
if (pDriver->DeviceObject)
{
UNICODE_STRING uSymblicLinkname;
RtlInitUnicodeString(&uSymblicLinkname, _SYB_NAME);
IoDeleteSymbolicLink(&uSymblicLinkname);
IoDeleteDevice(pDriver->DeviceObject);
}
}
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegpath)
{
DbgPrintEx(77,0,"加载成功\n");
pDriver->DriverUnload = UnloadDriver;
UNICODE_STRING uDeviceName;
UNICODE_STRING uSymbliclinkname;
PDEVICE_OBJECT pDevice;
RtlInitUnicodeString(&uDeviceName, _DEVICE_NAME);
RtlInitUnicodeString(&uSymbliclinkname, _SYB_NAME);
IoCreateDevice(pDriver, 0, &uDeviceName, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &pDevice);
IoCreateSymbolicLink(&uSymbliclinkname, &uDeviceName);
pDevice->Flags &= ~DO_DEVICE_INITIALIZING;
pDevice->Flags |= DO_BUFFERED_IO;
pDriver->MajorFunction[IRP_MJ_CREATE] = DisPatchCreate;
pDriver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchControl;
return STATUS_SUCCESS;
}