功能技术-文件遍历

本文最后更新于:2021-08-18 中午

文件遍历

文件搜索功能是应用程序中最常见的功能之一,同时对于恶意代码来说也是常见的功能,比如勒索病毒就会有大量的文件操作,遍历文件来对文件进行加密。

实现文件遍历的方法有很多,最常见的便是通过API实现,这里主要涉及的是FindFirstFile,FindNextFile以及FindClose等。

函数介绍

FindFirstFile

在目录中搜索名称与特定名称匹配的文件或者子目录。

1
2
3
4
HANDLE FindFirstFile(
LPCSTR lpFileName,
LPWIN32_FIND_DATAA lpFindFileData
);

参数

lpFileName:指定目录、路径、以及文件名。文件名可以包括通配符,例如“*”,“?”。此参数不应该为NULL,无效的字符串(例如,空字符串或缺少终止空字符的字符串),尾部以反斜杠(\)结尾。

如果字符串以通配符、句点“.”或者目录名称结尾,那么用户必须对路径上的根目录和所有子目录具有访问权限。

lpFindFileData:指向WIN32_FIND_DATA结构的指针,用于接收搜索到的文件或者目录的信息。

返回值

如果函数成功,则返回值是在后续调用FindNextFile或者FindClose中使用的搜索句柄,lpFindFileData参数包含搜索到的第一个文件或者目录的信息。

如果函数失败或无法从lpFindFileData参数的搜索字符串中找到文件,则返回值为INVALID_HANDLE_VALUE,并且lpFindFileData的内容是不确定的。

FindNextFile

继续搜索文件

1
2
3
4
BOOL FindNextFile(
HANDLE hFindFile,
LPWIN32_FIND_DATAA lpFindFileData
);

参数

hFindFile:指向前一次调用FindFirstFile或者FindFirstFileEx函数返回的搜索句柄。

lpFindFileData:指向WIN32_FIND_DATA结构的指针,该结构接收搜索到的文件或子目录的信息。

返回值

如果函数成功,则返回值不为0,lpFindFileData参数包含搜索到的下一个文件或者目录的信息。如果函数失败,则返回值为0,并且lpFindFileData的内容是不确定的。

WIN32_FIND_DATAA 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
typedef struct _WIN32_FIND_DATAA {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
CHAR cFileName[MAX_PATH];
CHAR cAlternateFileName[14];
DWORD dwFileType;
DWORD dwCreatorType;
WORD wFinderFlags;
} WIN32_FIND_DATAA, *PWIN32_FIND_DATAA, *LPWIN32_FIND_DATAA;

成员

dwFileAttributes:指定文件的文件属性。

ftCreationTime:指定文件或者目录何时创建的FILETIME结构。如果底层文件系统不支持创建时间,则此成员为0。

ftLastAccessTime:对于文件,结构指定文件最后读取、写入或者运行可执行文件的时间。

ftLastWriteTime:对于文件,该结构指定文件上次写入、截断或者覆盖的时间。

nFileSizeHigh:指定文件大小的高阶DWORD值,以字节为单位。

nFileSizeLow:指定文件大小的低阶DWORD值,以字节为单位。

dwReserved0:若dwFileAttributes成员包含FILE_ATTRIBUTE_REPARSE_POINT属性,则此成员将指定重新标记解析点。若此值未定义,则不应该使用。

dwReserved1:保留

cFileName:指向文件的名称。

cAlternateFileName:指向该文件的替代名称。

实现原理

文件的搜索功能主要是通过FindFirstFile和FindNextFile这两个函数来实现的。

首先是搜索的路径,假设当前要搜索C盘下所有的文件,那么路径就是”C:\\“,指定搜索所有的文件就加上通配符”*“,现在的路径就是”C:\\*.*“。

然后就可以调用FindFirstFile函数,进行搜索,搜索的结果保存在WIN32_FIND_DATA结构体指针指向的内存中。结构体中包含文件的各项信息。可以根据成员dwFileAttributes判断文件的属性,若文件属性是FILE_ATTRIBUTE_DIRECTORY,则说明这是一个目录,可以进行再次搜索,但是要注意要过滤掉当前目录“.”和上级目录“..”,根据cFileName获取文件的名称。

之后再调用FindNextFile函数搜索下一个文件即可,重复上述操作,直到根据返回值判断,搜索不到文件。

最后调用FindClose关闭搜索句柄。

编写代码

结果太多,为了结果更好展示,将其输出到TXT文件中。搜索时再加入一个判断,只输出exe类型文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <Windows.h>
#include <stdio.h>
void findFile(char* pszPath);
int main()
{
findFile("C:\\Users\\b1ackie\\Desktop");
system("pause");
return 0;
}
void findFile(char* pszPath)
{
DWORD dwBufferSize = 4096;
char* pszFileName = NULL;
char* pszNextPath = NULL;
WIN32_FIND_DATA FileData = { 0 };
BOOL flag = FALSE;
//申请动态内存
pszFileName = new char[dwBufferSize];
pszNextPath = new char[dwBufferSize];
//搜索当前路径下的所有文件
sprintf(pszFileName, "%s\\*.*", pszPath);
char ext[_MAX_EXT];
//创建一个TXT文件,将结果输出
FILE* fp;
fp = fopen("C:\\Users\\b1ackie\\Desktop\\1.txt", "a+");
HANDLE hFile = FindFirstFile(pszFileName, &FileData);
if (hFile != INVALID_HANDLE_VALUE)
{
do
{
//过滤掉当前目录和上级目录
if (!strcmp(FileData.cFileName,".") || !strcmp(FileData.cFileName, ".."))
{
continue;
}
//拼接文件路径
sprintf(pszNextPath, "%s\\%s", pszPath, FileData.cFileName);
//如果是一个目录的话,继续搜索
if (FileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
findFile(pszNextPath);
}
//不是的话就输出文件名字
else
{
//分割文件名字
_splitpath(pszNextPath, NULL, NULL, NULL, ext);
//判断是否是EXE文件
if (!strcmp(ext, ".exe"))
{
printf("%s\n", pszNextPath);
//写入文件
fprintf(fp, "%s\r\n", pszNextPath);
}
}
//继续搜索
} while (FindNextFile(hFile, &FileData));
}
fclose(fp);
FindClose(hFile);
delete []pszNextPath;
pszNextPath = NULL;
delete []pszFileName;
pszFileName = NULL;
}

测试

运行程序,选择遍历的目录为桌面,查看结果可以看到成功输出了当前桌面所有的exe文件(包含子文件夹中的)。

参考

《Windows黑客编程技术详解》