首页
社区
课程
招聘
[原创]驱动程序学习笔记(一)(二)(三)(四)(五)
发表于: 2012-3-18 23:25 79069

[原创]驱动程序学习笔记(一)(二)(三)(四)(五)

2012-3-18 23:25
79069
收藏
免费 6
支持
分享
最新回复 (90)
雪    币: 506
活跃值: (70)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
26
支持

最近学什么都浅尝辄,学几天驱动,学几天PE结构,看几天反汇编。
己经静不下心来学了,觉见要学的东西太多了。
2012-3-26 07:41
0
雪    币: 692
活跃值: (25)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
27
好文章  支持楼主继续发学习笔记
2012-3-26 08:54
0
雪    币: 113
活跃值: (100)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
28
status = IoCreateDevice(pDriverObject, 0x14u, &DeviceName, 0x22u, 0, 1, (PDEVICE_OBJECT *)&pDevObj);

是不是IoCreateDevice的时候已经把新创建的设备对象链接到驱动对象里了?
那在DriverEntry里应该可以遍历已经创建的设备对象了?
2012-3-26 11:09
0
雪    币: 114
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
29
ring3层在加载驱动服务对象时这个驱动就被运行起来了,运行时首先进入驱动程序的入口,此时驱动对象早已存在了,既然对象都存在了,那么关系到这个驱动的设备链表也已经初始化成功了,只是为空罢了,这时遍历不出任何东西。当你调用创建设备时,创建设备成功后,该设备会自动进入到设备链表,理论上只要在这个设备创建成功之后去遍历整个链表都会找到这个设备的,不论在哪里都行。但是具体这个设备是如何进入设备链表的,我不清楚,是发送请求?还是通过其他什么途径,也没人去解释,恐怕只有微软晓得了。我猜想可能是通过IO管理器,接受某个消息,负责维护设备链表的线程获得设备后将其加到链表里,不知道对不对,猜的。如果真想弄懂这种机制,去查查资料吧,自己挖掘也行。小弟惭愧,搞不懂
2012-3-26 12:57
0
雪    币: 220
活跃值: (40)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
30
[QUOTE =ronging;1056546]thanks, 有点理解,还有点胡涂。
就我的理解,IRP只有一个,相当于一段地址空间,每个DO使用它的一小部分,从这个角度来讲,是不是越在上的DO使用的IRP的地址越高,越在下的DO使用的IRP的地址越低?[QUOTE]

是的。
2012-3-26 15:31
0
雪    币: 207
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
31
很好的学习笔记
2012-3-26 15:55
0
雪    币: 113
活跃值: (100)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
32
thanks, 还有些问题想再讨论和确认一下。
假设有多个ring3的 app,当第一个app加载驱动服务的时候,DriverEntry运行,这个时候设备列表应该是空的,
status = IoCreateDevice(pDriverObject, 0x14u, &DeviceName, 0x22u, 0, 1, (PDEVICE_OBJECT *)&pDevObj);
会执行,从而有了第一个设备对象,

当第2个,第3个APP加载这个驱动的时候,理论上设备列表中应该有以前的设备对象了,是不是这样?并且我猜想IoCreateDevice创建设备对象的同时完成了链表的扩展,陆续创建了第2个,第3个设备对象。
2012-3-27 10:00
0
雪    币: 113
活跃值: (100)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
33
[QUOTE=carlcarl;1056870][QUOTE =ronging;1056546]thanks, 有点理解,还有点胡涂。
就我的理解,IRP只有一个,相当于一段地址空间,每个DO使用它的一小部分,从这个角度来讲,是不是越在上的DO使用的IRP的地址越高,越在下的DO使用的IRP的地址越低?



是的。


多谢,确认了一点。
2012-3-27 10:21
0
雪    币: 114
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
34
这个附上我得理解,我以前多次测试了,同一个驱动文件,在没卸载之前你又加载他,是加载失败的,嘿嘿,所以不可能几个app都来加载他吧,等我回去再实验一下。
2012-3-27 10:39
0
雪    币: 114
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
35
对于那个IoCreateDevice,在创建设备的同时已经把设备附到设备链上了,属于管理器所为吧
2012-3-27 10:43
0
雪    币: 114
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
36
刚才试了下,其实加载同一个驱动是不行的,但是你把这个驱动文件名称换一个之后就又可以加载了
2012-3-27 15:37
0
雪    币: 113
活跃值: (100)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
37
如果加载多个驱动不可以,那么意味着只会有一个IoCreateDevice,那多个设备是怎么创建出来的?
2012-3-27 18:37
0
雪    币: 114
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
38
一个驱动可以创建很多个设备,可以多次调用IoCreateDevice创建多个设备,但是一个驱动被重复加载是不可以的,在ring3层必须要创建相应的服务,一个驱动对应着一个服务,服务是不能重复的吧。
2012-3-27 19:30
0
雪    币: 114
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
39
终于来到我们将要看到的过滤了,刚刚起步感觉辛苦。其时内核编程真的有点难,难得不是编,而是自己如何挖掘
想要的知识,挖掘深藏在里面的知识。书是大牛写的,大牛写书时虽然考虑到我们小菜学内核的情况,但是写书时他
们都小有成就了,思维方式也和我们存在差距了,这节我们就以小菜的想法来切实完成这个串口过滤的驱动吧。这章
我先从头仔细看了一遍,没有动手去写代码,现在大概了解整个过程,自己一步一步实现这个过滤驱动吧。写过滤驱
动前首先要了解下面几点:

1:什么是过滤?--个人理解(当然是看了书之后的理解了),就是在上层设备与下层设备之间再加上一层过滤网,过滤
出我们喜欢的东西,不喜欢的就让它继续走。比如家里电饭锅淘米一样,滤除渣滓,留下上等大米,哈哈,想起大米
就饿了。

2:为什么要过滤?--这不废话吗,呵呵,当然个人用途不同了,杀毒软件监视这端口,不放过每一位从端口流过的数
据,他能怎么办呢,过滤呗,那些黑客呢,自然不是等闲之辈了,他们想从端口获得他们感兴趣的东西,密码?或是
截获他老婆跟比人的聊天记录?无聊……这里纯属自娱自乐,不然长时间对着电脑就没兴趣啦O(∩_∩)O~~

3:怎么样去过滤?--重点来了,过滤嘛,能有多难?不就是在上下层设备之间加一个过滤设备嘛,既不影响上层,也
不影响下层。说来说去就是这么个过程,首先创建一个设备(用于过滤)--将这个设备附加到指定的设备栈上--找到你

要绑定的串口--写出自
己的请求分发函数--不想过滤时就把驱动拆了,把设备从链子上摘下来便是。其中一个问题就是如何找到你要过滤的
指定设备传送的请求呢,下面一一道来。

**************第一步:生成设备,并绑定到指定的设备栈上,生成设备用到IoCreateDevice,而绑定设备则是用到

IoAttachDevice
ToDeviceStack,函数都很简单,看看DDK上的说明吧,

NTSTATUS
  IoCreateDevice(
    IN PDRIVER_OBJECT  DriverObject,--本驱动对象
    IN ULONG  DeviceExtensionSize,--这里不使用,为NULL即可
    IN PUNICODE_STRING  DeviceName  OPTIONAL,--可选的名字,过滤设备不要名称
    IN DEVICE_TYPE  DeviceType,--和要绑定的设备类型一致
    IN ULONG  DeviceCharacteristics,--书上凭经验设为O
    IN BOOLEAN  Exclusive,--保留为系统使用,直接定位false即可
    OUT PDEVICE_OBJECT  *DeviceObject--OUT宏指定为输出参数,即返回值,返回本过滤设备指针
    );

IoCreateDevice:下面一段英文解释
IoCreateDevice creates a device object and returns a pointer to the object. The caller is responsible
for deleting the object when it is no longer needed by calling IoDeleteDevice.

IoCreateDevice can only be used to create an unnamed device object, or a named device object for which
a security descriptor is set by an INF file. Otherwise, drivers must use IoCreateDeviceSecure to

create
named device objects. For more information, see Creating a Device Object. The caller is responsible

for
setting certain members of the returned device object. For more information, see Initializing a Device
Object and the device-type-specific documentation for your device.

Be careful to specify the DeviceType and DeviceCharacteristics values in the correct parameters. Both
parameters use system-defined FILE_XXX constants and some driver writers specify the values in the

wrong
parameters by mistake.

Device objects for disks, tapes, CD-ROMs, and RAM disks are given a Volume Parameter Block (VPB) that

is
initialized to indicate that the volume has never been mounted on the device.

If a driver's call to IoCreateDevice returns an error, the driver should release any resources that it
allocated for that device.
Callers of IoCreateDevice must be running at IRQL <= APC_LEVEL.
捡主要的讲,意思是该函数创建一个设备对象并返回这个对象的指针,当你不再需要这个设备时请调IoDeleteDevice
去删除它,这样我们又学到了这个函数。而且IoCreateDevice只能创建没有名字的设备,若想创建命名的设备,则使
用函数IoCreateDeviceSecure。另外这里提到是小心使用这两个参数,DeviceType,DeviceCharacteristics,他们
都是被定义成FILE_XXX 这样的结构,如果该函数调用失败,那么请释放之前所有用到的资源。最后这个函数运行在
APC_LEVEL级别以下。

而我们要创建的是过滤设备,最后我们这个设备什么事也不干,为了不影响串口功能的使用,还是把这个设备类型设
置成和原来那个设备一样的吧,即这个参数使用原来设备的类型,另外要注意的是,创建设备成功后,把这个设备相
关标志设置成和要过滤的设备标志一样。oldobj为要过滤的设备对象,fltobj为本过滤设备对象
if(oldobj->Flags & DO_BUFFERED_IO)
                (*fltobj)->Flags |= DO_BUFFERED_IO;
        if(oldobj->Flags & DO_DIRECT_IO)
                (*fltobj)->Flags |= DO_DIRECT_IO;
        if(oldobj->Flags & DO_BUFFERED_IO)
                (*fltobj)->Flags |= DO_BUFFERED_IO;
        if(oldobj->Characteristics & FILE_DEVICE_SECURE_OPEN)
                (*fltobj)->Characteristics |= FILE_DEVICE_SECURE_OPEN;
        (*fltobj)->Flags |=  DO_POWER_PAGABLE;

一切设置好以后,就可以开始绑定了,
IoAttachDeviceToDeviceStack(*fltobj,oldobj); returns a pointer to the device object to which the

SourceDevice
was attached。返回一个被过滤设备绑定后的设备指针,其实这里有点疑问?这个返回的不就是要被绑定的设备么

,这个设
备现在存在于最上层了,等待我后面验证。***********验证**********

默认情况下,绑定设备之后设备并没有启动,书上给出这么一句,
(*fltobj)->Flags = (*fltobj)->Flags & ~DO_DEVICE_INITIALIZING;
我在DDK帮助上找到这样一句,
Function and filter drivers must clear the DO_DEVICE_INITIALIZING flag. 这样就可以理解了,清除相应的
标志位就可以了,意思是不需要初始化,直接启动即可,因为他和要绑定的设备是同样的。

****************第二步:根据相应串口ID,设法找到串口设备的指针,这样才能提供给人家绑定啊,连串口设备的

指针都没有
你叫人家啥啊,书上说是打开串口,可我觉得整个过程根本就没有没碰到打开串口这个东西啊,我把它叫做获得串口
设备对象指针函数吧。假设这个设备有名字的话,可以用函数IoGetDeviceObjectPionter来得到对象指针。
DDK中有这样的评论:
This routine also returns a pointer to the corresponding file object. When unloading, a driver can

dereference
the file object as a means of indirectly dereferencing the device object. To do so, the driver calls

ObDereferenceObject
from its Unload routine, passing the file object pointer returned by IoGetDeviceObjectPointer.
这个例程同样返回一个文件对象指针,当卸载的时候这个驱动必须解除这个文件对象指针的引用。不管怎么样,一定
记得解除引用。一般地,串口在在设备中总是以固定形式的名字出现的,如\device\serial0,\device\serial1之类
的,那么可变的就是一个序号而已。在此引入内核中的一个格式化字符的函数,同sprintf有异曲同工之处。
即RtlStringCchPrintf,参数没什么好解释的,看DDK上微软的一个范例就OK了,

WCHAR pszDest[30];
size_t cchDest = 30;

LPCWSTR pszFormat = L"%s %d + %d = %d.";
WCHAR* pszTxt = L"The answer is";

NTSTATUS status =
    RtlStringCchPrintfW(pszDest, cchDest, pszFormat, pszTxt, 1, 2, 3);
The resultant string is "The answer is 1 + 2 = 3." It is contained in the buffer at pszDest。
照葫芦画瓢,把传进来的串口ID转换成\device\serial0这种类型即可,看代码。
        UNICODE_STRING name_str;
        static WCHAR name[32] = { 0 };
        PFILE_OBJECT fileobj = NULL;
        PDEVICE_OBJECT devobj = NULL;
        // 输入字符串。
        memset(name,0,sizeof(WCHAR)*32);
        RtlStringCchPrintfW(name,32,L"\\Device\\Serial%d",id);
        RtlInitUnicodeString(&name_str,name);
然后获得该设备对象的指针,
*status=IoGetDeviceObjectPointer(&name_str, FILE_ALL_ACCESS, &fileobj, &devobj);
记得上面所说的用过之后要调用ObDereferenceObject解除文件对象的引用哦。
这里是获得一个串口设备对象的指针,但是一个系统里有多少个串口我们并不晓得,我也没什么办法去列举出那些串
口,书上是假设最多有32个串口,不知正确与否。不过我可以提出一种思路,遍历所有的设备对象,然后匹配Serial%d
这个字符串,但是显然比作者的那个麻烦多了,图省事,直接假设吧,一般也没有那么串口。
那么构造一个循环,获得所有的串口设备对象的指针不难吧,就是不断传入相应的串口ID,然后调用获得串口设备对
象指针这个函数,接着再依次绑定这些串口,这里因为不知道有多少,所以干脆全部都绑定了,管他有多少呢,一个
都不能少是吧。相应的代码逻辑简单,就不列出了,代码传在附件中。
上传的附件:
2012-3-27 22:58
0
雪    币: 114
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
40
****************第三步,重点开始过滤数据了************************
过滤设备创建好了,把所有的串口也绑定了,一切准备工作做好了。小样儿,我看你流过串口的所有数据都不跑不掉
了吧,哼哼,所有IRP请求全部都在我的掌握之中,数据都流过我这里,想得到敏感的数据就易如反掌了。请求我在
前面就已经预习到了,不就那几个,我这里只做测试,就只过滤他的写请求吧。过滤的结果只有这么几种,要么不想
要得数据,全部让它过去吧,一律放行,要么是想要的数据,嘿嘿全部记录下来慢慢分析,要么就不给它走,拦截了
我们这里就让它过去吧,不干坏事,读取到信息就OK啦,防止影响他的功能被人发现哦。
这种处理相对简单,直接把当前的栈空间跳过,调用下一层驱动就行了。要想读取这个写请求的数据,得知道这些数
据放在哪啊,记得前面提到过不,不记得了没关系,在WinDbg中一个命令搞定,!strct _IRP
看仔细了,在偏移+04 struct   _MDL *MdlAddress内存描述符表指针,+0c    void     *SystemBuffer--系统缓冲
区,+3c void     *UserBuffer--用户地址空间缓冲区。书上49页介绍了这三个空间的效率及使用问题,但是实际上
我们并不知道他使用哪个地方去存放的数据,算了不猜了,三个都读一遍不就完了?这个好,读到数据就不为空,否
则就为空,好办,看例子
        PUCHAR buf = NULL;
        if(irp->MdlAddress != NULL)
        buf = (PUCHAR)MmGetSystemAddressForMdlSafe(irp->MdlAddress,NormalPagePriority);
        else buf = (PUCHAR)irp->UserBuffer;
        if(buf == NULL)
        buf = (PUCHAR)irp->AssociatedIrp.SystemBuffer;
完整的分发函数流程大概这样的,得到当前栈空间IoGetCurrentIrpStackLocation->遍历所有的设备,如果是发送给
本过滤设备的那么就记录->当然是电源IRP请求的话就放他走->如果是写请求那么就记录数据,其他请求一律放过,
代码看看下面的几个需要注意的地方,首先是
电源请求,
if(irpsp->MajorFunction == IRP_MJ_POWER)
            {
                // 直接发送,然后返回说已经被处理了。
                PoStartNextPowerIrp(irp);
                IoSkipCurrentIrpStackLocation(irp);
                return PoCallDriver(s_nextobj[i],irp);}
如果是写请求,读取缓冲区数据保存一下就OK了,
如果是其他请求,跳过当前栈空间一路call下去,当然书的作者还考虑到了一些不合法的请求。采用下面的处理即可
把IRP设置成无效,并且将它设置成完成就可以了。
    irp->IoStatus.Information = 0;
    irp->IoStatus.Status = STATUS_INVALID_PARAMETER;
    IoCompleteRequest(irp,IO_NO_INCREMENT);

*********************最后一步,卸载不用的驱动,回收资源*******************
偷鸡摸狗的事也干完了,数据也得到了,也保存了,歇息歇息,把绑定的设备给他摘了,创建的设备给他删了,做些
扫尾工作,做到来无影去无踪,让它去找他家的鸡吧。
遍历所有过滤设备,全部给他摘除,然后删除所有设备OK啦,睡大觉了,心想偷了这么多鸡,这下有鸡腿啃了。

for(i=0;i<32;i++)
        {
                if(过滤设备数组[i] != NULL)
                        IoDetachDevice(过滤设备数组[i]);//摘除绑定的设备
        }

     //因为不能保证所有的IRP都完成了,所以 睡眠5秒。等待所有irp处理结束,这里还是假设5秒后都处理完了
        interval.QuadPart = (5*1000 * DELAY_ONE_MILLISECOND);               
        KeDelayExecutionThread(KernelMode,FALSE,&interval);

        // 删除这些设备
        for(i=0;i<CCP_MAX_COM_ID;i++)
        {
                if(s_fltobj[i] != NULL)
                        IoDeleteDevice(s_fltobj[i]);}

本章学习的实例代码在附件中,源码还是很简单的,看看上面的分析步骤,很快就能看懂了。下一张就进入大家最感
兴趣的键盘驱动喽,貌似很重要,期待学习的进步吧,相信前面的基础知识够了。
2012-3-27 22:59
0
雪    币: 271
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
41
楼主加油啊,谢谢分享
2012-3-28 08:30
0
雪    币: 692
活跃值: (25)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
42
楼主问个问题,关于“ 而且IoCreateDevice只能创建没有名字的设备,若想创建命名的设备,则使
用函数IoCreateDeviceSecure”  但是IoCreateDevice不是可以传入devicename参数的么?为什么说IoCreateDevice只能创建没有名字的设备呢?
2012-3-28 08:56
0
雪    币: 113
活跃值: (100)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
43
设备对象的创建必须在DriverEntry的时候嘛?能不能在驱动提供服务,也就是说在dispatch的时候?
2012-3-28 10:43
0
雪    币: 114
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
44
DDK上原版说明的,请看
IoCreateDevice can only be used to create an unnamed device object, or a named device object for which a security descriptor is set by an INF file. Otherwise, drivers must use IoCreateDeviceSecure to create named device objects. For more information, see Creating a Device Object. The caller is responsible for setting certain members of the returned device object. For more information, see Initializing a Device Object and the device-type-specific documentation for your device.
2012-3-28 16:14
0
雪    币: 114
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
45
根据我以前那个笔记上提到的,各个函数的运行级别,
           DriverEntry,DriverUnload--Passive
        各种分发函数,--Passive
        完成函数--Dispatch
        各种NDIS回调函数--Dispatch
首先CPU运行级别没有问题,但是分发函数传进的一个参数就是DeviceObject,在前面起码得创建一个设备对象吧,至于到底能不能在分发函数中创建设备,我想没有问题吧,因为环境符合啊
2012-3-28 16:24
0
雪    币: 692
活跃值: (25)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
46
这样的话,即使传入一个devicename后,使用iocreatedevice创建出来的设备,也还是一个unnamed device?
2012-3-28 16:41
0
雪    币: 113
活跃值: (100)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
47
多谢了,LZ能不能解释一下什么是总线驱动,端口驱动?关于驱动程序的名词实在是太多。
2012-3-28 18:10
0
雪    币: 114
活跃值: (26)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
48
因为这个DeviceName是个可选的参数,好像加了这个参数之后就会有个东西跟他关联,具体是什么机制没有研究清楚,有待以后深入学习,我也刚刚学哈,也有很多不是太懂。WDM功能驱动和设备过滤驱动一般都是没有名字的。
2012-3-28 19:41
0
雪    币: 48
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
49
对我这种菜鸟来说是非常好的学习资料呀,感谢楼主
2012-3-28 22:05
0
雪    币: 220
活跃值: (40)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
50
一个驱动可以创建多个设备,这N个设备组成一个单链表,DriverObject就相当于是链表的头。IoCreateDevice创建的设备是插到该链表的首节点中。这是由IoCreateDevice内部完成的。

驱动卸载时,属于该驱动的所有设备对象都应该删除,而这个删除操作,必须由驱动自己遍历链表才能全部删除。
2012-3-29 23:03
0
游客
登录 | 注册 方可回帖
返回
//