最近在学习驱动编程方面的内容,在这将自己的一些心得分享出来,供大家参考,与大家共同进步,本人学习驱动主要是通过两本书——《独钓寒江 windows安全编程》 和 《windows驱动开发技术详解》。

驱动开发过程中,主要使用的C语言,虽说C中定义了许多数据类型,但是一般来说在编码上还是习惯与使用WDK的规范,虽说这个不是必须的,比如有这样一句

unsigned long ul = 0;

这个数据的大小根据不同的机器不同的编译器环境略有不同,这样代码就产生了不可控的行为,但是WDK上专门定义了相关的宏,环境不同,只需要修改一下宏定义,这样就避免了这个问题。

在这列举一些常用的数据类型,以免以后在编写代码或者查看例子代码时犯迷糊:

普通数据类型

#define ULONG unsigned long
#define UCHAR unsigned char
#define UINT unsigned int
#define VOID void
#define PULONG unsigned *
#define PUCHAR unsigned char*
#define PUINT unsigned int*
#define PVOID void*

字符串类型

在驱动的编程中,为字符串操作专门定义了一个数据类型UNICODE_STRING ANSI_STRING,他们的定义大致相同,只是一个是表示UNICODE字符串,一个表示ANSI字符串,下面主要来说明一下UNICODE_STRING

typedef struct _UNICODE_STRING {
USHORT Length; // 字符串的中字符所占的内存大小
USHORT MaximumLength;//用来存储字符串缓冲的大小
PWCHAR Buffer;//缓冲的地址
} UNICODE_STRING;

这个结构体在使用是需要注意的是上述两个大小单位是字节数而不是字符个数,另外在操作UNICODE_STRING 的时候只是简单的操作Buffer指向的内存,并不会特意的为其分配另外的空间,字符串处理函数主要有这样几个:

RtlInitUnicodeString(&uStr1, str1);
RtlCopyUnicodeString(&uStr1, &uStr2);
RtlAppendUnicodeToString(&uStr1, str1);
RtlAppendUnicodeStringToString(&uStr1, &uStr2);
RtlCompareUnicodeString(&uStr1, &uStr2, TRUE/FALSE);
RtlAnsiStringToUnicodeString(&uStr1, &aStr1, TRUE/FALSE);
RtlFreeUnicodeString(&uStr1);

这些函数从字面上就可以知道它们是干什么用的,需要注意的是,除了Init,这些函数只是简单的操作Buffer已指向的内存,并不会改变指针的指向。所以在使用时要特别注意不要试图改变静态常量区的内容,也要特别注意指向的内存是在栈中还是在堆中。下面是一个简单的例子:

    UNICODE_STRING uStr1 = { 0 };
UNICODE_STRING uStr2 = { 0 };
UNICODE_STRING uStr3 = { 0 }; ANSI_STRING aStr = { 0 };
RtlInitUnicodeString(&uStr1, L"Hello");
RtlInitUnicodeString(&uStr2, L"Goodbye"); //打印字符串结构用%Z表示%wZ表示是宽字符
DbgPrint("uStr1 = %wZ\n", &uStr1);
DbgPrint("uStr2 = %wZ\n", &uStr2); RtlInitAnsiString(&aStr, "Hello World");
DbgPrint("aStr = %Z\n", &aStr); /*这个操作是由于uStr3中的Buffer指向NULL,所以会失败*/
RtlCopyUnicodeString(&uStr3, &uStr1);
DbgPrint("uStr3 = %wZ\n", &uStr3); //失败 /*下面两个失败是由于Str1 Str2 指向的是字符串常量区,不可修改*/
RtlAppendUnicodeToString(&uStr1, &uStr2);
DbgPrint("uStr1 = %wZ\n", &uStr1); //失败 RtlAppendUnicodeStringToString(&uStr1, L"World");
DbgPrint("uStr1 = %wZ\n", &uStr1); //失败

LARGE_INTEGER

这个结构就像它的名字一样,用来表示一个比较大的整数,它的定义如下:

typedef union _LARGE_INTEGER {
struct {
DWORD LowPart;
LONG HighPart;
};
struct {
DWORD LowPart;
LONG HighPart;
} u;
LONGLONG QuadPart;
} LARGE_INTEGER, *PLARGE_INTEGER;

这是一个公用体,可以认为它是由两部分组成高32位的HighPart和低32位的LowPart,它分了高位优先和低位优先两种情况,也可以认为它是一个64位的整形。在使用时根据需求来决定

NTSTATUS

绝大多数驱动函数都返回这个值,用来表示当前处理的状态,一般STATUS_SUCCESS表示成功,其余的都表示失败。微软根据不同情况定义了它的状态值,一般常用的有下面几个

含义
STATUS_SUCCESS 函数执行成功
STATUS_UNSUCCESSFUL 函数执行不成功
STATUS_NOT_IMPLEMENTED 函数违背实现
STATUS_INVALID_INFO_CLASS 输入参数是无效的类别
STATUS_ACCESS_VIOLATION 不允许访问
STATUS_IN_PAGE_ERROR 发生页面故障
STATUS_INVALID_HANDLE 输入的是无效的句柄
STATUS_INVALID_PARAMETER 输入的是无效的参数
STATUS_NO_SUCH_DEVICE 指定的设备不存在
STATUS_NO_SUCH_FILE 指定的文件不存在
STATUS_INVALID_DEVICE_REQUEST 无效的设备请求
STATUS_END_OF_FILE 文件已到结尾
STATUS_INVALID_SYSTEM_SERVICE 无效的系统调用
STATUS_ACCESS_DENIED 访问被拒绝
STATUS_BUFFER_TOO_SMALL 输入的缓冲区过小
STATUS_OBJECT_TYPE_MISMATCH 输入的对象类型不匹配
STATUS_OBJECT_NAME_INVALIE 输入的对象名无效
STATUS_OBJECT_NAME_NOT_FOUND 输入的对象没有找到
STATUS_PORT_DISCONNNECTED 需要连接的端口没有被连接
STATUS_OBJECT_PATH_INVALID 输入的对象路径无效

另外在使用WinDbg进行调试的时候,一般都会得到函数调用的错误码,根据错误码可以找到对应的错误信息,微软提供了一种解决方案:

LPVOID lpMessageBuffer;
HMODULE Hand = LoadLibrary(_T("NTDLL.DLL"));
DWORD dwErrCode = 0;
//获取错误码 FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_FROM_HMODULE,
Hand,
dwErrCode,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMessageBuffer,
0,
NULL ); // Now display the string.
// Free the buffer allocated by the system.
LocalFree( lpMessageBuffer );
FreeLibrary(Hand);

驱动对象

驱动程序的入口函数是DriverEntry,函数会传入一个驱动对象的指针——PDRIVER_OBJECT,每个驱动都有一个唯一的驱动对象,就好像每个Win32应用程序有一个唯一的实例句柄。它的定义如下:

typedef struct _DRIVER_OBJECT {
CSHORT Type;
CSHORT Size;
PDEVICE_OBJECT DeviceObject;
ULONG Flags;
PVOID DriverStart;
ULONG DriverSize;
PVOID DriverSection;
PDRIVER_EXTENSION DriverExtension;
UNICODE_STRING DriverName;
PUNICODE_STRING HardwareDatabase;
PFAST_IO_DISPATCH FastIoDispatch;
PDRIVER_INITIALIZE DriverInit;
PDRIVER_STARTIO DriverStartIo;
PDRIVER_UNLOAD DriverUnload;
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT;

下面主要对几个重要的部分做介绍:

1. DeviceObject:保存的是驱动中设备对象的指针,另外每个设备对象又有一个指向下一个设备对象的指针,这样同一个驱动程序中的不同设备对象就构成了一个链表

2. DriverName:这个里面存储的是驱动程序的名称,该字符串一般为“\Driver\驱动名称”

3. HardwareDatabase:这里记录的是设备的硬件数据库键名,这个数据库一般是注册表,字符串一般为“REGISTRY\MACHINE\HARDWARE\DESCRIPTION\SYSTEM”

4. DriverStartIo:记录StartIo这个例程回调函数的地址

5. DriverUnload:当驱动卸载时会调用这个指针所指向的函数

6. MajorFunction,这是一个回调函数的指针数组,处理IRP包的不同请求,就好像应用层里面的消息处理函数,根据不同的请求,调用不同的函数。

设备对象

在windows平台将每个设备抽象为一个设备对象,驱动层一般通过设备对象来操作具体的设备,每个驱动可以有多个设备对象。

typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _DEVICE_OBJECT {
...
struct _DRIVER_OBJECT *DriverObject;
struct _DEVICE_OBJECT *NextDevice;
struct _DEVICE_OBJECT *AttachedDevice;
struct _IRP *CurrentIrp;
ULONG Flags;
PVOID DeviceExtension;
DEVICE_TYPE DeviceType;
CCHAR StackSize;
...
} DEVICE_OBJECT;

设备对象本身定义是十分复杂的,在这我们只列举出部分,以后写程序会经常使用的部分,下面是对这些部分的说明:

1. DriverObject: 指向所属驱动的驱动对象的指针

2. NextDevice:指向下一个设备驱动的指针

3. AttachedDevice:指向它被附加的驱动的指针,设备对象之上还可以在附加上其他的设备对象,这样每当有消息传来时总会由附加在它之上的设备对象处理,然后才会交由它自身处理,这个指针就是指向附加在它之上的设备对象的指针

4. CurrentIrp:指向当前IRP域的指针

5. Flags:表名该设备的一些标志信息,主要有下面几个值:

标志 描述
DO_BUFFERED_IO 读写使用缓冲方式,内核层在使用用户缓冲区时会将用户分区中的数据拷贝到内核分区中
DO_EXCLUSIVE 一次只允许一个线程使用这个设备对象
DO_DIRECT_IO 读写直接方式,应用层将某块内存锁定在内存,然后将内存映射到内核空间中,这种方式是最快的方式
DO_DEVICE_INITIALIZING 设备正在初始化
DO_POWER_PAGABLE 设备必须在PASSIVE_LEVEL上处理IRP_MJ_PNP请求
DO_POWER_INRUSH 设备上电期间需要大电流

6. DeviceExtension:指向一块扩展的内存,系统允许用户在创建设备对象时自定义一块区域用来保存结构体中没有但是用户自己感兴趣的内容。在驱动程序中需要尽量避免使用全局变量,所以可以通过使用这块扩展内存来传输全局变量

7. DeviceType:驱动的类型,主要有下面几个值

设备类型 描述
FILE_DEVICE_BEEP 该设备是一个蜂鸣器
FILE_DEVICE_CD_ROM 该设备时一个CD光驱
FILE_DEVICE_CD_ROM_FILE_SYSTEM CD光驱文件系统设备
FILE_DEVICE_CONTROLLER 控制器设备
FILE_DEVICE_DATALINK 数据链设备
FILE_DEVICE_DFS DFS设备对象
FILE_DEVICE_DISK 磁盘设备对象
FILE_DEVICE_DISK_FILE_SYSTEM 磁盘文件系统设备对象
FILE_DEVICE_FILE_SYSTEM 文件系统设备对象
FILE_DEVICE_INPORT_PORT 输入端口设备对象
FILE_DEVICE_KEYBOARD 键盘设备对象
FILE_DEVICE_MAILSLOT 邮件曹设备对象
FILE_DEVICE_MIDI_IN MIDI输入设备对象
FILE_DEVICE_MIDI_OUT MIDI输出设备对象
FILE_DEVICE_MOUSE 鼠标设备对象
FILE_DEVICE_MULTI_UNC_PROVIDER 多UNC设备对象
FILE_DEVICE_NAMED_PIPE 命名管道设备对象
FILE_DEVICE_NETWORK 网络设备对象
FILE_DEVICE_NETWORK_BROWSER 网络浏览器设备对象
FILE_DEVICE_NETWORK_FILE_SYSTEM 网络文件系统设备对象
FILE_DEVICE_NULL 空设备对象
FILE_DEVICE_PARALLEL_PORT 并口设备对象
FILE_DEVICE_PHYSICAL_NETCARD 物理网卡设备对象
FILE_DEVICE_PRINTER 打印机设备对象
FILE_DEVICE_SCANNER 扫描仪设备对象
FILE_DEVICE_SERIAL_MOUSE_PORT 串口鼠标设备对象
LE_DEVICE_SERIAL_PORT 串口设备对象
FILE_DEVICE_SCREEN 屏幕设备对象
FILE_DEVICE_SOUND 声音设备对象
FILE_DEVICE_STREAMS 流设备对象
LE_DEVICE_TAPE 磁带设备对象
FILE_DEVICE_TAPE_FILE_SYSTEM 磁带文件系统设备对象
FILE_DEVICE_TRANSPORT 传输设备对象
FILE_DEVICE_UNKNOWN 未知设备对象
FILE_DEVICE_VIDEO 视频设备对象
FILE_DEVICE_VIRTUAL_DISK 虚拟磁盘设备对象
FILE_DEVICE_WAVE_IN 声音输入设备对象
FILE_DEVICE_WAVE_OUT 声音输出设备对象

在创建设备对象时如果不知道这个设备对象是何种类型,可以直接给FILE_DEVICE_UNKNOWN;

8. StackSize:之前说到过,设备对象存在附加的情况,附加时每个设备对象会存储它上层的设备对象的指针,这样就形成了类似堆栈的结构,而这个值就表示从该设备对象到栈底还有多少个设备对象

为了便于理解我们做了这样一个示意图:

windows 驱动开发入门——驱动中的数据结构的更多相关文章

  1. 驱动开发:内核中实现Dump进程转储

    多数ARK反内核工具中都存在驱动级别的内存转存功能,该功能可以将应用层中运行进程的内存镜像转存到特定目录下,内存转存功能在应对加壳程序的分析尤为重要,当进程在内存中解码后,我们可以很容易的将内存镜像导 ...

  2. Kinect for Windows SDK开发入门(一):开发环境配置

    [译]Kinect for Windows SDK开发入门(一):开发环境配置 前几天无意中看到微软发布了Kinect for windows sensor,进去看了一下Kinect应用的例子,发现K ...

  3. Kinect for Windows SDK开发入门(15):进阶指引 下

    Kinect for Windows SDK开发入门(十五):进阶指引 下 上一篇文章介绍了Kinect for Windows SDK进阶开发需要了解的一些内容,包括影像处理Coding4Fun K ...

  4. Windows驱动开发入门指引

       1.  前言 因工作上项目的需要,笔者需要做驱动相关的开发,之前并没有接触过相关的知识,折腾一段时间下来,功能如需实现了,也积累了一些经验和看法,所以在此做番总结. 对于驱动开发的开发指引,微软 ...

  5. Windows内核驱动开发入门学习资料

    声明:本文所描述的所有资料和源码均搜集自互联网,版权归原始作者所有,所以在引用资料时我尽量注明原始作者和出处:本文所搜集资料也仅供同学们学习之用,由于用作其他用途引起的责任纠纷,本人不负任何责任.(本 ...

  6. 《windows内核安全与驱动开发》ctrl2cap中的ObReferenceObjectByName疑问

    国内有关于windows内核驱动这块的书籍实在是甚少,不过好在<windows内核安全与驱动开发>这本书还算不错(内容方面),但是不得不说这本书在许多地方存在着一些细节上的问题.比如我今天 ...

  7. 驱动开发入门——NTModel

    上一篇博文中主要说明了驱动开发中基本的数据类型,认识这些数据类型算是驱动开发中的入门吧,这次主要说明驱动开发中最基本的模型--NTModel.介绍这个模型首先要了解R3层是如何通过应用层API进入到内 ...

  8. wince驱动开发入门

    因为课题前期调研没做好,用的CPU板卡和数据采集卡来自两个部门.加上买的是裸板,自己定制的OS,技术支持不爱搭理.所以给的AI板卡的驱动一直装不上,自己在郁闷中寻找答案,就扎进了wince驱动的知识库 ...

  9. 2019.05.08 《Linux驱动开发入门与实战》

    第六章:字符设备 申请设备号---注册设备 1.字符设备的框架: 2.结构体,struct cdev: 3.字符设备的组成: 4.例子: 5.申请和释放设备号: 设备号和设备节点是什么关系.? 设备驱 ...

随机推荐

  1. SSH2——filter过滤器

    概述: 过滤器是Servlet2.3以上新添加的一个功能,其技术也是很强大的.通过Filter技术能够对WEBserver的文件进行拦截,从而实现一些特殊的功能. 在JSP开发应用中也是必备的技能之中 ...

  2. Spring基础知识之依赖注入

    Spring框架的四大原则: 1)使用POJO进行轻量级和最小侵入式的开发. 2)通过依赖注入和基于接口编程实现松耦合. 3)通过AOP和默认习惯进行声明式编程. 4)使用AOP和模板(templat ...

  3. Python笔记·第二章—— Python的编码问题(一)

    一.什么是编码 可以说,计算机是一个即聪明又笨蛋的家伙.说它聪明,是因为他可以做很多事情,它的强大无需多说,大家应该都有所了解以及感受.但是为什么说它又是个笨蛋呢,因为我们在电脑上写出的每一个字,保存 ...

  4. Elasticsearch批处理操作——bulk API

    Elasticsearch提供的批量处理功能,是通过使用_bulk API实现的.这个功能之所以重要,在于它提供了非常高效的机制来尽可能快的完成多个操作,与此同时使用尽可能少的网络往返. 1.批量索引 ...

  5. 带你深度解析Maven

    一.What`s Maven? Maven是基于项目对象模型(POM project object model),可以通过一小段描述信息(配置)来管理项目的构建,报告和文档的软件项目管理工具,简单的说 ...

  6. Structured Streaming从Kafka 0.8中读取数据的问题

    众所周知,Structured Streaming默认支持Kafka 0.10,没有提供针对Kafka 0.8的Connector,但这对高手来说不是事儿,于是有个Hortonworks的邵大牛(前段 ...

  7. 《程序设计语言——实践之路》【PDF】下载

    程序设计语言--实践之路>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230382240 内容简介 本书在美国大学已有使用了十余年,目前被欧 ...

  8. 56、jsのBOM对象与DOM对象

    javascript的Bom和Dom对象使我们学习的重点,这篇随笔可以重点阅读 一.BOM对象 1.window对象 所有浏览器都支持 window 对象.概念上讲.一个html文档对应一个windo ...

  9. Data Base mongodb高版本与低版本的区别

    mongodb高版本与低版本的区别 一.mongodb引擎: Mongodb 3.0支持用户自定义存储引擎,用户可配置使用mmapv1或者wiredTiger存储引擎. 3.2版本以后默认的开启的是w ...

  10. Servlet过滤器简单探索

    过滤器的工作时机介于浏览器和Servlet请求处理之间,可以拦截浏览器对Servlet的请求,也可以改变Servlet对浏览器的响应. 其工作模式大概是这样的: 一.Filter的原理 在Servle ...