首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >64位内开发第二十一讲,内核下的驱动程序与驱动程序通讯

64位内开发第二十一讲,内核下的驱动程序与驱动程序通讯

作者头像
IBinary
发布2022-09-19 15:35:54
1.1K0
发布2022-09-19 15:35:54
举报
文章被收录于专栏:逆向技术逆向技术

目录

驱动程序调用驱动程序

一丶驱动调用驱动介绍.

1.1 驱动调用驱动介绍

驱动调用驱动.其实就是两个内核内核驱动之间的通信. 比如应用程序和驱动程序通信就算为一种通信. 应用程序可以 发送 IRP_MJ_READ 请求(ReadFile) 发送给 DrvierA程序. 然后DriverA进行相应的 IRP处理操作. 当然发送 IRP_MJ_READ请求的时候可以发送同步请求或者异步请求.这就看DriverA 如何处理这些请求了.是否支持异步.

驱动程序调用驱动程序也是一样的. 也是 DriverA 发送请求给DriverB 然后DriverB 来处理DriverA的请求. 如果 DriverB 支持异步,那么DriverA也可以进行异步读取.

1.2 驱动程序调用驱动程序流程图

如图,应用程序调用 ReadFile的时候 就会产生 IRP_MJ_READ 请求. 此时这个请求就会发送到DriverA. DriverA进行处理,处理的方式是它读取DriverB的内容(调用ZwReadFile) 此时DriverB处理DriverA的请求,并且将数据返回. DriverA得到数据之后就处理 应用程序的请求.比如将DriverB返回的数据填充到应用程序的Buffer中.并且返回.

1.3 内核通信方式

ZwReadFile ZwWriteFile ZwDeviceIoControlFile 都可以进行通信.在文件句柄一讲中.只介绍ZwReadFile方式.其它方式是一样的. 参数调用可以参考Ring3. ZwReadFile方式搞懂了.那么其它的就会了.

二丶 文件句柄形式调用驱动程序

2.1 文件句柄-同步方式

2.1.1 文件句柄形式和简介

在应用层我们访问驱动层并且进行通信的时候. 第一步就是 CreateFile打开符号链接. 返回一个文件句柄,然后使用ReadFile /WriteFile/DeviceIoControl等函数来操作这个句柄发送对应的 IRP 请求给驱动.然后驱动响应这些请求来进行处理.

所谓文件句柄形式就是如上所说. 内核层也是一样的. 不过注意的是 内核层可以使用设备名直接打开一个驱动来进行操作. 所以我们需要准备一个 DriverB驱动并将其加载.

2.1.2 文件句柄同步与异步

文件句柄方式打开设备并且进行操作.支持同步异步. 如果要使用异步.那么我们的DrvierB也要支持异步处理.

文件句柄方式的内核操作API如下:

NTSYSAPI NTSTATUS ZwCreateFile(
  [out]          PHANDLE            FileHandle,   
  [in]           ACCESS_MASK        DesiredAccess,
  [in]           POBJECT_ATTRIBUTES ObjectAttributes,
  [out]          PIO_STATUS_BLOCK   IoStatusBlock,
  [in, optional] PLARGE_INTEGER     AllocationSize,
  [in]           ULONG              FileAttributes,
  [in]           ULONG              ShareAccess,
  [in]           ULONG              CreateDisposition,
  [in]           ULONG              CreateOptions,
  [in, optional] PVOID              EaBuffer,
  [in]           ULONG              EaLength
);

如果是同步进行驱动调用驱动操作. 那么下面几点必须要注意. DesiredAccess: 必须有此 SYNCHRONIZE 标志. CreateOptions: 必须有 FILE_SYNCHRONOUS_IO_NONALERT 或者 FILE_SYNCHRONOUS_IO_NONALERT 标志

NTSYSAPI NTSTATUS ZwReadFile(
  [in]           HANDLE           FileHandle,
  [in, optional] HANDLE           Event,
  [in, optional] PIO_APC_ROUTINE  ApcRoutine,
  [in, optional] PVOID            ApcContext,
  [out]          PIO_STATUS_BLOCK IoStatusBlock,
  [out]          PVOID            Buffer,
  [in]           ULONG            Length,
  [in, optional] PLARGE_INTEGER   ByteOffset,
  [in, optional] PULONG           Key
);

其实操作DriverB 和应用层操作DriverA一样.都是利用的文件API.

2.1.3 准备DriverB驱动

首先准备一个DriverB 驱动. 并且建立符号链接与设备. 并且进行加载. DriverB可以处理 IRP_MJ_READ的请求. 比如请求后直接填充HelloWorld并且返回.

下载Winobj 查看设备是否创建成功.如下图:

可以看到DrvierB已经创建了. 其设备名字为: \Device\DriverB 这个也是我们内核层需要打开的.

DriverB 和正常驱动一样.建立符号链接.建立设备链接. 设置缓冲区读取方式 初始化派遣函数 设置卸载函数.

所以这里之贴出 IRP_MJ_READ 的读请求处理的代码.

NTSTATUS CompleteReadRequest(PDEVICE_OBJECT device, PIRP irp)
{
    UNREFERENCED_PARAMETER(irp);
    if (config.GetMySelfDevice() == device && irp != nullptr)
    {
        KdBreakPoint();
        PIO_STACK_LOCATION irp_stack = IoGetCurrentIrpStackLocation(irp);
        if (irp->MdlAddress && irp_stack)
        {
            bool is_read = false;
            PVOID pBuffer = MmGetSystemAddressForMdlSafe(irp->MdlAddress, NormalPagePriority);
            ULONG uReadLen = irp_stack->Parameters.Read.Length;
            size_t srclen = (wcslen(L"HelloWorld")+1) * 2;
            if (uReadLen > srclen)
            {
                is_read = true;
                RtlCopyMemory(pBuffer, L"HelloWorld", srclen);
            }
            if (is_read)
            {
                irp->IoStatus.Status = STATUS_SUCCESS;
                irp->IoStatus.Information = srclen;
                IoCompleteRequest(irp, IO_NO_INCREMENT);
                return STATUS_SUCCESS;
            }
            else
            {
                irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
                irp->IoStatus.Information = 0;
                IoCompleteRequest(irp, IO_NO_INCREMENT);
                return STATUS_SUCCESS;
            }
        }
    return STATUS_SUCCESS;
}

可以看到我是用的 DO_DIRECT_IO 方式,并不是缓冲区方式. 这一点在创建设备并且设置缓冲区方式的时候设置的. 不了解 DO_DIRECT_IO 方式的 请看下其它资料.

2.1.4 DriverA的驱动代码-同步方式

DriverA就很简单了.直接 ZwCreateFile打开DriverB的设备.然后ZwReadFile读取数据即可.

这样触发的IRP_MJ_READ请求就会被DriverB捕获.然后DriverB就填充HelloWorld返回.DriverA自然就得到了 HelloWorld字符串了.

在上面我们 建立一个标准的驱动通讯图. 也就是1.2小节. 那是由 应用层往下发请求来操作DriverA的.然后DriverA 读取DriverB. 而在这里我们为了简单.直接在DrierA里面就读取DriverB. 读取的数据由 DbgPrint函数打印. 可以不使用应用层来通知我们.

代码如下:

void CallDriverMethod1()
{
    /*
    同步调用Driver B
    1.winobj 确定 DriverB的设备连接名. 一般都是 \\device\\DriverB
    2.CreateFile指定同步方式打开设备 DesiredAccess:SYNCHRONIZE CreateOptions:
    FILE_SYNCHRONOUS_IO_NONALERT or 
    FILE_SYNCHRONOUS_IO_ALERT
    3.调用ReadFile发送 IRP_MJ_READ 请求即可
    */
    HANDLE device_handle = NULL;
    OBJECT_ATTRIBUTES driver_name = { 0 };
    IO_STATUS_BLOCK status_block = { 0 };
    UNICODE_STRING uc_driver_name = RTL_CONSTANT_STRING(L"\\device\\DriverB");
    InitializeObjectAttributes(&driver_name, &uc_driver_name, OBJ_CASE_INSENSITIVE, NULL, NULL);
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    TCHAR read_buf[100] = { 0 };

    do
    {

        KdBreakPoint();
        status = ZwCreateFile(&device_handle,
            FILE_GENERIC_READ | SYNCHRONIZE, //或者直接使用 FILE_GENERIC_READ 
            &driver_name,
            &status_block,
            NULL,
            FILE_ATTRIBUTE_NORMAL,
            FILE_SHARE_READ,
            FILE_OPEN_IF,
            FILE_SYNCHRONOUS_IO_NONALERT,   //必须带有 FILE_SYNCHRONOUS_IO_ALERT 
                                            // 或 FILE_SYNCHRONOUS_IO_NONALERT
            NULL,
            0);
        if (!NT_SUCCESS(status))
            break;

        status = ZwReadFile(
            device_handle,   //文件句柄
            nullptr,
            nullptr,
            nullptr,
            &status_block,   //读取的字节数 以及返回值
            read_buf,        //缓冲区
            sizeof(read_buf) / sizeof(read_buf[0]),//读取的缓冲区长度
            0,               //读取的偏移
            nullptr);
        if (!NT_SUCCESS(status))
            break;

        DbgPrint("[A] --> read value is %ws \r\n" , read_buf);
    } while (false);

    if (device_handle != nullptr)
    {
        ZwClose(device_handle);
        device_handle = nullptr;
    }
    return;
}
2.1.5 效果

2.2 文件句柄-第一种异步方式

2.2.1 异步方式简介

学过IRP同步与异步方式.那么我们就知道异步方式更为方便也更为贴合系统底层设计. 但是偏一点复杂. 在之前 IRP同步与异步篇章中我们有讲到,应用层如何进行异步处理的. 分了两种方式.

回顾第一种 ReadFile 方式. 提供一个 OVERLAPPED 和初始化里面的一个事件. 打开设备的时候指定为异步方式. 当ReadFile调用完毕之后 会触发 IRP_MJ_READ请求. 请求发送给DriverA. DriverA挂起IRP的请求. 并记录当前挂起的IRP,然后在IRP_MJ_CLEARN请求中来完成挂起的IRP请求. 此时因为操作系统自身的机制.会设置ring3初始化的事件. 然后代码回退到ring3这边. ring3这边就等待这个事件.一旦被设置就会代表请求执行完成了. 此时就可以继续往下操作了.

第二种方式跟第一种方式相似. 唯一不同的就是 调用 ReadFileEx函数 而这个函数可以提供一个回调函数. 当内核异步处理完毕之后会调用我们的回调函数. 而我们也不需要设置 OVERLAPPED 事件了.

2.2.2 内核下的异步处理方式第一种

内核下其实和应用层的异步处理方式一样. 借用了 ReadFileEx的回调函数方式,同时又借用了第一种方式的事件形式.

简单来说就是 我们需要提供一个回调函数.并初始化一个事件. 当DriverB异步处理完成之后就会调用我们提供的回调函数. 但是我们需要在回调函数里面设置这个事件. 设置之后我们 读(ZwReadFile) 下面就等待这个事件.

其实也是利用了事件和回调函数的机制.

2.2.3 准备异步处理驱动DriverB

DriverB 驱动挂起IRP请求. 并建立一个 TimerDpc定时器.三秒执行一次Irp请求.

这点和 IRP 同步与异步一篇中讲解的 IRP超时处理是一样的,唯一不同的就是超时处理是取消IRP. 而我们这里是完成IRP.

其中这里只提供DriverB的两个核心处理函数. 至于 初始化timer DPC 以及停止计时器 请移步 IRP 同步与异步 一篇中的 IRP超时处理小节.

DriverB 读请求的处理.

NTSTATUS CompleteReadRequest(PDEVICE_OBJECT device, PIRP irp)
{
    UNREFERENCED_PARAMETER(irp);
    if (config.GetMySelfDevice() == device && irp != nullptr)
    {
        //挂起IRP
        IoMarkIrpPending(irp);
        //记录此irp
        PDEVICE_EXTENSION device_ex = nullptr;
        device_ex = (PDEVICE_EXTENSION)device->DeviceExtension;
        if (device_ex)
        {
            device_ex->irp_ptr = irp;
        }
        else
        {
            device_ex->irp_ptr = nullptr;
        }
        //设置超时处理
        LARGE_INTEGER timeout = { 0 };
        timeout.QuadPart = -10 * 3000000;
        KeSetTimer(&device_ex->timer,
            timeout,
            &device_ex->dpc);
        //返回Pending状态
        return STATUS_PENDING;
    }


    return STATUS_SUCCESS;
}

返回 pending状态,并且在设备扩展中记录此IRP.

延迟处理函数.

//延迟处理
VOID DelayDpc(
    _In_ struct _KDPC* Dpc,
    _In_opt_ PVOID DeferredContext,
    _In_opt_ PVOID SystemArgument1,
    _In_opt_ PVOID SystemArgument2
)
{
    UNREFERENCED_PARAMETER(Dpc);
    UNREFERENCED_PARAMETER(SystemArgument1);
    UNREFERENCED_PARAMETER(SystemArgument2);

    /*
    1.通过context得到当前的DeviceObje对象
    2.通过DeviceObj对象得到当前的设备扩展
    3.通过设备扩展找到记录的IRP并且取消IRP
    */
    KdBreakPoint();
    if (DeferredContext != nullptr)
    {
        PDEVICE_OBJECT device = nullptr;
        PDEVICE_EXTENSION device_ex = nullptr;
        device = (PDEVICE_OBJECT)DeferredContext;
        if (device->DeviceExtension != nullptr)
        {
            device_ex = (PDEVICE_EXTENSION)device->DeviceExtension;
            PIRP irp = device_ex->irp_ptr;
            //完成IRP
            PVOID pBuffer = MmGetSystemAddressForMdlSafe(irp->MdlAddress,NormalPagePriority);
            RtlStringCbCopyNW((NTSTRSAFE_PWSTR)pBuffer, 100, L"HelloWorld", (wcslen(L"HelloWorld") + 1) * 2);
            irp->IoStatus.Status = STATUS_SUCCESS;
            irp->IoStatus.Information = (wcslen(L"HelloWorld")+1)*2;
            IoCompleteRequest(irp, IO_NO_INCREMENT);
            DbgPrint("[B]-->延迟完成Irp请求\r\n");
            //讲device_ex记录的 Irp置空.
            device_ex->irp_ptr = nullptr;
        }
        else
        {
            DbgPrint("Irp操作函数执行中判断的附加数据为空\r\n");
        }

    }
    else
    {
        DbgPrint("参数为空\r\n");
    }
}
2.2.4 Driver A驱动代码以及注意事项

要进行异步读取,首先我们之前所说的 ZwCreateFile 参数中就不能带有 同步的标志了

其次要进行异步读取的时候 ZwReadFile中要读取的偏移量必须给定 否则函数会返回 STATUS_INVALID_PARAMETER 也就是错误码: ((NTSTATUS)0xC000000DL)

其次我们因为是异步读取,当底层驱动处理完毕之后会调用我们的回调函数. 所以我们需要提供一个回调函数. 我们还需要提供一个事件. 在我们回调函数里面设置事件. 这样就能通过事件进行通讯了.

代码如下:

//回调函数
VOID NTAPI Complete (
    _In_ PVOID ApcContext,
    _In_ PIO_STATUS_BLOCK IoStatusBlock,
    _In_ ULONG Reserved
    )
{
    UNREFERENCED_PARAMETER(Reserved);
    KdBreakPoint();
     DbgPrint("[A]--> 异步函数被执行\r\n");
     DbgPrint("[A]--->完成函数->读取的字节数 = %d \r\n", IoStatusBlock->Information);
     KeSetEvent((PKEVENT)ApcContext, IO_NO_INCREMENT, FALSE);
}
//异步调用
void CallDriverMethod2()
{
    /*
    异步调用Driver B
    1.winobj 确定 DriverB的设备连接名. 一般都是 \\device\\DriverB
    2.CreateFile 异步打开 DesiredAccess:不能带有SYNCHRONIZE 
    CreateOptions:不能带有 FILE_SYNCHRONOUS_IO_NONALERT or FILE_SYNCHRONOUS_IO_ALERT
    3.初始化事件 设置回调函数 调用ReadFile发送 IRP_MJ_READ 请求 
    */
    HANDLE device_handle = NULL;
    OBJECT_ATTRIBUTES driver_name = { 0 };
    IO_STATUS_BLOCK status_block = { 0 };
    UNICODE_STRING uc_driver_name = RTL_CONSTANT_STRING(L"\\device\\DriverB");
    InitializeObjectAttributes(&driver_name, &uc_driver_name, OBJ_CASE_INSENSITIVE, NULL, NULL);
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    TCHAR read_buf[100] = { 0 };

    do
    {

        KdBreakPoint();
        status = ZwCreateFile(&device_handle,
            FILE_READ_ATTRIBUTES,
            &driver_name,
            &status_block,
            NULL,
            FILE_ATTRIBUTE_NORMAL,
            FILE_SHARE_READ,
            FILE_OPEN_IF,
            0,
            NULL,
            0);
        if (!NT_SUCCESS(status))
            break;
        KEVENT event = { 0 }; //初始化一个自动无信号的事件
        KeInitializeEvent(&event, SynchronizationEvent, FALSE);
        LARGE_INTEGER offset = { 0 };
        status = ZwReadFile(
            device_handle,   //文件句柄
            nullptr,
            Complete,         //设置一个完成回调
            &event,          //给完成回调传一个event句柄.context
            &status_block,   //读取的字节数 以及返回值
            read_buf,        //缓冲区
            sizeof(read_buf) / sizeof(read_buf[0]),//读取的缓冲区长度
            &offset,               //读取的偏移 比如给定.
            nullptr);
        if (status == STATUS_PENDING)
        {
            DbgPrint("[A]---> 底层驱动正在进行异步操作\r\n");
            //采用无限等待方式进行等待
            KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, 0); 
            DbgPrint("[A]---> 数据读取完成\r\n");
            DbgPrint("[A] --> read value is %ws \r\n", read_buf);
        }

    } while (false);

    if (device_handle != nullptr)
    {
        ZwClose(device_handle);
        device_handle = nullptr;
    }
    return;
}
2.2.5 效果

2.3 文件句柄-第二种异步方式

2.3.1结构与简介

第二种方式是利用了 文件句柄 每打开一个设备,都会伴随着一个FILE_OBJCE,这个结构里面记录着文件对象,而这个对象里面有个事件域. 我们可以利用这个事件域来进行异步. 当DriverB执行完毕之后则会设置这个事件域.

其中结构如下,简单了解.

kd> dt _FILE_OBJECT
nt!_FILE_OBJECT
   +0x000 Type             : Int2B
   +0x002 Size             : Int2B
   +0x008 DeviceObject     : Ptr64 _DEVICE_OBJECT //记录着设备
   +0x010 Vpb              : Ptr64 _VPB
   +0x018 FsContext        : Ptr64 Void
   +0x020 FsContext2       : Ptr64 Void
   +0x028 SectionObjectPointer : Ptr64 _SECTION_OBJECT_POINTERS
   +0x030 PrivateCacheMap  : Ptr64 Void
   +0x038 FinalStatus      : Int4B
   +0x040 RelatedFileObject : Ptr64 _FILE_OBJECT
   +0x048 LockOperation    : UChar
   +0x049 DeletePending    : UChar
   +0x04a ReadAccess       : UChar
   +0x04b WriteAccess      : UChar
   +0x04c DeleteAccess     : UChar
   +0x04d SharedRead       : UChar
   +0x04e SharedWrite      : UChar
   +0x04f SharedDelete     : UChar
   +0x050 Flags            : Uint4B
   +0x058 FileName         : _UNICODE_STRING //记录了文件的名字
   +0x068 CurrentByteOffset : _LARGE_INTEGER
   +0x070 Waiters          : Uint4B
   +0x074 Busy             : Uint4B
   +0x078 LastLock         : Ptr64 Void
   +0x080 Lock             : _KEVENT
   +0x098 Event            : _KEVENT    //此事件
   +0x0b0 CompletionContext : Ptr64 _IO_COMPLETION_CONTEXT
   +0x0b8 IrpListLock      : Uint8B
   +0x0c0 IrpList          : _LIST_ENTRY
   +0x0d0 FileObjectExtension : Ptr64 Void
2.3.2 API编程获取方式

要通过文件句柄获取FILE_OBJECT 文件对象,那么我们离不开下面几个API.

NTSTATUS ObReferenceObjectByHandle(
  [in]            HANDLE                     Handle,  //句柄
  [in]            ACCESS_MASK                DesiredAccess,//权限
  [in, optional]  POBJECT_TYPE               ObjectType,//要获取的对象类型
  [in]            KPROCESSOR_MODE            AccessMode,//用户还是内核模式
  [out]           PVOID                      *Object,//成功之后返回的对象类型的指针
  [out, optional] POBJECT_HANDLE_INFORMATION HandleInformation//句柄信息
);

此API就是通过句柄查找对应的对象结构. 比如你是线程句柄那你就可以通过线程句柄查找线程对象, 进程句柄那么就可以查找进程对象. 文件句柄就可以查找文件对象. 等等.

它支持的类型如下,而我们只是使用到了文件句柄.

ObjectType 参数

对象指针类型

*ExEventObjectType

PKEVENT

*ExSemaphoreObjectType

PKSEMAPHORE

*IoFileObjectType

PFILE_OBJECT

*PsProcessType

PEPROCESS 或 PKPROCESS

*PsThreadType

PETHREAD 或 PKTHREAD

*SeTokenObjectType

PACCESS_TOKEN

*TmEnlistmentObjectType

PKENLISTMENT

*TmResourceManagerObjectType

2013 年 1 月 3 日

*TmTransactionManagerObjectType

PKTM

*TmTransactionObjectType

PKTRANSACTION

注意当获取对象成功之后其对象的引用计数会+1. 所以我们还需要配合下面的函数来减少引用计数.

void ObDereferenceObject(
  [in]  a    //指向对象的指针,减少引用计数.
);
2.3.3 DriverA异步处理2代码实现.
//异步调用2
void CallDriverMethod3()
{
    /*
    异步调用Driver B
    1.winobj 确定 DriverB的设备连接名. 一般都是 \\device\\DriverB
    2.CreateFile 异步打开 DesiredAccess:不能带有SYNCHRONIZE
    CreateOptions:不能带有 FILE_SYNCHRONOUS_IO_NONALERT or FILE_SYNCHRONOUS_IO_ALERT
    3.初始化事件 设置回调函数 调用ReadFile发送 IRP_MJ_READ 请求
    */
    HANDLE device_handle = NULL;
    OBJECT_ATTRIBUTES driver_name = { 0 };
    IO_STATUS_BLOCK status_block = { 0 };
    UNICODE_STRING uc_driver_name = RTL_CONSTANT_STRING(L"\\device\\DriverB");
    InitializeObjectAttributes(&driver_name, &uc_driver_name, OBJ_CASE_INSENSITIVE, NULL, NULL);
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    TCHAR read_buf[100] = { 0 };

    do
    {

        KdBreakPoint();
        status = ZwCreateFile(&device_handle,
            FILE_READ_ATTRIBUTES,
            &driver_name,
            &status_block,
            NULL,
            FILE_ATTRIBUTE_NORMAL,
            FILE_SHARE_READ,
            FILE_OPEN_IF,
            0,
            NULL,
            0);
        if (!NT_SUCCESS(status))
            break;

        LARGE_INTEGER offset = { 0 };
        status = ZwReadFile(
            device_handle,   //文件句柄
            nullptr,
            nullptr,         
            nullptr,          
            &status_block,   //读取的字节数 以及返回值
            read_buf,        //缓冲区
            sizeof(read_buf) / sizeof(read_buf[0]),//读取的缓冲区长度
            &offset,               //读取的偏移
            nullptr);
        if (status == STATUS_PENDING)
        {
            DbgPrint("[A]---> 底层驱动正在进行异步操作\r\n");
            PFILE_OBJECT fileobj = nullptr;
            status = ObReferenceObjectByHandle(device_handle, EVENT_MODIFY_STATE, *IoFileObjectType, KernelMode,(PVOID*)&fileobj, nullptr);
            if (NT_SUCCESS(status))
            {
                DbgPrint("[A]---> 获取文件对象成功,检查事件域\r\n");
                //采用无限等待方式进行等待
                KeWaitForSingleObject(&fileobj->Event, Executive, KernelMode, FALSE, 0);
                DbgPrint("[A]---> 数据读取完成\r\n");
                DbgPrint("[A] --> read value is %ws \r\n", read_buf);
                ObDereferenceObject(fileobj); //减少引用
            }
            else
            {
                DbgPrint("[A]---> 文件对象等待失败\r\n");
            }

        }

    } while (false);

    if (device_handle != nullptr)
    {
        ZwClose(device_handle);
        device_handle = nullptr;
    }
    return;
}
2.3.4 效果

3.1文件句柄-符号链接方式

3.1.1 符号链接方式简介

在进行驱动通信的时候,有时候并不能通过设备名来打开设备. 这点尤其在WDM中常见.

所以我们可以通过查找符号链接里面的设备名来打开设备从而进行通信.

主要使用了两个API.

打开符号链接对象,通过符号链接名字.

NTSYSAPI NTSTATUS ZwOpenSymbolicLinkObject(
  [out] PHANDLE            LinkHandle,  //打开成功之后传出的句柄
  [in]  ACCESS_MASK        DesiredAccess,//权限
  [in]  POBJECT_ATTRIBUTES ObjectAttributes//要打开的符号链接
);

通过符号链接名字查找设备名字

NTSYSAPI NTSTATUS ZwQuerySymbolicLinkObject(
  [in]            HANDLE          LinkHandle,  //符号链接句柄
  [in, out]       PUNICODE_STRING LinkTarget,  //传出的找到的设备名字,必须外面申请
  [out, optional] PULONG          ReturnedLength//传出的返回值
);

所以下面只是用这两个API进行 设备的查找.代码也就是填充这两个API所需要的参数.

至于同步异步等 跟文件句柄方式一样.

注意: 关闭句柄的时候 请用 ZwClose .内核下要注意资源的释放.

3.1.2 Driver A代码演示
void CallDriverMethod4()
{
    /*
    异步调用Driver B
    1.winobj 确定 DriverB的设备连接名. 一般都是 \\device\\DriverB
    2.CreateFile 异步打开 DesiredAccess:不能带有SYNCHRONIZE
    CreateOptions:不能带有 FILE_SYNCHRONOUS_IO_NONALERT or FILE_SYNCHRONOUS_IO_ALERT
    3.初始化事件 设置回调函数 调用ReadFile发送 IRP_MJ_READ 请求
    */
    HANDLE device_handle = NULL;
    OBJECT_ATTRIBUTES symbolic_name = { 0 };
    OBJECT_ATTRIBUTES device_name_attribute = { 0 };
    IO_STATUS_BLOCK status_block = { 0 };
    UNICODE_STRING uc_symbolic_name = RTL_CONSTANT_STRING(L"\\??\\DriverB");
    InitializeObjectAttributes(&symbolic_name, &uc_symbolic_name, OBJ_CASE_INSENSITIVE, NULL, NULL);
    NTSTATUS status = STATUS_UNSUCCESSFUL;
    TCHAR read_buf[100] = { 0 };

    HANDLE link_handle = nullptr;

    UNICODE_STRING uc_device_name = { 0 };
    ULONG device_name_real_size = 100;
    do
    {

        KdBreakPoint();
        status = ZwOpenSymbolicLinkObject(&link_handle, FILE_ALL_ACCESS, &symbolic_name);
        if (!NT_SUCCESS(status))
            break;
        uc_device_name.Buffer = (PWCH)ExAllocatePoolWithTag(NonPagedPool, device_name_real_size, (ULONG)0);
        if (uc_device_name.Buffer == nullptr)
        {
            break;
        }
        uc_device_name.Length = (USHORT)device_name_real_size;
        uc_device_name.MaximumLength = (USHORT)device_name_real_size;
        status = ZwQuerySymbolicLinkObject(link_handle, &uc_device_name, &device_name_real_size);
        if (!NT_SUCCESS(status))
            break;
        DbgPrint("通过符号链接名查找设备成功\r\n");
        DbgPrint("设备名字 = %wZ\r\n",&uc_device_name);
        InitializeObjectAttributes(&device_name_attribute, &uc_device_name, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, nullptr, nullptr);
        status = ZwCreateFile(&device_handle,
            FILE_READ_ATTRIBUTES,
            &device_name_attribute,
            &status_block,
            NULL,
            FILE_ATTRIBUTE_NORMAL,
            FILE_SHARE_READ,
            FILE_OPEN_IF,
            0,
            NULL,
            0);
        if (!NT_SUCCESS(status))
            break;


        LARGE_INTEGER offset = { 0 };
        status = ZwReadFile(
            device_handle,   //文件句柄
            nullptr,
            nullptr,
            nullptr,
            &status_block,   //读取的字节数 以及返回值
            read_buf,        //缓冲区
            sizeof(read_buf) / sizeof(read_buf[0]),//读取的缓冲区长度
            &offset,               //读取的偏移
            nullptr);
        if (status == STATUS_PENDING)
        {
            DbgPrint("[A]---> 底层驱动正在进行异步操作\r\n");
            PFILE_OBJECT fileobj = nullptr;
            status = ObReferenceObjectByHandle(device_handle, EVENT_MODIFY_STATE, *IoFileObjectType, KernelMode, (PVOID*)&fileobj, nullptr);
            if (NT_SUCCESS(status))
            {
                DbgPrint("[A]---> 获取文件对象成功,检查事件域\r\n");
                //采用无限等待方式进行等待
                KeWaitForSingleObject(&fileobj->Event, Executive, KernelMode, FALSE, 0);
                DbgPrint("[A]---> 数据读取完成\r\n");
                DbgPrint("[A] --> read value is %ws \r\n", read_buf);
                ObDereferenceObject(fileobj); //减少引用
            }
            else
            {
                DbgPrint("[A]---> 文件对象等待失败\r\n");
            }

        }

    } while (false);

    if (uc_device_name.Buffer != nullptr)
    {
        ExFreePoolWithTag(uc_device_name.Buffer, 0);
        uc_device_name.Buffer = 0;
        uc_device_name.Length = 0;
        uc_device_name.MaximumLength = 0;
    }
    if (link_handle != nullptr)
    {
        ZwClose(link_handle);
        link_handle = nullptr;
    }
    if (device_handle != nullptr)
    {
        ZwClose(device_handle);
        device_handle = nullptr;
    }
    return;
}
3.1.3 效果演示.

三丶高级驱动程序调用IRP方式

3.1 设备调用方式-同步调用

3.1.1 IRP方式调用简介

所谓IRP方式就是自己申请IRP 然后发送IRP请求去调用DriverB程序

也就是说让我们自己实现 ZwCreateFile ZwReadFile (ZwWriteFile ZwDeviceIoControFile) 等API.

ZwCreateFile被调用的时候,内部会产生 IRP_MJ_CREATE 请求 而ZwReadFile则会产生一个IRP_MJ_READ的请求. 并且将其发送给DriverB的派遣函数中.

所以我们就要模拟它们的调用

3.1.2 API简介

模拟它们的调用就需要一下几个API了. 如下:

1.通过设备名获取设备结构以及文件结构

NTSTATUS IoGetDeviceObjectPointer(
  [in]  PUNICODE_STRING ObjectName,
  [in]  ACCESS_MASK     DesiredAccess,
  [out] PFILE_OBJECT    *FileObject,
  [out] PDEVICE_OBJECT  *DeviceObject
);

2.手动创建IRP请求.

  • 创建同步请求IRP
__drv_aliasesMem PIRP IoBuildSynchronousFsdRequest(
  [in]           ULONG            MajorFunction,  //功能号
  [in]           PDEVICE_OBJECT   DeviceObject,   //要发送的给的设备
  [in, out]      PVOID            Buffer,         //对于功能号IRP_MJ_READ 则戴代表输入缓冲区
  [in, optional] ULONG            Length,         //长度
  [in, optional] PLARGE_INTEGER   StartingOffset,//读取的偏移
  [in]           PKEVENT          Event,         //事件域
  [out]          PIO_STATUS_BLOCK IoStatusBlock  //状态
);

其实你就可以把这个函数看作是 ZwReadFile 或者 ZwWriteFile 此函数的功能号只支持

IRP_MJ_READ IRP_MJ_WRITE IRP_MJ_PNP IRP_MJ_FLUSH_BUFFERS IRP_MJ_SHUTDOWN

通过这些功能号也就知道变相的等于一个函数顶替了几个函数.

值得注意的是这个函数是创建同步类型的IRP 如何保证同步,那就要用到这个函数中的事件域的参数.

3.获取下一层IO堆栈

__drv_aliasesMem PIO_STACK_LOCATION IoGetNextIrpStackLocation(
  [in] PIRP Irp
);

通过IRP返回他的下一层的堆栈.因为要模拟调用.所以我们要必须填写IRP的结构. 好在使用

IoBuildSynchronousFsdRequest 函数的时候我们并不需要填写很多. 而是由这个函数填写好了. 我们只需要填写必要的即可.

4.发送IRP请求

其实这个是核心

NTSTATUS IofCallDriver(
  PDEVICE_OBJECT        DeviceObject, //要给那个设备发送
  __drv_aliasesMem PIRP Irp           //要发送的IRP
);

在驱动中我们是使用的宏.这也是为了兼容各个版本. 参数参考上面的

#define IoCallDriver(a,b)   \
        IofCallDriver(a,b)
);
3.1.3 模拟调用核心思想

首先模拟调用就是 通过获取设备指针.和文件结构对象. 然后申请对应的IRP事件. 最后设置一下IRP. 然后进行IoCallDriver调用.

总结一下,分为如下几个步骤.

  • 1.IoGetDeviceObjectPointer通过设备名获取设备对象指针和文件结构对象 设备名--->如果设备名不好找则可以参考上面说的通过符号链接来查找 除了调用IoGetDeviceObjectPointer 也可以调用ZwCreateFile. 只不过我们获取的是一个句柄,我们要根据这个句柄来使用 ObReferenceByHandle获取设备结构和文件结构对象.
  • 2.申请对应的IRP 这个小节是申请同步类型IRP.所以使用 IoBuildSynchronousFsdRequest
  • 3.设置下一层堆栈的文件对象 通过 IoGetNextIrpStackLocation 获取下一层堆栈,然后将堆栈中的文件对象结构的域设置为第一步获取出来的文件对象结构
  • 4.调用IoCallDriver发送IRP请求.
  • 资源释放,因为IoGetDeviceObjectPointer会对对象进行引用计数+1(第一次为打开以后就是引用计数+1) 所以我们必须释放对象的引用.
3.1.4 DriverA的核心代码

DriverB还是一直在使用的支持异步挂起的驱动.核心在于DriverA的编写.

代码如下:

//直接IRP调用-同步方式
void CallDriverMethod5()
{



    NTSTATUS status = STATUS_UNSUCCESSFUL;
    IO_STATUS_BLOCK status_block = { 0 };
    TCHAR read_buf[100] = { 0 };
    PFILE_OBJECT file_object;
    PDEVICE_OBJECT device_obj;
    do
    {

        KdBreakPoint();
        //1.构建设备名字
        UNICODE_STRING uc_device_name = RTL_CONSTANT_STRING(L"\\device\\DriverB");
        //2.根据设备名字获取实际的设备对象和文件对象,等价于直接调用
        //CreateFile(返回的句柄可以通过句柄找对象方式获得设备对象和文件对象)
        status = IoGetDeviceObjectPointer(&uc_device_name, FILE_ALL_ACCESS, &file_object, &device_obj);
        if (!NT_SUCCESS(status))
            break;
        //3.申请 IRP接口 可以生成 IRP_MJ_READ 请求
        LARGE_INTEGER offset = { 0 };
        KEVENT event = { 0 };
        KeInitializeEvent(&event, NotificationEvent, FALSE);
        PIRP irp = IoBuildSynchronousFsdRequest(IRP_MJ_READ, device_obj, read_buf, 100, &offset, &event, &status_block);
        PIO_STACK_LOCATION irp_stack = IoGetNextIrpStackLocation(irp);
        irp_stack->FileObject = file_object;
        status =  IoCallDriver(device_obj, irp);

        if (status == STATUS_PENDING)
        {

                DbgPrint("[A]---> 获取文件对象成功,检查事件\r\n");
                //采用无限等待方式进行等待
                KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, 0);
                DbgPrint("[A]---> 数据读取完成\r\n");
                DbgPrint("[A] --> read value is %ws \r\n", read_buf);
        }
    } while (false);
    ObDereferenceObject(file_object); //减少引用
    ObDereferenceObject(device_obj);
    return;
}

在这里生成的功能号是 IRP_MJ_READ 所以变相的是相当于我们的 IoBuildSynchronousFsdRequest 函数是 ZwReadFile.

最后程序直接调用 IoCallDriver进行了发送.

3.1.5 效果

3.2 设备调用方式-异步方式

3.2.1 异步IRP申请说明

其中对于API来说. 在同步调用里面都已经说了. 异步调用方式就是申请IRP的方式的不同.采用的API不同.

API如下:

__drv_aliasesMem PIRP IoBuildAsynchronousFsdRequest(
  [in]           ULONG            MajorFunction,
  [in]           PDEVICE_OBJECT   DeviceObject,
  [in, out]      PVOID            Buffer,
  [in, optional] ULONG            Length,
  [in, optional] PLARGE_INTEGER   StartingOffset,
  [in, optional] PIO_STATUS_BLOCK IoStatusBlock
);

唯一不同的就是没有事件域了.对于 IoBuildAsynchronousFsdRequest 创建的IRP请求,当请求结束的时候,操作系统不会在进行事件通知了. 不过我们想要接受到事件的通知. 那么就要使用 IRP->UserEvent 操作系统会检查这个域是否为空,如果不是空则设置. 所以这个地方当我们的同步点使用. 当请求结束的时候(DriverB --> IoCompleteRequest则会设置这个域) 则会被设置.

3.2.2 异步IRP代码演示
void CallDriverMethod6()
{



    NTSTATUS status = STATUS_UNSUCCESSFUL;
    IO_STATUS_BLOCK status_block = { 0 };
    TCHAR read_buf[100] = { 0 };
    PFILE_OBJECT file_object;
    PDEVICE_OBJECT device_obj;
    do
    {

        KdBreakPoint();
        //1.构建设备名字
        UNICODE_STRING uc_device_name = RTL_CONSTANT_STRING(L"\\device\\DriverB");
        //2.根据设备名字获取实际的设备对象和文件对象,等价于直接调用
        //CreateFile(返回的句柄可以通过句柄找对象方式获得设备对象和文件对象)
        status = IoGetDeviceObjectPointer(&uc_device_name, FILE_ALL_ACCESS, &file_object, &device_obj);
        if (!NT_SUCCESS(status))
            break;
        //3.申请 IRP接口 可以生成 IRP_MJ_READ 请求
        LARGE_INTEGER offset = { 0 };
        KEVENT event = { 0 };
        KeInitializeEvent(&event, SynchronizationEvent, FALSE);
        PIRP irp = IoBuildAsynchronousFsdRequest(IRP_MJ_READ, device_obj, read_buf, 100, &offset, &status_block);
        irp->UserEvent = &event;  //注意此位置,设置一个事件对象的值
        PIO_STACK_LOCATION irp_stack = IoGetNextIrpStackLocation(irp);
        irp_stack->FileObject = file_object;
        status = IoCallDriver(device_obj, irp);

        if (status == STATUS_PENDING)
        {

            DbgPrint("[A]---> 获取文件对象成功,检查事件\r\n");
            //采用无限等待方式进行等待
            KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, 0);
            DbgPrint("[A]---> 数据读取完成\r\n");
            DbgPrint("[A] --> read value is %ws \r\n", read_buf);
        }
    } while (false);
    ObDereferenceObject(file_object); //减少引用
    ObDereferenceObject(device_obj); //减少引用
    return;
}

效果同上.

未完待续

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-08-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 驱动程序调用驱动程序
    • 一丶驱动调用驱动介绍.
      • 1.1 驱动调用驱动介绍
      • 1.2 驱动程序调用驱动程序流程图
      • 1.3 内核通信方式
    • 二丶 文件句柄形式调用驱动程序
      • 2.1 文件句柄-同步方式
      • 2.2 文件句柄-第一种异步方式
      • 2.3 文件句柄-第二种异步方式
      • 3.1文件句柄-符号链接方式
    • 三丶高级驱动程序调用IRP方式
      • 3.1 设备调用方式-同步调用
      • 3.2 设备调用方式-异步方式
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档