内核知识第五讲.驱动框架编写,以及3环和0环通信.

iBinary 2018-01-14 原文

内核知识第五讲.驱动框架编写,以及3环和0环通信.

 

         内核知识第五讲.驱动框架编写,以及3环和0环通信.

一丶了解内核驱动加载方式

内核加载方式有两种方式.

1.动态加载方式.

2.静态加载方式

动态加载方式:

 

  动态态加载方式则是调用3环API 进行代码加载.

详情请点击 : 内核驱动加载工具的编写.

 

静态加载方式

  静态的加载方式则是利用 后缀为.inf  的文件进行加载.

有关.inf的语法,可以百度或者通过学习WDK中提供的源码例子进行学习.

动态加载一般很常用.

二丶驱动框架的介绍.

 

在讲解内核驱动框架的是否,我们要先了解设备是什么.  设备和驱动之间的数据关系是什么.

1.什么是驱动.什么是设备

驱动:   驱动则是用来操作设备的. 

设备:   设备则是我们常说的外设. 比如键盘.  显示器.  鼠标等等..

其中.任何一个驱动操作设备.  都应该提供公共的方法.

打开.  关闭.  读. 写. 控制….

如图: 

  用户操作设备的是否. 这个时候会通过内核驱动.提供的 回调方法.(也就是公共的接口)进而来操作设备.

 

 2.驱动和设备之间的关系.

驱动和设备之间的关系是一对多的关系.

驱动可以操作很多设备.

比如我们的键盘驱动有一个. 但是可以操作的键盘则有很多个. 你键盘坏了.换了很多.但是驱动则没换过.

所以如果是数据关系的时候.   驱动作为外键放到设备表中.

例如以下:

  

设备

驱动

A键盘

标准驱动

B键盘

标准驱动

有了数据关系.那么就可以讲解驱动框架了.

3.驱动框架的介绍.

驱动对象.设备对象.

在驱动中有这两个概念. 

驱动对象:  简单来说就是一个结构体,存储了驱动的各种信息.

设备对象: 简单来说也是一个结构体,存储的是设备的各种信息.

但依据上面的数据关系来说. 设备对象中肯定会存储驱动对象结构体的指针.  驱动对象做外键存储到设备对象中.

设备对象结构体:

typedef struct _DRIVER_OBJECT {
    CSHORT Type;          //类型
    CSHORT Size;          //当前结构体大小.内核中任何一个结构体都是这两个成员开头.

    //
    // The following links all of the devices created by a single driver
    // together on a list, and the Flags word provides an extensible flag
    // location for driver objects.
    //

    PDEVICE_OBJECT DeviceObject;//设备对象指针,存疑?  不是说数据关系是  设备表中有驱动对象吗. 怎么驱动对象表中有设备对象指针.???????
    ULONG Flags;          //通讯协议以及方式.

    //
    // The following section describes where the driver is loaded.  The count
    // field is used to count the number of times the driver has had its
    // registered reinitialization routine invoked.
    //

    PVOID DriverStart;
    ULONG DriverSize;
    PVOID DriverSection;
    PDRIVER_EXTENSION DriverExtension;

    //
    // The driver name field is used by the error log thread
    // determine the name of the driver that an I/O request is/was bound.
    //

    UNICODE_STRING DriverName;

    //
    // The following section is for registry support.  Thise is a pointer
    // to the path to the hardware information in the registry
    //

    PUNICODE_STRING HardwareDatabase;

    //
    // The following section contains the optional pointer to an array of
    // alternate entry points to a driver for "fast I/O" support.  Fast I/O
    // is performed by invoking the driver routine directly with separate
    // parameters, rather than using the standard IRP call mechanism.  Note
    // that these functions may only be used for synchronous I/O, and when
    // the file is cached.
    //

    PFAST_IO_DISPATCH FastIoDispatch;

    //
    // The following section describes the entry points to this particular
    // driver.  Note that the major function dispatch table must be the last
    // field in the object so that it remains extensible.
    //

    PDRIVER_INITIALIZE DriverInit;
    PDRIVER_STARTIO DriverStartIo;
    PDRIVER_UNLOAD DriverUnload;
    PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];  //提供的公共方法的接口. 创建.打开,关闭.读写控制... 里面存放的是函数指针.单用户操作设备的是否.则会调用这些回调函数指针.

} DRIVER_OBJECT;
typedef struct _DRIVER_OBJECT *PDRIVER_OBJECT; 

存疑部分:

  上面标红的部分. 不是说表关系应该是上面那种吗.?如下图所示

设备

驱动

A键盘

标准驱动

B键盘

标准驱动

可为何设计为这样.

原因:

  我们的内核驱动可以操作设备. 但是我们要知道有多少设备怎么办. 所以这里给了一个设备对象的指针. 而不是我们说的数据关系.

而在设备对象中.存储的则是我们的驱动对象指针.

而这里的指针.则是一个链表形式的. 为了方便遍历.

例如:

  

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;
    struct _IRP *CurrentIrp;
    PIO_TIMER Timer;
    ULONG Flags;                                // See above:  DO_...
    ULONG Characteristics;                      // See ntioapi:  FILE_...
    __volatile PVPB Vpb;
    PVOID DeviceExtension;
    DEVICE_TYPE DeviceType;
    CCHAR StackSize;
    union {
        LIST_ENTRY ListEntry;
        WAIT_CONTEXT_BLOCK Wcb;
    } Queue;
    ULONG AlignmentRequirement;
    KDEVICE_QUEUE DeviceQueue;
    KDPC Dpc;

    //
    //  The following field is for exclusive use by the filesystem to keep
    //  track of the number of Fsp threads currently using the device
    //

    ULONG ActiveThreadCount;
    PSECURITY_DESCRIPTOR SecurityDescriptor;
    KEVENT DeviceLock;

    USHORT SectorSize;
    USHORT Spare1;

    struct _DEVOBJ_EXTENSION  *DeviceObjectExtension;
    PVOID  Reserved;

} DEVICE_OBJECT;

typedef struct _DEVICE_OBJECT *PDEVICE_OBJECT; 

三丶编写驱动框架.

上面我们已经简单的了解了驱动对象.设备对象是什么了.那么现在开始编写驱动框架

步骤

1.首先注册设备回调函数.当用户对设备进行操作的是否.驱动会调用这个回调函数进行操作.

2.创建设备.创建虚拟的设备给用户使用.

3.指定通讯方式. 什么意思?比如ring3中操作设备进行读写的时候 如果用ReadFile读取.那么你们的通讯方式是否是字符串

4.创建符号连接.

  符号连接: 我们知道.在操作系统下有C盘.D盘一说.但是在驱动下面.则没有怎么一说.只有卷一说.所以我们要绑定一下.

PS: 鉴于篇幅原因.只写重点.如果想要完整的驱动框架. 请下载资料进行查看.

1.注册回调函数.

  pDriverObject->MajorFunction[IRP_MJ_CREATE] = 创建的回调函数指针;
  pDriverObject->MajorFunction[IRP_MJ_CLOSE] =  关闭的回调函数指针;
  pDriverObject->MajorFunction[IRP_MJ_READ] =   读取的回调函数指针;
  pDriverObject->MajorFunction[IRP_MJ_WRITE] =  写入的回调函数指针;
  pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = 控制的回调函数指针;

回调函数的写法.

NTSTATUS 自定义的函数名(__in struct _DEVICE_OBJECT *DeviceObject,
                      __inout struct _IRP *Irp)
{
 
  .........
  return STATUS_SUCCESS;
}

 

2.创建虚拟设备.

创建设备等等.都属于IO操作.

IO操作创建设备的API

NTSTATUS 
  IoCreateDevice(
    IN PDRIVER_OBJECT  DriverObject,      //调用者驱动对象的指针.一般是驱动入口的参数
    IN ULONG  DeviceExtensionSize,           //设备扩展大小
    IN PUNICODE_STRING  DeviceName  OPTIONAL, //设备对象名称,注意不是我们ring3下的路径.
    IN DEVICE_TYPE  DeviceType,         //我们的设备类型
    IN ULONG  DeviceCharacteristics,      //驱动的附加信息.
    IN BOOLEAN  Exclusive,            //创建的设备是否其余的可以使用,是否独占
    OUT PDEVICE_OBJECT  *DeviceObject        //传出参数,设备的信息.
    );

注意红点标注:

  在内核中并没有路径一说.所以这个路径是特殊的.

UNICODE_STRING 内核中新的字符串格式.其实是一个结构体.系统提供了操作这种结构体的API

我们拼接一个路径

UNICODE_STRING  uStrDeviceName;

RtlInitUnicodeString(&uStrDeviceName,L”\\Device\\xxx名字即可”);

注意,创建设备的时候.我们前边需要加上 \Device.  这里因为是转义字符.所以加了好多\\

拼接好则可以给了.

status = IoCreateDevice(pDriverObject, 
                          0, 
                          &ustrDeviceName,          //设备路径
                          FILE_DEVICE_UNKNOWN,//设备类型设置为不知道
                          FILE_DEVICE_SECURE_OPEN, 
                          FALSE,                           //是否独占
                          &pDevObj);
  if (status != STATUS_SUCCESS)
  {
    return status;
  }        

 

3.设置通讯方式.

pDevObj->Flags |= DO_BUFFERED_IO; //指定通讯方式,为缓冲区

4.创建符号连接

我们创建的符号连接.可以通过 Win0bj工具进行查看. 这个工具可以查看所有设备.但是只有0环才可以操作.

 status = IoCreateSymbolicLink(&g_ustrSymbolName, &ustrDeviceName);
  if (status != STATUS_SUCCESS)
  {
    //删除设备
    IoDeleteDevice(pDevObj);
    return status;
  }

注意符号连接名字.我们也是初始化得出的.

RtlInitUnicodeString(&g_ustrSymbolName, L"\\DosDevices\\xxxx名字");

完整的框架已经写完.剩下的就是 三环和0环驱动通讯. 依赖我们写的框架.

 

四丶三环和0环的通讯.

三环操作设备的API就是 CreateFile ReadFile…..等等.不做介绍了.

利用三环程序.操作我们的设备.从中读取内容.

HANDLE hFile = CreateFile("\\\\?\\符号连接名称", 
             GENERIC_WRITE | GENERIC_READ, 
             0, 
             NULL, 
             OPEN_EXISTING, 
             FILE_ATTRIBUTE_NORMAL, 
             NULL);

打开我们的设备.注意文件名并不是路径.而是我们绑定的符号连接. 这里我们可以写成\\?

读取内容.

char szBuf[10];
ReadFile(hFile, szBuff, sizeof(szBuff), &dwBytes, NULL)

请注意,当读取设备的是否.我们一开始注册的回调函数就会来. 这时候我们给它什么都可以了.

但是此时需要讲解一下通讯协议.

当读取的是否,回调函数会来. 然后操作系统会填写 struct _IRP 结构体.用来和我们的零环通信.

typedef struct _IRP {
  .            //省略了两个成员,这两个成员一个是类型.一个是大小.
  .
  PMDL  MdlAddress;
  ULONG  Flags;
  union {
    struct _IRP  *MasterIrp;
    .
    .
    PVOID  SystemBuffer;//ring3下的缓冲区.操作系统会填写.我们给里面填写什么内容.那么ring3就读取到什么.
  } AssociatedIrp;
  .
  .
  IO_STATUS_BLOCK  IoStatus;
  KPROCESSOR_MODE  RequestorMode;
  BOOLEAN PendingReturned;
  .
  .
  BOOLEAN  Cancel;
  KIRQL  CancelIrql;
  .
  .
  PDRIVER_CANCEL  CancelRoutine;
  PVOID UserBuffer;
  union {
    struct {
    .
    .
    union {
      KDEVICE_QUEUE_ENTRY DeviceQueueEntry;
      struct {
        PVOID  DriverContext[4];
      };
    };
    .
    .
    PETHREAD  Thread;
    .
    .
    LIST_ENTRY  ListEntry;
    .
    .
    } Overlay;
  .
  .
  } Tail;
} IRP, *PIRP;

我们有了缓冲区,但是不知道缓冲区的大小.这个是否需要通过IO函数.从当前栈中获取参数.

IoGetCurrentIrpStackLocation(pIrp)

返回当前IRP的栈.我们从返回值中获取参数即可.

操作完成之后,我们完成IRP请求即可.这个IRP请求主要是为了多线程使用.有的时候可能在读取的是否.线程就切换了.

 

ring0下的读取回调完整编写.

 //获取当前irp堆栈
  PIO_STACK_LOCATION pIrpStack = NULL;
  PVOID lpBuff = NULL;
  ULONG Length = 0;

  //PsGetCurrentThreadId();
  KdBreakPoint();

  __try
  {
    pIrpStack = IoGetCurrentIrpStackLocation(pIrp);
    //check

    lpBuff = pIrp->AssociatedIrp.SystemBuffer;
    Length = pIrpStack->Parameters.Read.Length;
    
    KdPrint(("[FirstWDK] DispatchRead\n"));
    
    RtlStringCbCopyA(lpBuff, Length, "Hello World!");
    //check


    pIrp->IoStatus.Status = STATUS_SUCCESS;
    pIrp->IoStatus.Information = 6;
    
    //完成Irp请求
    IoCompleteRequest(pIrp, IO_NO_INCREMENT);
    //check
  }
  __except(1)
  {
  }


  return STATUS_SUCCESS;

 

课堂3环和0环的完整代码资料:

  链接:https://pan.baidu.com/s/1edffGy 密码:vpo0

原创不易,转载请注明出处.

发表于 2018-01-14 16:43 iBinary 阅读() 评论() 编辑 收藏

 

版权声明:本文为iBinary原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/iBinary/p/8283642.html

内核知识第五讲.驱动框架编写,以及3环和0环通信.的更多相关文章

  1. PHP内核之旅-1.生命周期

    1.SAPI接口 PHP具体应用的编程接口。 2.开始和结束   PHP开始执行以后会经过两个主要的阶段:   […]...

  2. CentOS 7 下编译Linux kernel 内核

    1. 准备编译环境 yum groupinstall "Development Tools"   2. 下载内 […]...

  3. Linux内核机制—rwsem

    基于linux-5.10.66一、相关结构1. rwsem 表示结构体 struct rw_semaphore/** 对于无竞争的 rwsem,count 和 owner 是任务在获取 rwsem 时需要触及的唯一字段。* 因此,它们...

  4. 在Ubuntu上编译安装linux内核详细过程

    在Ubuntu上编译安装linux内核详细过程 时间:20220414,版本:V0.1作者:robotech_erx1.预备1.1解释题目一个linux系统需要3个组件:内核、根文件系统、bootloader。不管是哪个发...

  5. 原子性操作

    1. 概念 原子操作是指不被打断的操作,即它的最小的执行单位。最简单的原子操作就是一条条的汇编指令(不包括一些 […]...

  6. x64内核HOOK技术之拦截进程.拦截线程.拦截模块

    x64内核HOOK技术之拦截进程.拦截线程.拦截模块             x64内核HOOK技术之拦截进程 […]...

  7. 内核知识第九讲,32位下的分页管理,36位下的分页管理.以及64位下的分页管理

    内核知识第九讲,32位下的分页管理,36位下的分页管理.以及64位下的分页管理       内核知识第九讲,3 […]...

  8. 死锁和饥饿的主要区别

    死锁和饥饿的主要区别是什么? 答:饥饿与死锁有一定联系:二者都是由于竞争资源而引起的,但又有明显差别,主要表现 […]...

随机推荐

  1. 用UltraISO制作支持windows 7的U盘启动盘

    用UltraISO制作U盘启动盘,有人写过,我也看过,不过依照网上的那些文章,成功的并不多,经过几次试验,在不 […]...

  2. 从零开始学 Web 之 jQuery(一)jQuery的概念,页面加载事件

    大家好,这里是「 从零开始学 Web 系列教程 」,并在下列地址同步更新…… gith […]...

  3. Mac版音频小工具

    QLab Pro for Mac(音频剪辑软件) QLab Mac版是一款Mac平台的音频编辑处理工具 ,用户 […]...

  4. 禁止网页在非微信浏览器打开 – 大象哥哥哥

    禁止网页在非微信浏览器打开 $(function(){ var ua = navigator.userAgen […]...

  5. nacos注册中心源码流程分析

    作为一个注册中心,和eureka类似,核心的功能点: 1.服务注册:nacos客户端携带自身信息向nacos服 […]...

  6. elementui 使用Form表单 的 resetForm表单功能出现的问题

    elementui 使用Form表单 的 resetForm表单功能出现的问题 代码因为在保密机上,这里只进行 […]...

  7. Debug日志可视化

    最近做语音SDK,老是会遇到需要真机测试得问题。真机测试又想看到Debug日志内容。建文本得方式就显得特别傻。 […]...

  8. linux 如何指定nologin用户执行命令 www用户安全做法

    在linux中建立网站时,我们一般分配一个www之类的用户给网站应用程序。如果我们使用root或者具有管理员权 […]...

展开目录

目录导航