驱动篇——内核空间与内核模块

2021年11月22日 阅读数:2
这篇文章主要向大家介绍驱动篇——内核空间与内核模块,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。因为系统内核的复杂性,故可能有错误或者不全面的地方,若有错误,欢迎批评指正,本教程将会长期更新。 若有好的建议,欢迎反馈。码字不易,若是本篇文章有帮助你的,若有闲钱,能够打赏支持个人创做。如想转载,请把个人转载信息附在文章后面,并声明个人我的信息和本人博客地址便可,但必须事先通知我html

你若是是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。函数

  看此教程以前,问个问题,你明确学驱动的目的了吗?你的开发环境准备好了吗?上一节的内容学会了吗? 没有的话就不要继续了,请从新学习前面驱动篇的教程内容继续。学习


🔒 华丽的分割线 🔒spa


练习及参考

本次答案均为参考,能够与个人答案不一致,但必须成功经过。操作系统

1️⃣ 编写驱动,申请一块内存,并在内存中存储GDT表的全部数据。而后在DebugView中显示出来,最后释放内存。线程

🔒 点击查看代码 🔒
#include <ntddk.h>

NTSTATUS UnloadDriver(PDRIVER_OBJECT DriverObject)
{
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{

    DriverObject->DriverUnload = UnloadDriver;

    /*读取 GDT 表*/

    char gdt[6];

    _asm
    {
        sgdt gdt;
    }

    short limit = *(short*)gdt;
    UINT32* base = *(UINT32*)&gdt[2];

    UINT32* mem = (UINT32*)ExAllocatePoolWithTag(NonPagedPool, limit, NULL);
    if (!mem)
    {
        DbgPrint("申请内存失败!!\n");
    }
    else
    {
        RtlMoveMemory(mem, base, limit);

        DbgPrint("\n=====打印申请的内存存的数据=====\n");
        for (int i = 0; i < limit / 4; i+=2)
        {
            DbgPrint("%0.8x`%0.8x\n", mem[i+1], mem[i]);
        }
        ExFreePool(mem);
    }
    return STATUS_SUCCESS;
}

2️⃣ 编写驱动,实现以下功能:
<1> 初始化一个字符串;
<2> 拷贝一个字符串;
<3> 比较两个字符串是否相等;
<4> ANSI_STRINGUNICODE_STRING字符串相互转换;调试

🔒 点击查看代码 🔒
#include <ntddk.h>

NTSTATUS UnloadDriver(PDRIVER_OBJECT DriverObject)
{
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{

    DriverObject->DriverUnload = UnloadDriver;

    UNICODE_STRING unicode_str;

    ANSI_STRING ansi_str;

    RtlInitAnsiString(&ansi_str, "WingSummer CNBLOG");
    RtlInitUnicodeString(&unicode_str, L"CNBLOG ONLY");

    DbgPrint("ANSI : %Z\n", &ansi_str);
    DbgPrint("Unicode : %wZ\n", &unicode_str);

    ANSI_STRING astr;

    RtlInitAnsiString(&astr, "Wingsummer CNBLOG");

    BOOLEAN b0 = RtlCompareString(&ansi_str, &astr, TRUE);
    BOOLEAN b1 = RtlCompareString(&ansi_str, &astr, FALSE);

    DbgPrint("RtlCompareString : %d %d", b0, b1);

    UNICODE_STRING ustr;

    RtlInitUnicodeString(&ustr, L"Hello CNBLOG");

    b0 = RtlCompareUnicodeString(&unicode_str, &ustr, FALSE);
    DbgPrint("RtlCompareUnicodeString : %d", b0);

    ANSI_STRING astr0;
    UNICODE_STRING ustr0 = {0};    //主要是为了让 IntelliSense 闭嘴

    RtlAnsiStringToUnicodeString(&ustr0, &ansi_str, TRUE);
    RtlUnicodeStringToAnsiString(&astr0, &ustr, TRUE);

    DbgPrint("%wZ\n%Z", &unicode_str, &astr);

    RtlFreeAnsiString(&astr0);
    RtlFreeUnicodeString(&ustr0);

    return STATUS_SUCCESS;
}

3️⃣ 思考题:为何DISPATCH_LEVEL不能访问分页内存。code

🔒 点击查看答案 🔒


  说到这个问题,咱们回过头来看看上篇文章的一个图:htm

  从图上能够看出,假设咱们的驱动运行在IRQLDISPATCH_LEVEL,并使用分页内存,且使用的分页内存的物理页被操做系统回收放到文件了。而后驱动程序须要访问这个内存,一定会触发缺页异常,正常的话会进入异常处理程序。对象

  然而不幸的是,这个异常处理程序处在APC_LEVEL这层IRQL,结果你如今的IRQLDISPATCH_LEVEL,很明显异常处理程序没法打断它,结果没法运行。既然异常处理不了,那就进入熟悉的蓝屏环节。

  综上所述,这就是为何DISPATCH_LEVEL不能访问分页内存。


内核空间与内核模块

  在以前的教程里,咱们介绍了普通的应用程序的4GB内存只有低2GB可能被自身使用,而高2GB共用。示意图以下所示:

  硬件种类繁多,不可能作一个兼容全部硬件的内核,因此,微软提供规定的接口格式,让硬件驱动人员安装规定的格式编写驱动程序 。在内核中,这些驱动程序每个都是一个模块,称为内核模块,均可以加载到内核中,都遵照PE结构。但本质上讲,任意一个sys文件与内核文件没有区别。每一个驱动都是一个模块。就和在3环的程序加载dll同样,加载一个贴上一个。以下图所示:

DRIVER_OBJECT

  DRIVER_OBJECT这个东西,在上一篇具备详细的讲解,此次咱们再把它给搬过来:

typedef struct _DRIVER_OBJECT {
    CSHORT Type;
    CSHORT Size;

    PDEVICE_OBJECT DeviceObject;
    ULONG Flags;

    PVOID DriverStart;
    ULONG DriverSize;
    PVOID DriverSection;
    PDRIVER_EXTENSION DriverExtension;

    UNICODE_STRING DriverName;
    PUNICODE_STRING HardwareDatabase;
    PFAST_IO_DISPATCH FastIoDispatch;

    PDRIVER_INITIALIZE DriverInit;
    PDRIVER_STARTIO DriverStartIo;
    PDRIVER_UNLOAD DriverUnload;
    PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];

} DRIVER_OBJECT;

  咱们如何经过调试信息输出呢,咱们以下示例:

DbgPrint("DRIVER_OBJECT 对象地址:%x \r\n",driver);
DbgPrint("驱动名称:%ws \r\n",driver->DriverName.Buffer);
DbgPrint("模块基址:%x \r\n",driver->DriverStart);
DbgPrint("模块大小:%x \r\n",driver->DriverSize);

遍历内核模块与隐藏

  上篇教程咱们讲过DriverSection,它是一个存储目前全部已加载的驱动程序信息相关的LDR_DATA_TABLE_ENTRY结构体的双向循环链表。而且咱们用WinDbg手动遍历了一下。那么咱们用代码遍历也是能够的。这里就留个做业了,具体代码留在下一篇进行讲解。
  若是咱们把这个项目从DriverSectionInLoadOrderLinks摘掉后,是否是能够实现隐藏呢?会不会对我加载后的驱动有没有影响?既然驱动已经加载到内存当中,就算你去除了它的存在信息,对它的功能也没有实际影响。能够实现隐藏,不过只是对付一些API,但它仍是被揪出来的,如何进一步隐藏将会在后面的教程介绍。

本节练习

本节的答案将会在下一节进行讲解,务必把本节练习作完后看下一个讲解内容。不要偷懒,实验是学习本教程的捷径。

  俗话说得好,光说不练假把式,以下是本节相关的练习。若是练习没作好,就不要看下一节教程了,越到后面,不作练习的话容易夹生了,开始还明白,后来就真的一点都不明白了。本节练习很少,请保质保量的完成。

1️⃣ 遍历内核模块,输出模块名称,基址以及大小。
2️⃣ 编写一个函数,找到一个未导出的函数,并调用。(例子:找到PspTerminateProcess,经过调用这个函数结束记事本进程)
提示:因为没学进程与线程,就提一下如何调用,防止找到函数结果搞不出来实验:一共两个参数,参数第一个是EPORCESS结构体,可从!process 0 0得到全部进程,PROCESS后面跟的数值就是,第二个参数填 0 就可。
3️⃣ 经过断链实现隐藏驱动模块。

下一篇

  驱动篇——常规的0环与3环通讯