精华内容
下载资源
问答
  • 对内核驱动程序的理解
    千次阅读 多人点赞
    2021-03-01 22:55:38

    内核

    从操作系统的层面看,也可以分层。 硬件层、内核层、应用层。 内核就是用来连接硬件、应用层的桥梁。

    所以,内核需要具备管理硬件的能力(决定内存用来做什么、决定哪个进程\线程使用CPU、决定设备之间的通信等等),向上还要有接口提供给应用。 也就是说,内核可以直接操作 内存和CPU,权限非常之高,非常危险

    分层思想,在很多地方都存在,主要专注于各层负责各层的事,解耦合,在计算机网络里有分层,在微服务里也有分层。

    内核的结构可以分为 宏内核monolithic kernel、微内核micro kernel、混合型内核。

    宏内核: 内核负责了关于硬件的大部分事情。

    微内核: 内核只保留了最基本的功能。大多数应用,比如程序驱动、文件系统是在用户空间管理的。体积小、可移植性强。但是驱动在内核外,驱动和硬件的交互就要频繁的切换到内核态。

    混合型内核: 相当于在宏内核里面,抽象出了微内核,可以在大的结构里面增强功能。

    Linux 内核, 是宏内核结构的。

    特点:

    多任务 mulitTask, 即并发,多个任务可以同时执行。

    对称多处理 symmetric multiprocessing, 每个处理器地位平等,都可以访问完成的内存和硬件资源。

    ELF , executable and linkable format , 可执行文件链接格式。 是 linxu 下的可执行文件的存储格式。把文件分为了一个个的段 segment。

    Unix 和 Linux 非常相似, 也是宏内核结构的。

    Windows 内核: 主流就是 NT 内核, new technology, 是混合型内核结构。

    特点:

    多任务、对称多处理。

    PE, portable executable , 可移植执行文件。 是 windows 下的可执行文件格式。扩展名通常为 .exe .dll .sys

    Mac OS 是 XNU 内核,混合型内核。 XNU: X is not Unix。

    内核态和用户态

    内核是一个完整的可执行程序。 程序启动后也会加载到内存里,成为一个进程。

    内存里分为 用户空间、内核空间。

    最小权限原则,多数应用程序都应该允许在最小的权限之下。 用户空间的代码只能访问用户态,内核空间的代码可以访问所有内存。

    如果用户态想要操作硬件资源,就要发起系统调用,通过某种中断方式——trap,进入到内核态,从而提高权限。

    进程: 程序启动后,会在内存中创建一个执行的副本,这个副本就是进程。通常的应用程序都是 用户态的进程。

    Linux内核是一大段程序,启动之后也会加载到内存中,这是内核态的进程。

    用户态进程想要申请 内存资源等, 就要通过 系统调用,向内核申请。

    一个进程里可以有很多线程。 可以创建 用户态的线程,也可以通过系统调用创建 内核态的线程。

    用户态线程

    完全在用户空间创建,操作系统并不知道他的存在。

    创建、销毁、切换线程不需要走系统调用,开销低。

    但是无法利用多核的优势,操作系统调度的是他属于的哪个进程,也只能用分配到这个进程的时间片来执行,在这个进程里面的线程也不能 并行执行。当阻塞时,也要让出整个进程。

    内核态线程

    可以通过 系统调用创建一个内核态的线程。

    创建、销毁成本高,需要系统调用切换到内核态。

    可以利用多核CPU的优势,可以并行执行,操作IO时也不用进行系统调用切换到内核态提高权限。

    用户态线程和内核态线程的映射

    多对一: 多个用户态线程 复用 一个内核态线程,

    也就是, 用内核态线程 来执行 用户态线程里的程序。

    怎么执行? 程序就是存储在内存中的一些指令,让内核态的线程执行这些指令就可以。

    一对一:

    Windows NT 内核就是这种模型。

    n对m:n个用户态线程 对 m个内核态线程。 m通常小于n,m一般为CPU的核数。

    Linux 内核的 NGPL 实现了这种模型。但目前都是 一对一模型。

    Linux 的 PThreadAPI 创建 用户态线程; KThreadAPI创建 内核态线程。

    更多相关内容
  • 在linux庞大的源码树中,设备驱动程序部分的代码已经占了相当大的比例,现实的工作中,大量的采用linux系统的平台需要设备驱动程序才能把linux的内核真正运行起来,无论是日常工作的需要还是只为单纯满足linux内核...
  • 深入Linux设备驱动程序内核机制---高清版.pdf 个人收集电子书,仅用学习使用,不可用于商业用途,如有版权问题,请联系删除!
  • 预览合集,请购买正版书籍:全部有书签导引,便于查阅 Linux 设备驱动程序开发 精通 [印] Sreekrishnan Venkaveswaran 高清_.pdf Linux 设备驱动 第三版_En_.pdf ...Linux 设备驱动程序内核机制 深入 陈学松著_.pdf
  • 使用设备树时,给驱动程序调用的过程 我们把设备树.dts 变成plantform_device的资源 过程一步步了解清楚 吃

    使用设备树时,给驱动程序调用的过程

    我们把设备树.dts 变成plantform_device的资源 过程一步步了解清楚
    在这里插入图片描述

    设备树中运行时配置启动信息的处理

    设备树只是起一个信息传递的作用,对这些信息配置的处理,也比较简单,即从设备树的DTB文件中,把这些设备信息提取出来赋给内核中的某个变量即可。
    1./chosen节点中bootargs属性就是内核启动的命令行参数,它里面可以指定根文件系统在哪里,第一个运行的应用程序是哪一个,指定内核的打印信息从哪个设备里打印出来。

    2./memory中的reg属性指定了不同板子内存的大小和起始地址。

    3.根节点的#address-cells和#size-cells属性指定属性参数的位数,比如指定前面memory中的reg属性的地址是32位还是64位,大小是用一个32位表示,还是两个32位表示。

    设备树中平台信息的处理(选择machinedesc)

    一个编译成uImage的内核镜像文件,可以支持多个单板,这里假设支持smdk2410、smdk2440、jz2440(其中smdk2410、smdk2440是厂家的公板,国内的厂家参考公板设计出了自己的板子,比如jz2440)。
    这些板子的配置稍有不同,需要做一些单独的初始化,在内核里面,对于这些单板,都构造了一个machinedesc结构体,里面有.init和.nr。 对于JZ2440,它源自smdk2440,内核没有它的单独文件,它使用smdk2440的相关文件,代码。 在上一节视频里面我们说过,以前uboot使用ATAGS给内核传参数时,它会传入一个机器ID,内核会使用这个机器ID找到最合适的machinedesc。即机器ID与machine_desc里面的.nr比较,相等就表示找到了对应的machinedesc。 当我们的uboot不使用ATAGS传参数,而使用DTB文件时,那么这时内核是如何选择对应的machinedesc呢? 在设备树文件的根节点里,有如下两行:

    c model = "SMDK24440"; compatible = "samsung,smdk2440""samsung,smdk24140""samsung,smdk24xx";
    

    这里的compatible属性声明想要什么machinedesc,属性值可以是一系列字符串,依次与machinedesc匹配。 内核最好支持samsung,smdk2440,如果不支持,再尝试是否支持samsung,smdk24140,再不支持,最后尝试samsung,smdk24xx

    a. 设备树根节点的compatible属性列出了一系列的字符串, 表示它兼容的单板名,从"最兼容"到次之;
    b. 内核中有多个machinedesc, 其中有dtcompat成员, 它指向一个字符串数组, 里面表示该machine_desc支持哪些单板;

    c. 使用compatile属性的值, 跟’’‘每一个machinedesc.dtcompat’’'比较, 成绩为"吻合的compatile属性值的位置", 成绩越低越匹配, 对应的machine_desc即被选中

    dtb转换为devicenode

    在dts文件里,每个大括号{ }代表一个节点,比如根节点里有个大括号,对应一个devicenode结构体;memory也有一个大括号,也对应一个devicenode结构体。 节点里面有各种属性,也可能里面还有子节点,所以它们还有一些父子关系。 根节点下的memory、chosen、led等节点是并列关系,兄弟关系。 对于父子关系、兄弟关系,在device_node结构体里面肯定有成员来描述这些关系。

    打开include/linux/Of.h可以看到devicenode结构体的定义如下:
    struct devicenode { const char *name; // 来自节点中的name属性, 如果没有该属性, 则设为"NULL"
    const char *type; // 来自节点中的device_type属性, 如果没有该属性, 则设为"NULL"
    phandle phandle;
    const char *fullname; // 节点的名字, node-name[@unit-address] struct fwnodehandle fwnode;

    struct  property *properties;  // 节点的属性
            struct  property *deadprops;    /* removed properties */
            struct  device_node *parent;   // 节点的父亲
            struct  device_node *child;    // 节点的孩子(子节点)
            struct  device_node *sibling;  // 节点的兄弟(同级节点)
        #if defined(CONFIG_OF_KOBJ)
            struct  kobject kobj;
        #endif
            unsigned long _flags;
            void    *data;
        #if defined(CONFIG_SPARC)
            const char *path_component_name;
            unsigned int unique_id;
            struct of_irq_controller *irq_trans;
        #endif
        };
    

    把device_node 转换成planfrom_device

    哪些device_node可以转换为plantfrom_device?

    a.根节点下含有compatile属性的子节点
    b.除了根节点的子节点外,子节点的子节点里compatile属性里有特殊值(“simple-bus”,“simple-mfd”,“isa”,“arm,amba-bus”)之一,那么子节点的子节点可以转换为plantform_device
    c.i2c,spi已经转化为plantform_device,所以他们的子节点应该被,总线drv的probe来调用

    eg1:

    /dts-v1/;
    
    /memreserve/ 0x33f00000 0x100000;
    
    / {
    	model = "SMDK24440";
    	compatible = "samsung,smdk2440";
    
    	#address-cells = <1>;
    	#size-cells = <1>;
    		
    	memory {  /* /memory */
    		device_type = "memory";
    		reg =  <0x30000000 0x4000000 0 4096>;		
    	};
    
    	
    /*
    	cpus {
    		cpu {
    			compatible = "arm,arm926ej-s";
    		};
    	};
    */	
    	chosen {
    		bootargs = "noinitrd root=/dev/mtdblock4 rw init=/linuxrc console=ttySAC0,115200";
    	};
    
    	
    	led {
    		compatible = "jz2440_led";
    		pin = <S3C2410_GPF(5)>;
    	};
    };
    

    上面只有led节点有compatbile属性,他被转化为drv

    怎么转换device_node 变成planfrom_device

    a. platform_device中含有resource数组, 它来自device_node的reg, interrupts属性;
    b. platform_device.dev.of_node指向device_node, 可以通过它获得其他属性(我们自定义比如pin)
    eg:

     / {
              mytest {
                  compatile = "mytest", "simple-bus";
                  mytest@0 {
                        compatile = "mytest_0";
                  };
              };
              
              i2c {
                  compatile = "samsung,i2c";
                  at24c02 {
                        compatile = "at24c02";                      
                  };
              };
    
              spi {
                  compatile = "samsung,spi";              
                  flash@0 {
                        compatible = "winbond,w25q32dw";
                        spi-max-frequency = <25000000>;
                        reg = <0>;
                      };
              };
          };
    

    /mytest会被转换为platform_device,
    因为它兼容"simple-bus", 它的子节点/mytest/mytest@0 也会被转换为platform_device

    /i2c节点一般表示i2c控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;
    /i2c/at24c02节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个i2c_client。

    类似的也有/spi节点, 它一般也是用来表示SPI控制器, 它会被转换为platform_device, 在内核中有对应的platform_driver;
    /spi/flash@0节点不会被转换为platform_device, 它被如何处理完全由父节点的platform_driver决定, 一般是被创建为一个spi_device。

    platform_device跟platform_driver的匹配

    当我们把设备树文件转换为dev时,就能进行drv的匹配
    在这里插入图片描述
    比较的优先顺序:
    a. 比较 platform_dev.driver_override 和 platform_driver.drv->name
    b. 比较 platform_dev.dev.of_node的compatible属性 和 platform_driver.drv->of_match_table
    c. 比较 platform_dev.name 和 platform_driver.id_table
    d. 比较 platform_dev.name 和 platform_driver.drv->name
    有一个成功, 即匹配成功

    在根文件系统查看设备树(有助于调试)

    a. /sys/firmware/fdt // 原始dtb文件

    对这个文件使用命令

    hexdump -C /sys/firmware/fdt
    

    可以查看到原始的dtb文件
    在这里插入图片描述

    b. /sys/firmware/devicetree

    以目录结构程现的dtb文件, 根节点对应base目录, 每一个节点对应一个目录, 每一个属性对应一个文件
    同样用命令hexdump -C ./led 进行查看
    在这里插入图片描述

    /sys/devices/platform

    系统中所有的platform_device, 有来自设备树的, 也有来有.c文件中注册的
    对于来自设备树的platform_device,
    可以进入 /sys/devices/platform/<设备名>/of_node 查看它的设备树属性

    在这里插入图片描述

    /proc/device-tree

    /proc/device-tree 是链接文件, 指向 /sys/firmware/devicetree/base
    在这里插入图片描述

    展开全文
  • 俺花了N个大洋买来的,现在免费提供给大家
  • 内核驱动程序的是什么意思和一般意义上的驱动应该怎么理解好 我很疑惑,请教了
  • linux驱动程序开发.7z

    2021-11-09 15:48:16
    在你学习编写驱动时, 你通常会发现大量有关 Linux 内核的东西. 这也许会帮助你理解你的机器是如何工作的, 以及为什么事情不是如你所愿的快, 或者不是如你所要的进行. 我们会逐步介绍新概念, 由非常简单的驱动开始并...
  • Linux设备驱动程序中文版第三版指导你如何编写你自己的驱动, 以及如何利用内核相关的部分. 我们采用一种设备-独立的方法; 编程技术和接口, 在任何可能的时候, 不会捆绑到任何特定的设备. 每一个驱动都是不同的; 作为...
  • 深入理解linux内核 linux内核驱动开发2.6 两本书 chm格式 欢迎大家下载!
  • 使读者能将集中精力于 Linux 设备驱动本身,理解 Linux 内核模块、 Linux 设备驱动的结构、 Linux 设备驱 动中的并发控制等内容。另外,与《Linux Device Drivers》所不同的是,针对设备驱动的实例,本文还给 出了...
  • 本文将仍然秉承《Linux Device Drivers》一书以实例为主的风格,但是实例的背景将非常简单,以求使读者能将集中精力于 Linux 设备驱动本身,理解 Linux 内核模块、 Linux 设备驱动的结构、Linux 设备驱动中的并发...
  • 驱动程序提供的一组设备驱动接口函数Device Driver Interface给操作系统在linux中,这一组设备驱动接口函数一般包括open,close,read,write,ioctl等。 这一组函数是通过一个叫做file operations的结构体注册给linux...

    1.驱动与内核的关系

    驱动程序提供的一组设备驱动接口函数Device Driver Interface给操作系统在linux中,这一组设备驱动接口函数一般包括open,close,read,write,ioctl等。 这一组函数是通过一个叫做file operations的结构体注册给linux内核的。 Linux内核提供特定的系统功能函数进行驱动程序的注册。注册时提供设备驱动文件名称、设备号给对应的file operations结构体,file operations结构体中存储有一组设备驱动接口函数指针。

    2.驱动与硬件的关系

    硬件对于驱动程序来讲,可以抽象为一组寄存器和需要响应的中断源。对于统一寻址的系统,比如ARM,这一组寄存器就是一段地址空间。驱动就是按照芯片手册规定的原则,读取或者写入这些地址空间。中断源是硬件产生的中断,中断是由内核响应的,需要给这个中断注册一个中断处理函数。这可以通过调用linux内核提供特定的系统功能函数进行。 一般注册中断处理函数,可以在模块初始化函数里实现,或者在设备驱动接口程序open中实现。

    3.驱动与应用程序的关系

    对于应用程序来说,驱动所对应的设备文件就代表着驱动。应用程序通过linux系统调用使用驱动。 应用程序通过linux文件操作系统调用使用驱动。也就是说设备驱动对于用户来说同操作一个文件没有区别。代表这个驱动的是驱动所对应的设备文件。

    4.驱动提供给内核的信息

    Linux设备驱定作为一个linux内核模块存在。模块都有2个接口函数。 模块初始化函数和模块退出函数
    上面提到的驱动程序的注册。一般是由模块初始化函数来实现的。模块退出函数则用于取消内核注册,释放资源。 可见只有运行了驱动的这个模块初始化函数之后,驱动程序才能够被注册,内核才能找到设备驱动。 那么什么时候模块初始化函数才获得运行呢?动态加载时,即运行insmode时。静态加载时模块编译进内核系统初始化时会自动调用这个模块初始化函数。

     

    file_operation详解:https://blog.csdn.net/ldan508/article/details/50525955

    展开全文
  • 在上一篇文章中提到了 Windows Vista 及之后版本的 Windows 操作系统在驱动程序加载完成后,驱动中调用的一些系统回调函数(如 ObRegisterCallbacks,可用来监控系统中进线程句柄的操作,如打开进程、复制线程句柄...

        转自:小刀志

        

    在上一篇文章中提到了 Windows Vista 及之后版本的 Windows 操作系统在驱动程序加载完成后,驱动中调用的一些系统回调函数(如 ObRegisterCallbacks,可用来监控系统中对进线程句柄的操作,如打开进程、复制线程句柄等)等 API 中会通过 MmVerifyCallbackFunction 函数对该驱动程序进行完整性检查,检测未通过则会返回 0xC0000022 拒绝访问的返回值。在这篇文章中将会对这个函数进行简单的分析,以明确其原理。

    0x0 获取函数地址

    通过 Windbg 连接 64 位的 Windows 7 SP1 虚拟机,并通过 u nt!MmVerifyCallbackFunction 命令得到该函数的基地址。接下来使用 lm 命令获得 nt 内核模块的基地址,通过与前面的函数首地址相减得到函数的地址偏移 4700B0。

    在 IDA 中加载 64 位 Windows 7 SP1 的 ntoskrnl.exe 文件并指定 pdb 文件,在 IDA View-A 页面中定位到前面获得的函数地址偏移位置 PAGE:00000001404700B0,即定位到该函数所在。


    0x1 简单分析

    如果 pdb 文件正确加载的话会在 IDA View-A 页面中看到 IDA 已正确识别该函数的名称符号。通过 IDA 获得初步的 C 代码,对其进行一些修正后得到下述代码。

    ULONG64 __fastcall MmVerifyCallbackFunction(PVOID pAddr)
    {
      PVOID    address;  // rsi@1
      ULONG64  result;   // rax@2
      PVOID    thread;   // rbx@3
      BOOLEAN  status;   // edi@3
      PVOID    ldrentry; // rax@3
      bool     disable;  // zf@6
    
      address = pAddr;
      if ( (ULONG64)(pAddr + 0x70000000000) > 0x7FFFFFFFFF )
      {
        thread = *MK_FP(__GS__, 0x188);  // get _ETHREAD pointer from KPCR
        --*(WORD *)(thread + 0x1C4);     // Disable Kernel APCs // Thread->KernelApcDisable--;
        status = FALSE;
        ExAcquireResourceSharedLite(&PsLoadedModuleResource, TRUE);
        ldrentry = MiLookupDataTableEntry(address, TRUE);
        if ( ldrentry && *(BYTE *)(ldrentry + 0x68) & 0x20 )
          status = TRUE;
        ExReleaseResourceLite(&PsLoadedModuleResource);
        disable = (*(WORD *)(thread + 0x1C4))++ == -1;  // Enable Kernel APCs // Thread->KernelApcDisable++;
        // 1. Thread->KernelApcDisable == -1 before ++;
        // 2. EThread->ApcState->ApcListHead does not point to itself;
        // 3. EThread->SpecialApcDisable == 0;
        // means KernelApc/SpecialApc enabled && ApcList not empty now.
        if ( disable && *(QWORD *)(thread + 0x50) != thread + 0x50 && !*(WORD *)(thread + 0x1C6) )
          KiCheckForKernelApcDelivery();
        result = (ULONG)status;
      }
      else
      {
        result = FALSE;
      }
      return result;
    }

    0x2 代码解释

    首先是判断传入地址参数的有效性。为了更精确地理解 if ( (ULONG64)(pAddr + 0x70000000000) > 0x7FFFFFFFFF ) 这行语句执行的操作和作用,下面贴出其汇编指令代码。

    mov     rax, 70000000000h
    mov     rsi, rcx
    add     rax, rcx
    mov     rcx, 7FFFFFFFFFh
    cmp     rax, rcx
    ja      nt!MmVerifyCallbackFunction+0x3c

    其中 rcx 初始值是传入的地址参数的值。开始时并未明确这几行指令代码这样判断的目的是什么,通过 Windbg 跟踪 PsSetCreateProcessNotifyRoutineEx 等 API 函数对 MmVerifyCallbackFunction 的调用,发现 ecx 都是 0xfffff8800373c620 之类正常的地址数值,通过和 0x70000000000 相加得到的值 0xffffff800373c620 也远大于 0x7FFFFFFFFF 这个比较小的数。

    后经过计算得知,相加后的值在此处只有在一种情况才会小于 0x7FFFFFFFFF 值。地址参数是个 ULONG64 长度的数字,其值位于 [0xFFFFF900`00000000, 0xFFFFF97F`FFFFFFFF] 区间时,相加后的数值对 ja 条件成立,函数跳转到检测失败并返回的位置。后通过查阅资料得知,在 64 位 Windows 操作系统中,该地址空间区间范围正是内核地址空间中的会话空间(Session Space)。

    Session Space
    Session Data Structures, Session Pool and Session Images are loaded in this area.
    The session image space contains driver images like Win32K.sys (Window Manager), CDD.DLL (Canonical Display Driver), TSDDD.dll (Frame Buffer Display Driver), DXG.sys (DirectX Graphics Driver) etc.
    For any process that belongs to a session the field EPROCESS->Session points to a MM_SESSION_SPACE structure for that session. Session paged pool limits are pointed to by MM_SESSION_SPACE->PagesPoolStart and MM_SESSION_SPACE->PagesPoolEnd.

    在判断地址参数的有效性之后,首先通过 EThread->KernelApcDisable 禁用 Normal Kernel APC。GS 寄存器存储当前 CPU 核心的 _KPCR 结构地址。当前线程 EThread 地址位于 KPCR->Prcb->CurrentThread 位置,根据偏移 +0x188 获取到 EThread 的地址。对偏移为 0x1C4 的 EThread->KernelApcDisable 进行累减运算操作。

    在 EThread 结构中有个 ApcState 域,以及 KernelApcDisable 和 SpecialApcDisable 域。

    KernelApcDisable 和 SpecialApcDisable 域可以合并成一个 CombinedApcDisable 域,两者都是 16 位的整数值,0 表示不禁止 APC,负数表示禁止 APC。一个线程在执行过程中可以有多种因素要禁止 APC,这些因素以负值来表示,并累加起来,当因素消除的时候再减去相应的负值。

    只有当 KernelApcDisable 或 SpecialApcDisable 为 0 的时候,该线程才允许插入或提交 APC。这两个值分别控制普通的内核 APC 和特殊的内核 APC。

    ApcState 是一个结构成员,指定了一个线程的 APC 信息,包括 APC 链表、是否正在处理 APC 或者是否有内核 APC 或用户 APC 正在等待等信息。APC 链表头指针 ApcListHead 位于其结构第一个成员位置。

    为调用线程的共享读访问请求指定的资源。PsLoadedModuleResource 是一个 ERESOURCE 结构体数据类型的全局变量,在 MiInitializeLoadedModuleList 函数中初始化。通过 ExAcquireResourceSharedLite 函数请求 PsLoadedModuleResource 系列资源的读访问权限。根据 MSDN 上的描述,调用该函数之前必须禁用 Normal Kernel APC。直到资源释放之前,APC 投递必须保持禁用状态。以下是该函数的声明。

    BOOLEAN 
    ExAcquireResourceSharedLite(
      _Inout_ PERESOURCE Resource,
      _In_    BOOLEAN    Wait
    );

    ERESOURCE 结构类型是 Windows 操作系统内核中的读写锁对象类型。通过它和一系列的 Resource 例程可以实现同时只有一个 Writer 写入,多个 Reader 读访问的机制。

    详细信息:http://msdn.microsoft.com/en-us/library/windows/hardware/ff544363(v=vs.85).aspx

    在对指定资源的读访问请求完成后,通过 MiLookupDataTableEntry 函数对 PsLoadedModuleList 指针指向的已加载的内核模块链表进行遍历。MSDN 和其他文档中没有查到有关这个函数的定义信息,以下函数定义是通过 IDA 逆向 ntoskrnl.exe 推测得到的。其中的第二个参数 BOOLEAN bAcquiredResource 表示当前调用环境是否已获取到前述的系列资源的读访问权限,如果传入 FALSE 则在该函数内部会调用 ExAcquireResourceSharedLite 函数进行这些资源的读访问权限的获取。

    PLDR_DATA_TABLE_ENTRY
    NTAPI
    MiLookupDataTableEntry(
      IN PVOID Address, 
      IN BOOLEAN bAcquiredResource
    );
    PsLoadedModuleList 是一个 PLDR_DATA_TABLE_ENTRY 类型的全局指针,指向当前内核中已加载的内核模块的 LDR_DATA_TABLE_ENTRY 环形链表的第一个节点。每个节点是一个 LDR_DATA_TABLE_ENTRY 类型的结构体对象。以下是在 Windows 7 x64 SP1 操作系统环境下该结构体的数据类型定义。

    typedef struct _LDR_DATA_TABLE_ENTRY
    {
        LIST_ENTRY InLoadOrderLinks;
        LIST_ENTRY InMemoryOrderLinks;
        LIST_ENTRY InInitializationOrderLinks;
        PVOID DllBase;
        PVOID EntryPoint;
        ULONG SizeOfImage;
        UNICODE_STRING FullDllName;
        UNICODE_STRING BaseDllName;
        ULONG Flags;
        UINT16 LoadCount;
        UINT16 TlsIndex;
        union
        {
            LIST_ENTRY HashLinks;
            struct
            {
                PVOID SectionPointer;
                ULONG CheckSum;
            };
        };
        union
        {
            ULONG TimeDateStamp;
            PVOID LoadedImports;
        };
        PVOID EntryPointActivationContext;
        PVOID PatchInformation;
        LIST_ENTRY ForwarderLinks;
        LIST_ENTRY ServiceTagLinks;
        LIST_ENTRY StaticLinks;
        PVOID ContextInformation;
        ULONG_PTR OriginalBase;
        LARGE_INTEGER LoadTime;
    } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

    在 MiLookupDataTableEntry 函数中遍历链表时,通过结构体成员 DllBase 和 SizeOfImage 判断 Address 参数是否在该内核模块地址区间范围内。如果命中,则返回该 LDR_DATA_TABLE_ENTRY 链表节点的地址指针。

    在 MiLookupDataTableEntry 函数返回后,ldrentry 变量取得对应内核模块的 LDR_DATA_TABLE_ENTRY 指针。判断 lprentry + 0x68 位置的 BYTE 类型的数据是否对 0x20 标志位置位。通过上面的 LDR_DATA_TABLE_ENTRY 结构体定义发现,0x68 位置是 ULONG Flags 成员。该成员存储一些对应内核模块的属性标志。

    判断为真则赋值 status = TRUE,其在后面会用来赋值 result 并作为 MmVerifyCallbackFunction 函数的返回值。那么到现在可知该函数的关键判定就是在这一步了。如果在调用该函数之前就将对应的内核模块 LDR_DATA_TABLE_ENTRY 节点的 Flags 的 0x20 标志位置位,则会得到 MmVerifyCallbackFunction 函数校验通过的结果,达到绕过强制签名校验的目的。

    在判断标志位之后,函数会执行一些诸如资源权限释放、恢复 Normal Kernel APC、Kernel APC 投递检查并继续投递等操作。具体可以参考前面部分的内容,代码的注释已经写清楚。

    具体的验证可以通过 Windbg 跟一下。

    现在回想在编译链接驱动程序的时候,在 sources 文件中可选添加的 LINKER_FLAGS=/INTEGRITYCHECK 链接标记,其实就是给生成的 sys 文件的 PE 文件头中对应的 Flags 数据置位 0x20 标志位。

    0x3 备注

    文章中对部分 Windows 操作系统内核结构的解释参考自《Windows内核原理与实现(潘爱民著)》一书。

    ====================================================================================================

    博主后记:

        博主同样遇到了调用PsSetCreateProcessNotifyRoutineEx注册回调失败的情况。失败的机器运行在"Test Mode"下,并处于被调试状态。原作者可能遗漏了对这种情况的说明。下面分类说明:

    1.编译过程中不加/INTEGRITYCHECK标志。x64位调试机在Test Mode下可以加载驱动,但调用PsSetCreateProcessNotifyRoutineEx后失败;

    2.编译过程中添加加/INTEGRITYCHECK标志。x64位调试机在Test Mode下可以加载驱动,调用PsSetCreateProcessNotifyRoutineEx成功

    原因分析:

    出现上述原因正如MSDN所说:

    "If a kernel debugger is attached to the computer, Code Integrity still checks for a digital signature on every kernel-mode driver, but the operating system will load the drivers."(如果内核调试器已经附加到调试机,虽然OS仍会对每个驱动进行"Code Integrity"检测,但是OS仍会加载驱动)。因此出现了情况1.


    展开全文
  • Linux内核驱动程序

    千次阅读 2016-06-05 06:43:01
    本文译自Linux.orgDevynCJohnson的系列文章... 在上一篇文章Linux内核:源代码中我们探讨了源代码的组成结构以及各个部分的功能,今天我们来介绍一下Linux的驱动驱动是使内核能够与硬件或协议进行通信或控制的程
  • 深入理解linux内核,中文第三版,可随便复制粘贴,包含所有20章的内容
  • 秉承《Linux Device Drivers》一书以实例为主的风格,但是实例的背景将非常简单,以求使读者能将集中精力于 Linux 设备驱动本身,理解 Linux 内核模块、Linux 设备驱动的结构、Linux 设备驱动中的并发控制等内容
  • 学完本课程可以轻松的理解Windows内核,开阔思路,没有底层开发基础的人起到有非常好的指导作用。在此基础上可以开发出有趣且功能强大的软件。 课程目录: 第1章windows驱动基础 第一课 认识windows驱动 第二课 ...
  • LINUX设备驱动程序

    2011-02-28 11:05:18
    期望了解操作系统内部工作原理的读者来讲,《LINUX设备驱动程序(第3版)》也深入阐述了地址空间、异步事件以及I/O等方面的内容。 编辑本段作者简介  JonahanCorbet早在1981年就开始接触BSDUnix的源代码。那时,...
  • 应用层与内核驱动层交互的方式多种多样,这里只写出了我目前理解到的3种方式,至于其它等以后再做整理。 应用与驱动3种方式 所谓的应用与驱动层的交互主要是数据的传递,这里主要是使用内核提供给应用层的API接口,...
  • Linux内核驱动 --ioctl函数解析

    千次阅读 2021-06-18 21:03:06
    1、前言 当我们在讨论linux内核驱动开发时,就不得不提到ioctl这个及其重要的函数。它是设备驱动程序中实现设备控制的接口之一。当我们在用户空间中使用ioctl函数时,
  • 成为一名精通 Linux 程序设计的高级程序员一直是不少朋友孜孜以求的目标。根据中华英才网统计数据,北京地区 Linux 程序员月薪平均为 Windows 程序员的 1.8 倍、Java 程序员的 2.6 倍, Linux 程序员年终奖金平均为 ...
  • 深入理解linux内核,中文第三版,可随便复制粘贴,包含所有20章的内容
  • 对内核数据结构的同步访问 避免竞争条件的实例 第六章定时测量 时钟和定时器电路 Linux计时体系结构 更新时间和日期 更新系统统计数 软定时器和延迟函数 与定时测量相关的系统调用 第七章进程调度 调度策略 调度算法...
  • 真正的《深入理解linux内核》(中文第三版高清版) 由于本文档为pdf全部文档达300多M不能一次上传, 并且由于我现在最大只能上传20M的文件所以只好把章节压缩分割上传-不能一次上传请见谅~
  • 《深入理解linux内核中文第三版》,高清晰扫描,真正中文第三版,Linux 2.6内核进行了详细而细致的讲解。由于之前的压缩包分卷太多,现提供每一章的单独下载文件。100%高清晰第三版!
  • NVMe的Linux内核驱动分析

    千次阅读 2019-07-08 08:46:48
    1. 本文基于Linux 4.1.12 版本的内核进行介绍,其它版本的内核代码可能略有不同,但不影响理解。 2. 在阅读本文之前,如果能够阅读一下本号之前关于块设备和SCSI的文章,对理解本文将很有帮助。 3. 建议阅读本号...
  • 作用是将应用层序的请求传递给硬件,并充当底层驱动程序系统中的各种设备和组件进行寻址。目前支持模块的动态装卸(裁剪)。Linux内核就是基于这个策略实现的。Linux进程1.采用层次结构,每个进程都依赖于一个父...
  • 这是一本目前最新的Linux内核驱动程序的书籍。同时,书籍本身写的也是非常经典的。
  • 《深入理解linux内核中文第三版》,高清晰扫描,真正中文第三版,Linux 2.6内核进行了详细而细致的讲解。由于之前的压缩包分卷太多,现提供每一章的单独下载文件。100%高清晰第三版!

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 101,959
精华内容 40,783
关键字:

对内核驱动程序的理解