驱动开发:内核遍历文件或目录

简介: 在笔者前一篇文章`《驱动开发:内核文件读写系列函数》`简单的介绍了内核中如何对文件进行基本的读写操作,本章我们将实现内核下遍历文件或目录这一功能,该功能的实现需要依赖于`ZwQueryDirectoryFile`这个内核API函数来实现,该函数可返回给定文件句柄指定的目录中文件的各种信息,此类信息会保存在`PFILE_BOTH_DIR_INFORMATION`结构下,通过遍历该目录即可获取到文件的详细参数,如下将具体分析并实现遍历目录功能。

在笔者前一篇文章《驱动开发:内核文件读写系列函数》简单的介绍了内核中如何对文件进行基本的读写操作,本章我们将实现内核下遍历文件或目录这一功能,该功能的实现需要依赖于ZwQueryDirectoryFile这个内核API函数来实现,该函数可返回给定文件句柄指定的目录中文件的各种信息,此类信息会保存在PFILE_BOTH_DIR_INFORMATION结构下,通过遍历该目录即可获取到文件的详细参数,如下将具体分析并实现遍历目录功能。

该功能也是ARK工具的最基本功能,如下图是一款通用ARK工具的文件遍历功能的实现效果;

image.png

在概述中提到过,目录遍历的核心是ZwQueryDirectoryFile()系列函数,该函数可返回给定文件句柄指定的目录中文件的各种信息,其微软官方定义如下;

NTSYSAPI NTSTATUS ZwQueryDirectoryFile(
  [in]           HANDLE                 FileHandle,            // 返回的文件对象的句柄,表示要为其请求信息的目录。
  [in, optional] HANDLE                 Event,                 // 调用方创建的事件的可选句柄。 
  [in, optional] PIO_APC_ROUTINE        ApcRoutine,            // 请求的操作完成时要调用的可选调用方提供的 APC 例程的地址。
  [in, optional] PVOID                  ApcContext,            // 如果调用方提供 APC 或 I/O 完成对象与文件对象关联,则为调用方确定的上下文区域的可选指针。
  [out]          PIO_STATUS_BLOCK       IoStatusBlock,         // 指向 IO_STATUS_BLOCK 结构的指针,该结构接收最终完成状态和有关操作的信息。
  [out]          PVOID                  FileInformation,       // 指向接收有关文件的所需信息的输出缓冲区的指针。
  [in]           ULONG                  Length,                // FileInformation 指向的缓冲区的大小(以字节为单位)。
  [in]           FILE_INFORMATION_CLASS FileInformationClass,  // 要返回的有关目录中文件的信息类型。
  [in]           BOOLEAN                ReturnSingleEntry,     // 如果只应返回单个条目,则设置为 TRUE ,否则为 FALSE 。
  [in, optional] PUNICODE_STRING        FileName,              // 文件路径
  [in]           BOOLEAN                RestartScan            // 如果扫描是在目录中的第一个条目开始,则设置为 TRUE 。
);

该函数我们需要注意FileInformation参数,在本例中它被设定为了PFILE_BOTH_DIR_INFORMATION用于存储当前节点下文件或目录的一些属性,如文件名,文件时间,文件状态等,其次FileInformationClass参数也是有多种选择的,本例中我们需要遍历文件或目录则设置成FileBothDirectoryInformation就可以,在循环遍历文件时需要将当前目录.以及上一级目录..排除,而pDir->FileAttributes则用于判断当前节点是文件还是目录,属性FILE_ATTRIBUTE_DIRECTORY代表是目录,反之则是文件,实现目录文件遍历完整代码如下所示;

// 署名权
// right to sign one's name on a piece of work
// PowerBy: LyShark
// Email: me@lyshark.com
#include <ntifs.h>
#include <ntstatus.h>

// 遍历文件夹和文件
BOOLEAN MyQueryFileAndFileFolder(UNICODE_STRING ustrPath)
{
   
   
    HANDLE hFile = NULL;
    OBJECT_ATTRIBUTES objectAttributes = {
   
    0 };
    IO_STATUS_BLOCK iosb = {
   
    0 };
    NTSTATUS status = STATUS_SUCCESS;

    // 初始化结构
    InitializeObjectAttributes(&objectAttributes, &ustrPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);

    // 打开文件得到句柄
    status = ZwCreateFile(&hFile, FILE_LIST_DIRECTORY | SYNCHRONIZE | FILE_ANY_ACCESS,
        &objectAttributes, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ | FILE_SHARE_WRITE,
        FILE_OPEN, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT,
        NULL, 0);
    if (!NT_SUCCESS(status))
    {
   
   
        return FALSE;
    }

    // 为节点分配足够的空间
    ULONG ulLength = (2 * 4096 + sizeof(FILE_BOTH_DIR_INFORMATION)) * 0x2000;
    PFILE_BOTH_DIR_INFORMATION pDir = ExAllocatePool(PagedPool, ulLength);

    // 保存pDir的首地址
    PFILE_BOTH_DIR_INFORMATION pBeginAddr = pDir;

    // 获取信息,返回给定文件句柄指定的目录中文件的各种信息
    status = ZwQueryDirectoryFile(hFile, NULL, NULL, NULL, &iosb, pDir, ulLength, FileBothDirectoryInformation, FALSE, NULL, FALSE);
    if (!NT_SUCCESS(status))
    {
   
   
        ExFreePool(pDir);
        ZwClose(hFile);
        return FALSE;
    }

    // 遍历
    UNICODE_STRING ustrTemp;
    UNICODE_STRING ustrOne;
    UNICODE_STRING ustrTwo;

    RtlInitUnicodeString(&ustrOne, L".");
    RtlInitUnicodeString(&ustrTwo, L"..");

    WCHAR wcFileName[1024] = {
   
    0 };
    while (TRUE)
    {
   
   
        // 判断是否是..上级目录或是.本目录
        RtlZeroMemory(wcFileName, 1024);
        RtlCopyMemory(wcFileName, pDir->FileName, pDir->FileNameLength);

        RtlInitUnicodeString(&ustrTemp, wcFileName);

        // 是否是.或者是..目录
        if ((0 != RtlCompareUnicodeString(&ustrTemp, &ustrOne, TRUE)) && (0 != RtlCompareUnicodeString(&ustrTemp, &ustrTwo, TRUE)))
        {
   
   
            // 判断是文件还是目录
            if (pDir->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
            {
   
   
                // 目录
                DbgPrint("[目录] 创建时间: %u | 改变时间: %u 目录名: %wZ \n", pDir->CreationTime, &pDir->ChangeTime, &ustrTemp);
            }
            else
            {
   
   
                // 文件
                DbgPrint("[文件] 创建时间: %u | 改变时间: %u | 文件名: %wZ \n", pDir->CreationTime, &pDir->ChangeTime, &ustrTemp);
            }
        }

        // 遍历完毕直接跳出循环
        if (0 == pDir->NextEntryOffset)
        {
   
   
            break;
        }

        // 每次都要将pDir指向新的地址
        pDir = (PFILE_BOTH_DIR_INFORMATION)((PUCHAR)pDir + pDir->NextEntryOffset);
    }

    // 释放内存并关闭句柄
    ExFreePool(pBeginAddr);
    ZwClose(hFile);

    return TRUE;
}

VOID UnDriver(PDRIVER_OBJECT driver)
{
   
   
    DbgPrint("驱动卸载成功 \n");
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
   
   
    DbgPrint("Hello LyShark.com \n");

    // 遍历文件夹和文件
    UNICODE_STRING ustrQueryFile;
    RtlInitUnicodeString(&ustrQueryFile, L"\\??\\C:\\Windows");
    MyQueryFileAndFileFolder(ustrQueryFile);

    DbgPrint("驱动加载成功 \n");
    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

编译如上驱动程序并运行,则会输出C:\\Windows目录下的所有文件和目录,以及创建时间和修改时间,输出效果如下图所示;

image.png

你是否会觉得很失望,为什么不是递归枚举,这里为大家解释一下,通常情况下ARK工具并不会在内核层实现目录与文件的递归操作,而是将递归过程搬到了应用层,当用户点击一个新目录时,在应用层只需要拼接新的路径再次发送给驱动程序让其重新遍历一份即可,这样不仅可以提高效率而且还降低了蓝屏的风险,显然在应用层遍历是更合理的。

相关文章
|
6月前
|
存储 Unix Linux
Linux 下文件和目录的本质区别和组成
Linux 下文件和目录的本质区别和组成
260 0
|
3月前
|
存储 Unix Linux
在Linux中,文件系统层次结构是什么?以及目录作用是什么?
在Linux中,文件系统层次结构是什么?以及目录作用是什么?
|
6月前
|
存储 Linux 开发工具
Linux下的系统编程——文件与目录操作(六)
Linux下的系统编程——文件与目录操作(六)
72 0
Linux下的系统编程——文件与目录操作(六)
|
6月前
|
算法 网络协议 Linux
Linux模块文件编译到内核与独立编译成.ko文件的方法
Linux模块文件编译到内核与独立编译成.ko文件的方法
1448 0
|
Linux Shell C++
【Linux初阶】进程程序替换 | 初识、原理、函数、应用 & makefile工具的多文件编译
替换初识,替换原理,替换函数理解和使用,makefile工具的多文件编译,进程替换应用(简易命令行实现)
170 0
【Linux初阶】进程程序替换 | 初识、原理、函数、应用 & makefile工具的多文件编译
|
存储 API
驱动开发:内核文件读写系列函数
在应用层下的文件操作只需要调用微软应用层下的`API`函数及`C库`标准函数即可,而如果在内核中读写文件则应用层的API显然是无法被使用的,内核层需要使用内核专有API,某些应用层下的API只需要增加Zw开头即可在内核中使用,例如本章要讲解的文件与目录操作相关函数,多数ARK反内核工具都具有对文件的管理功能,实现对文件或目录的基本操作功能也是非常有必要的。
273 0
|
Linux
内核笔记](六)——在debugfs中添加一个调试目录
内核笔记](六)——在debugfs中添加一个调试目录
410 0
内核笔记](六)——在debugfs中添加一个调试目录
|
存储 Oracle 安全
Linux目录结构和常用命令大全(文件处理命令)
Linux目录结构和常用命令大全(文件处理命令)
210 0
Linux目录结构和常用命令大全(文件处理命令)
|
Linux C语言
linux多文件编译方法
  一. 常用编译命令选项   假设源程序文件名为test.c。   1. 无选项编译链接   用法:#gcc test.c   作用:将test.c预处理、汇编、编译并链接形成可执行文件。
718 0
|
存储 Linux Android开发
【Android 逆向】Linux 文件分类 ( 普通文件 | 目录文件 | 链接文件 | 字符设备文件 | 管道文件 | 块设备文件 )
【Android 逆向】Linux 文件分类 ( 普通文件 | 目录文件 | 链接文件 | 字符设备文件 | 管道文件 | 块设备文件 )
255 0
【Android 逆向】Linux 文件分类 ( 普通文件 | 目录文件 | 链接文件 | 字符设备文件 | 管道文件 | 块设备文件 )