NtDriver

image

NT驱动框架

内核层代码

入口函数DriverEntry

  • 1.初始化驱动对象和符号链接;
  • 2.创建设备对象;
  • 3.对刚创建的设备对象指定一个通信方式;
  • 4.创建符号链接
  • 5.注册分发函数
  • 6.注册卸载函数
/**
*  Copyright (c) 2022, 源代码已同步到gitee(https://gitee.com/ciscco/system_secure)
*  All rights reserved.
*
*  @file        ntdrv.c
*  @version     v0.1
*  @author      cisco(微信公众号:坚毅猿)
*  @date        2022-02-13 13:52
*
*  @brief
*  @note
*  @see         //www.greatytc.com/p/cba34b3ad6de
*/
#include <ntddk.h>
 
/// @brief      设备对象名 "\\device(固定不能变)\\自己任意取",符号链接名 "\\dosdevices(固定定不变或者可以使用"??"代替"dosdevices")\\自己任意取";
/// @note       "L"表示这里定义的是宽字节字符串,即"WCHAR *"或者"wchar_t *",但在驱动中统一使用的字符串类型是"UNICODE_STRING",所以需要调用函数"RtlInitUnicodeString(&uDeviceName, DEVICE_NAME)"来转换
///             "\\"代表"\"(是因为第一个'\'会被被识别成转义字符的标志,这是windows中表示路径的常规写法)
/// @see        DriverEntry()
/// @warning    但最好设备对象名、符号链接名和驱动名取相同名字,避免混淆。因为不清楚什么时候使用对应的名字往往会导致驱动加载出问题;
#define DEVICE_NAME L"\\device\\ntmodeldrv"
#define LINK_NAME L"\\dosdevices\\ntmodeldrv"
 
#define IOCTRL_BASE 0x800 ///< 控制码的起始值,比如有5个控制码,第一个为0x800,第二个0x801,第三个0x802 ...
/// MYIOCTRL_CODE 用来定义控制码
/// FILE_DEVICE_UNKNOWN 在DriveEntry中定义的设备对象的类型
///  METHOD_BUFFERED 通信协议,这里使用的是buffer io
///  @warning  在DriveEntry中指定设备对象的r3和r0通信方式只是用来规定Read和Write,
///            管不了DispatchIoctrl,DispatchIoctrl的通信协议是由这里的控制码METHOD_XXX来决定的
/// FILE_ANY_ACCESS 所以的权限,包含读和写
#define MYIOCTRL_CODE(i) \
    CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTRL_BASE+i, METHOD_BUFFERED,FILE_ANY_ACCESS)
/// 三个是r3和r0进行额外通信的控制码
/// @warning     要保证内核层和应用层定义一致,一般把这部分代码放在应用层和内核层共享的公共头文件中
/// @note        可以自定义更多的控制码,为了保证通信安全,也可以对控制码进行加密
///
#define CTL_HELLO MYIOCTRL_CODE(0)
#define CTL_PRINT MYIOCTRL_CODE(1)
#define CTL_BYE MYIOCTRL_CODE(2)
 
/**
*  @brief       DispatchCommon 为所有分发函数注册通用的分发函数,即对Irp请求不做任何处理直接返回,eg3:好比对一个变量初始化为0;[^13]
*  @param[in]   pObject 设备对象,在DriveEntry调用IoCreateDevice所创造设备对象,指向 DRIVER_OBJECT 结构;
*  @param[in]   pIrp    IO REQUST PASKET缩写,Irp数据包封装应用层发下的数据和命令。[^13]
*  @return      ntStatus 就不是放回给应用层的,而是返回给IO管理器的;
*  @warning     名字可以随便起,但接口定义,参数类型必须一样
*  @warning     在内核层会调用各种IO函数会有一个返回状态,这个返回状态就是存放在这里,从而放回给应用层;而在内核层也有各种函数返回值,
*               比如DriverEntry中的"return STAUS_SUCCESS",就不是放回给应用层的,而是返回给IO管理器的;
*  @see
*  @author      cisco(微信公众号:坚毅猿)
*  @date        2022-01-22 22:09
*/
NTSTATUS DispatchCommon(PDEVICE_OBJECT pObject, PIRP pIrp)
{
    UNREFERENCED_PARAMETER(pObject);
 
    pIrp->IoStatus.Status = STATUS_SUCCESS; ///< 将Irp的头部的Status设置为成功
    pIrp->IoStatus.Information = 0; ///< 传出的字节数设置为0
 
    IoCompleteRequest(pIrp, IO_NO_INCREMENT); ///< 把Irp终止掉
 
    return STATUS_SUCCESS;
}
 
/**
*  @brief       DispatchCreate 发现 DispatchCreate、DispatchCommon、DispatchClean和DispatchClose都是一样的,
*                              因为NT驱动是非常简单的单层驱动,只需要接受自己应用层创建和打开,直接返回成功即可,
*                              文件就允许被创建了,然后分配句柄表,在句柄表里面增加一项,但在过滤驱动中稍复杂,
*                              必须在DispatchCreate中监控别的进程对文件的创建和打开;
*  @param[in]   pObject 设备对象,在DriveEntry调用IoCreateDevice所创造设备对象,指向 DRIVER_OBJECT 结构;
*  @param[in]   pIrp    IO REQUST PASKET缩写,Irp数据包封装应用层发下的数据和命令。[^13]
*  @return      ntStatus 就不是放回给应用层的,而是返回给IO管理器的;
*  @warning     名字可以随便起,但接口定义,参数类型必须一样
*  @warning     在内核层会调用各种IO函数会有一个返回状态,这个返回状态就是存放在这里,从而放回给应用层;而在内核层也有各种函数返回值,
*               比如DriverEntry中的"return STAUS_SUCCESS",就不是放回给应用层的,而是返回给IO管理器的;
*  @warning     提权漏洞:一般驱动只允许自己的进程打开,不允许别的进程打开[^11]
*  @see
*  @author      cisco(微信公众号:坚毅猿)
*  @date        2022-02-15 22:09
*/
NTSTATUS DispatchCreate(PDEVICE_OBJECT pObject, PIRP pIrp)
{
    UNREFERENCED_PARAMETER(pObject);
 
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;
    /// 为了避免提权漏洞,可以在运行到这里之前拿到打开驱动的进程的pid,通过pid拿到这个进程的全路径,然后验证签名,
    /// 如果是自己的签名则允许打开,否则就做安全校验。
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
 
    return STATUS_SUCCESS;
}
 
/**
*  @brief       DispatchRead 读 是相对于应用层来说,数据流向:r0->R3;
*  @param[in]   pObject 设备对象,在DriveEntry调用IoCreateDevice所创造设备对象,指向 DRIVER_OBJECT 结构;
*  @param[in]   pIrp    IO REQUST PASKET缩写,Irp数据包封装应用层发下的数据和命令。[^13];
*  @return      ntStatus 就不是放回给应用层的,而是返回给IO管理器的;
*  @warning     名字可以随便起,但接口定义,参数类型必须一样;
*  @warning     在内核层会调用各种IO函数会有一个返回状态,这个返回状态就是存放在这里,从而放回给应用层;而在内核层也有各种函数返回值,
*               比如DriverEntry中的"return STAUS_SUCCESS",就不是放回给应用层的,而是返回给IO管理器的;
*  @warning     内存溢出可能会造成提权漏洞
*  @see
*  @author      cisco(微信公众号:坚毅猿)
*  @date        2022-02-15 22:09
*/
NTSTATUS DispatchRead(PDEVICE_OBJECT pObject, PIRP pIrp)
{
    UNREFERENCED_PARAMETER(pObject);
 
    PVOID pReadBuffer = NULL; ///< buffer io缓存的首地址
    ULONG uReadLength = 0; ///< 应用层传下来要读取的数据长度
    PIO_STACK_LOCATION pStack = NULL; ///< 当前驱动对应的栈
    ULONG uMin = 0; ///< 最小长度
    ULONG uHelloStr = 0; ///< 内核层想要上传的数据长度uHelloStr
 
    uHelloStr = (ULONG)(wcslen(L"hello world") + 1) * sizeof(WCHAR);  ///< 计算出"hello world"的长度 +1是因为'/0',即内核层想要上传的数据长度
 
    /// 第一步,拿到缓存的地址和长度[^13]
    /// 从头部拿缓存地址,因为创建设备对象之后,为设备对象指定的通信方式是buffer io,所以对应SystenBuffer
    pReadBuffer = pIrp->AssociatedIrp.SystemBuffer;
    /// 从栈上拿缓存长度
    pStack = IoGetCurrentIrpStackLocation(pIrp); ///< 拿到属于当前驱动对应的栈
    uReadLength = pStack->Parameters.Read.Length; ///< 拿到应用层传下来要读取的数据长度,是存放在联合体Parameters中结构体Read的Length成员中
 
    /// 第二步:读,写等操作[^8]
    uMin = uReadLength > uHelloStr ? uHelloStr : uReadLength; ///< 传两者中的最小值
    RtlCopyMemory(pReadBuffer, L"hello world", uMin); ///< 是将L"hello world"拷贝到pReadBuffer中去,"#define RtlCopyMemory(Destination,Source,Length) memcpy((Destination),(Source),(Length))"
    /// 为什么选择copy 应用层传下来要读取的数据长度uReadLength和内核层想要上传的数据长度uHelloStr 两者之间的最小值
    /// 情况1:应用层传下来要读取的数据长度uReadLength大于应用层上传的数据长度uHelloStr,这时候如果拷贝uReadLength的长度,
    /// 对于L"hello world"来说,紧接着L"hello world"后面的内存的数据是不确定的,如果是数据,那就拷多了浪费时间和空间,
    /// 如果这部分内存是无效的(无效就是没有对应的物理内存,只有虚拟内存,没有物理内存,就会缺页错误,也就是虚拟内存无效),
    /// 那就可能会导致系统奔溃,所以这种情况下这时候应该拷贝uHelloStr的长度,也就是两者中最小值。
    /// @todo 补地址转换这块知识
    /// 情况2:如果uReadLength小于uHelloStr,这时候如果拷贝uHelloStr的长度,这时候pReadBuffer没有足够容量容纳L"hello world",
    /// 会造成写溢出,此时如果蓝屏则是系统保护,不蓝屏则有可能已经造成提权漏洞,所以这种情况下这时候应该拷贝uHelloStr的长度,也就是两者中最小值。
    /// 综上所述, 应该拷贝两者中的最小值。
 
    /// 第三步,完成IRP
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = uMin; ///< Irp头部的IoStatus.Information设置成实际上拷贝的数据长度;[^13]
    IoCompleteRequest(pIrp, IO_NO_INCREMENT); ///< @warning     必须调用这个函数,如果不调用这个,会导致Irp不会被终止,相当于被挂起;
 
    return STATUS_SUCCESS;
}
/**
*  @brief       DispatchWrite 写 是相对于应用层来说,数据流向:r3->R0;
*  @param[in]   pObject 设备对象,在DriveEntry调用IoCreateDevice所创造设备对象,指向 DRIVER_OBJECT 结构;
*  @param[in]   pIrp    IO REQUST PASKET缩写,Irp数据包封装应用层发下的数据和命令。[^13];
*  @return      ntStatus 就不是放回给应用层的,而是返回给IO管理器的;
*  @warning     名字可以随便起,但接口定义,参数类型必须一样;
*  @warning     在内核层会调用各种IO函数会有一个返回状态,这个返回状态就是存放在这里,从而放回给应用层;
*               而在内核层也有各种函数返回值,比如DriverEntry中的"return STAUS_SUCCESS",就不是放回给应用层的,而是返回给IO管理器的;
*  @warning     内存溢出可能会造成提权漏洞
*  @see
*  @author      cisco(微信公众号:坚毅猿)
*  @date        2022-02-15 22:09
*/
NTSTATUS DispatchWrite(PDEVICE_OBJECT pObject, PIRP pIrp)
{
    UNREFERENCED_PARAMETER(pObject);
 
    PVOID pWriteBuff = NULL; ///< buffer io缓存的首地址
    ULONG uWriteLength = 0; ///< 应用层传下来要写入的数据长度
    PIO_STACK_LOCATION pStack = NULL; ///< 当前驱动对应的栈
 
    PVOID pBuffer = NULL; ///< 指向分配的内存,假设要往一个内存中写数据
 
    pWriteBuff = pIrp->AssociatedIrp.SystemBuffer;  ///< 从Irp头部拿缓存地址
 
    pStack = IoGetCurrentIrpStackLocation(pIrp); ///< 拿到属于当前驱动对应的栈
    uWriteLength = pStack->Parameters.Write.Length; ///< 拿到应用层传下来要读取的数据长度,是存放在联合体Parameters中结构体Read的Length成员中
 
    /**
 *  @brief       ExAllocatePoolWithTag windows内核中用来分配内存的函数,例程分配指定类型的池内存,并返回指向已分配块的指针。;
 *  @param[in]   PoolType 要分配的池内存的类型,内核层从堆[^15]上分配内存有两种类型(PagedPool(分页内存,比较多,对应的物理内存可以切换出去,)
 *                        和NonPagedPool(非分页内存比较少对应的物理内存是不会被切换出去的))[^9],而在应用层的malloc就没有这种讲究;
 *  @warning     PagedPool在某些时候不能使用,比如在中断请求级别较高的时候,比如DISPTCH_LEVEL,只能使用NonPagedPool的内存,
 *               因为PagedPool对应的物理内存可以切换出去,在切换出去的时候可能会引起睡眠,而在中断上下文中是不能够睡眠的,
 *               而分发函数的中断级别都处在PASSIVE_LEVEL,因此使用PagedPool没有问题[^16];
 *  @param[in]   NumberOfBytes 要分配的字节数;
 *  @param[in]   Tag 用于分配内存的池标记。将池标记指定为非零字符文本,即一到四个字符(最多4个字符),由单引号分隔(例如,'TSET'),
 *                   标记中的每个 ASCII 字符都必须是0x20(空格)到0x7E(波浪号)范围内的值。一般写模块名或者作者名,
 *                   而且是倒着写('TEST' 写成 'TSET'),因为ASCII字符也是使用8bit的整数表示,而x86上整数的存储是低位优先(小端/低尾端)[^17],
 *                   所以当使用windbg调试查看内存的时候就显示成正的'TEST'了,方便观察,(是因为无论是地位优先或者高位优先,
 *                   整数的存储是按Byte为基本单位,只有在存储的数据本身是以8bit为一个单位来编码的,读取的时候以8bit的整数倍来显示才会有这个反着输进去,正着显示出来的效果),
 *                   比如0x12345678,
 *                   1.如果以4bit当作一个单位来编码则为0x1 2 3 4 5 6 7 8 ,按低位优先存储后为 0x78 56 34 12,
 *                   以4bit为一个单位来显示则是0x7 8 5 6 3 4 1 2 与原来的0x1 2 3 4 5 6 7 8并不是反的,
 *                   以8bit为一个单位来显示则是0x78 56 34 12 与原来的0x1 2 3 4 5 6 7 8并不是反的,
 *                   以16bit为一个单位来显示则是0x7856 3412 与原来的0x1 2 3 4 5 6 7 8并不是反的,
 *                   若以32bit为一个单位来呈现则是0x78563412与原来的0x1 2 3 4 5 6 7 8并不是反的,
 *                   2.如果以8bit当作一个单位来编码则为0x12 34 56 78,按低位优先存储后为 0x78 56 34 12,
 *                   以4bit为一个单位来呈现则是0x7 8 5 6 3 4 1 2与原来的0x12 34 56 78不是反的,
 *                   以8bit为一个单位来呈现则是0x78 56 34 12与原来的0x12 34 56 78是反的,
 *                   若以16bit为一个单位来呈现则是0x7856 3412与原来的0x12 34 56 78是反的,
 *                   若以32bit为一个单位来呈现则是0x78563412与原来的0x12 34 56 78是反的,
 *                   3.如果以16bit当作一个单位来编码则为0x1234 5678,按低位优先存储后为 0x78 56 34 12,
 *                   以4bit为一个单位来呈现则是0x7 8 5 6 3 4 1 2与原来的0x1234 5678不是反的,
 *                   以8bit为一个单位来呈现则是0x78 56 34 12与原来的0x1234 5678不是反的,
 *                   以16bit为一个单位来呈现则是0x7856 3412与原来的0x1234 5678不是反的,
 *                   以32bit为一个单位来呈现则是0x78563412与原来的0x1234 5678不是反的,
 *
 *  @note        Tag的作用:一旦发生内存泄漏,在windg观察内存泄漏的时候,可以看到系统的内存迅速被吞噬,可以知道被泄露内存的Tag,进而知道内存泄漏是由哪个模块,哪个作者引起的;
 *  @return      ntStatus 成功返回0,否则为非0,如果例程成功,则它必须返回 STATUS_SUCCESS。
 *                        否则,它必须返回在 ntstatus中定义的错误状态值之一;
 *  @see         https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-exallocatepoolwithtag
 *  @author      cisco(微信公众号:坚毅猿)
 *  @date        2022-02-15 22:09
 */
    pBuffer = ExAllocatePoolWithTag(PagedPool, uWriteLength, 'TSET');
    if (pBuffer == NULL)
    {
        pIrp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES;  ///< 分配失败,设置Irp头部的错误码为资源不够
        pIrp->IoStatus.Information = 0;
        IoCompleteRequest(pIrp, IO_NO_INCREMENT);
        return STATUS_INSUFFICIENT_RESOURCES; ///< ///< 分配失败,返回错误码资源不够
    }
    /// 把分配的内存初始化为0
    memset(pBuffer, 0, uWriteLength);
 
    RtlCopyMemory(pBuffer, pWriteBuff, uWriteLength); ///< 这里pBuffer和pWriteBuff长度一样,不存在DisptachRead的问题
    /// 释放内存
    /// @todo: 为什么要设置为NULL
    ExFreePool(pBuffer);
    pBuffer = NULL;
 
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = uWriteLength; ///< Irp头部的IoStatus.Information设置成实际上拷贝的数据长度;[^13]
 
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
 
    return STATUS_SUCCESS;
}
 
/**
*  @brief       DispatchIoctrl 是用来响应应用层的DeviceIoControl,相当与READ和WRITE之外的扩展,可以自定义一些应用层和内核层之间的其他命令,
*               把这些命令发送到内核层中去,让驱动根据命令做其他操作。[^18]
*  @note        r3和r0进行通信,除了读和写之外还可以让驱动做一些事情,比如弹窗的拦截和阻止某些操作,强删文件或者强杀进程、
*               检测一次隐藏的木马,检测隐藏的端口;
*  @note        内核层和应用层有一组通信编码,来让内核层知道应用层需要我做什么事情
*  @param[in]   pObject 设备对象,在DriveEntry调用IoCreateDevice所创造设备对象,指向 DRIVER_OBJECT 结构;
*  @param[in]   pIrp    IO REQUST PASKET缩写,Irp数据包封装应用层发下的数据和命令。[^13]
*                        该结构指定注册表中驱动程序的 Parameters 项的路径;
*  @return      ntStatus 成功返回0,否则为非0,如果例程成功,则它必须返回 STATUS_SUCCESS。
*                        否则,它必须返回在 ntstatus中定义的错误状态值之一;
*  @pre
*  @see         https://docs.microsoft.com/zh-cn/windows/win32/api/ioapiset/nf-ioapiset-deviceiocontrol
*  @author      cisco(微信公众号:坚毅猿)
*  @date        2022-01-22 22:09
*/
 
NTSTATUS DispatchIoctrl(PDEVICE_OBJECT pObject, PIRP pIrp)
{
    UNREFERENCED_PARAMETER(pObject);
 
    ULONG uIoctrlCode = 0; ///< 操作的控制代码
    PVOID pInputBuff = NULL; ///< 指向输入缓冲区的指针,该缓冲区包含执行操作所需的数据
    PVOID pOutputBuff = NULL; ///< 指向输出缓冲区的指针,该缓冲区将接收操作返回的数据
 
    ULONG uInputLength = 0; ///< 输入缓冲区的大小(以字节为单位)。
    ULONG uOutputLength = 0; ///< 输出缓冲区的大小(以字节为单位)。
    PIO_STACK_LOCATION pStack = NULL; ///< 当前驱动对应的栈
    /// pInputBuff和pOutputBuff共用一块内存, 从Irp头部拿缓存地址
    pInputBuff = pOutputBuff = pIrp->AssociatedIrp.SystemBuffer;
    /// 拿到属于当前驱动对应的栈
    pStack = IoGetCurrentIrpStackLocation(pIrp);
    ///< 拿到应用层传下来要读取的数据长度,是存放在联合体Parameters中结构体DeviceIoControl的InputBufferLength成员中
    uInputLength = pStack->Parameters.DeviceIoControl.InputBufferLength;
    uOutputLength = pStack->Parameters.DeviceIoControl.OutputBufferLength;
    ///< 拿到应用层传下来的控制码,是存放在联合体Parameters中结构体DeviceIoControl的IoControlCode成员中
    uIoctrlCode = pStack->Parameters.DeviceIoControl.IoControlCode;
    /// 识别控制码
    switch (uIoctrlCode)
    {
    case CTL_HELLO:
        DbgPrint("Hello iocontrol\n");
        break;
    case CTL_PRINT:
        DbgPrint("%ws\n", pInputBuff);
        //*(DWORD *)pOutputBuff =2;
        break;
    case CTL_BYE:
        DbgPrint("Goodbye iocontrol\n");
        break;
    default:
        DbgPrint("Unknown iocontrol\n");
    }
 
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;//sizeof(DWORD);
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
 
    return STATUS_SUCCESS;
}
/// @note        对应文件句柄应用计数为0,handle句柄是不跨进程的[^19]
/// @warnig      写关闭需要单独处理,写关闭:以写的方式打开文件,然后写入文件,最后关闭,这时候其他进程有机可乘,关闭之前需要重新扫描一下。
NTSTATUS DispatchClean(PDEVICE_OBJECT pObject, PIRP pIrp)
{
    UNREFERENCED_PARAMETER(pObject);
 
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;
 
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
 
    return STATUS_SUCCESS;
}
/// 对应fileobject引用计数为0,fileobject是跨进程的[^19]
NTSTATUS DispatchClose(PDEVICE_OBJECT pObject, PIRP pIrp)
{
    UNREFERENCED_PARAMETER(pObject);
 
    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 0;
 
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
 
    return STATUS_SUCCESS;
}
 
VOID DriverUnload(PDRIVER_OBJECT pDriverObject)
{
    /// 卸载前把符号链接删掉
    UNICODE_STRING uLinkName = { 0 };
    RtlInitUnicodeString(&uLinkName, LINK_NAME);
    IoDeleteSymbolicLink(&uLinkName);
    /// 卸载前把设备对象删掉
    /// @warning      设备对象可能有多个,遍历链表把所有的设备对象删掉
    /// @warning      DriverEntry创建的符号链接和设备对象如果不清理掉,会导致重新加载驱动会失败
    ///              (因为没删除,重新加载,但设备对象已经存在,必然会加载失败),除非重启系统
    IoDeleteDevice(pDriverObject->DeviceObject);
 
    DbgPrint("Driver unloaded\n");
}
 
/**
*  @brief       DriverEntry 是加载驱动程序后调用的第一个驱动程序提供的例程,它负责初始化驱动程序;
*  @param[in]   pDriverObject 由IO管理器创建,标识驱动,创造设备对象,指向 DRIVER_OBJECT 结构体;
*  @param[in]   pRegPath 驱动安装后在注册表中的路径,指向 UNICODE_STRING 结构的指针,
*                        该结构指定注册表中驱动程序的 Parameters 项的路径;
*  @return      ntStatus 如果例程成功,则它必须返回 STATUS_SUCCESS。
*                        由于#define STATUS_SUCCESS                   ((NTSTATUS)0x00000000L)
*                         即成功返回0,否则为非0,**与应用层相反,应用层返回0则是失败**。
*                        否则,它必须返回在 ntstatus 中定义的错误状态值之一;
*  @warning     驱动入口函数名称是写死的不可修改。
*  @warning     不要使用.cpp来写驱动,因为C++在编译器里面会改名(命名粉碎规则(name mangling))
 驱动框架就不认识DriverEntry这个名字了,会导致编译出错;[^1]
*  @see         https://docs.microsoft.com/zh-cn/windows-hardware/drivers/wdf/driverentry-for-kmdf-drivers
*  @author      cisco(微信公众号:坚毅猿)
*  @date        2022-02-13 22:09
*/
NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,
    PUNICODE_STRING pRegPath)
{
    /// 函数内部并没有用到参数pRegPath,所以需要UNREFERENCED_PARAMETER(pRegPath),让编译器忽略掉未使用的变量;
    UNREFERENCED_PARAMETER(pRegPath);
 
    UNICODE_STRING uDeviceName = { 0 }; ///< 驱动设备对象的名字,UNICODE_STRING是windows内核中统一使用的字符串类型[^2]
    UNICODE_STRING uLinkName = { 0 };   ///< 符号链接的名字
    NTSTATUS ntStatus = 0;              ///< 返回状态
    PDEVICE_OBJECT pDeviceObject = NULL; ///< 设备对象
    ULONG i = 0;
 
    DbgPrint("Driver load begin\n");
    ///初始化,就是将"wchar_t *"转换 "UNICODE_STRING *",同时将DEVICE_NAME赋值给uDeviceName[^3]
    ///@todo  理清楚其实现逻辑
    RtlInitUnicodeString(&uDeviceName, DEVICE_NAME);
    RtlInitUnicodeString(&uLinkName, LINK_NAME);
 
    /**
 *  @brief       IoCreateDevice 创建设备对象;
 *  @param[in]   pDriverObject 由IO管理器创建,标识驱动,创造设备对象,指向 DRIVER_OBJECT 结构体;
 *  @param[in]   DeviceExtensionSize 设备扩展,创建设备对象的一个缓冲区空间,用来存放一些数据。不需要空间,则设置为0;
 *  @param[in]   DeviceName 设备对象名
 *  @param[in]   DeviceType 设备对象的类型,比如磁盘设备类型等,未知设备类型的定义为
 *               "#define FILE_DEVICE_UNKNOWN             0x00000022"
 *  @param[in]   DeviceCharacteristics 文件的属性
 *  @param[in]   Exclusive 排他性,最好设置为TURE表示只要有一个进程打开,其他进程就无法打开,从而提高驱动安全性;
 *  @param[in]   &pDeviceObject 被创建的设备对象的指针的指针,是二级指针;
 *  @warning     如果不传二级指针,pDeviceObject是不会发生改变的,存在内存泄漏;[^4]
 *  @todo        为什么会导致内存泄漏
 *  @return      ntStatus 如果例程成功,则它必须返回 STATUS_SUCCESS。
 *                        因为#define STATUS_SUCCESS                   ((NTSTATUS)0x00000000L)
 *                        即成功返回0,否则为非0,**与应用层相反,应用层返回0则是失败**。
 *                        否则,它必须返回在 ntstatus 中定义的错误状态值之一;
 *  @note        为什么要在驱动中创建一个设备对象?是因为只能用设备对象是用来接收应用层的IRP数据包。
 *               eg1:应用层调用CreateFile()进入内核层会封装成一个irp数据包,irp发给指定的由驱动创建的设备对象,
 *               设备对象接收到irp之后才会传给驱动分发函数去处理;[^5]
 *  @note        Q1:驱动对象和设备的对象的关系?互指。[^6]
 *               因为同一个驱动对象可以创建多个设备对象,比如可以创建控制设备对象,可以创建过滤设备对象等,
 *               这些设备对象都被串联起来存放在同一个链表中,而且每个设备对象都有一个指针指向创建它的驱动对象。
 *               "typedef struct _DEVICE_OBJECT *PDEVICE_OBJECT;"
 *               "typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) _DEVICE_OBJECT {
 *                   CSHORT Type;
 *                   USHORT Size;
 *                   LONG ReferenceCount;
 *                   struct _DRIVER_OBJECT *DriverObject; ///< 指向创建它的内核驱动对象
 *                   struct _DEVICE_OBJECT *NextDevice;   ///< 指向下一个设备对象
 *                   struct _DEVICE_OBJECT *AttachedDevice; ..."
 *  @see         C:\Program Files (x86)\Windows Kits\10\Include\10.0.22000.0\km\wdm.h
 *  @author      cisco(微信公众号:坚毅猿)
 *  @date        2022-01-22 22:09
 */
    ntStatus = IoCreateDevice(pDriverObject,
        0, &uDeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDeviceObject);
 
    if (!NT_SUCCESS(ntStatus))
    {
        /// 错误状态值ntStatus如果以十进制打印出来还可能是个负数,不如十六进制来得直观[^7]
        /// 通过查询ntstatus.h的宏可以很方便找到对应的错误类型。
        /// 例如:#define STATUS_OBJECT_NAME_INVALID       ((NTSTATUS)0xC0000033L);
        DbgPrint("IoCreateDevice failed:%x", ntStatus);
 
        return ntStatus;
    }
    /// 对刚创建的设备对象指定一个通信方式;
    /// @note       "|="是往Flges添加一些标志
    /// @note       Q2:何为通信?A2:应用层传数据到内核层,或者内核层发数据到应用层
    ///             Q3:何为通信协议?A3:把数据放在什么地方去
    /// DO_BUFFERED_IO规定R3和R0之间的read和write的通信方式有三种[^8]
    /// 1.buffered io 在内核层分配一块缓存,io管理器负责把应用层/内核层copy到buffer,
    ///               io管理器负责把buffer拷贝到io管理器负责把内核层/应用层
    ///               优点:安全简单,因为不会操作应用层的内存,buffer是来自内核态的,
    ///               应用层无法改内核层的数据,所以是安全的。
    ///               缺点:效率低,因为一次通信有两次拷贝,一般传输数据量是不大的,buffer io是够用的,
    ///               但如果是类似3d渲染,数据量大,direct io更适合;
    /// @todo        解释有点牵强
    /// 2.direct io  io管理器通过MPL把应用层/内核层的虚拟地址映射成物理地址,然后lock,
    ///              防止被这块内存切换出去(pageout),io管理器通过MPL把同一物理地址映射成内核层/应用层的物理地址。[^9]
    ///              效率是最高的,一次通信只有一次拷贝,但稍复杂
    /// 3.neither io 内核层直接访问应用层的数据,前提是应用层和内核层同处于一个进程上下文[^10]
    ///             (因为应用层内存地址是私有的,应用层进程切换之后内存就失效了)
    /// @warnnig     要对内核层传入的内存地址要做检查(ProbeForRead/ProbeForWrite),否则会有提取漏洞[^11]
    /// @note DO_DEVICE_INITIALIZING 是用来标识设备对象刚刚创建出来,正在初始化,还不能工作,
    ///       告诉应用层我现在暂时无法处理ipr请求,不要给我发,发了我也处理不了,
    ///       Q4:由谁来去除这个标志?A4:在DriverEntry中创建的设备对象是由io管理器把这个标志去掉,
    ///       但在其他地方创建的,比如过滤驱动,由驱动程序负责清除,即必须用程序员自己手动去除这个标志;
    pDeviceObject->Flags |= DO_BUFFERED_IO;
 
    /// 创建符号链接;
    /// @note        设备对象在应用层的体现,r0和r3通信的过程中,r3需要符号链接才能看到设备对象将其打开,
    ///              进而获得句柄,然后发送读写等各种请求;[^12]
    ///              eg2:磁盘中看到的盘符号(C:)、(D:)都是符号链接,对应内核的卷设备对象\Device\HarddiskVolume
    ///
    ntStatus = IoCreateSymbolicLink(&uLinkName, &uDeviceName);
    if (!NT_SUCCESS(ntStatus))
    {
        IoDeleteDevice(pDeviceObject);
        DbgPrint("IoCreateSymbolicLink failed:%x\n", ntStatus);
        return ntStatus;
    }
    /// "#define IRP_MJ_MAXIMUM_FUNCTION         0x1b" 分发函数存放在pDriverObject->MajorFunction,
    /// 0x1b+1=28,所有的分发函数有28个
    for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION + 1; i++)
    {
        pDriverObject->MajorFunction[i] = DispatchCommon;
    }
    /// 对感兴趣的Irp进行注册,还有两个重要的Irp(重命名和删除,Major),复制、粘贴、移动没有对应的Irp,因为这三个动作本质是读和写,
    /// 文件过滤驱动中为检测这三个动作,只需要监控都和写的Irp就可以了;[^13]
    /// @warnig      名字可以随便起,但接口定义,参数类型必须一样
    pDriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreate; ///< 创建
    pDriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead; ///< 读 是相对于应用层来说,数据流向:r0->R3
    pDriverObject->MajorFunction[IRP_MJ_WRITE] = DispatchWrite; ///< 写 是相对于应用层来说,数据流向:r3->R0
    pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoctrl;
    pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = DispatchClean;
    pDriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchClose;
 
    pDriverObject->DriverUnload = DriverUnload;
 
    DbgPrint("Driver load ok!\n");
 
    return STATUS_SUCCESS;
}

应用层代码

main

1.加载驱动LoadDriver

  • 1.得到完整的驱动路径
  • 2.打开服务控制管理器
  • 3.创建驱动所对应的服务
  • 4.开启此项服务,会触发驱动的DriverEntry的执行

2.测试驱动TestDriver

  • 1.打开驱动或者句柄
  • 2.读/写
  • 3.发送控制码
  • 4.把句柄关掉
#include <windows.h>
#include <winsvc.h>
#include <conio.h>
#include <stdio.h>
#include <winioctl.h>

#define DRIVER_NAME "ntmodeldrv" ///< 驱动名字
#define DRIVER_PATH ".\\ntmodeldrv.sys" ///< 驱动路径
/// 应用层的这部分控制码要和内核的控制码保持一致
#define IOCTRL_BASE 0x800

#define MYIOCTRL_CODE(i) \
   CTL_CODE(FILE_DEVICE_UNKNOWN, IOCTRL_BASE+i, METHOD_BUFFERED,FILE_ANY_ACCESS)

#define CTL_HELLO MYIOCTRL_CODE(0)
#define CTL_PRINT MYIOCTRL_CODE(1)
#define CTL_BYE MYIOCTRL_CODE(2)

/**
*  @brief       LoadDriver 装载NT驱动程序;
*  @param[in]   lpszDriverName 驱动名字;
*  @param[in]   lpszDriverPath 驱动的路径;
*  @return      BOOL 成功返回0,否则为非0;
*  @pre
*  @see         https://docs.microsoft.com/zh-cn/windows-hardware/drivers/wdf/driverentry-for-kmdf-drivers
*  @author      cisco(微信公众号:坚毅猿)
*  @date        2022-02-15 22:09
*/
BOOL LoadDriver(char* lpszDriverName, char* lpszDriverPath)
{
   //char szDriverImagePath[256] = "D:\\DriverTest\\ntmodelDrv.sys";
   char szDriverImagePath[256] = { 0 }; ///< 驱动的完整路径
   //得到完整的驱动路径
   GetFullPathName(lpszDriverPath, 256, szDriverImagePath, NULL);

   BOOL bRet = FALSE;

   SC_HANDLE hServiceMgr = NULL;//SCM管理器的句柄
   SC_HANDLE hServiceDDK = NULL;//NT驱动程序的服务句柄

   //打开服务控制管理器
   hServiceMgr = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

   if (hServiceMgr == NULL)
   {
       //OpenSCManager失败
       printf("OpenSCManager() Failed %d ! \n", GetLastError());
       bRet = FALSE;
       goto BeforeLeave;
   }
   else
   {
       ////OpenSCManager成功
       printf("OpenSCManager() ok ! \n");
   }

   //创建驱动所对应的服务
   hServiceDDK = CreateService(hServiceMgr,
       lpszDriverName, ///< 驱动程序的在注册表中的名字
       lpszDriverName, ///< 注册表驱动程序的 DisplayName 值
       SERVICE_ALL_ACCESS, ///< 加载驱动程序的访问权限
       SERVICE_KERNEL_DRIVER,///< 表示加载的服务是驱动程序
       SERVICE_DEMAND_START, ///< 注册表驱动程序的 Start 值,SERVICE_DEMAND_START表示需要的时候动态加载(3)[^20]
       SERVICE_ERROR_IGNORE, ///< 注册表驱动程序的 ErrorControl 值 ,SERVICE_ERROR_IGNORE表示系统没加载成功驱动,则忽略这个错误
       szDriverImagePath, ///< 注册表驱动程序的 ImagePath 值,测试的.exe和.sys文件放在同一个目录下,否则会报错,加载失败,errcode:2 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\ntmodelDrv\ImagePath
       NULL,  ///< GroupOrder HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GroupOrderList
       NULL, ///< @warnig 如果驱动依赖了其他服务,要晚于所依赖的服务启动;
       NULL,
       NULL,
       NULL);

   DWORD dwRtn;
   //判断服务是否失败
   if (hServiceDDK == NULL)
   {
       dwRtn = GetLastError();
       if (dwRtn != ERROR_IO_PENDING && dwRtn != ERROR_SERVICE_EXISTS)
       {
           //由于其他原因创建服务失败
           printf("CrateService() Failed %d ! \n", dwRtn);
           bRet = FALSE;
           goto BeforeLeave;
       }
       else
       {
           //服务创建失败,是由于服务已经创立过
           printf("CrateService() Failed Service is ERROR_IO_PENDING or ERROR_SERVICE_EXISTS! \n");
       }

       // 驱动程序已经加载,只需要打开
       hServiceDDK = OpenService(hServiceMgr, lpszDriverName, SERVICE_ALL_ACCESS);
       if (hServiceDDK == NULL)
       {
           //如果打开服务也失败,则意味错误
           dwRtn = GetLastError();
           printf("OpenService() Failed %d ! \n", dwRtn);
           bRet = FALSE;
           goto BeforeLeave;
       }
       else
       {
           printf("OpenService() ok ! \n");
       }
   }
   else
   {
       printf("CrateService() ok ! \n");
   }

   ///< 开启此项服务,会触发驱动的DriverEntry的执行
   bRet = StartService(hServiceDDK, NULL, NULL);
   if (!bRet)
   {
       DWORD dwRtn = GetLastError();
       if (dwRtn != ERROR_IO_PENDING && dwRtn != ERROR_SERVICE_ALREADY_RUNNING)
       {
           printf("StartService() Failed %d ! \n", dwRtn);
           bRet = FALSE;
           goto BeforeLeave;
       }
       else
       {
           if (dwRtn == ERROR_IO_PENDING)
           {
               //设备被挂住
               printf("StartService() Failed ERROR_IO_PENDING ! \n");
               bRet = FALSE;
               goto BeforeLeave;
           }
           else
           {
               //服务已经开启
               printf("StartService() Failed ERROR_SERVICE_ALREADY_RUNNING ! \n");
               bRet = TRUE;
               goto BeforeLeave;
           }
       }
   }
   bRet = TRUE;
   //离开前关闭句柄
BeforeLeave:
   if (hServiceDDK)
   {
       CloseServiceHandle(hServiceDDK);
   }
   if (hServiceMgr)
   {
       CloseServiceHandle(hServiceMgr);
   }
   return bRet;
}

//卸载驱动程序
BOOL UnloadDriver(char* szSvrName)
{
   BOOL bRet = FALSE;
   SC_HANDLE hServiceMgr = NULL;//SCM管理器的句柄
   SC_HANDLE hServiceDDK = NULL;//NT驱动程序的服务句柄
   SERVICE_STATUS SvrSta;
   //打开SCM管理器
   hServiceMgr = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
   if (hServiceMgr == NULL)
   {
       //带开SCM管理器失败
       printf("OpenSCManager() Failed %d ! \n", GetLastError());
       bRet = FALSE;
       goto BeforeLeave;
   }
   else
   {
       //带开SCM管理器失败成功
       printf("OpenSCManager() ok ! \n");
   }
   //打开驱动所对应的服务
   hServiceDDK = OpenService(hServiceMgr, szSvrName, SERVICE_ALL_ACCESS);

   if (hServiceDDK == NULL)
   {
       //打开驱动所对应的服务失败
       printf("OpenService() Failed %d ! \n", GetLastError());
       bRet = FALSE;
       goto BeforeLeave;
   }
   else
   {
       printf("OpenService() ok ! \n");
   }
   //停止驱动程序,如果停止失败,只有重新启动才能,再动态加载。
   if (!ControlService(hServiceDDK, SERVICE_CONTROL_STOP, &SvrSta))
   {
       printf("ControlService() Failed %d !\n", GetLastError());
   }
   else
   {
       //打开驱动所对应的失败
       printf("ControlService() ok !\n");
   }

   //动态卸载驱动程序。

   if (!DeleteService(hServiceDDK))
   {
       //卸载失败
       printf("DeleteSrevice() Failed %d !\n", GetLastError());
   }
   else
   {
       //卸载成功
       printf("DelServer:deleteSrevice() ok !\n");
   }

   bRet = TRUE;
BeforeLeave:
   //离开前关闭打开的句柄
   if (hServiceDDK)
   {
       CloseServiceHandle(hServiceDDK);
   }
   if (hServiceMgr)
   {
       CloseServiceHandle(hServiceMgr);
   }
   return bRet;
}

void TestDriver()
{
   //打开驱动或者句柄
   HANDLE hDevice = CreateFile("\\\\.\\NTmodeldrv", ///> 符号链接\\.\NTmodeldrv,use your own name
       GENERIC_WRITE | GENERIC_READ,
       0,
       NULL,
       OPEN_EXISTING,
       0,
       NULL);
   if (hDevice != INVALID_HANDLE_VALUE)
   {
       printf("Create Device ok ! \n");
   }
   else
   {
       printf("Create Device Failed %d ! \n", GetLastError());
       return;
   }
   CHAR bufRead[1024] = { 0 };
   WCHAR bufWrite[1024] = L"Hello, world";

   DWORD dwRead = 0;
   DWORD dwWrite = 0;

   /**
*  @brief       ReadFile 调用ReadFile之后会进入内核层,封装成Irp数据包,发送到内核层创建的设备对象上,
*                        交给ReadFile对应的分发函数把数据拿到,通过内核的io管理器把内核层buffer拷贝到bufRead上;
*  @param[in]   hFile 设备对象的句柄;
*  @param[in]   ipBuffer 存放从内核层读出来的数据的buffer;
*  @param[in]   nNumberOfBytesToRead 存放从内核读出来的数据buffer的长度;
*  @param[in]   lpNumberOfBytesToRead 实际从内存层读出来的数据长度;
*  @param[in]   lpOverlapped 用来控制异步通信
*  @see         https://docs.microsoft.com/zh-cn/windows-hardware/drivers/wdf/driverentry-for-kmdf-drivers
*  @author      cisco(微信公众号:坚毅猿)
*  @date        2022-02-15 22:09
*/
   ReadFile(hDevice, bufRead, 1024, &dwRead, NULL);
   printf("Read done!:%ws\n", bufRead);
   printf("Please press any key to write\n");
   getch();
   /**
*  @brief       WriteFile 调用ReadFile之后会进入内核层,封装成Irp数据包,发送到内核层创建的设备对象上,
*                        交给ReadFile对应的分发函数把数据拿到,通过内核的io管理器把内核层buffer拷贝到bufRead上;
*  @param[in]   hFile 设备对象的句柄;
*  @param[in]   ipBuffer 存放从内核层读出来的数据的buffer;
*  @param[in]   nNumberOfBytesToRead 存放从内核读出来的数据buffer的长度;
*  @param[in]   lpNumberOfBytesToRead 实际从内存层读出来的数据长度;
*  @param[in]   lpOverlapped 用来控制异步通信
*  @see         https://docs.microsoft.com/zh-cn/windows-hardware/drivers/wdf/driverentry-for-kmdf-drivers
*  @author      cisco(微信公众号:坚毅猿)
*  @date        2022-02-15 22:09
*/
   WriteFile(hDevice, bufWrite, (wcslen(bufWrite) + 1) * sizeof(WCHAR), &dwWrite, NULL);

   printf("Write done!\n");

   printf("Please press any key to deviceiocontrol\n");
   getch();
   CHAR bufInput[1024] = "Hello, world";
   CHAR bufOutput[1024] = { 0 };
   DWORD dwRet = 0;

   WCHAR bufFileInput[1024] = L"c:\\docs\\hi.txt";

   printf("Please press any key to send PRINT\n");
   getch();
   DeviceIoControl(hDevice,
       CTL_PRINT,
       bufFileInput,
       sizeof(bufFileInput),
       bufOutput,
       sizeof(bufOutput),
       &dwRet,
       NULL);
   printf("Please press any key to send HELLO\n");
   getch();
   DeviceIoControl(hDevice,
       CTL_HELLO,
       NULL,
       0,
       NULL,
       0,
       &dwRet,
       NULL);
   printf("Please press any key to send BYE\n");
   getch();
   DeviceIoControl(hDevice,
       CTL_BYE,
       NULL,
       0,
       NULL,
       0,
       &dwRet,
       NULL);
   printf("DeviceIoControl done!\n");
   //把句柄关掉
   CloseHandle(hDevice);
}

int main(int argc, char* argv[])
{
   //加载驱动
   BOOL bRet = LoadDriver(DRIVER_NAME, DRIVER_PATH);
   if (!bRet)
   {
       printf("LoadNTDriver error\n");
       return 0;
   }
   //加载成功

   printf("press any key to create device!\n");
   getch();

   TestDriver();

   //这时候你可以通过注册表,或其他查看符号连接的软件验证。
   printf("press any key to stop service!\n");
   getch();

   //卸载驱动
   bRet = UnloadDriver(DRIVER_NAME);
   if (!bRet)
   {
       printf("UnloadNTDriver error\n");
       return 0;
   }

   return 0;
}

编译

.sys可以作为资源打包到.exe中去,在启动.exe的时候把.sys释放出来,把.sys加载起来,然后把.sys删掉。
怎么打包?使用pe工具打包[21]
.sys x86平台编译x86的,x64必须编译成x64的
.exe 既可以编译成x86的,也可以编译成x64,x86的应用程序是可以加载x64的驱动

测试

x64需要关闭数字签名,x86测试方便些。

参考资料

1.[[命名粉碎规则]]
2.字符串类型
3.RtlInitUnicodeString
RtlInitUnicodeString(
    _Out_ PUNICODE_STRING DestinationString,
    _In_opt_z_ __drv_aliasesMem PCWSTR SourceString
    );
4.内存泄漏
5.Irp框架
6.设备对象
7.数的表示
8.[[通信方式]]
9.物理地址转换
10.进程上下文
11.提权漏洞
12.符号链接
13.[[Irp]]
14.内存溢出
15.堆
16.中断
17.整数存储
18.DeviceIoControl
/// @warning 这些数据都是封装在Irp中
BOOL DeviceIoControl(
 [in]                HANDLE       hDevice, ///< 要在其上执行操作的设备的句柄。设备通常是卷、目录、文件或流。
 [in]                DWORD        dwIoControlCode, ///< 操作的控制代码,可以自己定义
 [in, optional]      LPVOID       lpInBuffer, ///< 指向输入缓冲区的指针,该缓冲区包含执行操作所需的数据
 [in]                DWORD        nInBufferSize, ///< 输入缓冲区的大小(以字节为单位)。
 [out, optional]     LPVOID       lpOutBuffer, ///< 指向输出缓冲区的指针,该缓冲区将接收操作返回的数据
 [in]                DWORD        nOutBufferSize, ///< 输出缓冲区的大小(以字节为单位)。
 [out, optional]     LPDWORD      lpBytesReturned, ///< 这次io实际传输的长度
 [in, out, optional] LPOVERLAPPED lpOverlapped ///< 用作异步通信的
);
19.跨进程
20. 影响驱动启动的时机有两个因素

影响驱动启动的时机有两个因素,
一个是Startype

// Start Type
//
 
#define SERVICE_BOOT_START             0x00000000
#define SERVICE_SYSTEM_START           0x00000001
#define SERVICE_AUTO_START             0x00000002
#define SERVICE_DEMAND_START           0x00000003
#define SERVICE_DISABLED               0x00000004

StartType值有0,1,2,3,4,数值越小越早启动


image
  • SERVICE_BOOT_START(0)是内核刚刚初始化之后,此时加载的都是与系统核心有关的驱动程序,比如磁盘驱动;
  • SERVICE_SYSTEM_START(1) 稍晚一些;
  • SERVICE_AUTO_START(2) 是在登陆界面出现的时候开始,如果登陆较快可能驱动还没加载就登陆进去了;
  • SERVICE_DEMAND_START(3) 是需要的时候动态加载;
  • SERVICE_DISABLED(4) 是不加载,要假装之前必须把Start的值改小于4的值;
    另一个是GroupOrder
    当两个驱动的StartType相同,GroupOrder越靠前越先启动


    image

    @warnig 如果驱动依赖了其他服务,要晚于所依赖的服务启动;
    期望:驱动越早启动越好,如果驱动比病毒木马启动晚的话,可能来不及杀掉病毒和木马就被反杀了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,454评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,553评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,921评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,648评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,770评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,950评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,090评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,817评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,275评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,592评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,724评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,409评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,052评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,815评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,043评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,503评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,627评论 2 350

推荐阅读更多精彩内容