1. (原书)所有内核空间共享,DriverEntery是内核程序入口,在内核程序被加载时,这个函数被调用,加载入的进程为system进程,xp下它的pid是4。内核程序的编写有一定的规则:

    • 不能调用windows运用层API函数
    • 很多C标准函数失去意义,如printf,fopen,fwrite等,它们有专门的内核函数
    • 很多单纯的C标准,如string.h(不涉及I/O及网络等)等还是适用
    • 可以使用标准C语言,但是用浮点数之前要特殊处理
    • WDK提供大量System Routine以供调用,相当于运用层给程序员提供的API
    • 在内核空间可以做任何事情,可访问任何进程空间地址,可以修改windows内核原有函数
    • Sys文件可以使用IDA进行反汇编
  2. 调试环境已经搭建好,现在来搭建一下编译环境,使用VS系列,VC++因为编译器版本太低了,不支持我安装的WDK,听说是有人解决了这个问题,没找了,实在不想在环境上面折腾得要命。使用的是VS2005,VS2012应该也类似,对于一些注意的先记录下,以后要是有用2012的冲动试试再说。

    • 打开VS2005,新建win32控制台运用程序,建立一个空项目DriverTest。将驱动源文件first.c复制过来,在项目中打开
    • 进入配置管理器,新建一个解决方案配置,比如说“DriverDebug”
    • 选择“工具”->“选项”->“在项目和解决方案”的“VC++目录”中选择“包含文件”,添加:WDk安装目录和版本\inc\crt,WDk安装目录和版本\inc\ddk,WDk安装目录和版本\inc\api。注意将它们置顶(置顶后可能对编译其它类型工程有问题,比如标准CPP控制台程序,需要将“$(VCInstallDir)include”置顶,所以最好让WDK排它后面)
    • 选择“库文件”,添加:WDk安装目录和版本\lib\wxp\i386,置顶。
    • 进入配置属性,展开“C/C++”选项卡,在“常规”中将警告等级设置为2级(可选),将“调试信息格式”设置为“C7兼容”(可选)
    • 编辑“预处理器”定义为:WIN32=100;_X86_=1;WINVER=0x501;DBG=1(必选)
    • 在“代码生成”关闭“缓冲区安全检查”(可选),“运行时库”设置为“多线程调试”或者“多线程”(建议)
    • 在“高级”中修改调用约定为“__stdcall”(必选)
    • 进入“链接器”选项卡,在“常规”中修改“输出文件”后缀为.sys(必选),“启用增量链接”设置为“否”(建议)
    • 在“输入”中的“附加依赖项”添加:wdm.lib(WDM式驱动)或者ntoskrnl.lib(NT式驱动)(必选),“忽略所有默认库”选择为“是”(可选)
    • VS2008及以上版本中,设置“清单文件”的“启用用户帐号控制(UAC)”设置为“否”(必选)
    • 在“系统”中选择“子系统”为:控制台(必选),将“驱动程序”设置为:驱动程序(必选)
    • 在“高级”中将“输入点”填写为:DriverEntry(必选),VS2008以上版本将“随机基址”和“数据执行保护(DEP)”设置为:默认值(必选)
    • 在“命令行”的“附加选项”的“添加开关”填写:/SECTION:INIT,D /IGNORE:4078(建议)
    • 编译以后在工程目录的driverdebug目录下将生成DriverTest.sys。这本书,真的不适合内核开发的入门呀。。。。为了读它,要找N多相关资料。。。这里将会有很大东西不是原书上的!
  3. First.c源代码分析:DriverEntry为驱动入库函数,相当于win32编程中的main函数,接受两个参数:

    • PDRIVER_OBJECT:结构体指针,用于传递驱动对象,由I/O管理器传递进来。只要有驱动程序,就会有驱动对象DRIVER_OBJECT,I/O管理器调用nt!IopLoadDriver 读取注册表,获得驱动程序文件.SYS 的路径,将这个文件载入内存。之后会调用 nt!ObCreateObject 创建一个驱动对象,并初始化这个驱动对象。DRIVER_OBJECT结构体是驱动对象存在的形式
    • PUNICODE_STRING:结构体指针,用来指向驱动负责的注册表,也就是驱动程序在注册表中的路径
    • 入库函数一般需要放在INIT标志的内存,指明该函数只是在加载时载入内存,加载完成后该函数可从内存中卸载,使用语句(当然,不加也不会有错误。):
    •  #define INITCODE code_seg(“INIT”)
      
       #pragma INITCODE
    • driver->DriverUnload = DriverUnload(功能:驱动卸载)用于指定回调函数,DbgPrint输出,可以用KdPrint宏取代,后者是对前者的封装,在测试版中输出信息,而在发行版中什么都不做,但是要注意格式为KdPrint((“……”))形式(即一定要双括号)
  4. DebugView的使用:打开软件,在capture(捕获)菜单栏开启Capture Kernel(捕获内核)以及Capture Events(捕获事件)。然后使用InstDrv加载驱动并且运行,就可以看到驱动的DbgPrint或者KdPrint输出信息

  5. R3与RO联系:Win32是纯正的windows子系统,提供大量API函数,这些API分为2类,即:USER函数,GDI函数,KERNEL函数。NT native API是可由用户模式和核心模式程序调用的NT系统服务接口,它们直接由NT操作系统实现。SSDT是系统服务描述表,这个表把R3的API和R0的API联系起来,它不仅是一个地址索引表,还包含一些其他的信息,如:地址所有基地址、服务函数个数。

    • KERNEL函数通过SYSENTER查找SSDT表从运用层函数切换到内核层函数
    • USR函数和GDI函数通过SYSENTER查找Shadow SSDT表从运用层函数切换到内核层函数
  6. WinDbg指令:

    • g:运行
    • u:反汇编,如u ddk(模块名)!DriverEntry则表示反汇编入库函数
    • bp:下执行断点int 3(CC),如bp ddk(模块名)!DDK_Unload
    • bl:列举断点,列表中第一项表示序号,第二项的是e表示执行断点,是d表示禁用断点
    • bd:禁用断点,如bd 0(序号)
    • bc:清除断点,如bc 0(序号),这3个断点操作在菜单栏edit的breakpoints中可以看到
    • a:编辑汇编代码,如a  f8ef48c3(地址)进入交互模式,然后输入修改后的汇编代码如:xor        eax,eax,持续输入汇编代码可以改写之后的汇编语句
    • d:查看寄存器指向的地址,如d esp
    • dd:直接查看内存地址,如dd 054efc14
    • poi:类似C语言指针操作符*,如dd poi 054efc14查看054efc14保存的地址值对应的地址中的内容
    • dt:查看内核数据结构,如dt nt!_DRIVER_OBJECT可查看DRIVER_OBJECT结构体
  7. 添加驱动设备(编程的话,大部分函数都在Microsoft文档中可以看到(安装WDK的时候后在help栏有ducmentation,其中有离线版),做两个引例吧,这些东西记不住也没必要去记):

     //_stdcall
     #include <ntddk.h>
     #define INITCODE code_seg("INIT")
     #define PAGECODE code_seg("PAGE") /*表示内存不足时,可以被置换到硬盘*/
     #pragma INITCODE /*指的代码运行后 就从内存释放掉*/
     NTSTATUS CreateMyDevice (IN PDRIVER_OBJECT pDriverObject)
     {
         NTSTATUS status;
         PDEVICE_OBJECT pDevObj;/*用来返回创建设备结构体的指针*/
         //设备对象(DEVICE_OBJECT)由驱动创建。一个驱动可以创建多个设备对象。通过驱动对象(DRIVER_OBJECT),可以找到由该驱动创建的所有设备对象。一个驱动创建的所有设备对象链成一条链。该驱动的驱动对象可以找到这个链,一个设备对象也可以找到创建它的驱动的驱动对象。DEVICE_OBJECT是设备对象存在的形式
    
         //创建设备名称
         UNICODE_STRING devName;
         UNICODE_STRING symLinkName; // 结构体,包含了宽字节字符缓冲区与其长度
         RtlInitUnicodeString(&devName,L"\\Device\\testDDK_Device");//*对devName初始化字串为 "\\Device\\testDDK_Device"
         //这个宽字节的路径“\\Device\\ ”部分不能改变,后面是设备名称
    
         //创建设备对象
         status = IoCreateDevice( pDriverObject,\    //驱动程序对象指针。在入库函数DriverEntry过程里接收
             ,\    //指定驱动程序为设备扩展对象定义的结构体大小
             &devName,\    //设备名称,必须是完整的设备路径名,设置为NULL则是无名设备
             FILE_DEVICE_UNKNOWN,\    //设备类型
             , TRUE,\    //驱动程序的其它信息以及指定设备是否是独占的,TRUE则是
             &pDevObj);//输出,用来保存PDEVICE_OBJECT结构体指针,这个指针指向设备对象自身
         if (!NT_SUCCESS(status))
         {
                 if (status==STATUS_INSUFFICIENT_RESOURCES)
                 {
                     KdPrint(("资源不足 STATUS_INSUFFICIENT_RESOURCES"));
                 }
                 if (status==STATUS_OBJECT_NAME_EXISTS )
                 {
                     KdPrint(("指定对象名存在"));
                 }
                 if (status==STATUS_OBJECT_NAME_COLLISION)
                 {
                     KdPrint(("//对象名有冲突"));
                 }
             KdPrint(("设备创建失败...++++++++"));
             return status;
         }
         KdPrint(("设备创建成功...++++++++"));
         // IoCreateDevice 会把新创建的这个设备对象,链入驱动的设备链中
    
         pDevObj->Flags |= DO_BUFFERED_IO;
         //创建符号链接
    
         //驱动程序虽然有了设备名称,但是这种设备名称只能在内核态可见,而对于应用程序是不可见的,因此,驱动需要要暴露一个符号链接,该链接指向真正的设备名称
         RtlInitUnicodeString(&symLinkName,L"\\??\\TestLinkName");
         //这个宽字节的路径“\\??\\ ”部分不能改变,后面是符号链接名称,这里在《天书夜读》中使用的是“\\DosDevices\\”,暂时持疑问态度
    
         status = IoCreateSymbolicLink( &symLinkName,// Unicode字符串指针,是一个用户态可见的名称
         &devName );// Unicode字符串指针,是驱动程序创建的设备对象名称。
         if (!NT_SUCCESS(status)) /*status等于0*/
         {
                 IoDeleteDevice( pDevObj );//删除驱动设备
                 return status;
         }
         return STATUS_SUCCESS;
     }
    
     #pragma  PAGECODE
     VOID DDK_Unload (IN PDRIVER_OBJECT pDriverObject); //前置说明 卸载例程
     NTSTATUS ddk_DispatchRoutine_CONTROL(IN    PDEVICE_OBJECT pDevobj,IN PIRP pIrp);//派遣函数
    
     NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING B) //TYPEDEF LONG NTSTATUS
     {
         __asm  ;
         KdPrint(("驱动成功被加载.. "));
         //jmp指令
          CreateMyDevice(pDriverObject);//为驱动对象创建一个设备
    
         pDriverObject->MajorFunction[IRP_MJ_CREATE]=ddk_DispatchRoutine_CONTROL;//IRP_MJ_CREATE相关IRP处理函数
         pDriverObject->MajorFunction[IRP_MJ_CLOSE]=ddk_DispatchRoutine_CONTROL;//IRP_MJ_CREATE相关IRP处理函数
         pDriverObject->MajorFunction[IRP_MJ_READ]=ddk_DispatchRoutine_CONTROL;//IRP_MJ_CREATE相关IRP处理函数
         pDriverObject->MajorFunction[IRP_MJ_CLOSE]=ddk_DispatchRoutine_CONTROL;//IRP_MJ_CREATE相关IRP处理函数
         pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=ddk_DispatchRoutine_CONTROL; //IRP_MJ_CREATE相关IRP处理函数
    
          pDriverObject->DriverUnload=DDK_Unload;//卸载驱动对象,这里并没有真正卸载掉,因为没有删除所有的设备以及符号链接
         return STATUS_SUCCESS;
     }
     VOID DDK_Unload (IN PDRIVER_OBJECT pDriverObject)
     {
         PDEVICE_OBJECT pDev;//用来取得要删除设备对象
         UNICODE_STRING symLinkName; // 
    
         pDev=pDriverObject->DeviceObject;//从驱动对象取得设备对象,所有设备对象连成一条链,这里假定只有一个设备
         IoDeleteDevice(pDev); //删除设备
    
         //取符号链接名字
         RtlInitUnicodeString(&symLinkName,L"\\??\\TestLinkName");
         //删除符号链接
         IoDeleteSymbolicLink(&symLinkName);
         KdPrint(("驱动成功被卸载... ")); //sprintf,printf
         //取得要删除设备对象
         //删掉所有设备
         DbgPrint("卸载成功");
     }
    
     //分类处理
     NTSTATUS ddk_DispatchRoutine_CONTROL(IN PDEVICE_OBJECT pDevobj, IN PIRP pIrp)
     {
         PIO_STACK_LOCATION     irpsp = IoGetCurrentIrpStackLocation(pIrp);
         // IoGetCurrentIrpStackLocation得到调用者堆栈的指针
         switch(irpsp->MajorFunction)
         {
         case  IRP_MJ_CREATE:
             KdPrint(("IRP_MJ_CREATE"));
             break;
         case  IRP_MJ_CLOSE:
             KdPrint(("IRP_MJ_CLOSE"));
             break;
         case  IRP_MJ_READ:
             KdPrint(("IRP_MJ_READ"));
             break;
         case  IRP_MJ_WRITE:
             KdPrint(("IRP_MJ_WRITE"));
             break;
         case  IRP_MJ_DEVICE_CONTROL:
             KdPrint(("IRP_MJ_DEVICE_CONTROL"));
             break;
         default:
             KdPrint(("其它处理"));
         }
    
         pIrp->IoStatus.Information=;//设置IRP操作的字节数为0,这里无实际意义
         pIrp->IoStatus.Status=STATUS_SUCCESS;//设置IRP处理状态
         IoCompleteRequest(pIrp,IO_NO_INCREMENT);//指示完成此IRP的处理
         KdPrint(("离开派遣函数\n"));//调试信息
         return STATUS_SUCCESS; //返回成功,这样,发起I/O操作的Win32API将会返回TRUE,使用GetLastError和设置的IPR处理状态一致
     }
  8. DDK_Unload函数。

  9. IRP(I/O request package)是操作系统内核的一个数据结构。应用程序(.exe)与驱动程序(.sys)进行通信需要通过IRP包。当上层应用程序需要与驱动通信的时候,通过调用一定的API函数,IO管理器针对不同的API产生不同的IRP,IRP被传递到驱动内部不同的分发函数进行处理(DisPatch Function)。对于不会处理的IRP包需要提供一个默认的分发函数来处理。IRP分为很多种,被用于与windows程序交互的有以下五种:

    • #define     IRP_MJ_CREATE   0x00                   //CreateFile会产生此IRP
    • #define     IRP_MJ_CLOSE      0x02                   //CloseHandle会产生此IRP
    • #define     IRP_MJ_READ        0x03                   //ReadFile会产生此IRP
    • #define     IRP_MJ_WRITE      0x04                   //WriteFile会产生此IRP
    • #define    IRP_MJ_CONTROL 0x0E                  //DeviceIoControl会产生此IRP
  10. 其它还有IRP_MJ_PNP(即插即用0x1b,NT驱动不支持,WDM驱动支持)、IRP_MJ_POWER(电源管理0x16)、IRP_MJ_SYSTEM_CONTROL(系统控制0x17)等等。使用过程:

    • 创建IRP处理函数(有点类似MFC的回调函数机制)
    • 在驱动入口(DriveEntry)对IRP函数进行注册,在驱动对象(DriverObject)的MajorFunction字段中被指派,这个字段是一个数组,用以上的IRP宏定义进行偏移
    • 细化IRP函数
  11. 注册和细化派遣函数

    • 方式一:注册到同一个派遣函数再分类处理
    • 方式二:分开注册,对不同IRP包注册不同的派遣函数来处理

最新文章

  1. Java中JDBC连接数据库代码和步骤详解总结
  2. Java经典实例:正则表达式,替换匹配的文本
  3. Redis Cluster架构和设计机制简单介绍
  4. delphi 自定义消息
  5. js深入研究之无法理解的js类代码,extend扩展
  6. hdu2588 gcd 欧拉函数
  7. asp.net几种&lt;% %&gt;用法
  8. 本地ssh连接到vbox中的linux
  9. JVM年轻代、年老代、永久代
  10. VBScript中Msgbox函数的用法
  11. python中将字典形式的数据循环插入Excel
  12. Flask依赖和启动流程回顾
  13. Maven pom.xml详解(转)
  14. Centos6.5之ssh免密码登录配置
  15. 集合框架_DAY15
  16. libev与libevent区别
  17. sessionStorage和localStorage
  18. java8新特性-入门摘要
  19. 《区块链100问》第81集:应用类项目Augur
  20. 查看Oracle数据库中的所有用户名

热门文章

  1. 用git从github网站上下载代码的方式
  2. 数据库表反向生成(二) Django ORM inspectdb
  3. idea web项目动态部署
  4. python3.6如何安装pymssql
  5. Django内置的通用类视图
  6. Django Form表单学习总结
  7. 基于Eclipse IDE的Ardupilot飞控源码阅读环境搭建
  8. Kotlin——最详细的控制语句使用
  9. iOS 通讯录空格
  10. 双向链表--Java实现