8.3 Windows驱动开发:内核遍历文件或目录
在笔者前一篇文章《内核文件读写系列函数》
简单的介绍了内核中如何对文件进行基本的读写操作,本章我们将实现内核下遍历文件或目录这一功能,该功能的实现需要依赖于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驱动开发:内核遍历文件或目录的更多相关文章
- Windows驱动开发-内核常用内存函数
搞内存常用函数 C语言 内核 malloc ExAllocatePool memset RtlFillMemory memcpy RtlMoveMemory free ExFreePool
- 《Windows内核安全与驱动开发》阅读笔记 -- 索引目录
<Windows内核安全与驱动开发>阅读笔记 -- 索引目录 一.内核上机指导 二.内核编程环境及其特殊性 2.1 内核编程的环境 2.2 数据类型 2.3 重要的数据结构 2.4 函数调 ...
- Windows驱动开发(中间层)
Windows驱动开发 一.前言 依据<Windows内核安全与驱动开发>及MSDN等网络质料进行学习开发. 二.初步环境 1.下载安装WDK7.1.0(WinDDK\7600.16385 ...
- [Windows驱动开发](一)序言
笔者学习驱动编程是从两本书入门的.它们分别是<寒江独钓——内核安全编程>和<Windows驱动开发技术详解>.两本书分别从不同的角度介绍了驱动程序的制作方法. 在我理解,驱动程 ...
- windows驱动开发推荐书籍
[作者] 猪头三 个人网站 :http://www.x86asm.com/ [序言] 很多人都对驱动开发有兴趣,但往往找不到正确的学习方式.当然这跟驱动开发的本土化资料少有关系.大多学的驱动开发资料都 ...
- windows 驱动开发入门——驱动中的数据结构
最近在学习驱动编程方面的内容,在这将自己的一些心得分享出来,供大家参考,与大家共同进步,本人学习驱动主要是通过两本书--<独钓寒江 windows安全编程> 和 <windows驱动 ...
- C++第三十八篇 -- 研究一下Windows驱动开发(二)--WDM式驱动的加载
基于Windows驱动开发技术详解这本书 一.简单的INF文件剖析 INF文件是一个文本文件,由若干个节(Section)组成.每个节的名称用一个方括号指示,紧接着方括号后面的就是节内容.每一行就是一 ...
- C++第三十三篇 -- 研究一下Windows驱动开发(一)内部构造介绍
因为工作原因,需要做一些与网卡有关的测试,其中涉及到了驱动这一块的知识,虽然程序可以运行,但是不搞清楚,心里总是不安,觉得没理解清楚.因此想看一下驱动开发.查了很多资料,看到有人推荐Windows驱动 ...
- Windows驱动——读书笔记《Windows驱动开发技术详解》
=================================版权声明================================= 版权声明:原创文章 谢绝转载 请通过右侧公告中的“联系邮 ...
- Windows 驱动开发 - 8
最后的一点开发工作:跟踪驱动. 一.驱动跟踪 1. 包括TMH头文件 #include "step5.tmh" 2. 初始化跟踪 在DriverEntry中初始化. WPP_INI ...
随机推荐
- xv6book阅读 chapter1
xv6book主要研究了xv6如何实现它的类Unix接口,但是其思想和概念不仅仅适用于Unix.任何操作系统都必须将进程多路复用到底层硬件上,相互隔离进程,并提供受控制的进程间通信机制. 1 了解xv ...
- C# 实用第三方库
C# 实用第三方库 Autofac 依赖注入IOC框架 NuGet安装:Autofac.Autofac.Extras.DynamicProxy AutoMapper 对象映射 Mapster 对象映射 ...
- 一、@Configuration、@Conponent 、@ComponentScan 注解等
一句话概括 区别: @Configuration 中所有带 @Bean 注解的方法都会被动态代理,因此调用该方法返回的都是同一个实例.2. 可以直接调用方法,不需要 @Autowired 注入后使用. ...
- Liunx运维(四)-文本处理三剑客:grep、sed、awk
文档目录: 一.grep:文本过滤工具 二.sed:字符流编辑器 三.awk:文本分析工具 ---------------------------------------分割线:正文--------- ...
- 机器学习-无监督机器学习-LDA线性判别分析-25
目录 1. Linear Discriminant Analysis 线性判别分析 1. Linear Discriminant Analysis 线性判别分析 经常被用于分类问题的降维技术,相比于P ...
- 【荐】开源Winform控件库:花木兰控件库
微信好友推荐,挺好看的Winfrom控件库,下面来看看. 介绍 基于 C#(语言) 4.0 . VS2019 . Net Framework 4.0(不包括Net Framework 4.0 Clie ...
- 【TouchGFX 】使用 CubeMX 创建 TouchGFX 工程时 LCD 显示为雪花屏
经几个晚上折腾,修改大量的LTDC时钟.时序,FMC时序等,结果还是一样,耐心与好使的工程仔细对比,发现是时钟源配置问题,真是冤,聊以此以示纪念 实质上是没有分清有源和无源晶振 无源晶振又被叫做 谐振 ...
- 宝塔部署 springboot 项目遇到的 一些bug处理方案
1,上传的项目(jar包)的数据库用户名 .密码 , 和服务器的数据库用户名.密码不一致 2,数据库的表结构没有创建 3, 宝塔 phpmyadmin 进不去 原因: 服务器没有放行888端口, 宝塔 ...
- Spring——AOP练习
模仿前面的例子,完成模拟JDBC操作 1.UserDAO接口,具有insert(String name)方法,UserDAOImpl实现它 2. 用前置增强,在插入之前,完成数据库连接.事务创建工作 ...
- 百度网盘(百度云)SVIP超级会员共享账号每日更新(2023.11.17)
一.百度网盘SVIP超级会员共享账号 可能很多人不懂这个共享账号是什么意思,小编在这里给大家做一下解答. 我们多知道百度网盘很大的用处就是类似U盘,不同的人把文件上传到百度网盘,别人可以直接下载,避免 ...