Windows内核开发-2-开始内核开发-2-内核开发入门

Sna1lGo 2021-07-18 原文


Windows内核开发-2-开始内核开发-2-内核开发入门


Windows内核开发-2-开始内核开发-2-

第一个驱动程序:

直接采用vs2019中的Empty WDM Driver 模块创建:

 

 

初始的项目文件夹中有一个Driver Files里面会有一个.inf的文件,没用直接删除就好,然后在源文件里面创建一个.cpp的源文件。

DriverEntry和Unload Routines

DriverEntry:

每个驱动都有一个入口点,叫做DriverEntry,就好比平常写的C/C++代码里面的main函数。DriverEntry是由一个叫做IRQL_PASSIVE_LEVEL(0)的系统进程调用出来的。DriverEntry函数原型:

NTSTATUS
DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING
RegistryPath);

代码原型里的_In _是源代码注释语言(SAL)中的一部分,用来描述函数如何使用其参数,SAL对于编译器来说可以直接忽略,但是对程序员很有帮助。

相关链接:Understanding SAL | Microsoft Docs

这里的最小的DriverEntry示例可以只返回一个状态,比如:

NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
return STATUS_SUCCESS;
}

该DriverEntry函数包含在<ntddk.h>头文件中,但是添加了头文件后仍然是编译失败的,因为编译器会把警告当场错误来报错,但是不建议删除该功能,因为有时候警告就是会导致错误诞生:

 

 

可以对应修改这些警告,比如这里将形参删除,但是这样仅对于C++好用,因为C++有函数重载,所以这里用不上。这里有一个很经典的解决办法,就是采用一个宏函数:

UNREFERENCED_PARAMETER();

这样就可以暂时解决掉前面的报错说形参没有使用了:

NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);

return STATUS_SUCCESS;
}

但是这样仍然不行:

 

 

可以很明显得看出,编译器没问题,但是Linker链接器出了问题,DriverEntry是一个C函数,必须用C的linker来link,但是这里我们采用的是C++的默认,所以必须给该函数设置为C的默认LInker才行

extern "C"
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
UNREFERENCED_PARAMETER(DriverObject);
UNREFERENCED_PARAMETER(RegistryPath);

return STATUS_SUCCESS;
}

这样最简单的驱动程序的源代码就写好了,就相当于C语言中的:

#include<stdio.h>

int main()
{

return 0;
}

Unload Routines:

这是一个驱动的卸载函数,就相当于C++中类的析构函数一样,当驱动被卸载的时候就会自动调用该函数。在DriverEntry函数中创建的东西需要由Unload Routines来释放,这就非常像C++类中的构造函数和析构函数的关系了。如果没有该函数来释放驱动加载时所开辟的内容就会导致泄露,直到下次电脑重启时内核产生的泄露才会清楚。

该函数的函数指针,必须在DriverEntry给DriverEntry的参数DriverObject中的DriverUnload字段赋值才行。Unload函数和DriverEntry函数一样都需要接受一个_In _ PDRIVER_OBJECT DriverObject 参数,但是Unload函数不需要返回值,直接用void 定义就好。

比如:

#include <ntddk.h>

void SampleUnload(_In_ PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
}

extern "C"
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
DriverObject->DriverUnload = SampleUnload;
UNREFERENCED_PARAMETER(RegistryPath);

return STATUS_SUCCESS;
}

就是一个非常简单但是可以用的驱动了。

部署驱动程序Deploying the Driver

前面已经写好了一个驱动程序,但是我们还需要把它跑起来,驱动程序不像平时写的普通程序一样,采用IDE就可以正常使用了,需要将其加载到系统里面,通常为了避免风险,采用虚拟机来部署驱动程序。

安装驱动程序就像是在User用户态安装服务.exe一样,需要调用CreateService API或者采用现有的工具,这里采用比较常用的Sc.exe来进行部署内核驱动。

注册驱动

采用管理员权限的命令行:

sc create sample type=kernel binPath=C:\DriverTest\MyDriver3.sys

其中 sample是创建的名字,然后type表示创建的权限,binPath后面的是驱动的路径。如果没问天会弹出一个成功的标识符。

并且可以在注册表里面查到:

使用Win+R的弹出框里面输入regedit.exe,查看路径\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\sample,这就是我们刚刚写好且用sc创造的驱动:

 

 

启动驱动

前面是注册了该驱动,还得使用它。这里也和User用户态的服务Service类似,需要采用StartService API来使用或者采用一些软件,这里也可以用Sc.exe来继续使用。

sc start sample

这里的sample就是前面注册的驱动的名字。

但是这个通常会失效,因为对于64bit位的windows系统,加载驱动必须得要有驱动的签名才行,这里为了学习方便,避开签名这个东西,可以直接把系统置为测试版本。

bcdedit /set testsigning on

如果你要生成除了Windows10以外的版本,可以在项目属性里面配置你的驱动要部署在的系统环境里面:

 

 

最后再使用前面sc来加载驱动时会看到一个关于驱动的输出:

 

 

有了这个输出就表明我们的驱动已经成功加载了,可以使用Process Explorer工具来确认是否加载成功(下载地址:https://docs.microsoft.com/zh-cn/sysinternals/downloads/process-explorer

 

 

这里的驱动名称是你自己的sys驱动名称。

卸载驱动

不用了将驱动程序卸下,同样的可以采用 ControlService API或者Sc.exe来处理。Sc指令:

sc stop sample

就OK了。

简单跟踪

为了确保函数有确切被调用,这里提供一种基础的跟踪办法来确保函数被使用,驱动采用KdPrint这个宏来输出类似于printf风格的文本,该宏的内容可以被内核的调试器,或者其它工具查看到。

KdPrint这个宏只在debug模式下采用,它的底层调用的其实是DbgPrint 内核Kernel API。

下面更新一下DriverEntry和Unload函数:

void SampleUnload(_In_ PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
KdPrint(("Sample driver Unload called! \n"));
}

NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
DriverObject->DriverUnload = SampleUnload;
UNREFERENCED_PARAMETER(RegistryPath);
KdPrint(("Sample driver initialized successfully\n !"));

return STATUS_SUCCESS;
}

需要注意的是该宏函数在调用时采用了两个括号,因为它是一个宏函数,但是又显然它是可以接受任意变量的,由于宏函数不能接受可变的变量参数,所以编译器实际上调用的是DbgPrint函数。这里理解不了没关系,先这样用着就行。

重新生成驱动并加载来查看这些Print信息,这里需要采用一个内核的调试器才行,但是为了方便,先采用一个系统的内部工具:DebugView来查看。在使用DebugView之前,需要先给它在注册表里面配置内容不然用不上。

在\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\里新建一个Key(项)Key名为DebugPrintFilter,并且添加一个DWORD类型的值名为DEFAULT,这个DEFAULT要和默认的一个值区别开来,后面那个默认的值是注册表中的每一个项都有的,那个值暂时先不用管,然后给该名为DEFAULT类型为DWORD的变量赋值为8,如下图所示:

 

 

 

然后下载DebugView(DebugView – Windows Sysinternals | Microsoft Docs),并用管理员身份打开它,然后再在Capture选项中去掉Capture Win32 和Capture Global Win32,选中Capture Kernel:

 

 

这样,再使用Sc.exe来重新加载驱动就可以看到KdPrint打印的内容了。

 

 

总结Summary

这里明白了如何写一个驱动,以及如何再电脑上部署和查看驱动的消息,算是驱动入门了。

posted on
2021-07-18 19:38 
Sna1lGo 
阅读(0
评论(0
编辑 
收藏 
举报

 

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

Windows内核开发-2-开始内核开发-2-内核开发入门的更多相关文章

  1. Windows内核开发-Windows内部概述-1-

    Windows内核开发-Windows内部概述-1- Windows内部概述-1- 进程: 进程是一个程序的运 […]...

  2. Windows内核开发-6-内核机制 Kernel Mechanisms

    Windows内核开发-6-内核机制 Kernel Mechanisms Windows内核开发-6-内核机制 […]...

  3. Windows内核开发-5-(2)-内核模式调试

    Windows内核开发-5-(2)-内核模式调试 Windows内核开发-5-(2)-内核模式调试 普通用户模 […]...

  4. Windows内核开发-Windows内部概述-2-

    Windows内核开发-Windows内部概述-2- Windows内部概述-2- 线程: 执行代码的实体是线 […]...

随机推荐

  1. win7 无Internet访问权限的解决方法

     原文来自网络:http://social.technet.microsoft.com/Forums/en/w […]...

  2. 我的一个React路由嵌套(多级路由),路由传参之旅

    在上一篇react路由之旅中,我们简单地配置了react,进行了react路由及相关知识的学习,引入以及实现一 […]...

  3. Android 导出db并查看内容

    1、导出sqlite的db文件: 使用工具DDMS,切换到DDMS,显示File Explorer窗口,找到/ […]...

  4. 一些电脑不常用快捷键介绍

    这些个人觉得不常用,常用的一些没有写 按win+v打开剪贴版按win+shift+s屏幕截图按alt+tab切 […]...

  5. windows 10 如何管理自己的磁盘

    首先,进入系统的磁盘管理(进入磁盘管理方式有两种:1、通过电脑属性,进入磁盘管理。2、右键点击windows键 […]...

  6. IDE-IntelliJ IDEA 主题、字体、编辑区主题、文件编码修改、乱码问题

    主题修改 上图标注 1 所示为 IntelliJ IDEA 修改主题的地方,可以通过打开左上角的File -& […]...

  7. nodeJS 下载与安装,环境配置

    1。什么是nodeJs: 简单的说 Node.js 就是运行在服务端的 JavaScript。 Node.js […]...

  8. springBoot–01–快速入门

    笔记源码:https://gitee.com/ytfs-dtx/SpringBoot 1.1 原有Spring […]...

展开目录

目录导航