0%

驱动调用驱动

驱动调用驱动

其实在之前的文章已经记录过了,调用驱动其实就和用户层差不多,使用CreateFile类型的函数进行调用,但是需要注意的是一下两点:

  1. 同步设备的话呢:第二个DesireAccess参数需要设置为SYNCHRONIZE,如果不是的话呢要设置为0。
  2. 同步设备的话呢:倒数第三个参数CreateOptions需要指定为FILE_SYNCHRONOUS_IO_NONALERT或者FILE_SYNCHRONOUS_IO_NALERT,异步设备的话呢不需要指定。

同步调用的话呢没什么好说的,异步调用的话呢可以使用异步调用的回调函数,并且还可以使用返回的句柄转换为事件对象,用waitsingleforobject这个函数等待时间的完毕,但不要忘记将事件对象的计数减一。

通过符号链接打开设备

利用ZwOpenSymbolicLinkObject内核函数先得到符号连接的句柄,然后使用ZwQuerySymbolicLinkObject内核函数查找到设备名。
先初始化OBJECT_ATTRIBUTES对象,然后得到通过传递这个对象给ZwOpenSymbolicLinkObject函数得到符号链接句柄。通过设备链接句柄获得设备名称,然后通过这个设备名称初始化一个新的OBJECT_ATTRIBUTES对象,然后通过这个新的对象来CreateFile,下面的操作就一样了。

通过设备指针调用其他驱动程序

ZwCreateFile获得设备的设备句柄,而如果想获得涉设备的文件对象指针用到IoGetDeviceObjectPointer:

  1. 设备名
  2. 打开权限
  3. 返回的设备相关的文件对象指针
  4. 返回的设备对象指针

这个函数会让设备对象加一,所以我们要在使用完之后调用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:

  1. IRP的类型
  2. 设备对象指针
  3. 输入输出缓冲区
  4. 缓冲区大小
  5. 偏移量
  6. 【同步事件】
  7. 操作状态

创建IRP

  1. 首先我们先通过IoGetDeviceObjectPointer函数获得设备对象,然后判断一下是否成功,不成功返回,然后初始化一个同步事件,设置一个偏移量,然后就是创建一个同步的IRP,参数就是之前写的,然后得到下一层的IO堆栈,通过IoGetNextIrpStackLocation函数得到,这个函数的唯一一个参数就是我们刚才创建好的IRP对象指针,然后我们将这个IO堆栈对象的FileObject属性设置为我们之前在IoGetDeviceObjectPointer函数中得到的文件对象指针。最后调用IoCallDriver来调用IRP的派遣函数。然后我们就等待这个IRP结束,如果是被挂起的话呢说明异步,我们需要等待在之前创建的同步事件对象,然后关闭设备(这个不要忘记)ObDereferenceObject,这个函数的参数是我们之前得到的与驱动相关的文件对象指针。

  2. 使用loBuildAsynchronousFsdRcquest的话呢与之前那个不同的主要一点是提供Event的位置,一个是在函数中提供Event对象,而这个函数的话呢是在使用IoCallDriver之前和创建IRP之后将创建的IRP的UserEvent指定为我们创建的一个Event,其他的操作都是一样的。

  3. 用IoAllocateIrp相对麻烦一些,因为需要自己制定IRP的属性,这个函数只有两个参数,第一个是IO堆栈大小,第二个是是否要分配磁盘配额。在创建IRP之前都是一样,但是在创建完IRP之后是由比较大的区别的:

    1. 首先我们将我们的Event时间对象传给IRP:IRP->UserEvent = &event
    2. 将IO的状态指针传给他:IRP->UserIosb = &status_block
    3. 设置IRP的线程号:IRP->Tail.Overlay.Thread = PsGetCurrentThread()
    4. 设置IRP的BUFFER:IRP.>AssociatedIrp.SystemBuffer = NULL
    5. 获得设备堆栈:PIO_STACK_LOCATION stack = IoGetNextIrpStackLocation(IRP)
    6. 设置IRP的编号:stack->MajorFunction = IRP_MJ_READ
    7. 设置IRP的子IRP号:stack->MinorFunction = IRP_MN_NORMAL;//0
    8. 设置文件指针,这个指针是在我们得到设备指针的时候间接获得的:stack->FileObject = FileObject

其他方法获得设备指针

使用ObReferenceObjectByName获得设备指针,这个函数是DDK并没有公开的一个函数,但是在ntoskrnl.exe中存在,所以我们要指定原型:

1
2

  1. 是UNICODE的设备名
  2. 属性一般是:OBJ_CASE_INSENSITIVE
  3. 这个参数很少用到,用NULL
  4. 这个是获得的权限,一般是FILE_ALL_ACCESS
  5. 这个就是我们在商用extern设置的变量:IoDeviceObjectType
  6. 访问模式,内核的话呢:KernelMode
  7. 很少用到,一般是NULL
  8. 返回的对象指针

这个函数的功能强大,可以获取任何内核对象的指针,而我们之前的那个IoGetDeviceObjectPointer只能获得设备对象和文件的指针,因为这个函数也会使内核计数加一,所以最后还是要用到ObDereferenceObject使其减一。

IoGetDeviceObjectPointer的内部结构

  1. 用InitializeObjectAttributes函数构造OBJECT_ATTRIBUTES结构体
  2. 用ZwOpenFile内核函数打开设备文件对象的句柄
  3. 使用ObReferenceObjectByHandle函数获得这个文件对象的指针
  4. 用IoGetBaseFileSystemDeviceObject函数从设备文件对象指针得到设备指针

3
4

这里有个错误就是在他的注释上,我们得到的不是设备对象指针,而是设备的文件对象的指针!
代码上没啥问题。