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标志的内存,指明该函数只是在加载时载入内存,加载完成后该函数可从内存中卸载,使用语句(当然,不加也不会有错误。):
      1. #define INITCODE code_seg(“INIT”)
      2.  
      3. #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,其中有离线版),做两个引例吧,这些东西记不住也没必要去记):

    1. //_stdcall
    2. #include <ntddk.h>
    3. #define INITCODE code_seg("INIT")
    4. #define PAGECODE code_seg("PAGE") /*表示内存不足时,可以被置换到硬盘*/
    5. #pragma INITCODE /*指的代码运行后 就从内存释放掉*/
    6. NTSTATUS CreateMyDevice (IN PDRIVER_OBJECT pDriverObject)
    7. {
    8. NTSTATUS status;
    9. PDEVICE_OBJECT pDevObj;/*用来返回创建设备结构体的指针*/
    10. //设备对象(DEVICE_OBJECT)由驱动创建。一个驱动可以创建多个设备对象。通过驱动对象(DRIVER_OBJECT),可以找到由该驱动创建的所有设备对象。一个驱动创建的所有设备对象链成一条链。该驱动的驱动对象可以找到这个链,一个设备对象也可以找到创建它的驱动的驱动对象。DEVICE_OBJECT是设备对象存在的形式
    11.  
    12. //创建设备名称
    13. UNICODE_STRING devName;
    14. UNICODE_STRING symLinkName; // 结构体,包含了宽字节字符缓冲区与其长度
    15. RtlInitUnicodeString(&devName,L"\\Device\\testDDK_Device");//*对devName初始化字串为 "\\Device\\testDDK_Device"
    16. //这个宽字节的路径“\\Device\\ ”部分不能改变,后面是设备名称
    17.  
    18. //创建设备对象
    19. status = IoCreateDevice( pDriverObject,\ //驱动程序对象指针。在入库函数DriverEntry过程里接收
    20. ,\ //指定驱动程序为设备扩展对象定义的结构体大小
    21. &devName,\ //设备名称,必须是完整的设备路径名,设置为NULL则是无名设备
    22. FILE_DEVICE_UNKNOWN,\ //设备类型
    23. , TRUE,\ //驱动程序的其它信息以及指定设备是否是独占的,TRUE则是
    24. &pDevObj);//输出,用来保存PDEVICE_OBJECT结构体指针,这个指针指向设备对象自身
    25. if (!NT_SUCCESS(status))
    26. {
    27. if (status==STATUS_INSUFFICIENT_RESOURCES)
    28. {
    29. KdPrint(("资源不足 STATUS_INSUFFICIENT_RESOURCES"));
    30. }
    31. if (status==STATUS_OBJECT_NAME_EXISTS )
    32. {
    33. KdPrint(("指定对象名存在"));
    34. }
    35. if (status==STATUS_OBJECT_NAME_COLLISION)
    36. {
    37. KdPrint(("//对象名有冲突"));
    38. }
    39. KdPrint(("设备创建失败...++++++++"));
    40. return status;
    41. }
    42. KdPrint(("设备创建成功...++++++++"));
    43. // IoCreateDevice 会把新创建的这个设备对象,链入驱动的设备链中
    44.  
    45. pDevObj->Flags |= DO_BUFFERED_IO;
    46. //创建符号链接
    47.  
    48. //驱动程序虽然有了设备名称,但是这种设备名称只能在内核态可见,而对于应用程序是不可见的,因此,驱动需要要暴露一个符号链接,该链接指向真正的设备名称
    49. RtlInitUnicodeString(&symLinkName,L"\\??\\TestLinkName");
    50. //这个宽字节的路径“\\??\\ ”部分不能改变,后面是符号链接名称,这里在《天书夜读》中使用的是“\\DosDevices\\”,暂时持疑问态度
    51.  
    52. status = IoCreateSymbolicLink( &symLinkName,// Unicode字符串指针,是一个用户态可见的名称
    53. &devName );// Unicode字符串指针,是驱动程序创建的设备对象名称。
    54. if (!NT_SUCCESS(status)) /*status等于0*/
    55. {
    56. IoDeleteDevice( pDevObj );//删除驱动设备
    57. return status;
    58. }
    59. return STATUS_SUCCESS;
    60. }
    61.  
    62. #pragma PAGECODE
    63. VOID DDK_Unload (IN PDRIVER_OBJECT pDriverObject); //前置说明 卸载例程
    64. NTSTATUS ddk_DispatchRoutine_CONTROL(IN PDEVICE_OBJECT pDevobj,IN PIRP pIrp);//派遣函数
    65.  
    66. NTSTATUS DriverEntry(PDRIVER_OBJECT pDriverObject,PUNICODE_STRING B) //TYPEDEF LONG NTSTATUS
    67. {
    68. __asm ;
    69. KdPrint(("驱动成功被加载.. "));
    70. //jmp指令
    71. CreateMyDevice(pDriverObject);//为驱动对象创建一个设备
    72.  
    73. pDriverObject->MajorFunction[IRP_MJ_CREATE]=ddk_DispatchRoutine_CONTROL;//IRP_MJ_CREATE相关IRP处理函数
    74. pDriverObject->MajorFunction[IRP_MJ_CLOSE]=ddk_DispatchRoutine_CONTROL;//IRP_MJ_CREATE相关IRP处理函数
    75. pDriverObject->MajorFunction[IRP_MJ_READ]=ddk_DispatchRoutine_CONTROL;//IRP_MJ_CREATE相关IRP处理函数
    76. pDriverObject->MajorFunction[IRP_MJ_CLOSE]=ddk_DispatchRoutine_CONTROL;//IRP_MJ_CREATE相关IRP处理函数
    77. pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]=ddk_DispatchRoutine_CONTROL; //IRP_MJ_CREATE相关IRP处理函数
    78.  
    79. pDriverObject->DriverUnload=DDK_Unload;//卸载驱动对象,这里并没有真正卸载掉,因为没有删除所有的设备以及符号链接
    80. return STATUS_SUCCESS;
    81. }
    82. VOID DDK_Unload (IN PDRIVER_OBJECT pDriverObject)
    83. {
    84. PDEVICE_OBJECT pDev;//用来取得要删除设备对象
    85. UNICODE_STRING symLinkName; //
    86.  
    87. pDev=pDriverObject->DeviceObject;//从驱动对象取得设备对象,所有设备对象连成一条链,这里假定只有一个设备
    88. IoDeleteDevice(pDev); //删除设备
    89.  
    90. //取符号链接名字
    91. RtlInitUnicodeString(&symLinkName,L"\\??\\TestLinkName");
    92. //删除符号链接
    93. IoDeleteSymbolicLink(&symLinkName);
    94. KdPrint(("驱动成功被卸载... ")); //sprintf,printf
    95. //取得要删除设备对象
    96. //删掉所有设备
    97. DbgPrint("卸载成功");
    98. }
    99.  
    100. //分类处理
    101. NTSTATUS ddk_DispatchRoutine_CONTROL(IN PDEVICE_OBJECT pDevobj, IN PIRP pIrp)
    102. {
    103. PIO_STACK_LOCATION irpsp = IoGetCurrentIrpStackLocation(pIrp);
    104. // IoGetCurrentIrpStackLocation得到调用者堆栈的指针
    105. switch(irpsp->MajorFunction)
    106. {
    107. case IRP_MJ_CREATE:
    108. KdPrint(("IRP_MJ_CREATE"));
    109. break;
    110. case IRP_MJ_CLOSE:
    111. KdPrint(("IRP_MJ_CLOSE"));
    112. break;
    113. case IRP_MJ_READ:
    114. KdPrint(("IRP_MJ_READ"));
    115. break;
    116. case IRP_MJ_WRITE:
    117. KdPrint(("IRP_MJ_WRITE"));
    118. break;
    119. case IRP_MJ_DEVICE_CONTROL:
    120. KdPrint(("IRP_MJ_DEVICE_CONTROL"));
    121. break;
    122. default:
    123. KdPrint(("其它处理"));
    124. }
    125.  
    126. pIrp->IoStatus.Information=;//设置IRP操作的字节数为0,这里无实际意义
    127. pIrp->IoStatus.Status=STATUS_SUCCESS;//设置IRP处理状态
    128. IoCompleteRequest(pIrp,IO_NO_INCREMENT);//指示完成此IRP的处理
    129. KdPrint(("离开派遣函数\n"));//调试信息
    130. return STATUS_SUCCESS; //返回成功,这样,发起I/O操作的Win32API将会返回TRUE,使用GetLastError和设置的IPR处理状态一致
    131. }
  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包注册不同的派遣函数来处理

《天书夜读:从汇编语言到windows内核编程》五 WDM驱动开发环境搭建的更多相关文章

  1. 《天书夜读:从汇编语言到windows内核编程》六 驱动、设备、与请求

    1)跳入到基础篇的内核编程第7章,驱动入口函数DriverEnter的返回值决定驱动程序是否加载成功,当打算反汇编阅读驱动内核程序时,可寻找该位置. 2)DRIVER_OBJECT下的派遣函数(分发函 ...

  2. Unix/Linux环境C编程新手教程(12) openSUSECCPP以及Linux内核驱动开发环境搭建

    1. openSUSE是一款优秀的linux. watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaXRjYXN0Y3Bw/font/5a6L5L2T/font ...

  3. Unix/Linux环境C编程入门教程(12) openSUSECCPP以及Linux内核驱动开发环境搭建

    1. openSUSE是一款优秀的linux. 2.选择默认虚拟机 3.选择稍后安装操作系统 4.选择linux  opensuse 5. 选择默认虚拟机名称 6.设置处理器为双核. 7.内存设置为2 ...

  4. windows平台CodeBlocks MinGW C++11开发环境搭建

    前言: 本文是以单独下载codeblock编辑器跟MinGW编译器这种方式进行安装,下载带MinGW编译器的codeblocks版本安装配置方式跟这个类似. 一: 下载并安装MinGW 这个参考我写的 ...

  5. 《天书夜读:从汇编语言到windows内核编程》八 文件操作与注册表操作

    1)Windows运用程序的文件与注册表操作进入R0层之后,都有对应的内核函数实现.在windows内核中,无论打开的是文件.注册表或者设备,都需要使用InitializeObjectAttribut ...

  6. 《天书夜读:从汇编语言到windows内核编程》四 windows内核调试环境搭建

    1) 基础篇是讲理论的,先跳过去,看不到代码运行的效果要去记代码是一个痛苦的事情.这里先跳入探索篇.其实今天的确也很痛苦,这作者对驱动开发的编译与调试环境介绍得太模糊了,我是各种尝试,对这个环境的搭建 ...

  7. windows下Qt5.2 for android开发环境搭建

    windows下Qt5.2 forAndroid开发环境配置 1.下载安装Qt 5.2.0 for Android (Windows 32-bit)   http://qt-project.org/d ...

  8. Windows 7下Node.js Web开发环境搭建笔记

    Node.js是什么? 我们看看百科里怎么说的?JavaScript是一种运行在浏览器的脚本,它简单,轻巧,易于编辑,这种脚本通常用于浏览器的前端编程,但是一位开发者Ryan有一天发现这种前端式的脚本 ...

  9. 《天书夜读:从汇编语言到windows内核编程》十一 用C++编写内核程序

    ---恢复内容开始--- 1) C++的"高级"特性,是它的优点也是它的缺点,微软对于使用C++写内核程序即不推崇也不排斥,使用C++写驱动需注意: a)New等操作符不能直接使用 ...

随机推荐

  1. 利用工具爬取网站所有的html和js文件

    例图: 该工具下载地址为:http://www.tenmax.com/teleport/ultra/download.htm

  2. sql server作业实现数据同步

    作业介绍  SQL SERVER的作业是一系列由SQL SERVER代理按顺序执行的指定操作.作业可以执行一系列活动,包括运行Transact-SQL脚本.命令行应用程序.Microsoft Acti ...

  3. SqlServer2008 导入导出txt或Execl数据

    --右键user表所在的数据库,然后任务--导出数据,然后根据提示设置就行 --从txt中导入 EXEC master..xp_cmdshell 'bcp Northwind.dbo.sysusers ...

  4. mySQL使用实践

    1.虚拟机安装mySQL 服务器, 宿主机分别使用navicat工具和java代码 访问mySQL,组网图如下: 2. 查看mySQL的服务器状态,如下: 3. 服务器上查看数据库和数据表内容如下: ...

  5. Druid连接池

    Druid 连接池简介 Druid首先是一个数据库连接池.Druid是目前最好的数据库连接池,在功能.性能.扩展性方面,都超过其他数据库连接池,包括DBCP.C3P0.BoneCP.Proxool.J ...

  6. Fedora 19 搭建Qt环境

    1.搭建桌面环境fedora的源里包含的需要的套件包,用下面命令安装sudo yum intall qt qt-devel qt-x11 qt-doc qt-demos qt-examples qt- ...

  7. OOAD-设计模式(二)之GRASP模式与GOF设计模式概述

    一.GRASP模式(通用责任分配软件模式)概述 1.1.理解责任 1)什么是责任 责任是类间的一种合约或义务,也可以理解成一个业务功能,包括行为.数据.对象的创建等 知道责任——表示知道什么 行为责任 ...

  8. Ajax.Nodejs.跨域访问

    使用环境: 客户端: jQuery 服务器: Node.js 在通过Ajax调用非本域的链接/接口时, 一般是不能成功的, 就算是同一个IP下不同的端口也被认作跨域访问 解决办法记录如下: 客户端: ...

  9. python自动化运维五:paramiko

    p { margin-bottom: 0.25cm; line-height: 120% } a:link { } paramiko是基于python实现的SSH2远程安全连接,支持认证以及密钥方式, ...

  10. PHPExcel-1.8导出

    //PHPExcel-1.8导出excel<?phpheader("Content-type: text/html; charset=utf-8");mysql_query( ...