寒江独钓-第二章 驱动基础知识

2.1.2共享的内核空间
    进程的空间被分成两部分:一部分供进程独立使用,称为用户空间;另一部分容纳操作系统的内核,称为内核空间.
    在32位系统上,低2GB是用户空间,高2GB是内核空间.
    x86架构下r0层的代码才能访问内核空间,普通应用程序都运行在r3层,要 访问r0层的功能 一般通过操作系统提供的一个入口(在该入口调用sysenter指令)

windows的系统进程是名为"System"的进程,这个进程pid在xp下始终为4,调用PsGetCurrentProcessId会发现内核模块中分发函数调用时,当前进程一般都不是System进程.但是DriverEntry函数被调用时,一般都位于系统进程中,因为windows用系统进程来加载内核模块.

2.2.1数据类型
    从x86到x64,除了所有的指针从4字节变成了8字节,其它数据类型的字节宽度都没有变化.

2.2.2返回状态
    大部分内核API的返回值都是一个返回状态,也就是一个错误码,类型为NTSTATUS.
     使用宏NT_SUCCESS()可以判断一个返回值是否成功.


2.2.3字符串
    驱动里的字符串用一个结构来容纳,定义如下:
   
   
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
}UNICODE_STRING *PUNICODE_STRING;
    UNICODE_STRING是可以直接打印的(注意是指针,结构本身不能打印).
   
   
UNICDE_STRING str = RTL_CONSTANT_STRING(L:"打印内容");
DbgPrint("%wZ",&str);
    
2.3.1驱动对象
    windows内核中,一个驱动,一个设备,一个文件等都是一个对象,每个内核对象都用一个结构体来表示.
    驱动对象的结构如下:
  
  
typedef struct _DRIVER_OBJECT
{
//结构的类型和大小
CSHORT Type;
CSHORT Size;
//设备对象指针
PDEVICE_OBJECT DeviceObject;
...
//这个内核模块在内核空间中的开始地址和大小
PVOID DiverStart;
ULONG DiverSize;
...
//驱动的名字
UNICODE_STRING DriverName;
...
//快速IO分发函数
PFAST_IO_DISPATCH FastIoDispatch;
...
//驱动的卸载函数
PDRIVER_UNLOAD.DriverUnload;
//普通分发函数
PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
}DRIVER_OBJECT;
   内核模块并不生成一个进程,只是填写一组回调函数让windows来调用.
   
2.3.2设备对象
    在内核中,大部分消息都以请求(IRP)的方式传递.设备对象(DEVICE_OBJECT)是唯一可以接收请求的实体.
    在WDK的wdm.h设备对象的定义如下:
   
   
typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT)
_DEVICE_OBJECT
{
CSHORT Type;
USHORT Size;
//引用计数
ULONG ReferenceCount;
//这个设备所属的驱动对象
struct _DRIVER_OBJECT *DriverObject;
//下一个设备对象.在一个驱动对象中有n个设备,这些设备用这个指针连接起来作为一个单向链表
string _DEVICE_OBJECT *NextDevice;
//设备类型
DEVICE_TYPE DeviceType;
//IRP栈大小
HAR StackSize;
...
}DEVICE_OBJECT;
    当Windows内核向一个设备发送一个请求时,与设备关联驱动对象的分发函数的某一个会被调用.
    分发函数的原型如下:
   
   
//分发函数数的第一个参数device是请求的目标设备,第二个参数是IRP的指针
NTSTATUS Mydispatch(PDEVICE_OBJECT device,PIRP irp);

2.3.3请求
    何为请求?
    如果要求网卡发送一个数据包,或者向网卡请求把已经在缓冲区接收到的包读出来,这就是一个请求;
    如果读取一个文件从0开始的512字节,这也是一个请求;
    如果在磁盘的64MB位置写入长达512个字节的一组数据,这也是一个请求.

    这些操作最终在内核中会被IO管理器翻译成请求(IRP或者与之等效的其他形式,比如快速IO调用)发送往某个设备对象
    WDK的wdm.h中IRP的结构结构如下:
   
   
typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT)
_IRP{
//类型和大小
CSHORT Type;
USHORT Size;
//内存描述符链表指针,实际上,这里用来描述一个缓冲区
PMDL MdlAddress;
...
//下面共用体有一个SystemBuffer.这是比MdlAddress稍微简的表示缓冲区的一种方式.
//IRP用MdlAddress还是用SystemBuffer取决于这次请求的IO方式.
union
{
struct _IRP *MasterIrp;
__volatile LONG IrpCount;
PVOID SystemBuffer;
}AssociatedIrp;
//IO状态.一般请求完成之后的返回情况放在这里
IO_STATUS_BLOCK IoStatus;
//IRP栈空间大小
CHAR StackCount;
//IRP当前栈空间数组索引
CHAR CurrentLocation;
....
//用来取消一个未决请求的函数
__volatile PDRIVER_CANCEL CancelRoutine;
//与MdlAddress和SystemBuffer一样都可以表示缓冲区.特性稍有不同.
PVOID UserBuffer;
union
{
...
//发出这个请求的线程
PETHREAD Thread;
...
struct
{
LIST_ENTRY ListEntry;
union
{
//一个IRP栈空间元素
struct _IO_STACK_LOCATION *CurrentStackLocation;
...
};
}Overlay;
...
}Tail;
}IRP,*PIRP;

        CurrentLocatiot是 当前IO_STACK_LOCATION的数组索引。索引是从1开始,没有0。当驱动程序准备向次低层驱动程序传递IRP时可以调用IoCallDriver例程,它其中的一个工作是递减当前IO_STACK_LOCATION的索引,使之与下一层的驱动程序匹配。但该索引不会设置成0,如果设置成0,系统将会崩溃。就是说,最底层的驱动程序不会调用IoCallDriver例程
    注意所谓的IRP的栈空间.一个IRP往往要传递N个设备才能完成,在传递过程中,可能会一些"中间变换",导致请求的参数变化.为了保存这种参数变化,设定每次"中转"都留一个"栈空间",用来保存中间参数 .CurrentLocation表示当前使用哪个IRP栈空间.
请求的类型如下:
生成请求:主功能号为IRP_MJ_CREATE的IRP
查询请求:主功能号为IRP_MJ_QUERY_INFORMATION的IRP
设置请求:主功能号为IRP_MJ_SET_INFORMATION的IRP
控制请求:主功能号为IRP_MJ_DEVICE_CONTROL的IRP
关闭请求:主功能号为IRP_MJ_CLOSE的IRP

2.4.2常用内核API

  • Ex-系列函数
功能简述
ExAllocatePool 内存分配.相当于C RunTime库里的malloc
ExFreePool 内存释放.相当于C RunTime库里的free
ExAcquireFastMutex 获取一个快速互斥体,用于多线程环境下的同步
ExReleseFastMutex 释放一个快速互斥体
ExRaiseStatus 抛出一个异常,带有一个错误的status值

  • Zw-系列函数名
对应的Nt-函数 功能简述
ZwCreateFileNtCreateFile打开文件或设备
ZwWriteFileNtWriteFile写文件或发送写请求给设备
ZwReadFileNtReadFile读文件或发送读请求给设备
ZwQueryDirectoryFileNtQueryDirectoryFile目录查询
ZwDeviceIoControlFileNtDeviceIoControlFile发送设备控制请求
ZwCreateKeyNtCreateKey打开一个注册表键
ZwQueryValueKeyNtQueryValueKey读取一个注册表中的值
   注意:Nt-系列函数在WDK帮助里是查不到的,头文件也没有,但是确实存在,自己声明后就可以使用.

  • Rtl-系列函数名
功能简述
RtlInitUnicodeString初始化一个字符串
RtlCopyUnicodeString拷贝字符串
RtlAppendUnicodeToString将一个字符串追加到另一个字符串后
RtlStringCbPrintf将字符串打印到一个字符串中,相当于sprintf
RtlCopyMemory内存数据块拷贝
RtlMoveMemory内存数据块移动
RtlZeroMemory 内存数据清零
RtlCompareMemory 比较内存
RtlGetVersion获得当前Windows版本

  • Io-系列函数
功能简述
IoCreateFile打开文件.这个函数比ZwCreateFile要更加底层
IoCreateDevice生成一个设备对象
IoCallDriver发送请求.Io管理器调用这个函数把不同的IRP发送到不同的设备
IoCompleteRequest完成请求.通知IO管理器这个IRP已经完成了
IoCopyCurrentIrpStackLocationToNext将当前IRP栈空间拷贝到下一个栈空间
IoSkipCurrentIrpStackLocationToNext跳过当前IRP的栈空间
IoGetCurrentIrpStackLocation获得IRP的当前栈空间指针
     Io系列函数是Io管理器将用户调用的API函数翻译成IRP或者等价请求发送到内核各个不同的设备的关键组件.

2.5 windows的驱动开发模型
     VXD windows 9x 上的驱动,已淘汰.
     KDM windows NT上的驱动,KDM和WDM都称为传统型驱动.
     WDM  windows2000时期及之后的驱动,必须满足被要求的特性(如电源管理,即插即用等),不满足的属于NT式驱动(KDM).
     WDF 调用了WDF相关内核API的驱动,可调用传统型驱动的API,为传统型的升级版

2.6.1内核编程的主要调用源
    假定函数B调用了函数A,则函数B为A的调用者;
    继续寻找B的调用者,直到代码可见范围内最用一个调用都函数M,函数M就是函数A调用源
    从调用源出发,最后调用函数A的整个路径,称为函数A的调用路径.
    单线程的用户态程序只有一个调用源,就是主函数.主函数也有调用者,但是被编译器隐藏了.

    内核函数的调用源如下:
  1. 入口函数DriverEntry和卸载函数DriverUnload.
  2. 各种分发函数(包括普通分发函数和快速Io分发函数).
  3. 处理请求时设置的完成函数.即请求完成后被系统调用的回调函数.
  4. 其它回调函数(各种NDIS回调函数,NDIS是网络相关的一些驱动程序函数)
     备注:调用源概念对逆向分析很重要.

2.6.2函数的多线程安全性
    函数的多线程安全性是指,一个函数被调用过程中,还未返回时,又被其它线程调用的情况下(函数被重入),函数执行结果的可靠性.
    如果是可靠的,则称这个函数是多线程安全的,如果是不可靠的,则称这个函数是非多线程安全的.
    
     多线程安全性的规则:
  1. 只运行于单线程环境的函数,不需要多线程安全性,可能运行于多线程环境的函数,必须是多线程安全的.
  2. 如果函数A的所有调用源只运行于同一单线程环境,则A也是只运行在单线程环境的
  3. 如果函数A的其中一个或多个调用源运行于一个或多个并发的多线程环境,而且调用路径上没有采取多线程序列化成单线程的强制措施(指如互斥体,自旋锁等同步手段),则函数A是运行在多线程环境的. 
  4. 只使用函数内部资源,完全不使用全局变量,静态变量或其它全局性资源的函数是多线程安全的.
  5. 如果对某个全局变量或者静态变量的所有访问都被强制为同一时刻只有一个线程访问,则使用这个全局变量和静态变量的函数多线程安全性没有影响.
    是否保证函数可重入性最终由调用源和调用路径决定.

2.6.3代码的中断级(IRQL级别)
    Passive级:被动级,许多比较复杂的内核API都必须在Passive级执行,这一点在WDK的文档上都有说明
    Dispatch级:比Passive级别高,只有比较简单的函数能在Dispatch级执行.

    调用任何内核API之前,必须查看WDK文档,了解这个内核API的中断级要求.
    判断正在编写的代码的中断级:
  1. 如果调用路径上没有导致中断屁股提高和降低的情况,则一个函数执行时的中断级和他的调用用源中断级相同.
  2. 如果调用路径上有获取自旋锁,则中断级随之提高;如果调用路径上有释放自旋锁,则中断级随之下降
    和多线程安全性一样,当前代码的中断级基本上取决于调用源的中断级和调用路径.

    内核代码主要调用源的运行中断级
调用源 一般的运行中断级
DriverEntry,DriverUnload Passive级
各种分发函数 Passive级
完成函数 Dispatch级
各种NDIS回调函数 Dispatch级

    注意:可以强制提高或降低代码的当前中断级.但是windows代码都运行在规范的中断级上,任意的降低中断级会导致产生不可预料结果.

2.6.4WDK中出现的特殊代码
    #define IN          //   IN表示这个参数用于输入
    #define OUT     // OUT表示这个参数用于返回结果

    #pragma alloc_text(INIT,DriverEntry)
    #pragma alloc_text(PAGE,NdisProtOpen)
    #pragma alloc_text这个宏指定某个函数的可执行代码编译出来后在sys文件中的位置.
    内核模块编译出来后是一个PE格式的sys文件,这个文件代码段(text段)中有不同的节(Section),不同的节被加载到内存中处理情况不同.
    INIT节是在初始化完毕后就被释放.PAGE节位于可以进行分页交换的内存空间.如果未用上述预编译指令处理,则代码默认位于PAGELK节,加载后位于不可分页交换的内存空间中.
    注意:放在PAGE节的函数不可以在Dispatch级调用,因为这种函数会诱发缺页中断,缺页中断不能在Dispatch级完成.










  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
版权声明 楚狂人Windows驱动编程基础教程 本书是免费电子书。作者保留一切权利。但在保证本书完整性(包括版权声明、前言、正文内容、后记、以及作者的信息),并不增删、改变其中任何文字内容的前提下,欢迎任何读者以任何形式(包括各种格式的文档)复制和转载本书。同时不限制利用此书赢利的行为(如收费注册下载,或者出售光盘或打印版本)。不满足此前提的任何转载、复制、赢利行为则是侵犯版权的行为。 发现本书的错漏之处,请联系作者。请不要修改本文中任何内容,不经过作者的同意发布修改后的版本。 前言 本书非常适合熟悉Windows应用编程的读者转向驱动开发。所有的内容都从最基础的编程方法入手。介绍相关的内核API,然后举出示范的例子。这本书只有不到70页,是一本非常精简的小册子。所以它并不直接指导读者开发某种特定类型的驱动程序。而是起到一个入门指导的作用。 即使都是使用C/C++语言的代码,在不同的应用环境中,常常看起来还是大相径庭。比如用TurboC++编写的DOS程序代码和用VC++编写的MFC应用程序的代码,看起来就几乎不像是同一种语言。这是由于它们所依赖的开发包不相同的缘故。 在任何情况下都以写出避免依赖的代码为最佳。这样可以避免重复劳动。但是我们在学习一种开发包的使用时,必须习惯这个环境的编码方式,以便获得充分利用这个开发包的能力。 本书的代码几乎都依赖于WDK(Windows Driver Kit)。但是不限WDK的版本。WDK还在不断的升级中。这个开发包是由微软公司免费提供的。读者可以在微软的网站上下载。 当然读者必须把WDK安装的计算机上并配置好开发环境。具体的安装和配置方法本书没有提供。因为网上已经有非常多的中文文档介绍它们。 读完这本书之后,读者一定可以更轻松的阅读其他专门的驱动程序开发的文档和相关书籍。而不至于看到大量无法理解的代码而中途放弃。如果有任何关于本书的内容的问题,读者可以随时发邮件到[email protected]或者[email protected]。能够回答的问题我一般都会答复。 写本书的时候,我和wowocock合作的一本名为《天书夜读》(在网上有一个大约20%内容的缩减电子版本)正在电子工业出版社编辑。预计还有不到一个月左右就会出版。这也是我自己所见的唯一一本中文原创的从汇编和反汇编角度来学习Windows内核编程和信息安全软件开发的书。希望读者多多支持。有想购买的读者请发邮件给我。我会在本书出版的第一时间,回复邮件告知购买的方法。 此外我正在写另一本关于Windows安全软件的驱动编程的书。但是题目还没有拟好。实际上,读者现在见到的免费版本的《Windows驱动编程基础教程》是从这本书的第一部分中节选出来的。这本书篇幅比较大,大约有600-800页。主要内容如下: 第一章驱动编程基础 第二章磁盘设备驱动 第三章磁盘还原与加密 第四章传统文件系统过滤 第五章小端口文件系统过滤 第六章文件系统保护与加密 第七章协议网络驱动 第八章物理网络驱动 第九章网络防火墙与安全连接 第十章打印机驱动与虚拟打印 第十一章视频驱动与过滤 附录A WDK的安装与驱动开发的环境配置 附录B 用WinDbg调试Windows驱动程序

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值