首页
论坛
课程
招聘
[原创]文件遍历技术
2021-11-13 18:12 19350

[原创]文件遍历技术

2021-11-13 18:12
19350

一.前言

(1).实验目的

给定一个文件夹的路径,打印这个文件夹中保存的目录路径与文件的路径与大小。

(2).实验环境

操作系统Win7 X86 SP1
编译器Visual Studio 2017

二.用户层文件遍历

想要在用户层实现文件遍历技术,关键是需要用到两个API,分别是FindFirstFile和FindNextFile。这两个API在文档中的定义如下

HANDLE WINAPI FindFirstFile(
  __in   LPCTSTR lpFileName,
  __out  LPWIN32_FIND_DATA lpFindFileData);
参数含义
lpFileName指定需要查询的目录的名称
lpFindFileData
指向WIN32_FIND_DATA结构的指针,用于接收搜索到的文件或目录的信息

返回值:如果函数执行成功,则会返回文件句柄,否则返回INVALID_HANDLE_VALUE。

BOOL WINAPI FindNextFile(
  __in   HANDLE hFindFile,
  __out  LPWIN32_FIND_DATA lpFindFileData);
参数含义
hFindFile指向前一次调用FindFirstFile或FindFirstEx函数返回的搜索句柄
lpFindFileData含义如上

返回值:如果执行成功,则返回值为TRUE,lpFindFileData中保存了下一个文件或者目录的信息。否则返回FALSE,且lpFindFileData中保存的内容不确定。

其中的WIN32_FIND_DATA在文档中的定义如下

typedef struct _WIN32_FIND_DATA {
  DWORD dwFileAttributes;
  FILETIME ftCreationTime;
  FILETIME ftLastAccessTime;
  FILETIME ftLastWriteTime;
  DWORD nFileSizeHigh;
  DWORD nFileSizeLow;
  DWORD dwReserved0;
  DWORD dwReserved1;
  TCHAR cFileName[MAX_PATH];
  TCHAR cAlternateFileName[14];
} WIN32_FIND_DATA, 
 *PWIN32_FIND_DATA, 
 *LPWIN32_FIND_DATA;

其中比较重要的是如下两个成员:

成员含义
dwFileAttributes指定文件的文件属性。如果是FILE_ATTRIBUTE_DIRECTORY则表示该文件是个目录
cFileName指向文件的名称。如果是.代表是当前目录,如果是..则代表的是上一层目录

在用户层要获取文件的大小,需要使用GetFileSize,该函数在文档中的定义如下

DWORD WINAPI GetFileSize(
  __in       HANDLE hFile,
  __out_opt  LPDWORD lpFileSizeHigh);
参数含义
hFile指向文件句柄
lpFileSizeHign保存DWORD指针,用来保存文件大小的高32。如果不需要,则可以指定为NULL

返回值:如果执行成功,则返回文件大小的低32为,否则为INVALID_FILE_SIZE。

据此可以写出以下的代码来实现遍历

#include <cstdio>
#include <windows.h>

#define DESKTOP_NAME "C:\\Users\\1900\\Desktop\\" //桌面路径
#define DIRECTORY_NAME "test"	// 要搜索的目录名

VOID ShowError(PCHAR msg);
VOID SearchFile();

int main()
{
	SearchFile();
	system("pause");

	return 0;
}

VOID SearchFile()
{
	BOOL bRet = FALSE;
	HANDLE hDirectory = NULL, hFile = NULL;
	WIN32_FIND_DATA wsaData;	// 保存文件信息
	CHAR szDirectoryName[MAX_PATH] = { 0 }, szFileName[MAX_PATH] = { 0 };

	strcpy(szDirectoryName, DESKTOP_NAME);
	strcat(szDirectoryName, "\\");
	strcat(szDirectoryName, DIRECTORY_NAME);
	strcat(szDirectoryName, "\\*.*");
	hDirectory = FindFirstFile(szDirectoryName, &wsaData);
	if (hDirectory == INVALID_HANDLE_VALUE)
	{
		ShowError("FindFirstFile");
		goto exit;
	}

	do {
		// 过滤掉当前目录和上一层目录
		if (wsaData.cFileName[0] != '.')
		{
			// 说明查询到的是目录
			if (wsaData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			{
				printf("type:Directory name: %s\n", wsaData.cFileName);
			}
			else // 说明查询到的是文件
			{
				memset(szFileName, 0, MAX_PATH);
				strcpy(szFileName, DESKTOP_NAME);
				strcat(szFileName, "\\");
				strcat(szFileName, DIRECTORY_NAME);
				strcat(szFileName, "\\");
				strcat(szFileName, wsaData.cFileName);
				hFile = CreateFile(szFileName,
								   GENERIC_READ,
								   0,
								   NULL,
								   OPEN_EXISTING,
								   FILE_ATTRIBUTE_NORMAL,
								   NULL);
				if (hFile == INVALID_HANDLE_VALUE)
				{
					ShowError("CreateFile");
					goto exit;
				}
				printf("type:File name: %s FileSize: %d\n", wsaData.cFileName, GetFileSize(hFile, NULL));
				if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile);
			}
		}
		bRet = FindNextFile(hDirectory, &wsaData);
	} while (bRet);

exit:
	if (hDirectory != INVALID_HANDLE_VALUE) CloseHandle(hDirectory);
	if (hFile != INVALID_HANDLE_VALUE) CloseHandle(hFile);
}

VOID ShowError(PCHAR msg)
{
	printf("%s Error %d\n", msg, GetLastError());
}

可以看到,目录中的文件信息被打印出来

三.驱动层文件遍历

要在驱动层实现上述的功能,稍微复杂一些,需要以下的步骤。

首先需要使用ZwCreateFile来打开一个目录的句柄,该函数定义如下

NTSTATUS 
  ZwCreateFile(
    __out PHANDLE  FileHandle,
    __in ACCESS_MASK  DesiredAccess,
    __in POBJECT_ATTRIBUTES  ObjectAttributes,
    __out PIO_STATUS_BLOCK  IoStatusBlock,
    __in_opt PLARGE_INTEGER  AllocationSize,
    __in ULONG  FileAttributes,
    __in ULONG  ShareAccess,
    __in ULONG  CreateDisposition,
    __in ULONG  CreateOptions,
    __in_opt PVOID  EaBuffer,
    __in ULONG  EaLength
    );
参数含义
FileHandle指向接收文件句柄的HANDLE变量指针
DesiredAccess指定一个ACCESS_MASK值,用于确定请求的访问
ObjectAttributes指向OBJECT_ATTRIBUTES结构的指针,指定对象名称和属性
IoStatusBlock指向IO_STATUS_BLOCK结构的指针,用于接收最终完成状态以及所请求操作的其他信息
AllocationSize指向LARGE_INTEGER的指针,其中包含为创建或覆盖文件分配的起始大小
Attributes指定要打开的文件的属性
ShareAccess指定共享访问类型
CreateDisposition指定文件存在或不存在时要执行的操作
CreateOptions指定驱动程序创建或打开文件时使用的选项
EaLength对于设备和中间驱动程序来说,这个值必须为0

这些参数中的ObjectAttributes是用来保存要打开的文件名的,要初始化这个变量需要用到InitializeObjectAttributes,该函数的定义如下

VOID 
  InitializeObjectAttributes(
    OUT POBJECT_ATTRIBUTES  InitializedAttributes,
    IN PUNICODE_STRING  ObjectName,
    IN ULONG  Attributes,
    IN HANDLE  RootDirectory,
    IN PSECURITY_DESCRIPTOR  SecurityDescriptor
    );
参数含义
InitializedAttributes指定要初始化的OBJECT_ATTRIBUTES结构
ObjectName指定要为其打开句柄的对象的Unicode字符串名称。这必须是一个完全限定的对象名,或者是RootDirectory参数指定的对象目录的相对路径名
Attributes指定句柄属性,OBJ_KERNEL_HANDLE代表只能在内核模式下访问句柄
RootDirectory为ObjectName参数中指定的路径名指定根对象目录的句柄。如果ObjectName参数是完全限定的对象名,则RootDirectory为NULL。使用ZwCreateDirectoryObject获取对象目录的句柄。
SecurityDescriptor指定创建对象时应用于该对象的安全描述符。驱动程序可以指定NULL以接受对象的默认安全性。此参数是可选的。

不同于用户层,驱动层要想获取目录中的所有文件,需要用到ZwQueryDirectoryFile函数,该函数可以在指定的文件句柄指定的目录中返回文件的各种信息,定义如下

NTSTATUS 
  ZwQueryDirectoryFile(
    __in HANDLE  FileHandle,
    __in_opt HANDLE  Event,
    __in_opt PIO_APC_ROUTINE  ApcRoutine,
    __in_opt PVOID  ApcContext,
    __out PIO_STATUS_BLOCK  IoStatusBlock,
    __out PVOID  FileInformation,
    __in ULONG  Length,
    __in FILE_INFORMATION_CLASS  FileInformationClass,
    __in BOOLEAN  ReturnSingleEntry,
    __in_opt PUNICODE_STRING  FileName,
    __in BOOLEAN  RestartScan
    );
参数含义
FileHandle指向文件句柄
Event调用者创建事件的可选句柄,为NULL就好
ApcRoutine在请求完成时调用的APC例程的进程,为NULL就好
ApcContext指定与文件对象相关联的I/O完成对象,为NULL就好
IoStatusBlock指向IO_STATUS_BLOCK结构的指针,该结构接收最终完成状态和有关的信息
FileInformation指向缓冲区的指针,用于接收该文件所需的信息。缓冲区中的返回信息由FileInformationClass参数来定义
Length由FileInformation指向的缓冲区的大小
FileInformationClass给出目录中文件的信息类型
ReturnSingleEntry如果为TRUE,则函数只返回一个条目
FileName指向FileHandle指定目录文件名称的调用者分配的UNICODE字符串,为NULL就好
RestartScan如果扫描要从目录第一个条目开始,那么就需要设置为TRUE。如果是从上次呼叫开始恢复扫描,就需要设置为FALSE

当FileInformationClass指定为FileBothDirectoryInformation则会返回文件和目录的信息,此时FileInformation会返回PFILE_BOTH_DIR_INFORMATION指针,该结构体定义如下

typedef struct _FILE_BOTH_DIR_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;
    ULONG EaSize;
    CCHAR ShortNameLength;
    WCHAR ShortName[12];
    WCHAR FileName[1];
} FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION;

其中的几个关键成员如下:

成员含义
NextEntryOffset如果缓冲区中有多个条目,则会下一个条目的偏移。如果没有则为0
FileAttributes指定文件的属性
FileNameLength指定文件名的字符串长度
FileName指定文件名字符串的第一个字符,随后的内存保存的就是字符串的内容

在驱动层,要获取文件的大小,则需要使用ZwQueryInformationFile,定义如下

NTSTATUS 
  ZwQueryInformationFile(
    IN HANDLE  FileHandle,
    OUT PIO_STATUS_BLOCK  IoStatusBlock,
    OUT PVOID  FileInformation,
    IN ULONG  Length,
    IN FILE_INFORMATION_CLASS  FileInformationClass
    );
参数含义
FileHandle指定文件句柄
IoStatusBlock含义同上
FileInformation指向接收由FileInformationClass指定的对象类型的缓冲区
Length指向FileInformation缓冲区的大小
FileInformationClass由FileInformation指向的缓冲区提供的信息类型

当FileInformationClass指定为FileStandardInformation的时候,FileInformation指向的就是FILE_STANDARD_INFORMATION指针。该结构体的定义如下

typedef struct FILE_STANDARD_INFORMATION {
  LARGE_INTEGER  AllocationSize;
  LARGE_INTEGER  EndOfFile;
  ULONG  NumberOfLinks;
  BOOLEAN  DeletePending;
  BOOLEAN  Directory;
} FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION;

其中的EndOfFile表示文件位置结尾的字节偏移量,它就等于文件的大小。

具体实现代码如下:

#include <ntifs.h>
#include <ntddk.h>

VOID DriverUnload(IN PDRIVER_OBJECT driverObject);
NTSTATUS SearchFile();
VOID ShowError(PCHAR msg, NTSTATUS status);

NTSTATUS DriverEntry(IN PDRIVER_OBJECT driverObject, IN PUNICODE_STRING registryPath)
{
	NTSTATUS status = STATUS_SUCCESS;

	DbgPrint("驱动加载成功\r\n");
	status = SearchFile();
	if (!NT_SUCCESS(status))
	{
		ShowError("SearchFile", status);
	}

exit:
	driverObject->DriverUnload = DriverUnload;
	return STATUS_SUCCESS;
}

NTSTATUS SearchFile()
{
	OBJECT_ATTRIBUTES objDirectoryAttributes, objFileAttributes;
	IO_STACK_LOCATION iosb;
	NTSTATUS status = STATUS_SUCCESS;
	HANDLE hDirectory = NULL, hFile = NULL;
	WCHAR wcsDirectoryPath[256] = L"\\??\\C:\\Users\\1900\\Desktop\\test";
	WCHAR wcsFilePath[1024] = { 0 };
	PVOID pBuffer = NULL;
	ULONG uBufferLength = 0;
	PFILE_BOTH_DIR_INFORMATION pDir = NULL;
	UNICODE_STRING uStrDirectoryPath, uStrFilePath;
	FILE_STANDARD_INFORMATION fsi = { 0 };
	
	RtlInitUnicodeString(&uStrDirectoryPath, wcsDirectoryPath);
	// 获取目录句柄
	InitializeObjectAttributes(&objDirectoryAttributes, &uStrDirectoryPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
	status = ZwCreateFile(&hDirectory,
						  FILE_ANY_ACCESS,
						  &objDirectoryAttributes, 
						  &iosb, 
						  NULL, 
						  FILE_ATTRIBUTE_NORMAL, 
						  0,
						  FILE_OPEN,
						  FILE_SYNCHRONOUS_IO_NONALERT,
						  NULL, 
						  0);
	if (!NT_SUCCESS(status))
	{
		ShowError("ZwCreateFile", status);
		goto exit;
	}

	pBuffer = NULL;
	uBufferLength = 1024;
	status = STATUS_BUFFER_OVERFLOW;

	pBuffer = ExAllocatePoolWithTag(PagedPool, uBufferLength, 'DRID');
	if (!pBuffer)
	{
		DbgPrint("ExAllocatePoolWithTag Error\r\n");
		status = STATUS_INSUFFICIENT_NVRAM_RESOURCES;
		goto exit;
	}

	status = ZwQueryDirectoryFile(hDirectory, 
								  NULL,
								  NULL,
								  NULL,
								  &iosb,
								  pBuffer,
								  uBufferLength,
								  FileBothDirectoryInformation,
								  FALSE,
								  NULL,
								  TRUE);

	if (!NT_SUCCESS(status))
	{
		ShowError("ZwQueryDirectoryFile", status);
		goto exit;
	}

	pDir = (PFILE_BOTH_DIR_INFORMATION)pBuffer;

	while (TRUE)
	{
		if (pDir->FileName[0] != L'.')
		{
			RtlZeroMemory(wcsFilePath, 1024);
			wcscpy(wcsFilePath, wcsDirectoryPath);
			wcscat(wcsFilePath, L"\\");
			wcscat(wcsFilePath + wcslen(wcsDirectoryPath), pDir->FileName);
			if (pDir->FileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			{
				DbgPrint("type:Directory name:%ws\r\n", wcsFilePath);
			}
			else
			{
				RtlZeroMemory(&iosb, sizeof(iosb));
				RtlZeroMemory(&uStrFilePath, sizeof(uStrFilePath));
				RtlZeroMemory(&objFileAttributes, sizeof(objFileAttributes));
				RtlInitUnicodeString(&uStrFilePath, wcsFilePath);
				// 获取文件句柄
				InitializeObjectAttributes(&objFileAttributes, &uStrFilePath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
				status = ZwCreateFile(&hFile,
									  FILE_ANY_ACCESS,
									  &objFileAttributes, 
									  &iosb, 
									  NULL, 
									  FILE_ATTRIBUTE_NORMAL, 
									  0,
									  FILE_OPEN,
									  FILE_SYNCHRONOUS_IO_NONALERT,
									  NULL, 
									  0);
				if (!NT_SUCCESS(status))
				{
					ShowError("ZwCreateFile", status);
					goto exit;
				}

				RtlZeroMemory(&iosb, sizeof(iosb));
				RtlZeroMemory(&fsi, sizeof(fsi));
				status = ZwQueryInformationFile(hFile, &iosb, &fsi, sizeof(fsi), FileStandardInformation);
				if (!NT_SUCCESS(status))
				{
					ShowError("ZwQueryInformationFile", status);
					goto exit;
				}

				DbgPrint("type:File name:%ws FileSize:0x%X\r\n", wcsFilePath, fsi.EndOfFile.u.LowPart);
				if (hFile)
				{
					ZwClose(hFile);
					hFile = NULL;
				}
			}
		}
		if (pDir->NextEntryOffset == 0)	break;
		pDir = (PFILE_BOTH_DIR_INFORMATION)((ULONG)pDir + pDir->NextEntryOffset);
	}
exit:
	if (pBuffer)
	{
		ExFreePool(pBuffer);
		pBuffer = NULL;
	}
	if (hFile) ZwClose(hFile);
	if (hDirectory) ZwClose(hDirectory);

	return status;
}

VOID ShowError(PCHAR msg, NTSTATUS status)
{
	DbgPrint("%s Error 0x%X\n", msg, status);
}

VOID DriverUnload(IN PDRIVER_OBJECT driverObject)
{
	DbgPrint("驱动卸载完成\r\n");
}

可以看到信息都被打印出来了


看雪招聘平台创建简历并且简历完整度达到90%及以上可获得500看雪币~

最后于 2021-11-30 09:57 被1900编辑 ,原因:
收藏
点赞2
打赏
分享
最新回复 (3)
雪    币: 5999
活跃值: 活跃值 (446)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Protected 活跃值 2021-12-10 22:03
2
0
学习了
雪    币: 218
活跃值: 活跃值 (280)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
dayang 活跃值 2022-1-2 16:27
3
0
长文件名怎么处理? 
雪    币: 1973
活跃值: 活跃值 (1591)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
gamehack 活跃值 2022-1-2 21:41
4
0
学习了,感谢分享!
游客
登录 | 注册 方可回帖
返回