初探内核下的文件管理技术:内核API

前言 Windows内核下的Rootkit开发技术学习。 用户数据都以文件的形式存储在本地磁盘上,Rootkit等恶意软件想要获取用户的隐私数据就需要有操作文件的功能,包括但不限于增、删、查、改。一般有...

前言

Windows内核下的Rootkit开发技术学习。

用户数据都以文件的形式存储在本地磁盘上,Rootkit等恶意软件想要获取用户的隐私数据就需要有操作文件的功能,包括但不限于增、删、查、改。一般有三种文件管理的方式,一是基于导出的内核API直接操作文件,二是通过程序直接构造输入输出请求包(I/O Request Package,IRP)并发送IRP来操作文件,三是根据文件系统格式(New Technology File System,NTFS)来解析硬盘上的二进制数据。本文先从相对简单的内核API实现开始学习,内核API和用户模式中的WIN32 API有一定的类似。

编程环境:

  • 主机 Win11 Visual Studio2019,SDK和WDK 10.0.19041.685
  • 虚拟机 Win10测试模式

前置

内核API

内核API是一组从内核组件中输出的函数,大多数函数在内核本身模块(NtOskrnl.exe)中实现,少部分在其它模块中(如hal.dll)。内核API是大量C函数的集合,其中大多数名称含有一个前缀,这个前缀指示了实现该函数的模块。

常见的一些内核API前缀:

  • Ex:通用执行体函数 ExAllocatePool
  • Ke:通用内核函数 KeAcquireSpinLock
  • Io:I/O管理器 IoCompleteRequest
  • Zw:原生API包装 ZwCreateFile

Zw前缀的内核API是原生API包装的,原生API指的是前缀为Nt的一系列函数。Nt系列函数(如NtCreateFile)是Windows内核提供的原生API,它们直接与内核模式交互。但应用程序调用WIN32API时,这些API最终会转换成对应的Nt函数,执行实际的操作。

所谓的包装意味着Zw系列和Nt系列在实现上几乎相同,但它们通过不同的入口点进入系统。当内核模式调用Nt系列函数时,函数会检查调用上下文,以确定调用来自内核模式还是用户模式,如果来自用户模式,函数可能会进行一些安全检查或模式转换。而调用Zw系列函数,系统假设调用已经在内核模式下了,即直接将PreviousMode设置成 KernelMode(0)。

OBJECT_ATTRIBUTES 结构

OBJECT_ATTRIBUTES是Windows内核编程中用于描述操作系统对象(如文件、设备、事件、互斥体等)属性的结构体。该结构体通常在创建或打开内核对象时被使用,传递给如ZwCreateFileZwOpenKey等内核API。

typedef struct _OBJECT_ATTRIBUTES {
    ULONG Length;           // 结构体的大小,以字节为单位。
    HANDLE RootDirectory;       //表示对象名称的根目录句柄。
    PUNICODE_STRING ObjectName;         //指向UNICODE_STRING结构体的指针,表示对象的名称。
    ULONG Attributes;           //指定对象的属性。
    PSECURITY_DESCRIPTOR SecurityDescriptor;        //指向安全描述符的指针。常设NULL。
    PSECURITY_QUALITY_OF_SERVICE SecurityQualityOfService;      //用于指定对象的安全服务质量属性,常设NULL。
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;

上述字段中,如果RootDirectory为NULL,则ObjectName必须是一个完整的绝对路径名。否则,ObjectName是相对与RootDirectory目录的路径名。

Attributes常见的属性标志:

  • OBJ_CASE_INSENSITIVE:表示对象名称比较不区分大小写。
  • OBJ_KERNEL_HANDLE:指示句柄只能在内核模式下访问。
  • OBJ_OPENIF:如果对象存在,则打开它,而不是失败。
  • OBJ_PERMANENT:创建一个永久对象,不会自动删除。

InitializeObjectAttributes 宏

InitializeObjectAttributes 宏用于初始化 OBJECT_ATTRIBUTES 结构体,这俩者都同属于ntdef.h

宏定义如下:

#define InitializeObjectAttributes(p, n, a, r, s) { \
    (p)->Length = sizeof(OBJECT_ATTRIBUTES);        \
    (p)->RootDirectory = r;                         \
    (p)->Attributes = a;                            \
    (p)->ObjectName = n;                            \
    (p)->SecurityDescriptor = s;                    \
    (p)->SecurityQualityOfService = NULL;           \
}

p 指向 OBJECT_ATTRIBUTES结构体的指针,字段如上述一样。

文件的创建和删除

ZwCreateFile

在内核层中的病毒木马要想创建或者释放植入程序,最先的是创建一个文件。ZwCreateFile函数用于创建或打开文件对象,它是内核模式下访问文件系统的关键API。该函数同样可以用于创建或打开目录。

函数原型如下:

NTSTATUS ZwCreateFile(
    PHANDLE FileHandle,         //指向接收文件句柄的指针。
    ACCESS_MASK DesiredAccess,      //指定文件访问的类型,例如读、写、执行等。
    POBJECT_ATTRIBUTES ObjectAttributes,//指向已经初始化的OBJECT_ATTRIBUTES结构体.
    PIO_STATUS_BLOCK IoStatusBlock, //指向IO_STATUS_BLOCK结构的指针
    PLARGE_INTEGER AllocationSize,  //指定文件的分配大小。
    ULONG FileAttributes,       //指定了创建或覆盖文件时的属性
    ULONG ShareAccess,          //指定文件的共享模式。
    ULONG CreateDisposition,        //指定文件的创建方式,如文件已存在时应如何处理。
    ULONG CreateOptions,        //指定文件的创建方式,如文件已存在时应如何处理。
    PVOID EaBuffer,         //指向扩展属性(EA,Extended Attributes)缓冲区的指针,通常用于网络文件系统。
    ULONG EaLength          //EaBuffer的长度
);

ZwCreateFile在使用时取决于字段CreateOptions,如果该字段设置了FILE_DIRECTORY_FILE标志,表明这是创建一个目录。

在内核模式下创建文件的步骤:

  • 初始化UNICODE_STRING结构,这是文件路径的表达形式。
  • 设置OBJECT_ATTRIBUTES结构体,调用宏初始化。
  • 调用ZwCreateFile函数。
  • 在执行完相应操作后使用ZwClose关闭文件句柄,释放资源。

具体代码:

#include 

void CreateFileTest()
{
    NTSTATUS status;
    HANDLE hFile;
    OBJECT_ATTRIBUTES objAttr;
    IO_STATUS_BLOCK ioStatusBlock;
    UNICODE_STRING fileName;

    //初始化UNICODE_STRING
    RtlInitUnicodeString(&fileName, L"\\??\\C:\\Users\\example\\Desktop\\syswork\\MyFile.txt");

    //初始化OBJECT_ATTRIBUTES
    InitializeObjectAttributes(&objAttr, &fileName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);

    //调用ZwCreateFile
    status = ZwCreateFile(&hFile, GENERIC_WRITE | GENERIC_READ, &objAttr, &ioStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);

    //检查
    if (NT_SUCCESS(status)) {
        DbgPrint("File created or opened successfully.\n");
    }
    else {
        DbgPrint("Failed to create or open file.Status: 0x%X\n", status);
    }

    //关闭文件句柄
    if (hFile) {
        ZwClose(hFile);
    }
}
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
    UNREFERENCED_PARAMETER(DriverObject);
    UNREFERENCED_PARAMETER(RegistryPath);

    CreateFileTest();

    return STATUS_SUCCESS;
}

需要注意的地方,初始化UNICODE_STRING时:文件路径要包含\\??\\,这是Windows内核中的一个符号链接,用于将内核模式的文件路径映射到用户模式的路径。

ZwCreateFileFILE_OPEN_IF标志告诉内核,如果文件已经存在则打开它,如果文件不存在则创建它。

驱动加载:

image.png

在虚拟机上加载驱动程序,要打开测试模式。还有visual studio生成解决方案时要用x64生成,使用x86生成的sys在win10上可能会报签名错误。

ZwDeleteFile

删除指定文件。函数声明如下:

NTSTATUS ZwDeleteFile(
    _In_ POBJECT_ATTRIBUTES ObjectAttributes
);

唯一的参数,指向一个 OBJECT_ATTRIBUTES 结构的指针。

如果要单独写一个删除文件的sys,步骤和ZwCreateFile是相同的,先初始化文件路径和OBJECT_ATTRIBUTES结构体,然后调用就好。这里演示,直接将ZwDeleteFile加到上述代码中。

部分代码:

        status = ZwDeleteFile(&objAttr);
        if (NT\_SUCCESS(status)) {
            DbgPrint("File deleted successfully.\\n");
        } else {
            DbgPrint("Failed to delete file. Status: 0x%X\\n", status);
        }

报未找到ZwDeleteFile的错误,可以添加一个#include,注意要放在ntddk前面。

驱动加载:

image.png

获取文件大小

ZwQueryInformationFile

要对文件进行操作免不了要获取文件的大小,以便申请数据缓冲区。在用户层中,通过GetFileSize函数获取文件大小;在内核模式下,要获取文件的大小,可以通过调用 ZwQueryInformationFile 函数,配合 FILE_STANDARD_INFORMATION 结构来完成。ZwQueryInformationFile 函数允许我们查询文件的各种属性,其中 FILE_STANDARD_INFORMATION 结构包含了文件的大小信息。

函数定义:

NTSYSAPI NTSTATUS ZwQueryInformationFile(
  [in]  HANDLE                 FileHandle,      //文件或设备的句柄。
  [out] PIO_STATUS_BLOCK       IoStatusBlock,   //指向一个 IO_STATUS_BLOCK 结构的指针,用于接收请求的状态信息和实际传输的字节数。
  [out] PVOID                  FileInformation,     //指向接收所请求信息的缓冲区。
  [in]  ULONG                  Length,          //指定FileInformation缓冲区的大小(以字节为单位)。
  [in]  FILE_INFORMATION_CLASS FileInformationClass     //一个枚举类型,指定要检索的文件信息类型。
);

其中字段FileInformationClass常用的类型包括:

  • FileBasicInformation: 基本文件信息,例如创建时间、上次访问时间。
  • FileStandardInformation: 标准文件信息,包括文件大小、分配大小、文件是否为目录等。
  • FilePositionInformation: 当前文件指针的位置。

ZwQueryInformationFile函数的返回值是一个NTSTATUS代码,表示操作的成功与否。

FILE_STANDARD_INFORMATION 结构

FILE_STANDARD_INFORMATION 结构用于存储与文件或目录相关的标准信息。它是内核APIZwQueryInformationFile常使用的一个信息结构,当该函数的FileInformationClass参数设置为FileStandarInformation时,这个结构会返回标准文件信息,包括文件大小、分配大小等。

结构定义:

typedef struct _FILE_STANDARD_INFORMATION {
    LARGE_INTEGER AllocationSize;      // 文件的分配大小
    LARGE_INTEGER EndOfFile;           // 实际大小
    ULONG NumberOfLinks;               // 指向文件的硬链接数
    BOOLEAN DeletePending;             // 文件是否已标记为删除
    BOOLEAN Directory;                 // 是否是目录
} FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION;

部分代码实现:

ZwQueryInformationFile的第一个参数是文件句柄,该句柄需要由ZwCreateFile或其它函数提供。因此同删除文件类似,可以对第一个具体代码进行修改,在创建或打开文件后,获取文件的大小。

变量要添加一行FILE_STANDARD_INFORMATION fileInfo;

if (NT_SUCCESS(status)) {
        DbgPrint("File opened successfully.\\n");

        // 查询文件信息以获取文件大小
        status = ZwQueryInformationFile(hFile, &ioStatusBlock, &fileInfo, sizeof(fileInfo), FileStandardInformation);

        if (NT_SUCCESS(status)) {
            // 输出文件大小信息
            DbgPrint("File size: %llu bytes\\n", fileInfo.EndOfFile.QuadPart);
        }
        else {
            DbgPrint("Failed to query file information. Status: 0x%X\\n", status);
        }

        // 关闭文件句柄
        ZwClose(hFile);
    }
    else {
        DbgPrint("Failed to open file. Status: 0x%X\\n", status);
    }

驱动加载:

image.png

读写文件

在内核模式下,读写文件操作通常使用 ZwReadFileZwWriteFile 函数。它们同用户模式的ReadFileWriteFile 函数类似,但是在内核模式中使用需要处理一些额外的事项,例如对象属性、I/O状态块等。

ZwReadFile ZwWriteFile

函数定义:

NTSTATUS ZwReadFile(
    HANDLE FileHandle,      //文件句柄。
    HANDLE Event,           //可选的事件句柄。
    PIO_APC_ROUTINE ApcRoutine,     //可选的APC例程,用于异步操作。
    PVOID ApcContext,               //可选的APC上下文
    PIO_STATUS_BLOCK IoStatusBlock,     
    PVOID Buffer,               //缓冲区。
    ULONG Length,               //要读取的字节数。
    PLARGE_INTEGER ByteOffset,      //从文件读取的位置。
    PULONG Key              //与文件关联的健,用于锁定操作。
);

ZwWriteFile函数的各个参数同ZwReadFile相同,除了Buffer,对于读取操作来说,该参数是存储数据的缓冲区;对于写入操作来说,该缓冲区存储的是要被写入的数据。

这俩个函数同样需要一个文件句柄调用,该句柄一般由ZwCreateFile提供。

代码实现

先声明变量,HANDLEOBJECT_ATTRIBUTES结构IO_STATUS_BLOCK都需要声明俩个,分别用于读取和写入操作。

    NTSTATUS status;
    HANDLE hFileRead, hFileWrite;
    OBJECT_ATTRIBUTES objAttrRead, objAttrWrite;
    IO_STATUS_BLOCK ioStatusBlockRead, ioStatusBlockWrite;
    UNICODE_STRING fileNameRead, fileNameWrite;
    LARGE_INTEGER byteOffset;
    CHAR buffer[1024];  // 读写缓冲区
    ULONG bytesRead;

同上一样需要先初始化UNICODE_STRINGOBJETC_ATTRIBUTES结构,然后调用ZwCreateFile打开一个文件,获得其文件句柄。再打开文件的基础下,再创建一个文件,之后从已打开的文件中读取数据写入到新的文件中。

关键代码:

        // 读取文件数据并写入到新文件中
            byteOffset.LowPart = byteOffset.HighPart = 0;

            while (NT\_SUCCESS(status)) {
                // 从读取文件中读取数据
                status = ZwReadFile(hFileRead, NULL, NULL, NULL, &ioStatusBlockRead, buffer, sizeof(buffer), &byteOffset, NULL);

                if (status == STATUS\_END\_OF\_FILE) {
                    DbgPrint("Reached end of file.\\n");
                    break;
                }

                if (NT\_SUCCESS(status)) {
                    bytesRead = (ULONG)ioStatusBlockRead.Information;

                    // 将读取的数据写入到写入文件中
                    status = ZwWriteFile(hFileWrite, NULL, NULL, NULL, &ioStatusBlockWrite, buffer, bytesRead, &byteOffset, NULL);

                    if (!NT\_SUCCESS(status)) {
                        DbgPrint("Failed to write data. Status: 0x%X\\n", status);
                        break;
                    }

                    byteOffset.QuadPart += bytesRead; // 更新写入文件的偏移
                } else {
                    DbgPrint("Failed to read data. Status: 0x%X\\n", status);
                    break;
                }

驱动加载:

image.png

image.png

重命名文件

ZwSetInformationFile

在内核模式下,可以使用 ZwSetInformationFile 函数来重命名文件。相关的结构是FILE_RENAME_INFORMATION

函数定义:

NTSTATUS ZwSetInformationFile(
  _In_  HANDLE                 FileHandle,
  _Out_ PIO_STATUS_BLOCK       IoStatusBlock,
  _In_  PVOID                  FileInformation,
  _In_  ULONG                  Length,
  _In_  FILE_INFORMATION_CLASS FileInformationClass
);

重点参数 FileInformationClass,枚举值,指定要设置的文件信息的类型,包括:

  • FileBasicInformation:设置基本文件信息。
  • FileRenameInformation:重命名文件。
  • FileDispositionInformation:删除文件。
  • FilePositionInformation:设置文件指针位置。
  • FileEndOfFileInformation:设置文件结束位置(文件大小)。

FILE_RENAME_INFORMATION 结构

结构定义:

 typedef struct _FILE_RENAME_INFORMATION {  
     BOOLEAN ReplaceIfExists;      
     HANDLE RootDirectory;     
     ULONG FileNameLength;   //文件名长度  
     WCHAR FileName[1];     //灵活的数组成员  
 } FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION;

ReplaceIfExists 设置为 TRUE 表示如果给定名称的文件已经存在,则应使用给定文件替换该文件。设置为 FALSE 表示如果给定名称的文件已经存在,则重命名操作会失败。

该结构的第四个成员FileName是一个灵活的数组成员,实际大小取决于要重命名的目标文件名的长度,这意味着该FILE_RENAME_INFORMATION结构的大小再编译的时候是不固定的,而是运行时动态分配的。

分配的内存大小需要根据文件名长度计算:sizeof(FILE_RENAME_INFORMATION) + FileNameLength

这里就涉及到了函数ExAllocatePoolWithTagExFreePoolWithTag,分别用于内存分配和释放内存。

部分代码实现:

实现步骤:

  • 照旧的初始化操作,ZwCreateFile返回一个文件句柄
  • 初始化新的UNICODE_STRING
  • 计算所需的内存大小,用ExAllocatePoolWithTag分配
  • 使用RtlZeroMermory函数将分配的内存空间清零
  • FILE_RENAME_INFORMATION结构填满
  • ZwSetInformationFile重命名文件
  • 调用ExFreePoolWithTag释放内存

代码如下:

    //变量声明:
        PFILE_RENAME_INFORMATION renameInfo;
        ULONG renameInfoSize;

        ......

        //打开文件后
    if (NT_SUCCESS(status)) {
        DbgPrint("File opened successfully.\n");

        // 初始化新文件名路径
        RtlInitUnicodeString(&newFileName, L"\\??\\C:\\syswork4\\NewFileName.txt");

        // 分配重命名信息结构
        renameInfoSize = sizeof(FILE_RENAME_INFORMATION) + newFileName.Length;
        renameInfo = (PFILE_RENAME_INFORMATION)ExAllocatePoolWithTag(NonPagedPool, renameInfoSize, 'tag1');
        if (renameInfo != NULL) {
            RtlZeroMemory(renameInfo, renameInfoSize);
            renameInfo->ReplaceIfExists = FALSE;
            renameInfo->RootDirectory = NULL;
            renameInfo->FileNameLength = newFileName.Length;
            RtlCopyMemory(renameInfo->FileName, newFileName.Buffer, newFileName.Length);

            // 重命名文件
            status = ZwSetInformationFile(hFile, &ioStatusBlock, renameInfo, renameInfoSize, FileRenameInformation);

            if (NT_SUCCESS(status)) {
                DbgPrint("File renamed successfully.\n");
            } else {
                DbgPrint("Failed to rename file. Status: 0x%X\n", status);
            }

            // 释放内存
            ExFreePoolWithTag(renameInfo, 'tag1');
        } else {
            DbgPrint("Failed to allocate memory for rename info.\n");
        }

        // 关闭文件句柄
        ZwClose(hFile);
    } else {
        DbgPrint("Failed to open file. Status: 0x%X\n", status);
    }

驱动加载:

image.png

image.png

文件遍历

ZwQueryDirectoryFile

在用户层中,是通过FindFirstFileFindNextFile俩个函数实现文件遍历的,内核中主要由函数ZwQueryDirectoryFile来完成文件遍历的操作。确切的说,该函数是用于枚举文件目录内容,可以返回目录中文件的列表和每个文件的详细信息。

函数定义:

NTSTATUS ZwQueryDirectoryFile(
    HANDLE FileHandle,
    HANDLE Event,
    PIO_APC_ROUTINE ApcRoutine,
    PVOID ApcContext,
    PIO_STATUS_BLOCK IoStatusBlock,
    PVOID FileInformation,
    ULONG Length,
    FILE_INFORMATION_CLASS FileInformationClass,//文件信息类型
    BOOLEAN ReturnSingleEntry,          //是否只返回一个条目(通常为 FALSE)
    PUNICODE_STRING FileName,           //可选的文件名掩码(例如 *.txt),如果为 NULL 则返回目录中的所有文件。
    BOOLEAN RestartScan             //是否从目录开头重新扫描。
);

大多数上面都介绍完了类似的。

FileInformationClass指定返回的文件信息类型,常见的:

  • FileDirectoryInformation: 返回目录中的文件和子目录。
  • FileFullDirectoryInformation: 类似于 FileDirectoryInformation,但提供更多信息
  • FileBothDirectoryInformation: 返回目录项,并包括 8.3 格式的短文件名。

PFILE_DIRECTORY_INFORMATION 结构

该结构是用于存储目录项信息的数据结构,包含了有关目录中每个文件或子目录的信息,例如文件名、文件大小、时间戳等。

typedef struct _FILE_DIRECTORY_INFORMATION {
    ULONG NextEntryOffset;             // 下一个条目的偏移量,以字节为单位。如果这是最后一个条目,则为零。
    ULONG FileIndex;                   // 文件索引号,用于标识文件。
    LARGE_INTEGER CreationTime;        // 创建时间。
    LARGE_INTEGER LastAccessTime;      // 最后访问时间。
    LARGE_INTEGER LastWriteTime;       // 最后写入时间。
    LARGE_INTEGER ChangeTime;          // 最后修改时间。
    LARGE_INTEGER EndOfFile;           // 文件的结束位置,通常表示文件的大小。
    LARGE_INTEGER AllocationSize;      // 文件分配的大小,通常是磁盘上为文件预留的空间。
    ULONG FileAttributes;              // 文件的属性,例如只读、隐藏等。
    ULONG FileNameLength;              // 文件名的长度(以字节为单位,不包含 NULL 终止符)。
    WCHAR FileName[1];                 // 文件名(可变长度的字符串)。
} FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION;

代码实现:

实现和重命名文件的代码类似,初始化时要分配好内存,可以直接分配一块例如1024大小的缓冲区:

    // 初始化文件信息缓冲区
    PVOID buffer;
    ULONG bufferSize = 1024;
    buffer = ExAllocatePoolWithTag(NonPagedPool, bufferSize, 'tag1');

接着初始目录句柄,然后ZwCreateFile获取一个文件句柄。

循环调用ZwQueryDirectoryFile函数来获取目录中的信息,循环中还有一个do循环是用来遍历该函数获得的目录信息(即多个FILE_DIRECTORY_INFORMATION 结构),创建一个UNICODE_STRING来存储当前目录项的名称。

最后要释放内存。

while (TRUE) {
    status = ZwQueryDirectoryFile(hDirectory, NULL, NULL, NULL, &ioStatusBlock, buffer,bufferSize,FileDirectoryInformation, FALSE, NULL, restartScan);
    if (status == STATUS_NO_MORE_FILES) {
        break;
    } else if (!NT_SUCCESS(status)) {
        DbgPrint("ZwQueryDirectoryFile failed. Status: 0x%X\n", status);
        break;
    }
    PFILE_DIRECTORY_INFORMATION fileInfo = (PFILE_DIRECTORY_INFORMATION)buffer;
            do {
                UNICODE_STRING currentFileName;
                currentFileName.Buffer = fileInfo->FileName;
                currentFileName.Length = (USHORT)fileInfo->FileNameLength;
                currentFileName.MaximumLength = (USHORT)fileInfo->FileNameLength;

                DbgPrint("Found file: %wZ\n", &currentFileName);

                fileInfo = (PFILE_DIRECTORY_INFORMATION)((PUCHAR)fileInfo + fileInfo->NextEntryOffset);
            } while (fileInfo->NextEntryOffset != 0);

            restartScan = FALSE;
        }

        ZwClose(hDirectory);
    } else {
        DbgPrint("Failed to open directory. Status: 0x%X\n", status);
    }

    ExFreePoolWithTag(buffer, 'tag1');
}

驱动加载:

image.png

内存分配

ExAllocatePoolWithTag

PVOID ExAllocatePoolWithTag(
  [in] __drv_strictTypeMatch(__drv_typeExpr)POOL_TYPE PoolType,
  [in] SIZE_T                                         NumberOfBytes,
  [in] ULONG                                          Tag
);

PoolType指定要分配的池内存类型,NumberOfBytes是要分配的内存数,Tag是用于分配内存的池标志。

关于池类型,常见的有:

  • NonPagedPool: 非分页池,分配的内存在物理内存中,不能被分页出内存。这种类型的内存在内核模式下是可以随时访问的。
  • PagedPool: 分页池,分配的内存可以被分页到磁盘,只有在访问时才加载到内存。
  • NonPagedPoolNx: 和 NonPagedPool 类似,但不允许执行代码。(安全)

不同的池类型对于IRQL也不同,在分配NonPagedPool类型的内存时,可以在任意IRQL下调用,但是分配PagedPool内存只能在IRQL<=APC_LEVEL时调用,如果在高IRQL时尝试分配分页内存,可能导致系统奔溃。

ExAllocatePool2

在window10 2004中,ExAllocatePoolWithTag已经被弃用了。要编写适用于更高win版本的驱动程序,可以使用ExAllocatePool2

ExAllocatePoolWithTag相比,该函数除非指定了特别的标识,否则内存将被初始化为零;ExAllocatePool2的函数签名还包含了更多参数,新的函数签名为:

PVOID ExAllocatePool2(
    POOL_FLAGS PoolFlags,
    SIZE_T NumberOfBytes,
    ULONG Tag
);

PoolFlags 是一个新的参数,可以控制内存分配的一些额外特性,而 ExAllocatePoolWithTag 只支持通过 POOL_TYPE 指定内存池的类型。

  • 发表于 2024-09-11 09:00:00
  • 阅读 ( 2592 )
  • 分类:安全开发

0 条评论

请先 登录 后评论
Sciurdae
Sciurdae

16 篇文章

站长统计