欢迎光临
我们一直在努力

Windows内核编程(一):基础知识和第一个程序

Windows内核编程(一):基础知识和第一个程序

这是Windows内核编程的第一篇博客。

基础知识

内核模块的扩展名一般是.sys,但是这个是非强制的,可以是其他扩展名,也可以没有扩展名。

动可以分为不同的类型,如NT驱动、WDM驱动、WDF驱动等。简单来说,NT驱动是最简单的驱动模型,不支持硬件特性;WDM驱动是在NT驱动基础上引入的一套驱动模型,支持即插即用、电源事件等特性;WDF驱动是对WDM驱动的封装与升级,屏蔽了部分细节,简化了大量接口。这三种类型的驱动大同小异,但NT驱动是所有驱动类型中的基础。

第一个程序

#define _AMD64_

#include <ntddk.h>

// 为DbgPrint函数提供定义
#pragma comment(lib, "ntoskrnl.lib")

VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
if (DriverObject != NULL)
{
//ws表示以'\\0'结束的UNICODE字符串,__FUNTIONW__表示当前函数的名字(UNICODE)
DbgPrint("[%ws]Driver Unload, Driver Object Address:%p", __FUNCTIONW__, DriverObject);
}

return;
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
DbgPrint("[%wd]Hello Kernel World\\n", __FUNCTIONW__);
if (RegistryPath != NULL)
{
// wZ表示UNICODE_STRING类型
DbgPrint("[%ws]Driver RegistryPath:%wZ\\n", __FUNCTIONW__, RegistryPath);
}
if (DriverObject != NULL)
{
DbgPrint("[%ws]Driver Object Address:%p\\n", __FUNCTIONW__, DriverObject);
DriverObject->DriverUnload = DriverUnload;
}

return STATUS_SUCCESS;
}

入口函数的参数

内核驱动程序有一个统一的入口函数:DirverEntry。它有两个参数:

  • DriverObject,表示一个驱动对象的指针。一个驱动文件(.sys)运行之后,操作系统在内存中为该驱动分配了一个类型为DRIVER_OBJECT的数据结构,用于记录该驱动的详细信息。这个参数就指向这个数据结构。
  • RegistryPath,表示当前驱动所对应的注册表的位置。UNICODE_STRING是内核表示字符串的结构体
  • 在这里插入图片描述

    请注意,Buffer指向的字符串,并不要求以’\\0’作为结束,在大多数情况下,Buffer指向的字符串没有以’\\0’结尾。

    为什么需要第二个参数?内核驱动是作为Windows系统服务(Service)而存在的。服务是一种特殊的应用程序,符合特殊的接口形式要求,操作系统许多重要功能依赖于服务。不同的服务通过服务的名字来识别,简称“服务名”。一个驱动文件需要运行(加载到内核中),首先要把这个驱动文件注册成一个服务,注册成功后,系统会把该服务的信息写入到注册表HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services 下 ,以服务的名字作为一个注册表的键名。

    入口函数的返回值

    DriverEntry返回值值实际上是一个LONG类型,Windows规定返回STATUS_SUCCESS表示成功,其它返回值表示失败。

    在这里插入图片描述

    内核服务作为Windows服务运行,在执行具体代码前,驱动文件SYS首先会被映射到内核地址空间,作为内核的一个驱动模块(MODULE),接着操作系统会对这个驱动模块执行加载操作,最后系统会调用驱动模块的DriverEntry函数,根据函数的返回值来判断驱动是否初始化成功。如果返回失败,系统会执行一系列清理工作,把驱动模块从内核空间中移除。

    DriverUnload

    上面的代码中定义了DriverUnload函数,并且把DriverObject的DriverUnload初始化为我们定义的同名函数。

    前面提过,驱动是作为服务方式运行的,服务可以被启动,也可以被停止,停止的实质就是系统把该驱动模块对应在内核地址空间中的 代 码 以 及 数 据 移 除 。 当 一 个 内 核 驱 动 被 要 求 停 止 时 ,DriverObject→DriverUnload指向的函数就会被系统调用,开发者可以在这个函数中执行一些清理相关的工作。

    DriverUnload函数非常重要,但重要并不等于必须,DriverUnload函数是可选的,开发者可以不提供DriverUnload函数,这样做的结果是该驱动不支持停止,也就是说,只要开发者不提供DriverUnload函数,这个驱动对应的服务一旦启动后,再也无法停止。该特性被很多安全软件利用,刻意不提供DriverUnload函数,避免驱动被恶意停止。

    请注意,如果DriverEntry函数返回初始化失败的返回值,DriverUnload函数不会被调用。该函数只有在服务初始化成功后被要求停止时才会触发。

    通过命令行编译驱动程序

    笔者使用的是cl和link工具链,下面是makefile:

    CFLAGS = /nologo /c /Od /kernel /GS- /Gs9999999 /Zi /D_WIN4 /D_KERNEL_NODE_

    LFLAGS = /nologo /SUBSYSTEM:NATIVE /DRIVER /ENTRY:DriverEntry

    all:First.sys

    First.obj : First.c
    cl $(CFLAGS) /Fo:First.obj First.c

    First.sys : First.obj
    link $(LFLAGS) /OUT:First.sys First.obj

    执行nmake命令即可编译,也可以使用cmake生成visual studio解决方案,使用msbuild进行生成。

    在这里插入图片描述

    赞(0)
    未经允许不得转载:171主机测评 » Windows内核编程(一):基础知识和第一个程序
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址