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

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

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

ZwQueryDirectoryFile是Windows操作系统中的一个系统调用函数,用于查询目录中的文件信息。具体而言,它可以用来枚举一个目录中的所有文件,并返回每个文件的名称、属性、时间戳等信息。

调用ZwQueryDirectoryFile函数需要指定以下参数:

  • 目录句柄:表示要查询的目录的句柄,可以通过调用ZwOpenFile函数打开目录获取。
  • 文件信息类:表示要返回的文件信息的类型,如文件名、文件大小、文件时间戳等。
  • 文件信息缓冲区:表示存放返回文件信息的缓冲区,其大小必须足够大以容纳查询结果。
  • 缓冲区大小:表示文件信息缓冲区的大小。
  • 是否遍历子目录:指定是否遍历目录中的子目录。
  • 文件名匹配模式:指定查询的文件名模式,支持通配符。
  • 是否返回长文件名:指定是否返回长文件名。
  • 函数执行成功时,将返回STATUS_SUCCESS,同时将文件信息写入文件信息缓冲区中。当返回STATUS_NO_MORE_FILES时,表示目录中没有更多的文件需要枚举。

需要注意的是,使用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代表是目录,反之则是文件,实现目录文件遍历完整代码如下所示;

#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 \n"); // 遍历文件夹和文件
UNICODE_STRING ustrQueryFile;
RtlInitUnicodeString(&ustrQueryFile, L"\\??\\C:\\Windows");
MyQueryFileAndFileFolder(ustrQueryFile); DbgPrint("驱动加载成功 \n");
Driver->DriverUnload = UnDriver;
return STATUS_SUCCESS;
}

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

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

8.3 Windows驱动开发:内核遍历文件或目录的更多相关文章

  1. Windows驱动开发-内核常用内存函数

    搞内存常用函数 C语言 内核 malloc ExAllocatePool memset RtlFillMemory memcpy RtlMoveMemory free ExFreePool

  2. 《Windows内核安全与驱动开发》阅读笔记 -- 索引目录

    <Windows内核安全与驱动开发>阅读笔记 -- 索引目录 一.内核上机指导 二.内核编程环境及其特殊性 2.1 内核编程的环境 2.2 数据类型 2.3 重要的数据结构 2.4 函数调 ...

  3. Windows驱动开发(中间层)

    Windows驱动开发 一.前言 依据<Windows内核安全与驱动开发>及MSDN等网络质料进行学习开发. 二.初步环境 1.下载安装WDK7.1.0(WinDDK\7600.16385 ...

  4. [Windows驱动开发](一)序言

    笔者学习驱动编程是从两本书入门的.它们分别是<寒江独钓——内核安全编程>和<Windows驱动开发技术详解>.两本书分别从不同的角度介绍了驱动程序的制作方法. 在我理解,驱动程 ...

  5. windows驱动开发推荐书籍

    [作者] 猪头三 个人网站 :http://www.x86asm.com/ [序言] 很多人都对驱动开发有兴趣,但往往找不到正确的学习方式.当然这跟驱动开发的本土化资料少有关系.大多学的驱动开发资料都 ...

  6. windows 驱动开发入门——驱动中的数据结构

    最近在学习驱动编程方面的内容,在这将自己的一些心得分享出来,供大家参考,与大家共同进步,本人学习驱动主要是通过两本书--<独钓寒江 windows安全编程> 和 <windows驱动 ...

  7. C++第三十八篇 -- 研究一下Windows驱动开发(二)--WDM式驱动的加载

    基于Windows驱动开发技术详解这本书 一.简单的INF文件剖析 INF文件是一个文本文件,由若干个节(Section)组成.每个节的名称用一个方括号指示,紧接着方括号后面的就是节内容.每一行就是一 ...

  8. C++第三十三篇 -- 研究一下Windows驱动开发(一)内部构造介绍

    因为工作原因,需要做一些与网卡有关的测试,其中涉及到了驱动这一块的知识,虽然程序可以运行,但是不搞清楚,心里总是不安,觉得没理解清楚.因此想看一下驱动开发.查了很多资料,看到有人推荐Windows驱动 ...

  9. Windows驱动——读书笔记《Windows驱动开发技术详解》

    =================================版权声明================================= 版权声明:原创文章 谢绝转载  请通过右侧公告中的“联系邮 ...

  10. Windows 驱动开发 - 8

    最后的一点开发工作:跟踪驱动. 一.驱动跟踪 1. 包括TMH头文件 #include "step5.tmh" 2. 初始化跟踪 在DriverEntry中初始化. WPP_INI ...

随机推荐

  1. 基于Kresling折纸结构双稳态空间的无人机着陆系统新结构

    摘要:本文利用动捕技术对无人机着陆系统模型进行动力学分析,对折纸结构双稳态着陆系统性能进行测试,为无人机着陆系统结构设计提供创新方法. 近期,一篇关于无人机着陆系统的研究论文"Evoluti ...

  2. 【django-vue】前后端分离项目

    博客目录 pip永久换源 虚拟环境搭建 项目前后端创建 项目目录调整 封装logger 封装全局异常 封装response 数据库配置 用户表继承AbstractUser配置 开放media访问 路飞 ...

  3. 【ToolChains】| CMake 技巧

    判断 CMake 编译环境 编译类型 CMAKE_BUILD_TYPE 可取值为:Debug, Release, RelWithDebInfo, MinSizeRel 等预设值 if (CMAKE_B ...

  4. 2019年第十届蓝桥杯国赛C++B组

    部分题目示意图来自网络,所以会带水印 最后编辑时间: 2021年5月12日 统一声明 如果不写默认带有常用头文件 如果不表明主函数默认表示在 void solve(){} 默认使用 using nam ...

  5. 图解 Promise 实现原理(二)—— Promise 链式调用

    本文首发于 vivo互联网技术 微信公众号 链接: https://mp.weixin.qq.com/s/Xz2bGaLxVL4xw1M2hb2nJQ作者:Morrain 很多同学在学习 Promis ...

  6. vivo 悟空活动中台-基于行为预设的动态布局方案

    本文首发于 vivo互联网技术 微信公众号 链接:https://mp.weixin.qq.com/s/CwLAV2j7Uxam01m1p7cXxg作者:悟空中台研发团队 [悟空活动中台]系列往期精彩 ...

  7. 体验有礼 | 1 分钟 Serverless 极速部署个人网盘,真网盘真好用!

    你想自己搭一个无敌好用的网盘吗? 想 接着看,还有奖品呢! -- 当前,网盘几乎已成为现代人的标配,而市面上的网盘功能.费用各异,让用户们陷入了对比价格和功能的迷阵中.别对比了,动手吧!作为对存储.流 ...

  8. 五、mycat水平分库

    系列导航 一.Mycat实战---为什么要用mycat 二.Mycat安装 三.mycat实验数据 四.mycat垂直分库 五.mycat水平分库 六.mycat全局自增 七.mycat-ER分片 一 ...

  9. 每天学五分钟 Liunx 0111 | 服务篇:进程权限

    程序存储在硬盘中,需要执行的时候被加载到内存里,内存中的程序以进程的方式运行,进程会根据程序的内容去做读写文件,执行指令等操作. 文件/指令等都有自己的执行权限,符合权限的才能被执行.相应的,进程也需 ...

  10. The requested URL could not be retrieved

    在开发过程中,调用对外接口,返回了一长串的标签提示,如下 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "ht ...