驱动调用驱动
其实在之前的文章已经记录过了,调用驱动其实就和用户层差不多,使用CreateFile类型的函数进行调用,但是需要注意的是一下两点:
- 同步设备的话呢:第二个DesireAccess参数需要设置为SYNCHRONIZE,如果不是的话呢要设置为0。
- 同步设备的话呢:倒数第三个参数CreateOptions需要指定为FILE_SYNCHRONOUS_IO_NONALERT或者FILE_SYNCHRONOUS_IO_NALERT,异步设备的话呢不需要指定。
同步调用的话呢没什么好说的,异步调用的话呢可以使用异步调用的回调函数,并且还可以使用返回的句柄转换为事件对象,用waitsingleforobject这个函数等待时间的完毕,但不要忘记将事件对象的计数减一。
通过符号链接打开设备
利用ZwOpenSymbolicLinkObject内核函数先得到符号连接的句柄,然后使用ZwQuerySymbolicLinkObject内核函数查找到设备名。
先初始化OBJECT_ATTRIBUTES对象,然后得到通过传递这个对象给ZwOpenSymbolicLinkObject函数得到符号链接句柄。通过设备链接句柄获得设备名称,然后通过这个设备名称初始化一个新的OBJECT_ATTRIBUTES对象,然后通过这个新的对象来CreateFile,下面的操作就一样了。
通过设备指针调用其他驱动程序
ZwCreateFile获得设备的设备句柄,而如果想获得涉设备的文件对象指针用到IoGetDeviceObjectPointer:
- 设备名
- 打开权限
- 返回的设备相关的文件对象指针
- 返回的设备对象指针
这个函数会让设备对象加一,所以我们要在使用完之后调用ObDereferenceObject,如果我们不减一,那么在下一次调用IoGetDeviceObjectPointer的时候并不是真的生成一个新的对象,而是将之前的那个对象的内核计数加一返回给你。
内核计数加一的时候会调用IRP_MJ_CREATE,计数减到0会调用IRP_MJ_CLOSE,所以我们完全可以使用上面操作内核计数的函数代替Creat和close。
可以通过loBuildSynchronousFsdRequest和loBuildAsynchronousFsdRcquest两个内核函数创建IRP,它们分别用来创建同步类型的IRP和创建异步类型的IRP。这两个内核函
数可以创建 IRP_MJ_PNP、 IRP_MJ_READ、 IRP_MJ_WRITE、 IRP_MJ_FLUSH BUFFERS和 IRP_MJ_SHUTDOWN类型的IRP。
可以通过loBuildDeviceloControlRequest内核函数创建IRP_MJ_INTERNAL_DEVICE_CONTROL和IRP_MJ_DEVICE_CONTROL两个类型的IRP,这个内核函数只能创建同步类型的IRP。
另外,还可以使用 IoAllocatelrp内核函数,它可以创建任意类型的 IRP.
IoBuildSynchronousFsdRequest、IoBuildAsynchronousFsdRequest、IoBuildDeviceloControlRequest 这三个内核函数是属于靠近上层的内核函数。而IoAllocatelrp是比较底层的内核函数,以上三个内核函数都是通过调用 IoAllocateIrp实现的。
创建完IRP之后还要构造IO堆栈,每个设备对象都要一个设备堆栈。
最后通过IoCallDriver内核函数调用响应的驱动,这个函数会根据IRP找到响应的派遣函数。
loBuildSynchronousFsdRequest和loBuildAsynchronousFsdRcquest两个内核函数的最大区别就在于是否有时间等待。
loBuildSynchronousFsdRequest:
- IRP的类型
- 设备对象指针
- 输入输出缓冲区
- 缓冲区大小
- 偏移量
- 【同步事件】
- 操作状态
创建IRP
首先我们先通过IoGetDeviceObjectPointer函数获得设备对象,然后判断一下是否成功,不成功返回,然后初始化一个同步事件,设置一个偏移量,然后就是创建一个同步的IRP,参数就是之前写的,然后得到下一层的IO堆栈,通过IoGetNextIrpStackLocation函数得到,这个函数的唯一一个参数就是我们刚才创建好的IRP对象指针,然后我们将这个IO堆栈对象的FileObject属性设置为我们之前在IoGetDeviceObjectPointer函数中得到的文件对象指针。最后调用IoCallDriver来调用IRP的派遣函数。然后我们就等待这个IRP结束,如果是被挂起的话呢说明异步,我们需要等待在之前创建的同步事件对象,然后关闭设备(这个不要忘记)ObDereferenceObject,这个函数的参数是我们之前得到的与驱动相关的文件对象指针。
使用loBuildAsynchronousFsdRcquest的话呢与之前那个不同的主要一点是提供Event的位置,一个是在函数中提供Event对象,而这个函数的话呢是在使用IoCallDriver之前和创建IRP之后将创建的IRP的UserEvent指定为我们创建的一个Event,其他的操作都是一样的。
用IoAllocateIrp相对麻烦一些,因为需要自己制定IRP的属性,这个函数只有两个参数,第一个是IO堆栈大小,第二个是是否要分配磁盘配额。在创建IRP之前都是一样,但是在创建完IRP之后是由比较大的区别的:
- 首先我们将我们的Event时间对象传给IRP:IRP->UserEvent = &event
- 将IO的状态指针传给他:IRP->UserIosb = &status_block
- 设置IRP的线程号:IRP->Tail.Overlay.Thread = PsGetCurrentThread()
- 设置IRP的BUFFER:IRP.>AssociatedIrp.SystemBuffer = NULL
- 获得设备堆栈:PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(IRP)
- 设置IRP的编号:stack->MajorFunction = IRP_MJ_READ
- 设置IRP的子IRP号:stack->MinorFunction = IRP_MN_NORMAL;//0
- 设置文件指针,这个指针是在我们得到设备指针的时候间接获得的:stack->FileObject = FileObject
其他方法获得设备指针
使用ObReferenceObjectByName获得设备指针,这个函数是DDK并没有公开的一个函数,但是在ntoskrnl.exe
中存在,所以我们要指定原型:
- 是UNICODE的设备名
- 属性一般是:OBJ_CASE_INSENSITIVE
- 这个参数很少用到,用NULL
- 这个是获得的权限,一般是FILE_ALL_ACCESS
- 这个就是我们在商用extern设置的变量:IoDeviceObjectType
- 访问模式,内核的话呢:KernelMode
- 很少用到,一般是NULL
- 返回的对象指针
这个函数的功能强大,可以获取任何内核对象的指针,而我们之前的那个IoGetDeviceObjectPointer只能获得设备对象和文件的指针,因为这个函数也会使内核计数加一,所以最后还是要用到ObDereferenceObject使其减一。
IoGetDeviceObjectPointer的内部结构
- 用InitializeObjectAttributes函数构造OBJECT_ATTRIBUTES结构体
- 用ZwOpenFile内核函数打开设备文件对象的句柄
- 使用ObReferenceObjectByHandle函数获得这个文件对象的指针
- 用IoGetBaseFileSystemDeviceObject函数从设备文件对象指针得到设备指针
这里有个错误就是在他的注释上,我们得到的不是设备对象指针,而是设备的文件对象的指针!
代码上没啥问题。